static void ad1889_stop_wav(ad1889_state_t *state)
{
	unsigned long flags;
	struct dmabuf *dmabuf = &state->dmabuf;

	spin_lock_irqsave(&state->card->lock, flags);

	if (dmabuf->enable & DAC_RUNNING) {
		u16 tmp;
		unsigned long cnt = dmabuf->dma_len;

		tmp = AD1889_READW(ad1889_dev, AD_DSWSMC);
		tmp &= ~0x0400; /* clear WAEN */
		AD1889_WRITEW(ad1889_dev, AD_DSWSMC, tmp);
		(void) AD1889_READW(ad1889_dev, AD_DSWSMC); /* flush posted PCI write */
		pci_unmap_single(ad1889_dev->pci, dmabuf->dma_handle, 
				cnt, PCI_DMA_TODEVICE);

		dmabuf->enable &= ~DAC_RUNNING;

		/* update dma pointers */
		dmabuf->rd_ptr += cnt;
		dmabuf->rd_ptr &= (DMA_SIZE - 1);

		dmabuf->dma_handle = 0;
		dmabuf->dma_len = 0;
		dmabuf->ready = 0;

		wake_up(&dmabuf->wait);
	}

	spin_unlock_irqrestore(&state->card->lock, flags);
}
static void ad1889_start_wav(ad1889_state_t *state)
{
	unsigned long flags;
	struct dmabuf *dmabuf = &state->dmabuf;
	int cnt;
	u16 tmp;

	spin_lock_irqsave(&state->card->lock, flags);

	if (dmabuf->dma_len)	/* DMA already in flight */
		goto skip_dma;

	/* setup dma */
	cnt = dmabuf->wr_ptr - dmabuf->rd_ptr;
	if (cnt == 0)		/* done - don't need to do anything */
		goto skip_dma;

	/* If the wr_ptr has wrapped, only map to the end */
	if (cnt < 0)
		cnt = DMA_SIZE - dmabuf->rd_ptr;

	dmabuf->dma_handle = pci_map_single(ad1889_dev->pci,
					dmabuf->rawbuf + dmabuf->rd_ptr,
					cnt, PCI_DMA_TODEVICE);
	dmabuf->dma_len = cnt;
	dmabuf->ready = 1;

	DBG("Starting playback at 0x%p for %ld bytes\n", dmabuf->rawbuf +
	    dmabuf->rd_ptr, dmabuf->dma_len);

        /* load up the current register set */
	AD1889_WRITEL(ad1889_dev, AD_DMAWAVCC, cnt);
	AD1889_WRITEL(ad1889_dev, AD_DMAWAVICC, cnt);
	AD1889_WRITEL(ad1889_dev, AD_DMAWAVCA, dmabuf->dma_handle);

	/* TODO: for now we load the base registers with the same thing */
	AD1889_WRITEL(ad1889_dev, AD_DMAWAVBC, cnt);
	AD1889_WRITEL(ad1889_dev, AD_DMAWAVIBC, cnt);
	AD1889_WRITEL(ad1889_dev, AD_DMAWAVBA, dmabuf->dma_handle);

	/* and we're off to the races... */
	AD1889_WRITEL(ad1889_dev, AD_DMACHSS, 0x8);
	tmp = AD1889_READW(ad1889_dev, AD_DSWSMC);
	tmp |= 0x0400; /* set WAEN */
	AD1889_WRITEW(ad1889_dev, AD_DSWSMC, tmp);
	(void) AD1889_READW(ad1889_dev, AD_DSWSMC); /* flush posted PCI write */

	dmabuf->enable |= DAC_RUNNING;

skip_dma:
	spin_unlock_irqrestore(&state->card->lock, flags);
}
static void ad1889_initcfg(ad1889_dev_t *dev)
{
	u16 tmp;

	/* make sure the interrupt bits are setup the way we want */
	tmp = AD1889_READW(dev, AD_DMAWAVCTRL);
	tmp &= ~0x00ff; /* flat dma, no sg, mask out the intr bits */
	tmp |= 0x0004;  /* intr on count, loop */
	AD1889_WRITEW(dev, AD_DMAWAVCTRL, tmp);

	/* unmute... */
	tmp = AD1889_READW(dev, AD_DSWADA);
	tmp &= ~0x8080;
	AD1889_WRITEW(dev, AD_DSWADA, tmp);
}
static inline void ad1889_set_adc_fmt(ad1889_dev_t *dev, int fmt)
{
	u16 tmp;

	tmp = AD1889_READW(ad1889_dev, AD_DSRAMC);
	if (fmt == AFMT_S16_LE) {
		tmp |= 0x0100; /* set WA16 */
	} else if (fmt == AFMT_U8) {
		tmp &= ~0x0100; /* clear WA16 */
	} 
	AD1889_WRITEW(ad1889_dev, AD_DSRAMC, tmp);
}
static inline void ad1889_set_adc_fmt(ad1889_dev_t *dev, int fmt)
{
	u16 tmp;

	DBG("Setting ADC format to 0x%x\n", fmt);

	tmp = AD1889_READW(ad1889_dev, AD_DSRAMC);
	if (fmt & AFMT_S16_LE) {
		tmp |= 0x0100; /* set WA16 */
	} else if (fmt & AFMT_U8) {
		tmp &= ~0x0100; /* clear WA16 */
	} 
	AD1889_WRITEW(ad1889_dev, AD_DSRAMC, tmp);
}
static void ad1889_initcfg(ad1889_dev_t *dev)
{
	u16 tmp16;
	u32 tmp32;

	/* make sure the interrupt bits are setup the way we want */
	tmp32 = AD1889_READL(dev, AD_DMAWAVCTRL);
	tmp32 &= ~0xff; /* flat dma, no sg, mask out the intr bits */
	tmp32 |= 0x6;  /* intr on count, loop */
	AD1889_WRITEL(dev, AD_DMAWAVCTRL, tmp32);

	/* unmute... */
	tmp16 = AD1889_READW(dev, AD_DSWADA);
	tmp16 &= ~0x8080;
	AD1889_WRITEW(dev, AD_DSWADA, tmp16);
}
static void ad1889_startstop_adc(ad1889_state_t *state, int start)
{
	u16 tmp;
	unsigned long flags;

	spin_lock_irqsave(&state->card->lock, flags);
	
	tmp = AD1889_READW(ad1889_dev, AD_DSRAMC);
	if (start) {
		state->dmabuf.enable |= ADC_RUNNING;
		tmp |= 0x0004; /* set ADEN */
	} else {
		state->dmabuf.enable &= ~ADC_RUNNING;
		tmp &= ~0x0004; /* clear ADEN */
	}
	AD1889_WRITEW(ad1889_dev, AD_DSRAMC, tmp);

	spin_unlock_irqrestore(&state->card->lock, flags);
}
static int ad1889_aclink_reset(struct pci_dev * pcidev)
{
	u16 stat;
	int retry = 200;
	ad1889_dev_t *dev = pci_get_drvdata(pcidev);

	AD1889_WRITEW(dev, AD_DSCCS, 0x8000); /* turn on clock */
	AD1889_READW(dev, AD_DSCCS); 

	WAIT_10MS();

	stat = AD1889_READW(dev, AD_ACIC);
	stat |= 0x0002;				/* Reset Disable */
	AD1889_WRITEW(dev, AD_ACIC, stat);
	(void) AD1889_READW(dev, AD_ACIC);	/* flush posted write */

	udelay(10);

	stat = AD1889_READW(dev, AD_ACIC);
	stat |= 0x0001;				/* Interface Enable */
	AD1889_WRITEW(dev, AD_ACIC, stat);

	do {
		if (AD1889_READW(dev, AD_ACIC) & 0x8000)	/* Ready */
			break;
		WAIT_10MS();
		retry--;
	} while (retry > 0);

	if (!retry) {
		printk(KERN_ERR "ad1889_aclink_reset: codec is not ready [0x%x]\n",
			    AD1889_READW(dev, AD_ACIC));
		return -EBUSY;
	}

	/* TODO reset AC97 codec */
	/* TODO set wave/adc pci ctrl status */

	stat = AD1889_READW(dev, AD_ACIC);
	stat |= 0x0004;				/* Audio Stream Output Enable */
	AD1889_WRITEW(dev, AD_ACIC, stat);
	return 0;
}
static u16 ad1889_codec_read(struct ac97_codec *ac97, u8 reg)
{
	ad1889_dev_t *dev = ac97->private_data;
	//DBG("Reading from 0x%lx\n", dev->regbase + 0x100 + reg);
	return AD1889_READW(dev, 0x100 + reg);
}	
static int ad1889_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
	unsigned long arg)
{
	int val = 0;
	ad1889_dev_t *dev = (ad1889_dev_t *)file->private_data;
	struct dmabuf *dmabuf;
	audio_buf_info abinfo;

	switch (cmd)
	{
	case OSS_GETVERSION:
		return put_user(SOUND_VERSION, (int *)arg);

	case SNDCTL_DSP_RESET:
		break;

	case SNDCTL_DSP_SYNC:
		break;

	case SNDCTL_DSP_SPEED:
		/* set sampling rate */
		if (get_user(val, (int *)arg))
			return -EFAULT;
		if (val > 5400 && val < 48000)
		{
			if (file->f_mode & FMODE_WRITE)
				AD1889_WRITEW(ad1889_dev, AD_DSWAS, val);
			if (file->f_mode & FMODE_READ)
				AD1889_WRITEW(ad1889_dev, AD_DSRES, val);
		}
		return 0;

	case SNDCTL_DSP_STEREO: /* undocumented? */
		if (get_user(val, (int *)arg))
			return -EFAULT;
		if (file->f_mode & FMODE_READ) {
			val = AD1889_READW(ad1889_dev, AD_DSWSMC);
			if (val) {
				val |= 0x0200;  /* set WAST */
			} else {
				val &= ~0x0200; /* clear WAST */
			}
			AD1889_WRITEW(ad1889_dev, AD_DSWSMC, val);
		}
		if (file->f_mode & FMODE_WRITE) {
			val = AD1889_READW(ad1889_dev, AD_DSRAMC);
			if (val) {
				val |= 0x0002;  /* set ADST */
			} else {
				val &= ~0x0002; /* clear ADST */
			}
			AD1889_WRITEW(ad1889_dev, AD_DSRAMC, val);
		}

		return 0;

	case SNDCTL_DSP_GETBLKSIZE:
		return put_user(DMA_SIZE, (int *)arg);

	case SNDCTL_DSP_GETFMTS:
		return put_user(AFMT_S16_LE|AFMT_U8, (int *)arg);

	case SNDCTL_DSP_SETFMT:
		if (get_user(val, (int *)arg))
			return -EFAULT;

		if (file->f_mode & FMODE_READ) 
			ad1889_set_adc_fmt(dev, val);

		if (file->f_mode & FMODE_WRITE) 
			ad1889_set_wav_fmt(dev, val);

		return put_user(val, (int *)arg);

	case SNDCTL_DSP_CHANNELS:
		break;

	case SNDCTL_DSP_POST:
		/* send all data to device */
		break;

	case SNDCTL_DSP_SUBDIVIDE:
		break;

	case SNDCTL_DSP_SETFRAGMENT:
		/* not supported; uses fixed fragment sizes */
		return put_user(DMA_SIZE, (int *)arg);

	case SNDCTL_DSP_GETOSPACE:
	case SNDCTL_DSP_GETISPACE:
		/* space left in dma buffers */
		if (cmd == SNDCTL_DSP_GETOSPACE)
			dmabuf = &dev->state[AD_WAV_STATE].dmabuf;
		else
			dmabuf = &dev->state[AD_ADC_STATE].dmabuf;
		abinfo.fragments = 1;
		abinfo.fragstotal = 1;
		abinfo.fragsize = DMA_SIZE;
		abinfo.bytes = DMA_SIZE;
		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_GETCAPS:
		return put_user(0, (int *)arg);

	case SNDCTL_DSP_GETTRIGGER:
	case SNDCTL_DSP_SETTRIGGER:
		break;

	case SNDCTL_DSP_GETIPTR:
	case SNDCTL_DSP_GETOPTR:
		break;

	case SNDCTL_DSP_SETDUPLEX:
		break;
	
	case SNDCTL_DSP_GETODELAY:
		break;

	case SOUND_PCM_READ_RATE:
		return put_user(AD1889_READW(ad1889_dev, AD_DSWAS), (int *)arg);

	case SOUND_PCM_READ_CHANNELS:
	case SOUND_PCM_READ_BITS:
		break;

	case SNDCTL_DSP_MAPINBUF:
	case SNDCTL_DSP_MAPOUTBUF:
	case SNDCTL_DSP_SETSYNCRO:
	case SOUND_PCM_WRITE_FILTER:
	case SOUND_PCM_READ_FILTER:
		break;

	default:
		break;
	}

	return -ENOTTY;
}
int ad1889_read_proc (char *page, char **start, off_t off,
		      int count, int *eof, void *data)
{
	char *out = page;
	int len, i;
	ad1889_dev_t *dev = data;
	ad1889_reg_t regs[] = {
		{ "WSMC", AD_DSWSMC, 16 },
		{ "RAMC", AD_DSRAMC, 16 },
		{ "WADA", AD_DSWADA, 16 },
		{ "SYDA", AD_DSSYDA, 16 },
		{ "WAS", AD_DSWAS, 16 },
		{ "RES", AD_DSRES, 16 },
		{ "CCS", AD_DSCCS, 16 },
		{ "ADCBA", AD_DMAADCBA, 32 },
		{ "ADCCA", AD_DMAADCCA, 32 },
		{ "ADCBC", AD_DMAADCBC, 32 },
		{ "ADCCC", AD_DMAADCCC, 32 },
		{ "ADCIBC", AD_DMAADCIBC, 32 },
		{ "ADCICC", AD_DMAADCICC, 32 },
		{ "ADCCTRL", AD_DMAADCCTRL, 16 },
		{ "WAVBA", AD_DMAWAVBA, 32 },
		{ "WAVCA", AD_DMAWAVCA, 32 },
		{ "WAVBC", AD_DMAWAVBC, 32 },
		{ "WAVCC", AD_DMAWAVCC, 32 },
		{ "WAVIBC", AD_DMAWAVIBC, 32 },
		{ "WAVICC", AD_DMAWAVICC, 32 },
		{ "WAVCTRL", AD_DMAWAVCTRL, 16 },
		{ "DISR", AD_DMADISR, 32 },
		{ "CHSS", AD_DMACHSS, 32 },
		{ "IPC", AD_GPIOIPC, 16 },
		{ "OP", AD_GPIOOP, 16 },
		{ "IP", AD_GPIOIP, 16 },
		{ "ACIC", AD_ACIC, 16 },
		{ "AC97_RESET", 0x100 + AC97_RESET, 16 },
		{ "AC97_MASTER_VOL_STEREO", 0x100 + AC97_MASTER_VOL_STEREO, 16 },
		{ "AC97_HEADPHONE_VOL", 0x100 + AC97_HEADPHONE_VOL, 16 },
		{ "AC97_MASTER_VOL_MONO", 0x100 + AC97_MASTER_VOL_MONO, 16 },
		{ "AC97_MASTER_TONE", 0x100 + AC97_MASTER_TONE, 16 },
		{ "AC97_PCBEEP_VOL", 0x100 + AC97_PCBEEP_VOL, 16 },
		{ "AC97_PHONE_VOL", 0x100 + AC97_PHONE_VOL, 16 },
		{ "AC97_MIC_VOL", 0x100 + AC97_MIC_VOL, 16 },
		{ "AC97_LINEIN_VOL", 0x100 + AC97_LINEIN_VOL, 16 },
		{ "AC97_CD_VOL", 0x100 + AC97_CD_VOL, 16 },
		{ "AC97_VIDEO_VOL", 0x100 + AC97_VIDEO_VOL, 16 },
		{ "AC97_AUX_VOL", 0x100 + AC97_AUX_VOL, 16 },
		{ "AC97_PCMOUT_VOL", 0x100 + AC97_PCMOUT_VOL, 16 },
		{ "AC97_RECORD_SELECT", 0x100 + AC97_RECORD_SELECT, 16 },
		{ "AC97_RECORD_GAIN", 0x100 + AC97_RECORD_GAIN, 16 },
		{ "AC97_RECORD_GAIN_MIC", 0x100 + AC97_RECORD_GAIN_MIC, 16 },
		{ "AC97_GENERAL_PURPOSE", 0x100 + AC97_GENERAL_PURPOSE, 16 },
		{ "AC97_3D_CONTROL", 0x100 + AC97_3D_CONTROL, 16 },
		{ "AC97_MODEM_RATE", 0x100 + AC97_MODEM_RATE, 16 },
		{ "AC97_POWER_CONTROL", 0x100 + AC97_POWER_CONTROL, 16 },
		{ 0 }
	};

	if (dev == NULL)
		return -ENODEV;

	for (i = 0; regs[i].name != 0; i++)
		out += sprintf(out, "%s: 0x%0*x\n", regs[i].name, 
			regs[i].width >> 2, 
			(regs[i].width == 16 
			 	? AD1889_READW(dev, regs[i].offset)
				: AD1889_READL(dev, regs[i].offset)));

	for (i = 0; i < AD_MAX_STATES; i++) {
		out += sprintf(out, "DMA status for %s:\n", 
			(i == AD_WAV_STATE ? "WAV" : "ADC")); 
		out += sprintf(out, "\t\t0x%p (IOVA: 0x%u)\n", 
			dev->state[i].dmabuf.rawbuf,
			dev->state[i].dmabuf.dma_handle);

		out += sprintf(out, "\tread ptr: offset %u\n", 
			(unsigned int)dev->state[i].dmabuf.rd_ptr);
		out += sprintf(out, "\twrite ptr: offset %u\n", 
			(unsigned int)dev->state[i].dmabuf.wr_ptr);
		out += sprintf(out, "\tdma len: offset %u\n", 
			(unsigned int)dev->state[i].dmabuf.dma_len);
	}

	len = out - page - off;
	if (len < count) {
		*eof = 1;
		if (len <= 0) return 0;
	} else {
		len = count;
	}
	*start = page + off;
	return len;
}