patch-2.3.16 linux/drivers/sound/maestro.c

Next file: linux/drivers/sound/maestro.h
Previous file: linux/drivers/sound/dev_table.h
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.3.15/linux/drivers/sound/maestro.c linux/drivers/sound/maestro.c
@@ -0,0 +1,2918 @@
+/*****************************************************************************
+ *
+ *      ESS Maestro/Maestro-2/Maestro-2E driver for Linux 2.2.x
+ *
+ *      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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ *	(c) Copyright 1999	 Alan Cox <alan.cox@linux.org>
+ *
+ *	Based heavily on SonicVibes.c:
+ *      Copyright (C) 1998-1999  Thomas Sailer (sailer@ife.ee.ethz.ch)
+ *
+ *	Heavily modified by Zach Brown <zab@redhat.com> based on lunch
+ *	with ESS engineers.  Many thanks to Howard Kim for providing 
+ *	contacts and hardware.  Honorable mention goes to Eric 
+ *	Brombaugh for the BOB routines and nice hacking in general.
+ *
+ *  Supported devices:
+ *  /dev/dsp0-7    standard /dev/dsp device, (mostly) OSS compatible
+ *  /dev/mixer  standard /dev/mixer device, (mostly) OSS compatible
+ *
+ *  Hardware Description
+ *
+ *	A working Maestro setup contains the Maestro chip wired to a 
+ *	codec or 2.  In the Maestro we have the APUs, the ASP, and the
+ *	Wavecache.  The APUs can be though of as virtual audio routing
+ *	channels.  They can take data from a number of sources and perform
+ *	basic encodings of the data.  The wavecache is a storehouse for
+ *	PCM data.  Typically it deals with PCI and interracts with the
+ *	APUs.  The ASP is a wacky DSP like device that ESS is loathe
+ *	to release docs on.  Thankfully it isn't required on the Maestro
+ *	until you start doing insane things like FM emulation and surround
+ *	encoding.  The codecs are almost always AC-97 compliant codecs, 
+ *	but it appears that early Maestros may have had PT101 (an ESS
+ *	part?) wired to them.  The only real difference in the Maestro
+ *	families is external goop like docking capability, memory for
+ *	the ASP, and trivial initialization differences.
+ *
+ *  Driver Operation
+ *
+ *	We only drive the APU/Wavecache as typical DACs and drive the
+ *	mixers in the codecs.  There are 64 APUs.  We assign 4 to each
+ *	/dev/dsp? device.  2 channels for both in and out.
+ *
+ *	For output we maintain a ring buffer of data that we are dmaing
+ *	to the card.  In mono operation this is nice and easy.  When
+ *	we receive data we tack it onto the ring buffer and make sure
+ *	the APU assigned to it is playing over the data.  When we fill
+ *	the ring buffer we put the client to sleep until there is
+ *	room again.  Easy.
+ *
+ *	However, this starts to stink when we use stereo.  The APUs
+ *	supposedly can decode LRLR packed stereo data, but it
+ *	doesn't work.  So we're forced to use dual mono APUs walking over
+ *	mono encoded data.  This requires us to split the input from
+ *	the client and complicates the buffer maths tremendously.  Ick.
+ *
+ *	Once input is actually written, it will be worth pointing out
+ *	that only 44/16 input actually works.
+ *
+ * TODO
+ *	Leaks memory?
+ *	recording is horribly broken
+ *	apus or dmas get out sync
+ *	bob can be started twice
+ *	anyone have a pt101 codec?
+ *	ess's ac97 codec (es1921) doesn't work
+ *	generally test across codecs..
+ *	mmap(), but beware stereo encoding nastiness.
+ */
+
+/*****************************************************************************/
+
+      
+#ifdef MODULE
+#include <linux/module.h>
+#ifdef MODVERSIONS
+#include <linux/modversions.h>
+#endif
+#endif
+
+#include <linux/version.h>
+#include <linux/string.h>
+#include <linux/ctype.h>
+#include <linux/ioport.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <linux/sound.h>
+#include <linux/malloc.h>
+#include <linux/soundcard.h>
+#include <linux/pci.h>
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <asm/spinlock.h>
+#include <asm/uaccess.h>
+#include <asm/hardirq.h>
+
+#include "maestro.h"
+#include "maestro_tables.h"
+
+/* --------------------------------------------------------------------- */
+
+#define M_DEBUG 1
+
+#ifdef M_DEBUG
+static int debug=0;
+#define M_printk(args...) {if (debug) printk(args);}
+#else
+#define M_printk(x)
+#endif
+
+/* --------------------------------------------------------------------- */
+
+#define DRIVER_VERSION "0.03"
+
+#ifndef PCI_VENDOR_ESS
+#define PCI_VENDOR_ESS			0x125D
+#define PCI_DEVICE_ID_ESS_ESS1968	0x1968		/* Maestro 2	*/
+#define PCI_DEVICE_ID_ESS_ESS1978      	0x1978		/* Maestro 2E	*/
+
+#define PCI_VENDOR_ESS_OLD		0x1285		/* vendor id for maestro 1 */
+#define PCI_DEVICE_ID_ESS_ESS0100	0x0100		/* maestro 1 */
+#endif /* PCI_VENDOR_ESS */
+
+#define ESS_CHAN_HARD		0x100
+
+#define ESS_CFMT_STEREO     0x01
+#define ESS_CFMT_16BIT      0x02
+#define ESS_CFMT_MASK       0x03
+#define ESS_CFMT_ASHIFT     0   
+#define ESS_CFMT_CSHIFT     4
+
+#define ESS_ENABLE_PE		1
+#define ESS_ENABLE_RE		2
+
+#define ESS_STATE_MAGIC		0x125D1968
+#define ESS_CARD_MAGIC		0x19283746
+
+#define DAC_RUNNING		1
+#define ADC_RUNNING		2
+
+static const unsigned sample_size[] = { 1, 2, 2, 4 };
+static const unsigned sample_shift[] = { 0, 1, 1, 2 };
+
+enum card_types_t {
+	TYPE_MAESTRO,
+	TYPE_MAESTRO2,
+	TYPE_MAESTRO2E
+};
+
+static const char *card_names[]={
+	[TYPE_MAESTRO] = "ESS Maestro",
+	[TYPE_MAESTRO2] = "ESS Maestro 2",
+	[TYPE_MAESTRO2E] = "ESS Maestro 2E"
+};
+
+#define SND_DEV_DSP16   5 
+
+/* --------------------------------------------------------------------- */
+
+struct ess_state {
+	unsigned int magic;
+	/* FIXME: we probably want submixers in here, but only one record pair */
+	u8 apu[4];		/* Left, Right, Left In, Right In */
+	u8 apu_mode[4];		/* Running mode for this APU */
+	u8 apu_pan[4];		/* Panning setup for this APU */
+	struct ess_card *card;	/* Card info */
+	/* wave stuff */
+	unsigned int rateadc, ratedac;
+	unsigned char fmt, enable;
+
+	spinlock_t lock;
+	struct semaphore open_sem;
+	mode_t open_mode;
+	wait_queue_head_t open_wait;
+
+	/* soundcore stuff */
+	int dev_audio;
+
+	struct dmabuf {
+		void *rawbuf;
+		unsigned buforder;
+		unsigned numfrag;
+		unsigned fragshift;
+		unsigned hwptr, swptr;
+		unsigned total_bytes;
+		int count;
+		unsigned error; /* over/underrun */
+		wait_queue_head_t wait;
+		/* redundant, but makes calculations easier */
+		unsigned fragsize;
+		unsigned dmasize;
+		unsigned fragsamples;
+		/* OSS stuff */
+		unsigned mapped:1;
+		unsigned ready:1;
+		unsigned endcleared:1;
+		unsigned ossfragshift;
+		int ossmaxfrags;
+		unsigned subdivision;
+		u16 base;		/* Offset for ptr */
+	} dma_dac, dma_adc;
+};
+	
+struct ess_card {
+	unsigned int magic;
+
+	/* We keep maestro cards in a linked list */
+	struct ess_card *next;
+
+	int dev_mixer;
+
+	int card_type;
+
+	/* as most of this is static,
+		perhaps it should be a pointer to a global struct */
+	struct mixer_goo {
+		int modcnt;
+		int supported_mixers;
+		int stereo_mixers;
+		int record_sources;
+		/* the caller must guarantee arg sanity before calling these */
+		int (*read_mixer)(struct ess_card *card, int index);
+		void (*write_mixer)(struct ess_card *card,int mixer, int vol);
+		int (*recmask_io)(struct ess_card *card,int rw,int mask);
+	} mix;
+	
+	struct ess_state channels[8];
+	u16 maestro_map[32];	/* Register map */
+
+	/* hardware resources */
+	u32 iobase;
+	u32 irq;
+
+};
+
+extern __inline__ unsigned ld2(unsigned int x)
+{
+	unsigned r = 0;
+	
+	if (x >= 0x10000) {
+		x >>= 16;
+		r += 16;
+	}
+	if (x >= 0x100) {
+		x >>= 8;
+		r += 8;
+	}
+	if (x >= 0x10) {
+		x >>= 4;
+		r += 4;
+	}
+	if (x >= 4) {
+		x >>= 2;
+		r += 2;
+	}
+	if (x >= 2)
+		r++;
+	return r;
+}
+
+
+/* --------------------------------------------------------------------- */
+
+static struct ess_card *devs = NULL;
+
+/* --------------------------------------------------------------------- */
+
+
+/*
+ *	ESS Maestro AC97 codec programming interface.
+ */
+	 
+static void maestro_ac97_set(int io, u8 cmd, u16 val)
+{
+	int i;
+	/*
+	 *	Wait for the codec bus to be free 
+	 */
+	 
+	for(i=0;i<10000;i++)
+	{
+		if(!(inb(io+ESS_AC97_INDEX)&1))
+			break;
+	}
+	/*
+	 *	Write the bus
+	 */ 
+	outw(val, io+ESS_AC97_DATA);
+	udelay(1);
+	/* should actually be delaying 10 milliseconds? */
+	outb(cmd, io+ESS_AC97_INDEX);
+	udelay(1);
+}
+
+static u16 maestro_ac97_get(int io, u8 cmd)
+{
+	int sanity=100000;
+	u16 data;
+	int i;
+	
+	/*
+	 *	Wait for the codec bus to be free 
+	 */
+	 
+	for(i=0;i<10000;i++)
+	{
+		if(!(inb(io+ESS_AC97_INDEX)&1))
+			break;
+	}
+
+	outb(cmd|0x80, io+ESS_AC97_INDEX);
+	udelay(1);
+	
+	while(inb(io+ESS_AC97_INDEX)&1)
+	{
+		sanity--;
+		if(!sanity)
+		{
+			printk(KERN_ERR "maestro: ac97 codec read timeout.\n");
+			return 0;
+		}
+	}
+	data=inw(io+ESS_AC97_DATA);
+	udelay(1);
+	return data;
+}
+
+/*
+ *	The Maestro can be wired to a standard AC97 compliant codec
+ *	(see www.intel.com for the pdf's on this), or to a PT101 codec
+ *	which appears to be the ES1918 (data sheet on the esstech.com.tw site)
+ *
+ *	The PT101 setup is untested.
+ */
+ 
+static u16 maestro_ac97_init(int iobase)
+{
+
+	int val, seid, caps;
+	u16 vend1, vend2;
+
+#if 0 /* an experiment for another time */
+		/* aim at the second codec */
+		outw(0x21, iobase+0x38);
+		outw(0x5555, iobase+0x3a);
+		outw(0x5555, iobase+0x3c);
+		udelay(1);
+		vend1 = maestro_ac97_get(iobase, 0x7c);
+		vend2 = maestro_ac97_get(iobase, 0x7e);
+		if(vend1 != 0xffff || vend2 != 0xffff) {
+			printk("maestro: It seems you have a second codec: %x %x, please report this.\n",
+				vend1,vend2);
+		}
+		/* back to the first */
+		outw(0x0, iobase+0x38);
+		outw(0x0, iobase+0x3a);
+		outw(0x0, iobase+0x3c);
+#endif
+
+	/* should make sure we're ac97 2.1? */
+	vend1 = maestro_ac97_get(iobase, 0x7c);
+	vend2 = maestro_ac97_get(iobase, 0x7e);
+
+	val = maestro_ac97_get(iobase, 0x00);
+	seid = val >> 8;
+	caps = val & 255;
+
+	printk(KERN_INFO "maestro: AC97 Codec detected: v: 0x%2x%2x 3d: 0x%x caps: 0x%x\n",
+		vend1,vend2,seid, caps);
+
+	switch ((long)(vend1 << 16) | vend2) {
+		/* magic vendor specifc init code, _no_ idea what these do */
+#if 0
+	case 0x83847609:	/* ESS 1921 */
+		maestro_ac97_set(iobase,0x76,0xABBA); /* o/~ Take a chance on me o/~ */
+		udelay(20);
+		maestro_ac97_set(iobase,0x78,0x3002);
+		udelay(20);
+		maestro_ac97_set(iobase,0x78,0x3802);
+		udelay(20);
+		break;
+#endif
+	default: break;
+	}
+
+	/* set master, headphone, master mono */
+        maestro_ac97_set(iobase, 0x02, 0x0000);
+	/* always set headphones to max unmuted, OSS won't 
+		let us change it :( */
+        maestro_ac97_set(iobase, 0x04, 0x0000);
+        maestro_ac97_set(iobase, 0x06, 0x0000);
+        maestro_ac97_set(iobase, 0x08, 0x0606);
+	/* beep, phone, mic, line, cd video, aux */
+	maestro_ac97_set(iobase, 0x0A, 0x1F1F);
+	maestro_ac97_set(iobase, 0x0C, 0x1F1F);
+	maestro_ac97_set(iobase, 0x0E, 0x1F1F);
+	maestro_ac97_set(iobase, 0x10, 0x1F1F);
+	maestro_ac97_set(iobase, 0x12, 0x1F1F);
+	maestro_ac97_set(iobase, 0x14, 0x1F1F);
+	maestro_ac97_set(iobase, 0x16, 0x1F1F);
+	/* unmute, but set pcm out to 1/2 */
+	maestro_ac97_set(iobase, 0x18, 0x0808);
+	/* null record select */
+	maestro_ac97_set(iobase, 0x1A, 0x0000);
+	/* record gain, record gain mic.. */
+	maestro_ac97_set(iobase, 0x1C, 0x0404);
+	maestro_ac97_set(iobase, 0x1E, 0x0404);
+	/* null misc stuff */
+	maestro_ac97_set(iobase, 0x20, 0x0000);
+	/* power up various units? */
+	maestro_ac97_set(iobase, 0x26, 0x000F);
+
+	return 0;
+}
+
+static u16 maestro_pt101_init(int iobase)
+{
+	printk(KERN_INFO "maestro: PT101 Codec detected, initializing but _not_ installing mixer device.\n");
+	/* who knows.. */
+	maestro_ac97_set(iobase, 0x2A, 0x0001);
+	maestro_ac97_set(iobase, 0x2C, 0x0000);
+	maestro_ac97_set(iobase, 0x2C, 0xFFFF);
+	maestro_ac97_set(iobase, 0x10, 0x9F1F);
+	maestro_ac97_set(iobase, 0x12, 0x0808);
+	maestro_ac97_set(iobase, 0x14, 0x9F1F);
+	maestro_ac97_set(iobase, 0x16, 0x9F1F);
+	maestro_ac97_set(iobase, 0x18, 0x0404);
+	maestro_ac97_set(iobase, 0x1A, 0x0000);
+	maestro_ac97_set(iobase, 0x1C, 0x0000);
+	maestro_ac97_set(iobase, 0x02, 0x0404);
+	maestro_ac97_set(iobase, 0x04, 0x0808);
+	maestro_ac97_set(iobase, 0x0C, 0x801F);
+	maestro_ac97_set(iobase, 0x0E, 0x801F);
+	return 0;
+}
+
+static void maestro_ac97_reset(int ioaddr)
+{
+	outw(0x2000,  ioaddr+0x36);
+	udelay(20);
+	outw(0x0000,  ioaddr+0x36);
+	udelay(200);
+}
+
+/*
+ *	Indirect register access. Not all registers are readable so we
+ *	need to keep register state ourselves
+ */
+ 
+#define WRITEABLE_MAP	0xEFFFFF
+#define READABLE_MAP	0x64003F
+
+/*
+ *	The Maestro engineers were a little indirection happy. These indirected
+ *	registers themselves include indirect registers at another layer
+ */
+ 
+static void maestro_write(struct ess_state *ess, u16 reg, u16 data)
+{
+	long ioaddr = ess->card->iobase;
+	unsigned long flags;
+	save_flags(flags);
+	cli();
+	outw(reg, ioaddr+0x02);
+	outw(data, ioaddr+0x00);
+	ess->card->maestro_map[reg]=data;
+	restore_flags(flags);
+}
+
+static u16 maestro_read(struct ess_state *ess, u16 reg)
+{
+	long ioaddr = ess->card->iobase;
+	if(READABLE_MAP & (1<<reg))
+	{
+		unsigned long flags;
+		save_flags(flags);
+		cli();
+		outw(reg, ioaddr+0x02);
+		ess->card->maestro_map[reg]=inw(ioaddr+0x00);
+		restore_flags(flags);
+	}
+	return ess->card->maestro_map[reg];
+}
+
+/*
+ *	These routines handle accessing the second level indirections to the
+ *	wave ram.
+ */
+
+/*
+ *	The register names are the ones ESS uses (see 104T31.ZIP)
+ */
+ 
+#define IDR0_DATA_PORT		0x00
+#define IDR1_CRAM_POINTER	0x01
+#define IDR2_CRAM_DATA		0x02
+#define IDR3_WAVE_DATA		0x03
+#define IDR4_WAVE_PTR_LOW	0x04
+#define IDR5_WAVE_PTR_HI	0x05
+#define IDR6_TIMER_CTRL		0x06
+#define IDR7_WAVE_ROMRAM	0x07
+
+static void apu_index_set(struct ess_state *ess, u16 index)
+{
+	int i;
+	maestro_write(ess, IDR1_CRAM_POINTER, index);
+	for(i=0;i<1000;i++)
+		if(maestro_read(ess, IDR1_CRAM_POINTER)==index)
+			return;
+	printk(KERN_WARNING "maestro: APU register select failed.\n");
+}
+
+static void apu_data_set(struct ess_state *ess, u16 data)
+{
+	int i;
+	for(i=0;i<1000;i++)
+	{
+		if(maestro_read(ess, IDR0_DATA_PORT)==data)
+			return;
+		maestro_write(ess, IDR0_DATA_PORT, data);
+	}
+}
+
+/*
+ *	This is the public interface for APU manipulation. It handles the
+ *	interlock to avoid two APU writes in parallel etc. Don't diddle
+ *	directly with the stuff above.
+ */
+
+static void apu_set_register(struct ess_state *ess, u16 channel, u8 reg, u16 data)
+{
+	unsigned long flags;
+	
+	if(channel&ESS_CHAN_HARD)
+		channel&=~ESS_CHAN_HARD;
+	else
+	{
+		if(channel>3)
+			printk("BAD CHANNEL %d.\n",channel);
+		else
+			channel = ess->apu[channel];
+	}
+	
+	reg|=(channel<<4);
+	
+	save_flags(flags);
+	cli();
+	apu_index_set(ess, reg);
+	apu_data_set(ess, data);
+	restore_flags(flags);
+}
+
+static u16 apu_get_register(struct ess_state *ess, u16 channel, u8 reg)
+{
+	unsigned long flags;
+	u16 v;
+	
+	if(channel&ESS_CHAN_HARD)
+		channel&=~ESS_CHAN_HARD;
+	else
+		channel = ess->apu[channel];
+
+	reg|=(channel<<4);
+	
+	save_flags(flags);
+	cli();
+	apu_index_set(ess, reg);
+	v=maestro_read(ess, IDR0_DATA_PORT);
+	restore_flags(flags);
+	return v;
+}
+
+
+/*
+ *	The wavecache does pci fetches for us and feeds
+ *	them to the APUs..  
+ *	XXX describe interface
+ */
+ 
+static void wave_set_register(struct ess_state *ess, u16 reg, u16 value)
+{
+	long ioaddr = ess->card->iobase;
+	unsigned long flags;
+	
+	save_flags(flags);
+	cli();
+	outw(reg, ioaddr+0x10);
+	outw(value, ioaddr+0x12);
+	restore_flags(flags);
+}
+
+static u16 wave_get_register(struct ess_state *ess, u16 reg)
+{
+	long ioaddr = ess->card->iobase;
+	unsigned long flags;
+	u16 value;
+	
+	save_flags(flags);
+	cli();
+	outw(reg, ioaddr+0x10);
+	value=inw(ioaddr+0x12);
+	restore_flags(flags);
+	
+	return value;
+}
+
+static void sound_reset(int ioaddr)
+{
+	outw(0x2000, 0x18+ioaddr);
+	udelay(10);
+	outw(0x0000, 0x18+ioaddr);
+	udelay(10);
+}
+
+static void set_apu_fmt(struct ess_state *s, int apu, int mode)
+{
+	if(mode&ESS_CFMT_16BIT) { 
+		s->apu_mode[apu] = 0x10;
+		s->apu_mode[apu+1] = 0x10;
+	} else {
+		s->apu_mode[apu] = 0x30;
+		s->apu_mode[apu+1] = 0x30;
+	}
+}
+
+static void set_fmt(struct ess_state *s, unsigned char mask, unsigned char data)
+{
+	s->fmt = (s->fmt & mask) | data;
+	set_apu_fmt(s, 0, s->fmt & ESS_CFMT_MASK);
+	set_apu_fmt(s, 2, (s->fmt >> ESS_CFMT_CSHIFT) & ESS_CFMT_MASK);
+}
+
+static u16 compute_rate(u32 freq)
+{
+	if(freq==48000)
+		return 0xFFFF;
+	freq<<=16;
+	freq/=48000;
+	return freq;
+}
+
+static void set_dac_rate(struct ess_state *s, unsigned rate)
+{
+	u32 freq;
+
+	if (rate > 48000)
+		rate = 48000;
+	if (rate < 4000)
+		rate = 4000;
+
+	s->ratedac = rate;
+
+	if(!(s->fmt & ESS_CFMT_16BIT))
+		rate >>= 1; /* who knows */
+
+/*	M_printk("computing dac rate %d with mode %d\n",rate,s->fmt);*/
+
+	freq = compute_rate(rate);
+	
+	/* Load the frequency, turn on 6dB, turn off the effects */
+	apu_set_register(s, 0, 2, (freq&0xFF)<<8|0x10);
+	apu_set_register(s, 0, 3, freq>>8);
+	apu_set_register(s, 1, 2, (freq&0xFF)<<8|0x10);
+	apu_set_register(s, 1, 3, freq>>8);
+}
+
+static void set_adc_rate(struct ess_state *s, unsigned rate)
+{
+	u32 freq;
+
+	if (rate > 48000)
+		rate = 48000;
+	if (rate < 4000)
+		rate = 4000;
+
+	s->rateadc = rate;
+
+	freq = compute_rate(rate);
+	
+	/* Load the frequency, turn on 6dB, turn off the effects */
+	apu_set_register(s, 2, 2, (freq&0xFF)<<8|0x10);
+	apu_set_register(s, 2, 3, freq>>8);
+	apu_set_register(s, 3, 2, (freq&0xFF)<<8|0x10);
+	apu_set_register(s, 3, 3, freq>>8);
+}
+
+
+/*
+ *	Native play back driver 
+ */
+
+static void ess_play_setup(struct ess_state *ess, int mode, u32 rate, void *buffer, int size)
+{
+	u32 pa;
+	u32 tmpval;
+	int high_apu = 0;
+	int channel;
+
+	M_printk("mode=%d rate=%d buf=%p len=%d.\n",
+		mode, rate, buffer, size);
+		
+	/* all maestro sizes are in 16bit words */
+	size >>=1;
+
+	/* we're given the full size of the buffer, but
+	in stereo each channel will only play its half */
+	if(mode&ESS_CFMT_STEREO) {
+		size >>=1; 
+		high_apu++;
+	}
+	
+	for(channel=0; channel <= high_apu; channel++)
+	{
+		int i;
+		
+		/*
+		 *	To understand this it helps to know how the
+		 *	wave cache works. There are 4 DSP wavecache 
+		 *	blocks which are 0x1FC->0x1FF. They are selected
+		 *	by setting bits  22,21,20 of the address to
+		 *	1 X Y   where X Y select the block.
+		 *
+		 *	In addition stereo pairing is supported. This is
+		 *	set in the wave cache control for the channel as is
+		 *	8bit unsigned.
+		 *
+		 *	Note that this causes a problem. With our limit of
+		 *	about 12 full duplex pairs (48 channels active) we
+		 *	will need to do a lot of juggling to get all the
+		 *	memory we want sufficiently close together.
+		 *	
+		 *	Even with 64K blocks that means
+		 *		24 channel pairs
+		 *		6 pairs/block
+		 *	
+		 *	10K per channel pair = 5000 samples.
+		 */
+		
+		if(!channel) 
+			pa = virt_to_bus(buffer);
+		else
+		/* right channel plays its split half.
+			*2 accomodates for rampant shifting earlier */
+			pa = virt_to_bus(buffer + size*2);
+
+		M_printk("sending pa %x to %d\n",pa,channel);
+		
+		wave_set_register(ess, 0x01FC, (pa&0xFFE00000)>>12);
+
+		/* set the wavecache control reg */
+		tmpval = (pa - 0x10) & 0xFFF8;
+#if 0
+		if(mode & 1) tmpval |= 2; /* stereo */
+#endif
+		if(!(mode & 2)) tmpval |= 4; /* 8bit */ 
+		wave_set_register(ess, ess->apu[channel]<<3, tmpval);
+		
+		pa&=0x1FFFFF;			/* Low 21 bits */	
+		pa>>=1; /* words */
+		
+		/* base offset of dma calcs when reading the pointer
+			on this left one */
+		if(!channel) ess->dma_dac.base = pa&0xFFFF;
+		
+#if 0
+		if(mode&ESS_CFMT_STEREO)			/* Enable stereo */
+			pa|=0x00800000;
+#endif
+		pa|=0x00400000;			/* System RAM */
+		
+		/* Begin loading the APU */		
+		for(i=0;i<15;i++)		/* clear all PBRs */
+			apu_set_register(ess, channel, i, 0x0000);
+			
+		/* Load the frequency, turn on 6dB, turn off the effects */
+/*		apu_set_register(ess, channel, 2, (rate&0xFF)<<8|0x10);
+		apu_set_register(ess, channel, 3, rate>>8);*/
+		
+		/* Load the buffer into the wave engine */
+		apu_set_register(ess, channel, 4, ((pa>>16)&0xFF)<<8);
+		/* XXX reg is little endian.. */
+		apu_set_register(ess, channel, 5, pa&0xFFFF);
+		apu_set_register(ess, channel, 6, (pa+size)&0xFFFF);
+		/* setting loop == sample len */
+		apu_set_register(ess, channel, 7, size);
+		
+		/* clear effects/env.. */
+		apu_set_register(ess, channel, 8, 0x0000);
+		/* aplitudeNow to 0xd0? */
+		apu_set_register(ess, channel, 9, 0xD000);
+
+		/* set the panning reg of the apu to left/right/mid.. */
+
+		/* clear routing stuff */
+		apu_set_register(ess, channel, 11, 0x0000);
+		/* mark dma and turn on filter stuff? */
+		apu_set_register(ess, channel, 0, 0x400F);
+		
+		if(mode&ESS_CFMT_STEREO)
+			/* set panning: left or right */
+			apu_set_register(ess, channel, 10, 0x8F00 | (channel ? 0x10 : 0));
+		else
+			apu_set_register(ess, channel, 10, 0x8F08);
+
+	}
+	
+	/* clear WP interupts */
+	outw(1, ess->card->iobase+0x04);
+	/* enable WP ints */
+	outw(inw(ess->card->iobase+0x18)|4, ess->card->iobase+0x18);
+
+	set_dac_rate(ess,rate);
+
+	for(channel=0; channel<=high_apu; channel++)
+	{
+		/* Turn on the DMA */
+		if(mode&ESS_CFMT_16BIT)
+		{
+			apu_set_register(ess, channel, 0,
+				(apu_get_register(ess, channel, 0)&0xFF0F)|0x10);
+			ess->apu_mode[channel]=0x10;
+		}
+		else
+		{
+			apu_set_register(ess, channel, 0,
+				(apu_get_register(ess, channel, 0)&0xFF0F)|0x30);
+			ess->apu_mode[channel]=0x30;
+		}
+	}
+}
+
+/* --------------------------------------------------------------------- */
+
+static void set_dmaa(struct ess_state *s, unsigned int addr, unsigned int count)
+{
+}
+
+static void set_dmac(struct ess_state *s, unsigned int addr, unsigned int count)
+{
+}
+
+/* Playback pointer */
+extern __inline__ unsigned get_dmaa(struct ess_state *s)
+{
+	long ioport = s->card->iobase;
+	int offset;
+	
+	outw(1, ioport+2);
+	outw(s->apu[0]<<4|5, ioport);
+	outw(0, ioport+2);
+	offset=inw(ioport);
+
+/*	M_printk("dmaa: offset: %d, base: %d\n",offset,s->dma_dac.base);*/
+	
+	offset-=s->dma_dac.base;
+
+	return (offset&0xFFFE)/*<<1*/; /* XXX printk didn't have it */
+}
+
+/* Record pointer */
+extern __inline__ unsigned get_dmac(struct ess_state *s)
+{
+	long ioport = s->card->iobase;
+	int offset;
+	
+	outw(1, ioport+2);
+	outw(s->apu[2]<<4|5, ioport);
+	outw(0, ioport+2);
+	offset=inw(ioport);
+	
+	/* The offset is an address not a position relative to base */
+	
+	return (offset&0xFFFE)<<1; /* hardware is in words */
+}
+
+/*
+ *	Meet Bob, the timer...
+ */
+
+static void ess_interrupt(int irq, void *dev_id, struct pt_regs *regs);
+
+#define ESS_HW_TIMER
+
+#ifndef ESS_HW_TIMER
+
+/* old kernel timer based timer ints, should BOB prove flakey */
+
+static struct timer_list tmp_timer;
+
+static int bob_stopped;
+
+static void ess_interrupt_fake(unsigned long v)
+{
+	ess_interrupt(5, (void *)v, NULL);
+	del_timer(&tmp_timer);
+	if(!bob_stopped)
+	{
+		tmp_timer.expires=jiffies+1;
+		add_timer(&tmp_timer);
+	}
+	else
+		M_printk("Stopping bob (SW)\n");
+}
+
+static void stop_bob(struct ess_state *s)
+{
+	bob_stopped=1;
+}
+
+static void kill_bob(struct ess_state *s)
+{
+	del_timer(&tmp_timer);
+	M_printk("Killing bob (SW)\n");
+}
+
+static void start_bob(struct ess_state *s)
+{
+	static int init=1;
+	if(init)
+	{
+		init=0;
+		init_timer(&tmp_timer);
+		tmp_timer.function = ess_interrupt_fake;
+	}
+	bob_stopped = 0;
+	if(!timer_pending(&tmp_timer))
+	{
+		del_timer(&tmp_timer);
+		tmp_timer.expires = jiffies+1;
+		tmp_timer.data = (unsigned long)s->card;
+		add_timer(&tmp_timer);
+		M_printk("Starting bob (SW)\n");
+	}
+}
+
+#else
+
+/* nice HW BOB implementation.  cheers, eric. */
+
+static void stop_bob(struct ess_state *s)
+{
+	/* Mask IDR 11,17 */
+	maestro_write(s,  0x11, maestro_read(s, 0x11)&~1);
+	maestro_write(s,  0x17, maestro_read(s, 0x17)&~1);
+}
+
+/* eventually we could be clever and limit bob ints
+	to the frequency at which our smallest duration
+	chunks may expire */
+static void start_bob(struct ess_state *s)
+{
+	stop_bob(s);	// make sure bob's not already running
+	
+	maestro_write(s, 6, 0x8000 |(1<<12) | (5<<5) | 11);		// (50MHz/2^14)/12 = 254 Hz = 40 mS
+	
+	/* Now set IDR 11/17 */
+	maestro_write(s, 0x11, maestro_read(s, 0x11)|1);
+	maestro_write(s, 0x17, maestro_read(s, 0x17)|1);
+}
+#endif // ESS_HW_TIMER
+/* --------------------------------------------------------------------- */
+
+static int adc_active = 0;
+
+extern inline void stop_adc(struct ess_state *s)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&s->lock, flags);
+	/* Stop left and right recording APU */
+	s->enable &= ~ADC_RUNNING;
+	apu_set_register(s, 2, 0, apu_get_register(s, 2, 0)&0xFF0F);
+	apu_set_register(s, 3, 0, apu_get_register(s, 3, 0)&0xFF0F);
+	adc_active&=~1;
+//	if(!adc_active)
+//		stop_bob(s);
+	spin_unlock_irqrestore(&s->lock, flags);
+}	
+
+extern inline void stop_dac(struct ess_state *s)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&s->lock, flags);
+	s->enable &= ~DAC_RUNNING;
+	apu_set_register(s, 0, 0, apu_get_register(s, 0, 0)&0xFF0F);
+	apu_set_register(s, 1, 0, apu_get_register(s, 1, 0)&0xFF0F);
+	adc_active&=~2;
+//	if(!adc_active)
+//		stop_bob(s);
+	spin_unlock_irqrestore(&s->lock, flags);
+}	
+
+static void start_dac(struct ess_state *s)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&s->lock, flags);
+	if ((s->dma_dac.mapped || s->dma_dac.count > 0) && s->dma_dac.ready) {
+		s->enable |= DAC_RUNNING;
+
+		apu_set_register(s, 0, 0, 
+			(apu_get_register(s, 0, 0)&0xFF0F)|s->apu_mode[0]);
+
+		if(s->fmt & ESS_CFMT_STEREO) 
+			apu_set_register(s, 1, 0, 
+				(apu_get_register(s, 1, 0)&0xFF0F)|s->apu_mode[1]);
+	}
+//	if(!adc_active)
+//		start_bob(s);
+	adc_active|=2;
+	spin_unlock_irqrestore(&s->lock, flags);
+}	
+
+static void start_adc(struct ess_state *s)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&s->lock, flags);
+	if ((s->dma_adc.mapped || s->dma_adc.count < (signed)(s->dma_adc.dmasize - 2*s->dma_adc.fragsize)) 
+	    && s->dma_adc.ready) {
+		s->enable |= ADC_RUNNING;
+		apu_set_register(s, 2, 0, 
+			(apu_get_register(s, 2, 0)&0xFF0F)|s->apu_mode[2]);
+		apu_set_register(s, 3, 0, 
+			(apu_get_register(s, 3, 0)&0xFF0F)|s->apu_mode[3]);
+	}
+//	if(!adc_active)
+//		start_bob(s);
+	adc_active|=1;
+	spin_unlock_irqrestore(&s->lock, flags);
+}	
+
+/* --------------------------------------------------------------------- */
+
+#define DMABUF_DEFAULTORDER (15-PAGE_SHIFT)
+#define DMABUF_MINORDER 1
+
+static void dealloc_dmabuf(struct dmabuf *db)
+{
+	unsigned long map, mapend;
+
+	if (db->rawbuf) {
+		/* undo marking the pages as reserved */
+		mapend = MAP_NR(db->rawbuf + (PAGE_SIZE << db->buforder) - 1);
+		for (map = MAP_NR(db->rawbuf); map <= mapend; map++)
+			clear_bit(PG_reserved, &mem_map[map].flags);	
+		free_pages((unsigned long)db->rawbuf, db->buforder);
+	}
+	db->rawbuf = NULL;
+	db->mapped = db->ready = 0;
+}
+
+
+static int prog_dmabuf(struct ess_state *s, unsigned rec)
+{
+	struct dmabuf *db = rec ? &s->dma_adc : &s->dma_dac;
+	unsigned rate = rec ? s->rateadc : s->ratedac;
+	int order;
+	unsigned bytepersec;
+	unsigned bufs;
+	unsigned long map, mapend;
+	unsigned char fmt;
+	unsigned long flags;
+
+	spin_lock_irqsave(&s->lock, flags);
+	fmt = s->fmt;
+	if (rec) {
+		s->enable &= ~ESS_ENABLE_RE;
+		fmt >>= ESS_CFMT_CSHIFT;
+	} else {
+		s->enable &= ~ESS_ENABLE_PE;
+		fmt >>= ESS_CFMT_ASHIFT;
+	}
+	spin_unlock_irqrestore(&s->lock, flags);
+
+	db->hwptr = db->swptr = db->total_bytes = db->count = db->error = db->endcleared = 0;
+
+	if (!db->rawbuf) {
+		db->ready = db->mapped = 0;
+
+		/* alloc as big a chunk as we can */
+		for (order = DMABUF_DEFAULTORDER; order >= DMABUF_MINORDER && !db->rawbuf; order--)
+			db->rawbuf = (void *)__get_free_pages(GFP_KERNEL|GFP_DMA, order);
+
+		if (!db->rawbuf)
+			return -ENOMEM;
+
+		db->buforder = order;
+
+		if ((virt_to_bus(db->rawbuf) ^ (virt_to_bus(db->rawbuf) + (PAGE_SIZE << db->buforder) - 1)) & ~0xffff)
+			printk(KERN_DEBUG "maestro: DMA buffer crosses 64k boundary: busaddr 0x%lx  size %ld\n", 
+			       virt_to_bus(db->rawbuf), PAGE_SIZE << db->buforder);
+
+		if ((virt_to_bus(db->rawbuf) + (PAGE_SIZE << db->buforder) - 1) & ~0xffffff)
+			printk(KERN_DEBUG "maestro: DMA buffer beyond 16MB: busaddr 0x%lx  size %ld\n", 
+			       virt_to_bus(db->rawbuf), PAGE_SIZE << db->buforder);
+
+		/* now mark the pages as reserved; otherwise remap_page_range doesn't do what we want */
+		mapend = MAP_NR(db->rawbuf + (PAGE_SIZE << db->buforder) - 1);
+		for (map = MAP_NR(db->rawbuf); map <= mapend; map++)
+			set_bit(PG_reserved, &mem_map[map].flags);
+	}
+	bytepersec = rate << sample_shift[fmt];
+	bufs = PAGE_SIZE << db->buforder;
+	if (db->ossfragshift) {
+		if ((1000 << db->ossfragshift) < bytepersec)
+			db->fragshift = ld2(bytepersec/1000);
+		else
+			db->fragshift = db->ossfragshift;
+	} else {
+		db->fragshift = ld2(bytepersec/100/(db->subdivision ? db->subdivision : 1));
+		if (db->fragshift < 3)
+			db->fragshift = 3;
+	}
+	db->numfrag = bufs >> db->fragshift;
+	while (db->numfrag < 4 && db->fragshift > 3) {
+		db->fragshift--;
+		db->numfrag = bufs >> db->fragshift;
+	}
+	db->fragsize = 1 << db->fragshift;
+	if (db->ossmaxfrags >= 4 && db->ossmaxfrags < db->numfrag)
+		db->numfrag = db->ossmaxfrags;
+	db->fragsamples = db->fragsize >> sample_shift[fmt];
+	db->dmasize = db->numfrag << db->fragshift;
+	memset(db->rawbuf, (fmt & ESS_CFMT_16BIT) ? 0 : 0x80, db->dmasize);
+	spin_lock_irqsave(&s->lock, flags);
+	if (rec) {
+		set_dmac(s, virt_to_bus(db->rawbuf), db->numfrag << db->fragshift);
+		/* program enhanced mode registers */
+		/* FILL */
+	} else {
+		//set_dmaa(s, virt_to_bus(db->rawbuf), db->numfrag << db->fragshift);
+		/* program enhanced mode registers */
+		/* FILL */
+		//set_dac_rate(s, s->ratedac);	// redundant
+		ess_play_setup(s, fmt, s->ratedac, 
+			db->rawbuf, db->numfrag << db->fragshift);
+	}
+	spin_unlock_irqrestore(&s->lock, flags);
+	db->ready = 1;
+	return 0;
+}
+
+extern __inline__ void clear_advance(struct ess_state *s)
+{
+	unsigned char c = (s->fmt & (ESS_CFMT_16BIT << ESS_CFMT_ASHIFT)) ? 0 : 0x80;
+	unsigned char *buf = s->dma_dac.rawbuf;
+	unsigned bsize = s->dma_dac.dmasize;
+	unsigned bptr = s->dma_dac.swptr;
+	unsigned len = s->dma_dac.fragsize;
+
+	if (bptr + len > bsize) {
+		unsigned x = bsize - bptr;
+		memset(buf + bptr, c, x);
+		/* account for wrapping? */
+		bptr = 0;
+		len -= x;
+	}
+	memset(buf + bptr, c, len);
+}
+
+/* call with spinlock held! */
+static void ess_update_ptr(struct ess_state *s)
+{
+	unsigned hwptr;
+	int diff;
+
+	/* update ADC pointer */
+	if (s->dma_adc.ready) {
+		M_printk("adc ready.. \n");
+		hwptr = (/*s->dma_adc.dmasize - */get_dmac(s)) % s->dma_adc.dmasize;
+		diff = (s->dma_adc.dmasize + hwptr - s->dma_adc.hwptr) % s->dma_adc.dmasize;
+		s->dma_adc.hwptr = hwptr;
+		s->dma_adc.total_bytes += diff;
+		s->dma_adc.count += diff;
+		if (s->dma_adc.count >= (signed)s->dma_adc.fragsize) 
+			wake_up(&s->dma_adc.wait);
+		if (!s->dma_adc.mapped) {
+			if (s->dma_adc.count > (signed)(s->dma_adc.dmasize - ((3 * s->dma_adc.fragsize) >> 1))) {
+				s->enable &= ~ESS_ENABLE_RE;
+				/* FILL ME */
+//				wrindir(s, SV_CIENABLE, s->enable);
+				stop_adc(s);
+				s->dma_adc.error++;
+			}
+		}
+	}
+	/* update DAC pointer */
+	if (s->dma_dac.ready) {
+		hwptr = (/*s->dma_dac.dmasize -*/ get_dmaa(s)) % s->dma_dac.dmasize;
+		diff = (s->dma_dac.dmasize + hwptr - s->dma_dac.hwptr) % s->dma_dac.dmasize;
+/*		M_printk("updating dac: hwptr: %d diff: %d\n",hwptr,diff);*/
+		s->dma_dac.hwptr = hwptr;
+		s->dma_dac.total_bytes += diff;
+		if (s->dma_dac.mapped) {
+			s->dma_dac.count += diff;
+			if (s->dma_dac.count >= (signed)s->dma_dac.fragsize)
+				wake_up(&s->dma_dac.wait);
+		} else {
+			s->dma_dac.count -= diff;
+			if (s->dma_dac.count <= 0) {
+				s->enable &= ~ESS_ENABLE_PE;
+/* FILL ME */
+//				wrindir(s, SV_CIENABLE, s->enable);
+
+				stop_dac(s);
+				s->dma_dac.error++;
+			} else if (s->dma_dac.count <= (signed)s->dma_dac.fragsize && !s->dma_dac.endcleared) {
+				clear_advance(s);
+				s->dma_dac.endcleared = 1;
+			}
+			if (s->dma_dac.count + (signed)s->dma_dac.fragsize <= (signed)s->dma_dac.dmasize)
+				wake_up(&s->dma_dac.wait);
+		}
+	}
+}
+
+static void ess_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+        struct ess_state *s;
+        struct ess_card *c = (struct ess_card *)dev_id;
+	int i;
+	u32 event;
+
+	event = inb(c->iobase+0x1A);
+	
+	outw(inw(c->iobase+4)&1, c->iobase+4);
+
+/*	M_printk("maestro int: %x\n",event);*/
+
+	if(event&(1<<6))
+	{
+		/* XXX if we have a hw volume control int enable
+			all the ints?  doesn't make sense.. */
+		event = inw(c->iobase+0x18);
+		outb(0xFF, c->iobase+0x1A);
+	}
+	else
+	{
+		/* else ack 'em all, i imagine */
+		outb(0xFF, c->iobase+0x1A);
+	}
+		
+	/*
+	 *	Update the pointers for all APU's we are running.
+	 */
+	for(i=0;i<8;i++)
+	{
+		s=&c->channels[i];
+		if(s->dev_audio == -1)
+			break;
+		spin_lock(&s->lock);
+		ess_update_ptr(s);
+		spin_unlock(&s->lock);
+	}
+}
+
+
+/* --------------------------------------------------------------------- */
+
+static const char invalid_magic[] = KERN_CRIT "maestro: invalid magic value in %s\n";
+
+#define VALIDATE_MAGIC(FOO,MAG)                         \
+({                                                \
+	if (!(FOO) || (FOO)->magic != MAG) { \
+		printk(invalid_magic,__FUNCTION__);            \
+		return -ENXIO;                    \
+	}                                         \
+})
+
+#define VALIDATE_STATE(a) VALIDATE_MAGIC(a,ESS_STATE_MAGIC)
+#define VALIDATE_CARD(a) VALIDATE_MAGIC(a,ESS_CARD_MAGIC)
+
+/* --------------------------------------------------------------------- */
+
+/* ac97 mixer routines.  */
+
+#define AC97_STEREO_MASK (SOUND_MASK_VOLUME|\
+	SOUND_MASK_PCM|SOUND_MASK_LINE|SOUND_MASK_CD|\
+	SOUND_MASK_VIDEO|SOUND_MASK_LINE1|SOUND_MASK_IGAIN)
+
+#define AC97_SUPPORTED_MASK (AC97_STEREO_MASK | \
+	SOUND_MASK_BASS|SOUND_MASK_TREBLE|SOUND_MASK_MIC|\
+	SOUND_MASK_SPEAKER)
+
+#define AC97_RECORD_MASK (SOUND_MASK_MIC|\
+	SOUND_MASK_CD| SOUND_MASK_VIDEO| SOUND_MASK_LINE1| SOUND_MASK_LINE|\
+	SOUND_MASK_PHONEIN)
+
+#define supported_mixer(CARD,FOO) ( CARD->mix.supported_mixers & (1<<FOO) )
+
+static struct ac97_mixer_hw {
+	unsigned char offset;
+	int scale;
+} ac97_hw[SOUND_MIXER_NRDEVICES]= {
+	[SOUND_MIXER_VOLUME]	=	{0x02,63},
+	[SOUND_MIXER_BASS]	=	{0x08,15},
+	[SOUND_MIXER_TREBLE]	=	{0x08,15},
+	[SOUND_MIXER_SPEAKER]	=	{0x0a,15},
+	[SOUND_MIXER_MIC]	=	{0x0e,31},
+	[SOUND_MIXER_LINE]	=	{0x10,31},
+	[SOUND_MIXER_CD]	=	{0x12,31},
+	[SOUND_MIXER_VIDEO]	=	{0x14,31},
+	[SOUND_MIXER_LINE1]	=	{0x16,31},
+	[SOUND_MIXER_PCM]	=	{0x18,31},
+	[SOUND_MIXER_IGAIN]	=	{0x1c,31}
+};
+
+/* reads the given OSS mixer from the ac97
+	the caller must have insured that the ac97 knows
+	about that given mixer, and should be holding a
+	spinlock for the card */
+static int ac97_read_mixer(struct ess_card *card, int mixer) 
+{
+	u16 val;
+	int ret=0;
+	struct ac97_mixer_hw *mh = &ac97_hw[mixer];
+
+	val = maestro_ac97_get(card->iobase , mh->offset);
+
+	if(AC97_STEREO_MASK & (1<<mixer)) {
+		/* nice stereo mixers .. */
+		int left,right;
+
+		left = (val >> 8)  & 0x7f;
+		right = val  & 0x7f;
+
+		right = 100 - ((right * 100) / mh->scale);
+		left = 100 - ((left * 100) / mh->scale);
+		ret = left | (right << 8);
+	} else if (mixer == SOUND_MIXER_SPEAKER) {
+		ret = 100 - ((((val & 0x1e)>>1) * 100) / mh->scale);
+	} else if (mixer == SOUND_MIXER_MIC) {
+		ret = 100 - (((val & 0x1f) * 100) / mh->scale);
+	/*  the low bit is optional in the tone sliders and masking
+		it lets is avoid the 0xf 'bypass'.. */
+	} else if (mixer == SOUND_MIXER_BASS) {
+		ret = 100 - ((((val >> 8) & 0xe) * 100) / mh->scale);
+	} else if (mixer == SOUND_MIXER_TREBLE) {
+		ret = 100 - (((val & 0xe) * 100) / mh->scale);
+	}
+
+	M_printk("read mixer %d (0x%x) %x -> %x\n",mixer,mh->offset,val,ret);
+
+	return ret;
+}
+
+/* write the OSS encoded volume to the given OSS encoded mixer,
+	again caller's job to make sure all is well in arg land,
+	call with spinlock held */
+static void ac97_write_mixer(struct ess_card *card,int mixer, int vol) 
+{
+	u16 val=0;
+	unsigned left, right;
+	struct ac97_mixer_hw *mh = &ac97_hw[mixer];
+
+	/* cleanse input a little */
+	right = ((vol >> 8)  & 0x7f) ;
+	left = (vol  & 0x7f) ;
+
+	if(right > 100) right = 100;
+	if(left > 100) left = 100;
+
+	M_printk("wrote mixer %d (0x%x) %d,%d",mixer,mh->offset,left,right);
+
+	if(AC97_STEREO_MASK & (1<<mixer)) {
+		/* stereo mixers */
+
+		right = ((100 - right) * mh->scale) / 100;
+		left = ((100 - left) * mh->scale) / 100;
+
+		val = (left << 8) | right;
+	} else if (mixer == SOUND_MIXER_SPEAKER) {
+		val = (((100 - left) * mh->scale) / 100) << 1;
+	} else if (mixer == SOUND_MIXER_MIC) {
+		val = maestro_ac97_get(card->iobase , mh->offset) & ~0x001f;
+		val |= (((100 - left) * mh->scale) / 100);
+	/*  the low bit is optional in the tone sliders and masking
+		it lets is avoid the 0xf 'bypass'.. */
+	} else if (mixer == SOUND_MIXER_BASS) {
+		val = maestro_ac97_get(card->iobase , mh->offset) & ~0x0f00;
+		val |= ((((100 - left) * mh->scale) / 100) << 8) & 0xe0;
+	} else if (mixer == SOUND_MIXER_TREBLE)  {
+		val = maestro_ac97_get(card->iobase , mh->offset) & ~0x000f;
+		val |= (((100 - left) * mh->scale) / 100) & 0xe;
+	}
+
+	maestro_ac97_set(card->iobase , mh->offset, val);
+	
+	M_printk(" -> %x\n",val);
+}
+
+enum ac97_recsettings {
+	AC97_REC_MIC=0,
+	AC97_REC_CD,
+	AC97_REC_VIDEO,
+	AC97_REC_AUX,
+	AC97_REC_LINE,
+	AC97_REC_STEREO, /* combination of all enabled outputs..  */
+	AC97_REC_MONO,        /*.. or the mono equivalent */
+	AC97_REC_PHONE        
+};
+static unsigned int ac97_rm2oss[] = {
+	[AC97_REC_MIC] = SOUND_MASK_MIC, 
+	[AC97_REC_CD] = SOUND_MASK_CD, 
+	[AC97_REC_VIDEO] = SOUND_MASK_VIDEO, 
+	[AC97_REC_AUX] = SOUND_MASK_LINE1, 
+	[AC97_REC_LINE] = SOUND_MASK_LINE, 
+	[AC97_REC_PHONE] = SOUND_MASK_PHONEIN
+};
+
+/* indexed by bit position, XXX dependant on OSS header internals */
+static unsigned int ac97_oss_rm[] = {
+	[SOUND_MIXER_MIC] = AC97_REC_MIC,
+	[SOUND_MIXER_CD] = AC97_REC_CD,
+	[SOUND_MIXER_VIDEO] = AC97_REC_VIDEO,
+	[SOUND_MIXER_LINE1] = AC97_REC_AUX,
+	[SOUND_MIXER_LINE] = AC97_REC_LINE,
+	[SOUND_MIXER_PHONEIN] = AC97_REC_PHONE
+};
+	
+/* read or write the recmask 
+	the ac97 can really have left and right recording
+	inputs independantly set, but OSS doesn't seem to 
+	want us to express that to the user. 
+	the caller guarantees that we have a supported bit set,
+	and they must be holding the card's spinlock */
+static int ac97_recmask_io(struct ess_card *card, int rw, int mask) 
+{
+	unsigned int val;
+
+	if (rw) {
+		/* read it from the card */
+		val = maestro_ac97_get(card->iobase, 0x1a) & 0x7;
+		return ac97_rm2oss[val];
+	}
+
+	/* else, write the first set in the mask as the
+		output */	
+
+	val = ffs(mask); 
+	val = ac97_oss_rm[val-1];
+	val |= val << 8;  /* set both channels */
+
+	maestro_ac97_set(card->iobase,0x1a,val);
+
+	return 0;
+};
+
+static int ac97_mixer_ioctl(struct ess_card *card, unsigned int cmd, unsigned long arg)
+{
+	unsigned long flags;
+	int i, val=0;
+	struct ess_state *s = &card->channels[0];
+
+	VALIDATE_CARD(card);
+        if (cmd == SOUND_MIXER_INFO) {
+		mixer_info info;
+		strncpy(info.id, card_names[card->card_type], sizeof(info.id));
+		strncpy(info.name,card_names[card->card_type],sizeof(info.name));
+		info.modify_counter = card->mix.modcnt;
+		if (copy_to_user((void *)arg, &info, sizeof(info)))
+			return -EFAULT;
+		return 0;
+	}
+	if (cmd == SOUND_OLD_MIXER_INFO) {
+		_old_mixer_info info;
+		strncpy(info.id, card_names[card->card_type], sizeof(info.id));
+		strncpy(info.name,card_names[card->card_type],sizeof(info.name));
+		if (copy_to_user((void *)arg, &info, sizeof(info)))
+			return -EFAULT;
+		return 0;
+	}
+	if (cmd == OSS_GETVERSION)
+		return put_user(SOUND_VERSION, (int *)arg);
+
+	if (_IOC_TYPE(cmd) != 'M' || _IOC_SIZE(cmd) != sizeof(int))
+                return -EINVAL;
+
+        if (_IOC_DIR(cmd) == _IOC_READ) {
+                switch (_IOC_NR(cmd)) {
+                case SOUND_MIXER_RECSRC: /* give them the current record source */
+			spin_lock_irqsave(&s->lock, flags);
+			val = card->mix.recmask_io(card,1,0);
+			spin_unlock_irqrestore(&s->lock, flags);
+			break;
+			
+                case SOUND_MIXER_DEVMASK: /* give them the supported mixers */
+			val = card->mix.supported_mixers;
+			break;
+
+                case SOUND_MIXER_RECMASK: /* Arg contains a bit for each supported recording source */
+			val = card->mix.record_sources;
+			break;
+			
+                case SOUND_MIXER_STEREODEVS: /* Mixer channels supporting stereo */
+			val = card->mix.stereo_mixers;
+			break;
+			
+                case SOUND_MIXER_CAPS:
+			val = SOUND_CAP_EXCL_INPUT;
+			break;
+
+		default: /* read a specific mixer */
+			i = _IOC_NR(cmd);
+
+			if ( ! supported_mixer(card,i)) 
+				return -EINVAL;
+
+			spin_lock_irqsave(&s->lock, flags);
+			val = card->mix.read_mixer(card,i);
+			spin_unlock_irqrestore(&s->lock, flags);
+
+			break;
+		}
+		return put_user(val,(int *)arg);
+	}
+	
+        if (_IOC_DIR(cmd) != (_IOC_WRITE|_IOC_READ))
+		return -EINVAL;
+	
+	card->mix.modcnt++;
+
+	get_user_ret(val, (int *)arg, -EFAULT);
+
+	switch (_IOC_NR(cmd)) {
+	case SOUND_MIXER_RECSRC: /* Arg contains a bit for each recording source */
+
+		if(! (val &= card->mix.record_sources)) return -EINVAL;
+
+		spin_lock_irqsave(&s->lock, flags);
+		card->mix.recmask_io(card,0,val);
+		spin_unlock_irqrestore(&s->lock, flags);
+		return 0;
+
+	default:
+		i = _IOC_NR(cmd);
+
+		if ( ! supported_mixer(card,i)) 
+			return -EINVAL;
+
+		spin_lock_irqsave(&s->lock, flags);
+		card->mix.write_mixer(card,i,val);
+		spin_unlock_irqrestore(&s->lock, flags);
+
+		return 0;
+	}
+}
+
+/* --------------------------------------------------------------------- */
+
+static loff_t ess_llseek(struct file *file, loff_t offset, int origin)
+{
+	return -ESPIPE;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int ess_open_mixdev(struct inode *inode, struct file *file)
+{
+	int minor = MINOR(inode->i_rdev);
+	struct ess_card *card = devs;
+
+	while (card && card->dev_mixer != minor)
+		card = card->next;
+	if (!card)
+		return -ENODEV;
+
+	file->private_data = card;
+	MOD_INC_USE_COUNT;
+	return 0;
+}
+
+static int ess_release_mixdev(struct inode *inode, struct file *file)
+{
+	struct ess_card *card = (struct ess_card *)file->private_data;
+
+	VALIDATE_CARD(card);
+	
+	MOD_DEC_USE_COUNT;
+	return 0;
+}
+
+static int ess_ioctl_mixdev(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct ess_card *card = (struct ess_card *)file->private_data;
+
+	VALIDATE_CARD(card);
+
+	return ac97_mixer_ioctl(card, cmd, arg);
+}
+
+static /*const*/ struct file_operations ess_mixer_fops = {
+	&ess_llseek,
+	NULL,  /* read */
+	NULL,  /* write */
+	NULL,  /* readdir */
+	NULL,  /* poll */
+	&ess_ioctl_mixdev,
+	NULL,  /* mmap */
+	&ess_open_mixdev,
+	NULL,	/* flush */
+	&ess_release_mixdev,
+	NULL,  /* fsync */
+	NULL,  /* fasync */
+	NULL,  /* check_media_change */
+	NULL,  /* revalidate */
+	NULL,  /* lock */
+};
+
+/* --------------------------------------------------------------------- */
+
+static int drain_dac(struct ess_state *s, int nonblock)
+{
+        DECLARE_WAITQUEUE(wait,current);
+	unsigned long flags;
+	int count, tmo;
+
+	if (s->dma_dac.mapped || !s->dma_dac.ready)
+		return 0;
+        current->state = TASK_INTERRUPTIBLE;
+        add_wait_queue(&s->dma_dac.wait, &wait);
+        for (;;) {
+                spin_lock_irqsave(&s->lock, flags);
+		count = s->dma_dac.count;
+                spin_unlock_irqrestore(&s->lock, flags);
+		if (count <= 0)
+			break;
+		if (signal_pending(current))
+                        break;
+                if (nonblock) {
+                        remove_wait_queue(&s->dma_dac.wait, &wait);
+                        current->state = TASK_RUNNING;
+                        return -EBUSY;
+                }
+		tmo = (count * HZ) / s->ratedac;
+		tmo >>= sample_shift[(s->fmt >> ESS_CFMT_ASHIFT) & ESS_CFMT_MASK];
+		if (!schedule_timeout(tmo ? : 1) && tmo)
+			printk(KERN_DEBUG "maestro: dma timed out??\n");
+        }
+        remove_wait_queue(&s->dma_dac.wait, &wait);
+        current->state = TASK_RUNNING;
+        if (signal_pending(current))
+                return -ERESTARTSYS;
+        return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+static ssize_t ess_read(struct file *file, char *buffer, size_t count, loff_t *ppos)
+{
+	struct ess_state *s = (struct ess_state *)file->private_data;
+	ssize_t ret;
+	unsigned long flags;
+	unsigned swptr;
+	/* for damned dual players */
+	int cnt;
+
+	VALIDATE_STATE(s);
+	if (ppos != &file->f_pos)
+		return -ESPIPE;
+	if (s->dma_adc.mapped)
+		return -ENXIO;
+	if (!s->dma_adc.ready && (ret = prog_dmabuf(s, 1)))
+		return ret;
+	if (!access_ok(VERIFY_WRITE, buffer, count))
+		return -EFAULT;
+	ret = 0;
+#if 0
+   spin_lock_irqsave(&s->lock, flags);
+   ess_update_ptr(s);
+   spin_unlock_irqrestore(&s->lock, flags);
+#endif
+	while (count > 0) {
+		spin_lock_irqsave(&s->lock, flags);
+		swptr = s->dma_adc.swptr;
+		cnt = s->dma_adc.dmasize-swptr;
+		if (s->dma_adc.count < cnt)
+			cnt = s->dma_adc.count;
+		spin_unlock_irqrestore(&s->lock, flags);
+		if (cnt > count)
+			cnt = count;
+		if (cnt <= 0) {
+			start_adc(s);
+			if (file->f_flags & O_NONBLOCK) 
+				return ret ? ret : -EAGAIN;
+			if (!interruptible_sleep_on_timeout(&s->dma_adc.wait, HZ)) {
+				printk(KERN_DEBUG "maestro: read: chip lockup? dmasz %u fragsz %u count %i hwptr %u swptr %u\n",
+				       s->dma_adc.dmasize, s->dma_adc.fragsize, s->dma_adc.count, 
+				       s->dma_adc.hwptr, s->dma_adc.swptr);
+				stop_adc(s);
+				spin_lock_irqsave(&s->lock, flags);
+				set_dmac(s, virt_to_bus(s->dma_adc.rawbuf), s->dma_adc.numfrag << s->dma_adc.fragshift);
+				/* program enhanced mode registers */
+				/* FILL ME */
+//				wrindir(s, SV_CIDMACBASECOUNT1, (s->dma_adc.fragsamples-1) >> 8);
+//				wrindir(s, SV_CIDMACBASECOUNT0, s->dma_adc.fragsamples-1);
+				s->dma_adc.count = s->dma_adc.hwptr = s->dma_adc.swptr = 0;
+				spin_unlock_irqrestore(&s->lock, flags);
+			}
+			if (signal_pending(current)) 
+				return ret ? ret : -ERESTARTSYS;
+			continue;
+		}
+		if (copy_to_user(buffer, s->dma_adc.rawbuf + swptr, cnt)) 
+			return ret ? ret : -EFAULT;
+		swptr = (swptr + cnt) % s->dma_adc.dmasize;
+		spin_lock_irqsave(&s->lock, flags);
+		s->dma_adc.swptr = swptr;
+		s->dma_adc.count -= cnt;
+		spin_unlock_irqrestore(&s->lock, flags);
+		count -= cnt;
+		buffer += cnt;
+		ret += cnt;
+		start_adc(s);
+	}
+
+	return ret;
+}
+
+/* god this is gross..*/
+int split_stereo(unsigned char *real_buffer,unsigned char  *tmp_buffer, int offset, 
+	int count, int bufsize, int mode)
+{  
+	/* oh, bother.	stereo decoding APU's don't work in 16bit so we
+	use dual linear decoders.  which means we have to hack up stereo
+	buffer's we're given.  yuck. 
+
+	and we have to be able to work a byte at a time..*/
+
+	unsigned char *so,*left,*right;
+	int i;
+
+	so = tmp_buffer;
+	left = real_buffer + offset;
+	right = real_buffer + bufsize/2 + offset;
+
+	M_printk("writing %d to %p and %p from %p:%d bufs: %d\n",count/2, left,right,real_buffer,offset,bufsize);
+
+	if(mode & ESS_CFMT_16BIT) {
+		for(i=count/4; i ; i--) {
+			*(right++) = (*(so+2));
+			*(right++) = (*(so+3));
+			*(left++) = (*so);
+			*(left++) = (*(so+1));
+			so+=4;
+		}
+	} else {
+		for(i=count/2; i ; i--) {
+			*(right++) = (*(so+1));
+			*(left++) = (*so);
+			so+=2;
+		}
+	}
+
+	return 0;
+}
+
+static ssize_t ess_write(struct file *file, const char *buffer, size_t count, loff_t *ppos)
+{
+	struct ess_state *s = (struct ess_state *)file->private_data;
+	ssize_t ret;
+	unsigned long flags;
+	unsigned swptr;
+	unsigned char *splitbuf = NULL;
+	int cnt;
+
+	VALIDATE_STATE(s);
+	if (ppos != &file->f_pos)
+		return -ESPIPE;
+	if (s->dma_dac.mapped)
+		return -ENXIO;
+	if (!s->dma_dac.ready && (ret = prog_dmabuf(s, 0)))
+		return ret;
+	if (!access_ok(VERIFY_READ, buffer, count))
+		return -EFAULT;
+	/* I wish we could be more clever than this */
+	if (!(splitbuf = kmalloc(count,GFP_KERNEL)))
+		return -ENOMEM; 
+	ret = 0;
+#if 0
+   spin_lock_irqsave(&s->lock, flags);
+   ess_update_ptr(s);
+   spin_unlock_irqrestore(&s->lock, flags);
+#endif
+	while (count > 0) {
+		spin_lock_irqsave(&s->lock, flags);
+
+		if (s->dma_dac.count < 0) {
+			s->dma_dac.count = 0;
+			s->dma_dac.swptr = s->dma_dac.hwptr;
+		}
+		swptr = s->dma_dac.swptr;
+
+		if(s->fmt & ESS_CFMT_STEREO) {
+			/* in stereo we have the 'dual' buffers.. */
+			cnt = ((s->dma_dac.dmasize/2)-swptr)*2;
+		} else {
+			cnt = s->dma_dac.dmasize-swptr;
+		}
+		if (s->dma_dac.count + cnt > s->dma_dac.dmasize)
+			cnt = s->dma_dac.dmasize - s->dma_dac.count;
+
+		spin_unlock_irqrestore(&s->lock, flags);
+
+		if (cnt > count)
+			cnt = count;
+
+		/* our goofball stereo splitter can only deal in mults of 4 */
+		if (cnt > 0) 
+			cnt &= ~3;
+
+		if (cnt <= 0) {
+			/* buffer is full, wait for it to be played */
+			start_dac(s);
+			if (file->f_flags & O_NONBLOCK) {
+				if(!ret) ret = -EAGAIN;
+				goto return_free;
+			}
+			if (!interruptible_sleep_on_timeout(&s->dma_dac.wait, HZ)) {
+				printk(KERN_DEBUG "maestro: write: chip lockup? dmasz %u fragsz %u count %i hwptr %u swptr %u\n",
+				       s->dma_dac.dmasize, s->dma_dac.fragsize, s->dma_dac.count, 
+				       s->dma_dac.hwptr, s->dma_dac.swptr);
+				stop_dac(s);
+				spin_lock_irqsave(&s->lock, flags);
+				set_dmaa(s, virt_to_bus(s->dma_dac.rawbuf), s->dma_dac.numfrag << s->dma_dac.fragshift);
+				/* program enhanced mode registers */
+//				wrindir(s, SV_CIDMAABASECOUNT1, (s->dma_dac.fragsamples-1) >> 8);
+//				wrindir(s, SV_CIDMAABASECOUNT0, s->dma_dac.fragsamples-1);
+				/* FILL ME */
+				s->dma_dac.count = s->dma_dac.hwptr = s->dma_dac.swptr = 0;
+				spin_unlock_irqrestore(&s->lock, flags);
+			}
+			if (signal_pending(current)) {
+				if (!ret) ret = -ERESTARTSYS;
+				goto return_free;
+			}
+			continue;
+		}
+		if(s->fmt & ESS_CFMT_STEREO) {
+			if (copy_from_user(splitbuf, buffer, cnt)) {
+				if (!ret) ret = -EFAULT;
+				goto return_free;
+			}
+			split_stereo(s->dma_dac.rawbuf,splitbuf,swptr,cnt,s->dma_dac.dmasize,s->fmt);
+		} else {
+			if (copy_from_user(s->dma_dac.rawbuf + swptr, buffer, cnt)) {
+				if (!ret) ret = -EFAULT;
+				goto return_free;
+			}
+		}
+
+		if(s->fmt & ESS_CFMT_STEREO) {
+			/* again with the weird pointer magic*/
+			swptr = (swptr + (cnt/2)) % (s->dma_dac.dmasize/2);
+		} else {
+			swptr = (swptr + cnt) % s->dma_dac.dmasize;
+		}
+		spin_lock_irqsave(&s->lock, flags);
+		s->dma_dac.swptr = swptr;
+		s->dma_dac.count += cnt;
+		s->dma_dac.endcleared = 0;
+		spin_unlock_irqrestore(&s->lock, flags);
+		count -= cnt;
+		buffer += cnt;
+		ret += cnt;
+		start_dac(s);
+	}
+return_free:
+	if (splitbuf) kfree(splitbuf);
+	return ret;
+}
+
+static unsigned int ess_poll(struct file *file, struct poll_table_struct *wait)
+{
+	struct ess_state *s = (struct ess_state *)file->private_data;
+	unsigned long flags;
+	unsigned int mask = 0;
+
+	VALIDATE_STATE(s);
+	if (file->f_mode & FMODE_WRITE)
+		poll_wait(file, &s->dma_dac.wait, wait);
+	if (file->f_mode & FMODE_READ)
+		poll_wait(file, &s->dma_adc.wait, wait);
+	spin_lock_irqsave(&s->lock, flags);
+	ess_update_ptr(s);
+	if (file->f_mode & FMODE_READ) {
+		if (s->dma_adc.count >= (signed)s->dma_adc.fragsize)
+			mask |= POLLIN | POLLRDNORM;
+	}
+	if (file->f_mode & FMODE_WRITE) {
+		if (s->dma_dac.mapped) {
+			if (s->dma_dac.count >= (signed)s->dma_dac.fragsize) 
+				mask |= POLLOUT | POLLWRNORM;
+		} else {
+			if ((signed)s->dma_dac.dmasize >= s->dma_dac.count + (signed)s->dma_dac.fragsize)
+				mask |= POLLOUT | POLLWRNORM;
+		}
+	}
+	spin_unlock_irqrestore(&s->lock, flags);
+	return mask;
+}
+
+/* this needs to be fixed to deal with the dualing apus/buffers */
+#if 0
+static int ess_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	struct ess_state *s = (struct ess_state *)file->private_data;
+	struct dmabuf *db;
+	int ret;
+	unsigned long size;
+
+	VALIDATE_STATE(s);
+	if (vma->vm_flags & VM_WRITE) {
+		if ((ret = prog_dmabuf(s, 1)) != 0)
+			return ret;
+		db = &s->dma_dac;
+	} else if (vma->vm_flags & VM_READ) {
+		if ((ret = prog_dmabuf(s, 0)) != 0)
+			return ret;
+		db = &s->dma_adc;
+	} else 
+		return -EINVAL;
+	if (vma->vm_offset != 0)
+		return -EINVAL;
+	size = vma->vm_end - vma->vm_start;
+	if (size > (PAGE_SIZE << db->buforder))
+		return -EINVAL;
+	if (remap_page_range(vma->vm_start, virt_to_phys(db->rawbuf), size, vma->vm_page_prot))
+		return -EAGAIN;
+	db->mapped = 1;
+	return 0;
+}
+#endif
+
+static int ess_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct ess_state *s = (struct ess_state *)file->private_data;
+	unsigned long flags;
+        audio_buf_info abinfo;
+        count_info cinfo;
+	int val, mapped, ret;
+	unsigned char fmtm, fmtd;
+
+	VALIDATE_STATE(s);
+        mapped = ((file->f_mode & FMODE_WRITE) && s->dma_dac.mapped) ||
+		((file->f_mode & FMODE_READ) && s->dma_adc.mapped);
+	switch (cmd) {
+	case OSS_GETVERSION:
+		return put_user(SOUND_VERSION, (int *)arg);
+
+	case SNDCTL_DSP_SYNC:
+		if (file->f_mode & FMODE_WRITE)
+			return drain_dac(s, 0/*file->f_flags & O_NONBLOCK*/);
+		return 0;
+		
+	case SNDCTL_DSP_SETDUPLEX:
+		return 0;
+
+	case SNDCTL_DSP_GETCAPS:
+		return put_user(0/*DSP_CAP_DUPLEX | DSP_CAP_REALTIME | DSP_CAP_TRIGGER | DSP_CAP_MMAP*/, (int *)arg);
+		
+        case SNDCTL_DSP_RESET:
+		if (file->f_mode & FMODE_WRITE) {
+			stop_dac(s);
+			synchronize_irq();
+			s->dma_dac.swptr = s->dma_dac.hwptr = s->dma_dac.count = s->dma_dac.total_bytes = 0;
+		}
+		if (file->f_mode & FMODE_READ) {
+			stop_adc(s);
+			synchronize_irq();
+			s->dma_adc.swptr = s->dma_adc.hwptr = s->dma_adc.count = s->dma_adc.total_bytes = 0;
+		}
+		return 0;
+
+        case SNDCTL_DSP_SPEED:
+                get_user_ret(val, (int *)arg, -EFAULT);
+		if (val >= 0) {
+			if (file->f_mode & FMODE_READ) {
+				stop_adc(s);
+				s->dma_adc.ready = 0;
+				set_adc_rate(s, val);
+			}
+			if (file->f_mode & FMODE_WRITE) {
+				stop_dac(s);
+				s->dma_dac.ready = 0;
+				set_dac_rate(s, val);
+			}
+		}
+		return put_user((file->f_mode & FMODE_READ) ? s->rateadc : s->ratedac, (int *)arg);
+		
+        case SNDCTL_DSP_STEREO:
+		get_user_ret(val, (int *)arg, -EFAULT);
+		fmtd = 0;
+		fmtm = ~0;
+		if (file->f_mode & FMODE_READ) {
+			stop_adc(s);
+			s->dma_adc.ready = 0;
+			if (val)
+				fmtd |= ESS_CFMT_STEREO << ESS_CFMT_CSHIFT;
+			else
+				fmtm &= ~(ESS_CFMT_STEREO << ESS_CFMT_CSHIFT);
+		}
+		if (file->f_mode & FMODE_WRITE) {
+			stop_dac(s);
+			s->dma_dac.ready = 0;
+			if (val)
+				fmtd |= ESS_CFMT_STEREO << ESS_CFMT_ASHIFT;
+			else
+				fmtm &= ~(ESS_CFMT_STEREO << ESS_CFMT_ASHIFT);
+		}
+		set_fmt(s, fmtm, fmtd);
+		return 0;
+
+        case SNDCTL_DSP_CHANNELS:
+                get_user_ret(val, (int *)arg, -EFAULT);
+		if (val != 0) {
+			fmtd = 0;
+			fmtm = ~0;
+			if (file->f_mode & FMODE_READ) {
+				stop_adc(s);
+				s->dma_adc.ready = 0;
+				if (val >= 2)
+					fmtd |= ESS_CFMT_STEREO << ESS_CFMT_CSHIFT;
+				else
+					fmtm &= ~(ESS_CFMT_STEREO << ESS_CFMT_CSHIFT);
+			}
+			if (file->f_mode & FMODE_WRITE) {
+				stop_dac(s);
+				s->dma_dac.ready = 0;
+				if (val >= 2)
+					fmtd |= ESS_CFMT_STEREO << ESS_CFMT_ASHIFT;
+				else
+					fmtm &= ~(ESS_CFMT_STEREO << ESS_CFMT_ASHIFT);
+			}
+			set_fmt(s, fmtm, fmtd);
+		}
+		return put_user((s->fmt & ((file->f_mode & FMODE_READ) ? (ESS_CFMT_STEREO << ESS_CFMT_CSHIFT) 
+					   : (ESS_CFMT_STEREO << ESS_CFMT_ASHIFT))) ? 2 : 1, (int *)arg);
+		
+	case SNDCTL_DSP_GETFMTS: /* Returns a mask */
+                return put_user(AFMT_S8|AFMT_S16_LE, (int *)arg);
+		
+	case SNDCTL_DSP_SETFMT: /* Selects ONE fmt*/
+		get_user_ret(val, (int *)arg, -EFAULT);
+		if (val != AFMT_QUERY) {
+			fmtd = 0;
+			fmtm = ~0;
+			if (file->f_mode & FMODE_READ) {
+				stop_adc(s);
+				s->dma_adc.ready = 0;
+				if (val == AFMT_S16_LE)
+					fmtd |= ESS_CFMT_16BIT << ESS_CFMT_CSHIFT;
+				else
+					fmtm &= ~(ESS_CFMT_16BIT << ESS_CFMT_CSHIFT);
+			}
+			if (file->f_mode & FMODE_WRITE) {
+				stop_dac(s);
+				s->dma_dac.ready = 0;
+				if (val == AFMT_S16_LE)
+					fmtd |= ESS_CFMT_16BIT << ESS_CFMT_ASHIFT;
+				else
+					fmtm &= ~(ESS_CFMT_16BIT << ESS_CFMT_ASHIFT);
+			}
+			set_fmt(s, fmtm, fmtd);
+		}
+ 		return put_user((s->fmt & ((file->f_mode & FMODE_READ) ? 
+			(ESS_CFMT_16BIT << ESS_CFMT_CSHIFT) 
+			: (ESS_CFMT_16BIT << ESS_CFMT_ASHIFT))) ? 
+				AFMT_S16_LE : 
+				AFMT_S8, 
+			(int *)arg);
+		
+	case SNDCTL_DSP_POST:
+                return 0;
+
+        case SNDCTL_DSP_GETTRIGGER:
+		val = 0;
+		if (file->f_mode & FMODE_READ && s->enable & ESS_ENABLE_RE) 
+			val |= PCM_ENABLE_INPUT;
+		if (file->f_mode & FMODE_WRITE && s->enable & ESS_ENABLE_PE) 
+			val |= PCM_ENABLE_OUTPUT;
+		return put_user(val, (int *)arg);
+		
+	case SNDCTL_DSP_SETTRIGGER:
+		get_user_ret(val, (int *)arg, -EFAULT);
+		if (file->f_mode & FMODE_READ) {
+			if (val & PCM_ENABLE_INPUT) {
+				if (!s->dma_adc.ready && (ret =  prog_dmabuf(s, 1)))
+					return ret;
+				start_adc(s);
+			} else
+				stop_adc(s);
+		}
+		if (file->f_mode & FMODE_WRITE) {
+			if (val & PCM_ENABLE_OUTPUT) {
+				if (!s->dma_dac.ready && (ret = prog_dmabuf(s, 0)))
+					return ret;
+				start_dac(s);
+			} else
+				stop_dac(s);
+		}
+		return 0;
+
+	case SNDCTL_DSP_GETOSPACE:
+		if (!(file->f_mode & FMODE_WRITE))
+			return -EINVAL;
+		if (!(s->enable & ESS_ENABLE_PE) && (val = prog_dmabuf(s, 0)) != 0)
+			return val;
+		spin_lock_irqsave(&s->lock, flags);
+		ess_update_ptr(s);
+		abinfo.fragsize = s->dma_dac.fragsize;
+                abinfo.bytes = s->dma_dac.dmasize - s->dma_dac.count;
+                abinfo.fragstotal = s->dma_dac.numfrag;
+                abinfo.fragments = abinfo.bytes >> s->dma_dac.fragshift;      
+		spin_unlock_irqrestore(&s->lock, flags);
+		return copy_to_user((void *)arg, &abinfo, sizeof(abinfo)) ? -EFAULT : 0;
+
+	case SNDCTL_DSP_GETISPACE:
+		if (!(file->f_mode & FMODE_READ))
+			return -EINVAL;
+		if (!(s->enable & ESS_ENABLE_RE) && (val = prog_dmabuf(s, 1)) != 0)
+			return val;
+		spin_lock_irqsave(&s->lock, flags);
+		ess_update_ptr(s);
+		abinfo.fragsize = s->dma_adc.fragsize;
+                abinfo.bytes = s->dma_adc.count;
+                abinfo.fragstotal = s->dma_adc.numfrag;
+                abinfo.fragments = abinfo.bytes >> s->dma_adc.fragshift;      
+		spin_unlock_irqrestore(&s->lock, flags);
+		return copy_to_user((void *)arg, &abinfo, sizeof(abinfo)) ? -EFAULT : 0;
+		
+        case SNDCTL_DSP_NONBLOCK:
+                file->f_flags |= O_NONBLOCK;
+                return 0;
+
+        case SNDCTL_DSP_GETODELAY:
+		if (!(file->f_mode & FMODE_WRITE))
+			return -EINVAL;
+		spin_lock_irqsave(&s->lock, flags);
+		ess_update_ptr(s);
+                val = s->dma_dac.count;
+		spin_unlock_irqrestore(&s->lock, flags);
+		return put_user(val, (int *)arg);
+
+        case SNDCTL_DSP_GETIPTR:
+		if (!(file->f_mode & FMODE_READ))
+			return -EINVAL;
+		spin_lock_irqsave(&s->lock, flags);
+		ess_update_ptr(s);
+                cinfo.bytes = s->dma_adc.total_bytes;
+                cinfo.blocks = s->dma_adc.count >> s->dma_adc.fragshift;
+                cinfo.ptr = s->dma_adc.hwptr;
+		if (s->dma_adc.mapped)
+			s->dma_adc.count &= s->dma_adc.fragsize-1;
+		spin_unlock_irqrestore(&s->lock, flags);
+                return copy_to_user((void *)arg, &cinfo, sizeof(cinfo));
+
+        case SNDCTL_DSP_GETOPTR:
+		if (!(file->f_mode & FMODE_WRITE))
+			return -EINVAL;
+		spin_lock_irqsave(&s->lock, flags);
+		ess_update_ptr(s);
+                cinfo.bytes = s->dma_dac.total_bytes;
+                cinfo.blocks = s->dma_dac.count >> s->dma_dac.fragshift;
+                cinfo.ptr = s->dma_dac.hwptr;
+		if (s->dma_dac.mapped)
+			s->dma_dac.count &= s->dma_dac.fragsize-1;
+		spin_unlock_irqrestore(&s->lock, flags);
+                return copy_to_user((void *)arg, &cinfo, sizeof(cinfo));
+
+        case SNDCTL_DSP_GETBLKSIZE:
+		if (file->f_mode & FMODE_WRITE) {
+			if ((val = prog_dmabuf(s, 0)))
+				return val;
+			return put_user(s->dma_dac.fragsize, (int *)arg);
+		}
+		if ((val = prog_dmabuf(s, 1)))
+			return val;
+		return put_user(s->dma_adc.fragsize, (int *)arg);
+
+        case SNDCTL_DSP_SETFRAGMENT:
+                get_user_ret(val, (int *)arg, -EFAULT);
+		if (file->f_mode & FMODE_READ) {
+			s->dma_adc.ossfragshift = val & 0xffff;
+			s->dma_adc.ossmaxfrags = (val >> 16) & 0xffff;
+			if (s->dma_adc.ossfragshift < 4)
+				s->dma_adc.ossfragshift = 4;
+			if (s->dma_adc.ossfragshift > 15)
+				s->dma_adc.ossfragshift = 15;
+			if (s->dma_adc.ossmaxfrags < 4)
+				s->dma_adc.ossmaxfrags = 4;
+		}
+		if (file->f_mode & FMODE_WRITE) {
+			s->dma_dac.ossfragshift = val & 0xffff;
+			s->dma_dac.ossmaxfrags = (val >> 16) & 0xffff;
+			if (s->dma_dac.ossfragshift < 4)
+				s->dma_dac.ossfragshift = 4;
+			if (s->dma_dac.ossfragshift > 15)
+				s->dma_dac.ossfragshift = 15;
+			if (s->dma_dac.ossmaxfrags < 4)
+				s->dma_dac.ossmaxfrags = 4;
+		}
+		return 0;
+
+        case SNDCTL_DSP_SUBDIVIDE:
+		if ((file->f_mode & FMODE_READ && s->dma_adc.subdivision) ||
+		    (file->f_mode & FMODE_WRITE && s->dma_dac.subdivision))
+			return -EINVAL;
+                get_user_ret(val, (int *)arg, -EFAULT);
+		if (val != 1 && val != 2 && val != 4)
+			return -EINVAL;
+		if (file->f_mode & FMODE_READ)
+			s->dma_adc.subdivision = val;
+		if (file->f_mode & FMODE_WRITE)
+			s->dma_dac.subdivision = val;
+		return 0;
+
+        case SOUND_PCM_READ_RATE:
+		return put_user((file->f_mode & FMODE_READ) ? s->rateadc : s->ratedac, (int *)arg);
+
+        case SOUND_PCM_READ_CHANNELS:
+		return put_user((s->fmt & ((file->f_mode & FMODE_READ) ? (ESS_CFMT_STEREO << ESS_CFMT_CSHIFT) 
+					   : (ESS_CFMT_STEREO << ESS_CFMT_ASHIFT))) ? 2 : 1, (int *)arg);
+
+        case SOUND_PCM_READ_BITS:
+		return put_user((s->fmt & ((file->f_mode & FMODE_READ) ? (ESS_CFMT_16BIT << ESS_CFMT_CSHIFT) 
+					   : (ESS_CFMT_16BIT << ESS_CFMT_ASHIFT))) ? 16 : 8, (int *)arg);
+
+        case SOUND_PCM_WRITE_FILTER:
+        case SNDCTL_DSP_SETSYNCRO:
+        case SOUND_PCM_READ_FILTER:
+                return -EINVAL;
+		
+	}
+//	return mixer_ioctl(s, cmd, arg);
+	return -EINVAL;
+}
+
+static int ess_open(struct inode *inode, struct file *file)
+{
+	int minor = MINOR(inode->i_rdev);
+	struct ess_card *c = devs;
+	struct ess_state *s = NULL, *sp;
+	int i;
+	unsigned char fmtm = ~0, fmts = 0;
+
+	/*
+	 *	Scan the cards and find the channel. We only
+	 *	do this at open time so it is ok
+	 */
+	 
+	while (c!=NULL)
+	{
+		for(i=0;i<8;i++)
+		{
+			sp=&c->channels[i];
+			if(sp->dev_audio < 0)
+				continue;
+			if((sp->dev_audio ^ minor) & ~0xf)
+				continue;
+			s=sp;
+		}
+		c=c->next;
+	}
+		
+	if (!s)
+		return -ENODEV;
+		
+       	VALIDATE_STATE(s);
+	file->private_data = s;
+	/* wait for device to become free */
+	down(&s->open_sem);
+	while (s->open_mode & file->f_mode) {
+		if (file->f_flags & O_NONBLOCK) {
+			up(&s->open_sem);
+			return -EWOULDBLOCK;
+		}
+		up(&s->open_sem);
+		interruptible_sleep_on(&s->open_wait);
+		if (signal_pending(current))
+			return -ERESTARTSYS;
+		down(&s->open_sem);
+	}
+	if (file->f_mode & FMODE_READ) {
+		fmtm &= ~((ESS_CFMT_STEREO | ESS_CFMT_16BIT) << ESS_CFMT_CSHIFT);
+		if ((minor & 0xf) == SND_DEV_DSP16)
+			fmts |= ESS_CFMT_16BIT << ESS_CFMT_CSHIFT;
+		s->dma_adc.ossfragshift = s->dma_adc.ossmaxfrags = s->dma_adc.subdivision = 0;
+		set_adc_rate(s, 8000);
+	}
+	if (file->f_mode & FMODE_WRITE) {
+		fmtm &= ~((ESS_CFMT_STEREO | ESS_CFMT_16BIT) << ESS_CFMT_ASHIFT);
+		if ((minor & 0xf) == SND_DEV_DSP16)
+			fmts |= ESS_CFMT_16BIT << ESS_CFMT_ASHIFT;
+		s->dma_dac.ossfragshift = s->dma_dac.ossmaxfrags = s->dma_dac.subdivision = 0;
+		set_dac_rate(s, 8000);
+	}
+	set_fmt(s, fmtm, fmts);
+	s->open_mode |= file->f_mode & (FMODE_READ | FMODE_WRITE);
+	start_bob(s);
+	up(&s->open_sem);
+	MOD_INC_USE_COUNT;
+	return 0;
+}
+
+static int ess_release(struct inode *inode, struct file *file)
+{
+	struct ess_state *s = (struct ess_state *)file->private_data;
+
+	VALIDATE_STATE(s);
+	if (file->f_mode & FMODE_WRITE)
+		drain_dac(s, file->f_flags & O_NONBLOCK);
+	down(&s->open_sem);
+	if (file->f_mode & FMODE_WRITE) {
+		stop_dac(s);
+		dealloc_dmabuf(&s->dma_dac);
+	}
+	if (file->f_mode & FMODE_READ) {
+		stop_adc(s);
+		dealloc_dmabuf(&s->dma_adc);
+	}
+	s->open_mode &= (~file->f_mode) & (FMODE_READ|FMODE_WRITE);
+	stop_bob(s);
+	up(&s->open_sem);
+	wake_up(&s->open_wait);
+	MOD_DEC_USE_COUNT;
+	return 0;
+}
+
+static /*const*/ struct file_operations ess_audio_fops = {
+	&ess_llseek,
+	&ess_read,
+	&ess_write,
+	NULL,  /* readdir */
+	&ess_poll,
+	&ess_ioctl,
+	NULL,	/* XXX &ess_mmap, */
+	&ess_open,
+	NULL,	/* flush */
+	&ess_release,
+	NULL,  /* fsync */
+	NULL,  /* fasync */
+	NULL,  /* check_media_change */
+	NULL,  /* revalidate */
+	NULL,  /* lock */
+};
+
+
+/* --------------------------------------------------------------------- */
+
+/*	XXX get rid of this
+ *	maximum number of devices 
+ */
+#define NR_DEVICE 4
+
+/* --------------------------------------------------------------------- */
+
+static int maestro_install(struct pci_dev *pcidev, int card_type, int index)
+{
+	u16 w;
+	u32 n;
+	int iobase;
+	int i;
+	struct ess_card *card;
+	struct ess_state *ess;
+	int apu;
+	int num = 0;
+			
+	iobase = pcidev->resource[0].start;
+
+	card = kmalloc(sizeof(struct ess_card), GFP_KERNEL);
+	if(card == NULL)
+	{
+		printk(KERN_WARNING "maestro: out of memory\n");
+		return 0;
+	}
+	
+	memset(card, 0, sizeof(*card));
+
+	card->iobase = iobase;
+	card->card_type = card_type;
+	card->irq = pcidev->irq;
+	card->next = devs;
+	card->magic = ESS_CARD_MAGIC;
+	devs = card;
+	
+	/* init our 8 groups of 4 apus */
+	for(i=0;i<8;i++)
+	{
+		struct ess_state *s=&card->channels[i];
+		
+		s->card = card;
+		init_waitqueue_head(&s->dma_adc.wait);
+		init_waitqueue_head(&s->dma_dac.wait);
+		init_waitqueue_head(&s->open_wait);
+		init_MUTEX(&s->open_sem);
+		s->magic = ESS_STATE_MAGIC;
+		
+		s->apu[0] = 4*i;
+		s->apu[1] = (4*i)+1;
+		s->apu[2] = (4*i)+2;
+		s->apu[3] = (4*i)+3;
+		
+		if(s->dma_adc.ready || s->dma_dac.ready || s->dma_adc.rawbuf)
+			printk("BOTCH!\n");
+		/* register devices */
+		if ((s->dev_audio = register_sound_dsp(&ess_audio_fops, -1)) < 0)
+			break;
+	}
+	
+	num = i;
+	
+	/* clear the rest if we ran out of slots to register */
+	for(;i<8;i++)
+	{
+		struct ess_state *s=&card->channels[i];
+		s->dev_audio = -1;
+	}
+	
+	ess = &card->channels[0];
+	
+	/*
+	 *	Ok card ready. Begin setup proper
+	 */
+	 
+	printk(KERN_INFO "maestro: Configuring %s at 0x%04X\n", card_names[card_type], iobase);
+
+	/*
+	 *	Disable ACPI
+	 */
+	 
+	pci_write_config_dword(pcidev, 0x54, 0x00000000);
+	pci_write_config_dword(pcidev, 0x56, 0x00000000);
+	
+	/*
+	 *	Use TDMA for now. TDMA works on all boards, so while its
+	 *	not the most efficient its the simplest.
+	 */ 
+	 
+	pci_read_config_word(pcidev, 0x50, &w);
+
+	/* Clear DMA bits */
+	w&=~(1<<10|1<<9|1<<8);
+
+	/* TDMA on */
+	w|=(1<<8);
+
+/* XXX do we care about these two ? */
+	/*
+	 *	MPU at 330
+	 */
+	
+	w&=~((1<<4)|(1<<3));
+	
+	/*
+	 *	SB at 0x220
+	 */
+
+	w&=~(1<<2);
+	
+#if 0 /* huh?  its sub decode.. */
+
+	/*
+	 *	Reserved write as 0
+	 */
+	 
+	w&=~(1<<1);
+#endif
+	
+	/*
+	 *	Some of these are undocumented bits
+	 */
+	 
+	w&=~(1<<13)|(1<<14);		/* PIC Snoop mode bits */
+	w&=~(1<<11);			/* Safeguard off */
+	w|= (1<<7);			/* Posted write */
+	w|= (1<<6);			/* ISA timing on */
+	w&=~(1<<1);			/* Subtractive decode off */
+	/* XXX huh?  claims to be reserved.. */
+	w&=~(1<<5);			/* Don't swap left/right */
+	
+	pci_write_config_word(pcidev, 0x50, w);
+	
+	pci_read_config_word(pcidev, 0x52, &w);
+	w&=~(1<<15);		/* Turn off internal clock multiplier */
+	/* XXX how do we know which to use? */
+	w&=~(1<<14);		/* External clock */
+	
+	w&=~(1<<7);		/* HWV off */
+	w&=~(1<<6);		/* Debounce off */
+	w&=~(1<<5);		/* GPIO 4:5 */
+	w&=~(1<<4);		/* Disconnect from the CHI */
+	w&=~(1<<3);		/* IDMA off (undocumented) */
+	w&=~(1<<2);		/* MIDI fix off (undoc) */
+	w&=~(1<<1);		/* reserved, always write 0 */
+	w&=~(1<<0);		/* IRQ to ISA off (undoc) */
+	pci_write_config_word(pcidev, 0x52, w);
+
+	/*
+	 *	DDMA off
+	 */
+
+	pci_read_config_word(pcidev, 0x60, &w);
+	w&=~(1<<0);
+	pci_write_config_word(pcidev, 0x60, w);
+	
+	/*
+	 *	Legacy mode
+	 */
+
+	pci_read_config_word(pcidev, 0x40, &w);
+	w|=(1<<15);	/* legacy decode off */
+	w&=~(1<<14);	/* Disable SIRQ */
+	w&=~(0x1f);	/* disable mpu irq/io, game port, fm, SB */
+	 
+	pci_write_config_word(pcidev, 0x40, w);
+
+	sound_reset(iobase);
+
+	/*
+	 *	Reset the CODEC
+	 */
+	 
+	maestro_ac97_reset(iobase);
+		
+	/*
+	 *	Ring Bus Setup
+	 */
+	 	 
+	n=inl(iobase+0x34);
+	n&=~0xF000;
+	n|=12<<12;		/* Direct Sound, Stereo */
+
+	n=inl(iobase+0x34);
+	n&=~0x0F00;		/* Modem off */
+	outl(n, iobase+0x34);
+
+	n=inl(iobase+0x34);
+	n&=~0x00F0;
+	n|=9<<4;		/* DAC, Stereo */
+	outl(n, iobase+0x34);
+	
+	n=inl(iobase+0x34);
+	n&=~0x000F;		/* ASSP off */
+	outl(n, iobase+0x34);
+	
+	
+	n=inl(iobase+0x34);
+	n|=(1<<29);		/* Enable ring bus */
+	outl(n, iobase+0x34);
+	
+	
+	n=inl(iobase+0x34);
+	n|=(1<<28);		/* Enable serial bus */
+	outl(n, iobase+0x34);
+	
+	n=inl(iobase+0x34);
+	n&=~0x00F00000;		/* MIC off */
+	outl(n, iobase+0x34);
+	
+	n=inl(iobase+0x34);
+	n&=~0x000F0000;		/* I2S off */
+	outl(n, iobase+0x34);
+	
+	w=inw(iobase+0x18);
+	w&=~(1<<7);		/* ClkRun off */
+	outw(w, iobase+0x18);
+
+	w=inw(iobase+0x18);
+	w&=~(1<<6);		/* Harpo off */
+	outw(w, iobase+0x18);
+	
+	w=inw(iobase+0x18);
+	w&=~(1<<4);		/* ASSP irq off */
+	outw(w, iobase+0x18);
+	
+	w=inw(iobase+0x18);
+	w&=~(1<<3);		/* ISDN irq off */
+	outw(w, iobase+0x18);
+	
+	w=inw(iobase+0x18);
+	w|=(1<<2);		/* Direct Sound IRQ on */
+	outw(w, iobase+0x18);
+
+	w=inw(iobase+0x18);
+	w&=~(1<<1);		/* MPU401 IRQ off */
+	outw(w, iobase+0x18);
+
+	w=inw(iobase+0x18);
+	w|=(1<<0);		/* SB IRQ on */
+	outw(w, iobase+0x18);
+
+
+	outb(0, iobase+0xA4);
+	outb(3, iobase+0xA2);
+	outb(0, iobase+0xA6);
+	
+	for(apu=0;apu<16;apu++)
+	{
+		/* Write 0 into the buffer area 0x1E0->1EF */
+		outw(0x01E0+apu, 0x10+iobase);
+		outw(0x0000, 0x12+iobase);
+	
+		/*
+		 * The 1.10 test program seem to write 0 into the buffer area
+		 * 0x1D0-0x1DF too.
+		 */
+		outw(0x01D0+apu, 0x10+iobase);
+		outw(0x0000, 0x12+iobase);
+	}
+
+#if 1
+	wave_set_register(ess, IDR7_WAVE_ROMRAM, 
+		(wave_get_register(ess, IDR7_WAVE_ROMRAM)&0xFF00));
+	wave_set_register(ess, IDR7_WAVE_ROMRAM,
+		wave_get_register(ess, IDR7_WAVE_ROMRAM)|0x100);
+	wave_set_register(ess, IDR7_WAVE_ROMRAM,
+		wave_get_register(ess, IDR7_WAVE_ROMRAM)&~0x200);
+	wave_set_register(ess, IDR7_WAVE_ROMRAM,
+		wave_get_register(ess, IDR7_WAVE_ROMRAM)|~0x400);
+#else		
+	maestro_write(ess, IDR7_WAVE_ROMRAM, 
+		(maestro_read(ess, IDR7_WAVE_ROMRAM)&0xFF00));
+	maestro_write(ess, IDR7_WAVE_ROMRAM,
+		maestro_read(ess, IDR7_WAVE_ROMRAM)|0x100);
+	maestro_write(ess, IDR7_WAVE_ROMRAM,
+		maestro_read(ess, IDR7_WAVE_ROMRAM)&~0x200);
+	maestro_write(ess, IDR7_WAVE_ROMRAM,
+		maestro_read(ess, IDR7_WAVE_ROMRAM)|0x400);
+#endif
+	
+	maestro_write(ess, IDR2_CRAM_DATA, 0x0000);
+	maestro_write(ess, 0x08, 0xB004);
+	/* Now back to the DirectSound stuff */
+	maestro_write(ess, 0x09, 0x001B);
+	maestro_write(ess, 0x0A, 0x8000);
+	maestro_write(ess, 0x0B, 0x3F37);
+	maestro_write(ess, 0x0C, 0x0098);
+	
+	maestro_write(ess, 0x0C, 
+		(maestro_read(ess, 0x0C)&~0xF000)|0x8000);
+	maestro_write(ess, 0x0C, 
+		(maestro_read(ess, 0x0C)&~0x0F00)|0x0500);
+
+	maestro_write(ess, 0x0D, 0x7632);
+			
+	/* Wave cache control on - test off, sg off, 
+		enable, enable extra chans 1Mb */
+		
+	outw(inw(0x14+iobase)|(1<<8),0x14+iobase);
+	outw(inw(0x14+iobase)&0xFE03,0x14+iobase);
+	outw((inw(0x14+iobase)&0xFFFC), 0x14+iobase);
+	outw(inw(0x14+iobase)|(1<<7),0x14+iobase);
+
+	outw(0xA1A0, 0x14+iobase);	/* 0300 ? */
+
+	if(maestro_ac97_get(iobase, 0x00)==0x0080)
+	{
+		maestro_pt101_init(iobase);
+	}
+	else
+	{
+		maestro_ac97_init(iobase);
+		card->mix.supported_mixers = AC97_SUPPORTED_MASK;
+		card->mix.stereo_mixers = AC97_STEREO_MASK;
+		card->mix.record_sources = AC97_RECORD_MASK;
+		card->mix.read_mixer = ac97_read_mixer;
+		card->mix.write_mixer = ac97_write_mixer;
+		card->mix.recmask_io = ac97_recmask_io;
+
+		if ((card->dev_mixer = register_sound_mixer(&ess_mixer_fops, -1)) < 0)
+			printk("maestro: couldn't register mixer!\n");
+	}
+	
+	/* Now clear the channel data */	
+	for(apu=0;apu<64;apu++)
+	{
+		for(w=0;w<0x0E;w++)
+			apu_set_register(ess, apu|ESS_CHAN_HARD, w, 0);
+	}
+	
+	if(request_irq(card->irq, ess_interrupt, SA_SHIRQ, card_names[card_type], card))
+	{
+		printk(KERN_ERR "maestro: unable to allocate irq %d,\n", card->irq);
+		return 0;
+	}
+
+//	ess_play_test(ess);
+	printk("maestro: %d channels configured.\n", num);
+	return 1; 
+}
+
+#ifdef MODULE
+
+int __init init_module(void)
+#else
+int __init init_maestro(void)
+#endif
+{
+	struct pci_dev *pcidev = NULL;
+	int index = 0;
+
+	if (!pci_present())   /* No PCI bus in this machine! */
+		return -ENODEV;
+	printk(KERN_INFO "maestro: version " DRIVER_VERSION " time " __TIME__ " " __DATE__ "\n");
+
+	pcidev = NULL;
+
+	/*
+	 *	Find the ESS Maestro 2.
+	 */
+
+	while((pcidev = pci_find_device(PCI_VENDOR_ESS, PCI_DEVICE_ID_ESS_ESS1968, pcidev))!=NULL)
+	{
+		index+=maestro_install(pcidev, TYPE_MAESTRO2, index);
+		if(index == NR_DEVICE)
+			return index;
+	}
+
+	/*
+	 *	Find the ESS Maestro 2E
+	 */
+
+	while((pcidev = pci_find_device(PCI_VENDOR_ESS, PCI_DEVICE_ID_ESS_ESS1978, pcidev))!=NULL)
+	{
+		index+=maestro_install(pcidev, TYPE_MAESTRO2E, index);
+		if(index == NR_DEVICE)
+			return index;
+	}
+
+	/*
+	 *	ESS Maestro 1
+	 */
+
+	while((pcidev = pci_find_device(PCI_VENDOR_ESS_OLD, PCI_DEVICE_ID_ESS_ESS0100, pcidev))!=NULL)
+	{
+		index+=maestro_install(pcidev, TYPE_MAESTRO, index);
+		if(index == NR_DEVICE)
+			return index;
+	}
+	if(index==0)
+		return -ENODEV;
+	return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+#ifdef MODULE
+
+MODULE_AUTHOR("Alan Cox <alan@redhat.com>");
+MODULE_DESCRIPTION("ESS Maestro Driver");
+#ifdef M_DEBUG
+MODULE_PARM(debug,"i");
+#endif
+
+void cleanup_module(void)
+{
+	struct ess_card *s;
+
+	while ((s = devs)) {
+		int i;
+		devs = devs->next;
+//		ess_play_test(&s->channels[0]);
+#ifndef ESS_HW_TIMER
+		kill_bob(&s->channels[0]);
+#else
+		stop_bob(&s->channels[0]);
+#endif
+//		outb(~0, s->ioenh + SV_CODEC_INTMASK);  /* disable ints */
+//		synchronize_irq();
+//		inb(s->ioenh + SV_CODEC_STATUS); /* ack interrupts */
+//		wrindir(s, SV_CIENABLE, 0);     /* disable DMAA and DMAC */
+		//outb(0, s->iodmaa + SV_DMA_RESET);
+		//outb(0, s->iodmac + SV_DMA_RESET);
+		free_irq(s->irq, s);
+		unregister_sound_mixer(s->dev_mixer);
+		for(i=0;i<8;i++)
+		{
+			struct ess_state *ess = &s->channels[i];
+			if(ess->dev_audio != -1)
+				unregister_sound_dsp(ess->dev_audio);
+		}
+		kfree(s);
+	}
+	M_printk("maestro: unloading\n");
+}
+
+#endif /* MODULE */
+
+#if 0
+/*============================================================================
+ *  ex-code that we're not using anymore..
+ *============================================================================
+ */
+/*
+ *	The ASSP is fortunately not double indexed
+ */
+ 
+static void assp_set_register(int ioaddr, u32 reg, u32 value)
+{
+	unsigned long flags;
+	
+	save_flags(flags);
+	cli();
+	outl(reg, ioaddr+0x80);
+	outl(value, ioaddr+0x84);
+	restore_flags(flags);
+}
+
+static u32 assp_get_register(int ioaddr, u32 reg)
+{
+	unsigned long flags;
+	u32 value;
+	
+	save_flags(flags);
+	cli();
+	outl(reg, ioaddr+0x80);
+	value=inl(ioaddr+0x84);
+	restore_flags(flags);
+	
+	return value;
+}
+
+/* the ASP is basically a DSP that one can dma instructions
+	into.  it can do things like surround encoding or
+	fm synth in sb emul mode.  It is highly proprietary
+	and the ESS dudes are none too excited about telling
+	us about it.  so screw it, we'll just turn it off and
+	not bother with it.  Its not needed for apu/dac work. */
+
+
+static void asp_load(int ioaddr, u16 l, u16 h, u16 *data, int len)
+{
+	int i;
+	outw(l, ioaddr+0x80);
+	outw(h, ioaddr+0x82);
+	for(i=0;i<len;i++)
+		outw(*data++, ioaddr+0x84);
+}
+
+static void asp_memset(int ioaddr, u16 l, u16 h, u16 v, int len)
+{
+	int i;
+	outw(l, ioaddr+0x80);
+	outw(h, ioaddr+0x82);
+	for(i=0;i<len;i++)
+		outw(v, ioaddr+0x84);
+}
+
+/*
+ *	Load a code table into the ASP.
+ */
+
+#define ASSP_LOAD_PROGRAM 0x02 
+#define ASSP_LOAD_DATA 0x03 
+
+static void load_tables(int iobase)
+{
+	outb(0x00, ESS_SETUP_A4+iobase); /* start ASSP programming */
+
+	asp_load(iobase, 0x0, ASSP_LOAD_PROGRAM, asp_block_0,
+		sizeof(asp_block_0)/2);
+
+	asp_load(iobase, 0x0800, ASSP_LOAD_PROGRAM,
+		asp_block_1, sizeof(asp_block_1)/2);
+
+	asp_memset(iobase, 0x1000, ASSP_LOAD_DATA, 0, 1024);
+
+/*
+ * At page 25 of the Maestro-2E data sheet, Table 7, there is a layout of the
+ * ASSP memory mapping that describe the 0x2000-0x23FF as a data area.
+ * The 1.10 version of the test code load 0x3B4 words of data into this area.
+ * I have grabbed the data with hexdump and inserted them into this code.
+ */ 
+	asp_load(iobase, 0x2000, ASSP_LOAD_DATA, asp_block_4,
+		sizeof(asp_block_4)/2);
+
+	asp_memset(iobase, 0x11BC, ASSP_LOAD_DATA, 0x18, 36);
+
+	asp_load(iobase, 0x13DC, ASSP_LOAD_DATA, asp_block_2,
+		sizeof(asp_block_2)/2);
+
+	asp_load(iobase, 0x1300, ASSP_LOAD_DATA, asp_block_3,
+		sizeof(asp_block_3)/2);
+
+	outb(0x41, ESS_SETUP_A4+iobase); /* stop programming and run ASSP */
+}
+/*
+ *	Do not use the main maestro_reset.  it is known
+ *	to leave certain chips in an unstable state.
+ *	best to just reset the direct sound (apus) and
+ *	assp pieces seperately.
+ */
+
+static void maestro_reset(int ioaddr)
+{
+	outw(0x8000, 0x18+ioaddr);
+	udelay(10);
+	outw(0x0000, 0x18+ioaddr);
+	udelay(10);
+}
+#endif

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