/*
 *  Routines for HAL2 soundcards
 *  Copyright (c) by Ulf Carlsson <ulfc@thepuffingroup.com>
 *
 *
 *   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
 *
 */

#define SNDRV_MAIN_OBJECT_FILE
#include <sound/driver.h>
#include <sound/hal2.h>
#include <sound/control.h>

#include <asm/sgi/sgihpc.h>
#include <asm/addrspace.h>

MODULE_AUTHOR("Ulf Carlsson <ulfc@thepuffingroup.com>");
MODULE_DESCRIPTION("Routines for HAL2 soundcards");
MODULE_LICENSE("GPL");

#undef SNDRV_HAL2_DEBUG

#define H2_INDIRECT_WAIT(regs)	while(regs->isr & H2_ISR_TSTATUS);

#define H2_READ_ADDR(addr)	(addr | (1<<7))
#define H2_WRITE_ADDR(addr)	(addr)

inline void snd_hal2_isr_write(snd_hal2_card_t *hal2, unsigned short val)
{
	hal2->hal2.ctl_regs->isr = val;
}

inline unsigned short snd_hal2_isr_look(snd_hal2_card_t *hal2)
{
	return hal2->hal2.ctl_regs->isr;
}

inline unsigned short snd_hal2_rev_look(snd_hal2_card_t *hal2)
{
	return hal2->hal2.ctl_regs->rev;
}

unsigned short snd_hal2_i_look16(snd_hal2_card_t *hal2, unsigned short addr)
{
	snd_hal2_ctl_regs_t *regs = hal2->hal2.ctl_regs;

	regs->iar = H2_READ_ADDR(addr);
	H2_INDIRECT_WAIT(regs);
	return (regs->idr0 & 0xffff);
}

unsigned long snd_hal2_i_look32(snd_hal2_card_t *hal2, unsigned short addr)
{
	unsigned long ret;
	snd_hal2_ctl_regs_t *regs = hal2->hal2.ctl_regs;

	regs->iar = H2_READ_ADDR(addr);
	H2_INDIRECT_WAIT(regs);
	ret = regs->idr0 & 0xffff;
	regs->iar = H2_READ_ADDR(addr | 0x1);
	H2_INDIRECT_WAIT(regs);
	ret |= (regs->idr0 & 0xffff) << 16;
	return ret;
}

void snd_hal2_i_write16(snd_hal2_card_t *hal2, unsigned short addr,
			unsigned short val)
{
	snd_hal2_ctl_regs_t *regs = hal2->hal2.ctl_regs;

	regs->idr0 = val;
	regs->idr1 = 0;
	regs->idr2 = 0;
	regs->idr3 = 0;
	regs->iar = H2_WRITE_ADDR(addr);
	H2_INDIRECT_WAIT(regs);
}

void snd_hal2_i_write32(snd_hal2_card_t *hal2, unsigned short addr,
			unsigned long val)
{
	snd_hal2_ctl_regs_t *regs = hal2->hal2.ctl_regs;

	regs->idr0 = val & 0xffff;
	regs->idr1 = val >> 16;
	regs->idr2 = 0;
	regs->idr3 = 0;
	regs->iar = H2_WRITE_ADDR(addr);
	H2_INDIRECT_WAIT(regs);
}

static void snd_hal2_i_setbit16(snd_hal2_card_t *hal2, unsigned short addr,
				unsigned short bit)
{
	snd_hal2_ctl_regs_t *regs = hal2->hal2.ctl_regs;

	regs->iar = H2_READ_ADDR(addr);
	H2_INDIRECT_WAIT(regs);
	regs->idr0 = regs->idr0 | bit;
	regs->idr1 = 0;
	regs->idr2 = 0;
	regs->idr3 = 0;
	regs->iar = H2_WRITE_ADDR(addr);
	H2_INDIRECT_WAIT(regs);
}

#if 0
static void snd_hal2_i_setbit32(snd_hal2_card_t *hal2, unsigned short addr,
				unsigned long bit)
{
	unsigned long tmp;
	snd_hal2_ctl_regs_t *regs = hal2->hal2.ctl_regs;

	regs->iar = H2_READ_ADDR(addr);
	H2_INDIRECT_WAIT(regs);
	tmp = regs->idr0 | (regs->idr1 << 16) | bit;
	regs->idr0 = tmp & 0xffff;
	regs->idr1 = tmp >> 16;
	regs->idr2 = 0;
	regs->idr3 = 0;
	regs->iar = H2_WRITE_ADDR(addr);
	H2_INDIRECT_WAIT(regs);
}
#endif

static void snd_hal2_i_clearbit16(snd_hal2_card_t *hal2, unsigned short addr,
				  unsigned short bit)
{
	snd_hal2_ctl_regs_t *regs = hal2->hal2.ctl_regs;

	regs->iar = H2_READ_ADDR(addr);
	H2_INDIRECT_WAIT(regs);
	regs->idr0 = regs->idr0 & ~bit;
	regs->idr1 = 0;
	regs->idr2 = 0;
	regs->idr3 = 0;
	regs->iar = H2_WRITE_ADDR(addr);
	H2_INDIRECT_WAIT(regs);
}

#if 0
static void snd_hal2_i_clearbit32(snd_hal2_card_t *hal2, unsigned short addr,
				  unsigned long bit)
{
	unsigned long tmp;
	snd_hal2_ctl_regs_t *regs = hal2->hal2.ctl_regs;

	regs->iar = H2_READ_ADDR(addr);
	H2_INDIRECT_WAIT(regs);
	tmp = (regs->idr0 | (regs->idr1 << 16)) & ~bit;
	regs->idr0 = tmp & 0xffff;
	regs->idr1 = tmp >> 16;
	regs->idr2 = 0;
	regs->idr3 = 0;
	regs->iar = H2_WRITE_ADDR(addr);
	H2_INDIRECT_WAIT(regs);
}
#endif

void snd_hal2_dump_regs(snd_hal2_card_t *hal2)
{
	printk("isr: %08hx ", snd_hal2_isr_look(hal2));
	printk("rev: %08hx\n", snd_hal2_rev_look(hal2));
	printk("relay: %04hx\n", snd_hal2_i_look16(hal2, H2I_RELAY_C));
	printk("port en: %04hx ", snd_hal2_i_look16(hal2, H2I_DMA_PORT_EN));
	printk("dma end: %04hx ", snd_hal2_i_look16(hal2, H2I_DMA_END));
	printk("dma drv: %04hx\n", snd_hal2_i_look16(hal2, H2I_DMA_DRV));
	printk("syn ctl: %04hx ", snd_hal2_i_look16(hal2, H2I_SYNTH_C));
	printk("aesrx ctl: %04hx ", snd_hal2_i_look16(hal2, H2I_AESRX_C));
	printk("aestx ctl: %04hx ", snd_hal2_i_look16(hal2, H2I_AESTX_C));
	printk("dac ctl1: %04hx ", snd_hal2_i_look16(hal2, H2I_DAC_C1));
	printk("dac ctl2: %08lx ", snd_hal2_i_look32(hal2, H2I_DAC_C2));
	printk("adc ctl1: %04hx ", snd_hal2_i_look16(hal2, H2I_ADC_C1));
	printk("adc ctl2: %08lx ", snd_hal2_i_look32(hal2, H2I_ADC_C2));
	printk("syn map: %04hx\n", snd_hal2_i_look16(hal2, H2I_SYNTH_MAP_C));
	printk("bres1 ctl1: %04hx ", snd_hal2_i_look16(hal2, H2I_BRES1_C1));
	printk("bres1 ctl2: %04lx ", snd_hal2_i_look32(hal2, H2I_BRES1_C2));
	printk("bres2 ctl1: %04hx ", snd_hal2_i_look16(hal2, H2I_BRES2_C1));
	printk("bres2 ctl2: %04lx ", snd_hal2_i_look32(hal2, H2I_BRES2_C2));
	printk("bres3 ctl1: %04hx ", snd_hal2_i_look16(hal2, H2I_BRES3_C1));
	printk("bres3 ctl2: %04lx\n", snd_hal2_i_look32(hal2, H2I_BRES3_C2));
}

static int snd_hal2_compute_rate(snd_hal2_card_t *hal2, unsigned int rate)
{
	/* We default to 44.1 kHz and if it isn't possible to fall back to
	 * 48.0 kHz with the needed adjustments of real_rate.
	 */

#ifdef SNDRV_HAL2_DEBUG
	snd_printk("rate: %d\n", rate);
#endif
	if (rate < 1)
		rate = 1;	/* this is fixed later.. */

	/* Note: This is NOT the way they set up the bresenham clock generators
	 * in the specification. I've tried to implement that method but it
	 * doesn't work. It's probably another silly bug in the spec.
	 *
	 * I accidently discovered this method while I was testing and it seems
	 * to work very well with all frequencies, and thee shall follow rule #1
	 * of programming :-)
	 */
	
	if (44100 % rate == 0) {
		unsigned short inc;

		inc = 44100 / rate;

		hal2->hal2.master = 44100;
		hal2->hal2.inc = inc;
		hal2->hal2.mod = 1;
	} else {
		unsigned short inc;
		inc = 48000 / rate;
		if (inc < 1) inc = 1;

		rate = 48000 / inc;

		hal2->hal2.master = 48000;
		hal2->hal2.inc = inc;
		hal2->hal2.mod = 1;
	}

#ifdef SNDRV_HAL2_DEBUG
	snd_printk("real_rate: %d\n", rate);
#endif
	return rate;
}

static void snd_hal2_set_rate(snd_hal2_card_t *hal2)
{
	unsigned int master = hal2->hal2.master;
	int inc = hal2->hal2.inc;
	int mod = hal2->hal2.mod;

#ifdef SNDRV_HAL2_DEBUG
	snd_printk("master: %d inc: %d mod: %d\n", master, inc, mod);
#endif
	snd_hal2_i_write16(hal2, H2I_BRES1_C1, (master == 44100) ? 1 : 0);
	snd_hal2_i_write32(hal2, H2I_BRES1_C2, ((0xffff & (mod - inc - 1)) << 16) | 1);
}

void snd_hal2_interrupt(snd_hal2_card_t *hal2)
{
	snd_pcm1_substream_t *substream1;
	
	if (hal2 && hal2->pcm) {
		if (hal2->hal2.dac.pbus.pbus &&
		    hal2->hal2.dac.pbus.pbus->pbdma_ctrl & 0x01) {
			substream1 = (snd_pcm1_substream_t *) hal2->playback_substream1;
			if (substream1 && substream1->ack)
				substream1->ack(hal2->playback_substream);
		}
		if (hal2->hal2.adc.pbus.pbus &&
		    hal2->hal2.adc.pbus.pbus->pbdma_ctrl & 0x01) {
			substream1 = (snd_pcm1_substream_t *) hal2->capture_substream1;
			if (substream1 && substream1->ack)
				substream1->ack(hal2->capture_substream);
		}
	}
}

static int snd_hal2_playback_ioctl(void *private_data, snd_pcm_substream_t *substream,
				   unsigned int cmd, void *arg)
{
	snd_hal2_card_t *hal2 = (snd_hal2_card_t *) private_data;
	snd_pcm1_substream_t *substream1 = (snd_pcm1_substream_t *) substream->private_data;

	switch (cmd) {
	case SNDRV_PCM1_IOCTL_RATE:
		substream1->real_rate = snd_hal2_compute_rate(hal2, substream1->rate);
		return 0;
	}
	return -ENXIO;
}

static int snd_hal2_capture_ioctl(void *private_data, snd_pcm_substream_t *substream,
				  unsigned int cmd, void *arg)
{
	snd_hal2_card_t *hal2 = (snd_hal2_card_t *) private_data;
	snd_pcm1_substream_t *substream1 = (snd_pcm1_substream_t *) substream->private_data;

	switch (cmd) {
	case SNDRV_PCM1_IOCTL_RATE:
		substream1->real_rate = snd_hal2_compute_rate(hal2, substream1->rate);
		return 0;
	}
	return -ENXIO;
}

static int snd_hal2_playback_open(void *private_data, snd_pcm_substream_t *substream)
{
	int err;
	snd_hal2_card_t *hal2;
	snd_hal2_pbus_t *pbus;

	hal2 = (snd_hal2_card_t *) private_data;

	hal2->playback_substream = substream;
	hal2->playback_substream1 = (snd_pcm1_substream_t *) substream->private_data;

	/* Maybe we shouldn't allocate the dma here, but when the driver is
	 * loaded so that we can *beep*.
	 */
	if ((err = snd_pcm1_dma_alloc(substream, hal2->hal2.dac.dmaptr,
				      "HAL2 PCM - playback")) < 0)
		return err;

	pbus = &hal2->hal2.dac.pbus;
	pbus->pbusnr = 1;
	pbus->pbus = &hpc3c0->pbdma[pbus->pbusnr];
	
#ifdef SNDRV_HAL2_DEBUG
	snd_printk("playback open ok!\n");
#endif
	hal2->usecount++;

	return 0;
}

static int snd_hal2_capture_open(void *private_data, snd_pcm_substream_t *substream)
{
	int err;
	snd_hal2_card_t *hal2;
	snd_hal2_pbus_t *pbus;

	hal2 = (snd_hal2_card_t *) private_data;

	hal2->capture_substream = substream;
	hal2->capture_substream1 = (snd_pcm1_substream_t *) substream->private_data;

	if ((err = snd_pcm1_dma_alloc(substream, hal2->hal2.adc.dmaptr,
				      "HAL2 PCM - playback")) < 0)
		return err;

	pbus = &hal2->hal2.adc.pbus;
	pbus->pbusnr = 2;
	pbus->pbus = &hpc3c0->pbdma[pbus->pbusnr];

#ifdef SNDRV_HAL2_DEBUG
	snd_printk("capture open ok!\n");
#endif
	hal2->usecount++;

	return 0;
}

static void snd_hal2_playback_close(void *private_data, snd_pcm_substream_t *substream)
{
	snd_hal2_card_t *hal2 = (snd_hal2_card_t *) private_data;

	if (hal2->hal2.dac.ringbuf) {
		snd_free_pages((char *) hal2->hal2.dac.ringbuf,
			       hal2->hal2.dac.blocks * sizeof(snd_hal2_ring_t));
		hal2->hal2.dac.ringbuf = NULL;
	}

	snd_pcm1_dma_free(substream);

#ifdef SNDRV_HAL2_DEBUG
	snd_printk("playback close ok!\n");
#endif
	hal2->usecount--;
}

static void snd_hal2_capture_close(void *private_data, snd_pcm_substream_t *substream)
{
	snd_hal2_card_t *hal2 = (snd_hal2_card_t *) private_data;

	if (hal2->hal2.adc.ringbuf) {
		snd_free_pages((char *) hal2->hal2.adc.ringbuf,
			       hal2->hal2.adc.blocks * sizeof(snd_hal2_ring_t));
		hal2->hal2.adc.ringbuf = NULL;
	}

	snd_pcm1_dma_free(substream);

#ifdef SNDRV_HAL2_DEBUG
	snd_printk("capture close ok!\n");
#endif
	hal2->usecount--;
}

#if 0
static void snd_hal2_pio_memset(void *s, int c, size_t n)
{
	snd_hal2_reg_t *reg;

	reg = (snd_hal2_reg_t *) (s + n);

	while ((void *) reg-- > s)
		*reg = c;
}
#endif

static void snd_hal2_playback_prepare(void *private_data,
				      snd_pcm_substream_t *substream,
				      unsigned char *buffer, unsigned int size,
				      unsigned int offset, unsigned int count)
{
	int blocks, block_size;
	int sample_size;
	int i;
	int pg;
	snd_hal2_ring_t *ring;
	snd_hal2_pbus_t *pbus;

	snd_hal2_card_t *hal2 = (snd_hal2_card_t *) private_data;
	snd_pcm1_substream_t *substream1 = (snd_pcm1_substream_t *) substream->private_data;

#ifdef SNDRV_HAL2_DEBUG
	snd_printk("playback prepare..\n");
#endif

	/* Recalculate unsupported values of block and block_size..
	 */
	block_size = substream1->block_size;

	if (block_size > PAGE_SIZE)
		block_size = substream1->block_size = PAGE_SIZE;

	blocks = substream1->blocks = substream1->used_size / block_size;

	if (blocks > PAGE_SIZE / sizeof(snd_hal2_ring_t)) {
		snd_printk("too many blocks: %d\n", blocks);
		return;
	}

#ifdef SNDRV_HAL2_DEBUG
	snd_printk("DAC: blocks: %d block_size: %d size: %u\n",
		   blocks, block_size, size);
#endif

	if (hal2->hal2.dac.ringbuf) {
		snd_free_pages((char *) hal2->hal2.dac.ringbuf,
			       hal2->hal2.dac.blocks * sizeof(snd_hal2_ring_t));
	}

	hal2->hal2.dac.ringbuf = (snd_hal2_ring_t *)
		snd_malloc_pages(blocks * sizeof(snd_hal2_ring_t), &pg, 0);
	if (hal == NULL2->hal2.dac.ringbuf) {
		snd_printd("Oops, no memory for DMA descriptors");
		return;
	}
	hal2->hal2.dac.blocks = blocks;


	/* Setup the ring of DMA descriptors to point to their parts of the DMA
	 * buffer and to generate interrrupts.
	 */
	i = 0;
	ring = hal2->hal2.dac.ringbuf;
	ring->desc.pbuf = PHYSADDR(buffer);
	ring->desc.pnext = PHYSADDR(ring + 1);
	ring->desc.cntinfo = block_size | HPCDMA_XIE;
	while (++i < blocks) {
		ring++;
		ring->desc.pbuf = PHYSADDR(buffer + i * block_size);
		ring->desc.pnext = PHYSADDR(ring + 1);
		ring->desc.cntinfo = block_size | HPCDMA_XIE;
	}
	ring->desc.pnext = PHYSADDR(hal2->hal2.dac.ringbuf);
	
	/* The PBUS can prolly not read thes stuff when it's in the cache so we
	 * have to flush it back to main memory
	 */
	dma_cache_wback_inv((unsigned long) hal2->hal2.dac.ringbuf,
			    blocks * sizeof(snd_hal2_ring_t));


	/* Now we set up some PBUS information. The PBUS needs information about
	 * what portion of the fifo it will use. If it's receiving or
	 * transmitting, and finally whether the stream is little endian or big
	 * endian. The information is written later, on the trigger call.
	 */
	
	sample_size = substream1->voices * 2;

	pbus = &hal2->hal2.dac.pbus;
	pbus->highwater = (sample_size * 2) >> 1;	/* halfwords */
	pbus->fifobeg = 0;				/* playback is first */
	pbus->fifoend = (sample_size * 4) >> 3;		/* doublewords */
	pbus->ctrl = HPC3_PDMACTRL_RT |
		((substream1->mode & SNDRV_PCM1_MODE_BIG) ? 0 : HPC3_PDMACTRL_SEL);

#ifdef SNDRV_HAL2_DEBUG
	snd_printk("DAC: mode: %x voices: %d\n", substream1->mode, substream1->voices);
#endif

	/* We disable everything before we do anything at all
	 */
	pbus->pbus->pbdma_ctrl = HPC3_PDMACTRL_LD;
	snd_hal2_i_clearbit16(hal2, H2I_DMA_PORT_EN, H2I_DMA_PORT_EN_CODECTX);
	snd_hal2_i_clearbit16(hal2, H2I_DMA_DRV, (1 << pbus->pbusnr));

#if 0
	snd_hal2_pio_memset(hal2->hal2.aes_regs, 0, sizeof(snd_hal2_aes_regs_t));
	snd_hal2_pio_memset(hal2->hal2.syn_regs, 0, sizeof(snd_hal2_syn_regs_t));
#endif

	/* Setup the HAL2 for playback
	 */
	substream1->real_rate = snd_hal2_compute_rate(hal2, substream1->rate);
	snd_hal2_set_rate(hal2);
	snd_hal2_i_write16(hal2, H2I_DAC_C1,
			   (pbus->pbusnr << H2I_DAC_C1_DMA_SHIFT) |
			   (1 << H2I_DAC_C1_CLKID_SHIFT) |
			   (substream1->voices << H2I_DAC_C1_DATAT_SHIFT));
	snd_hal2_i_write32(hal2, H2I_DAC_C2, 0); /* disable muting */

	/* The spec says that we should write 0x08248844 but that's WRONG. HAL2
	 * does 8 bit DMA, not 16 bit even if it generates 16 bit audio.
	 */
	hpc3c0->pbus_dmacfgs[pbus->pbusnr][0] = 0x08208844;	/* Magic :-) */
}

static void snd_hal2_capture_prepare(void *private_data,
				     snd_pcm_substream_t *substream,
				     unsigned char *buffer, unsigned int size,
				     unsigned int offset, unsigned int count)
{
	int blocks, block_size;
	int sample_size;
	int i;
	int pg;
	snd_hal2_ring_t *ring;
	snd_hal2_pbus_t *pbus;

	snd_hal2_card_t *hal2 = (snd_hal2_card_t *) private_data;
	snd_pcm1_substream_t *substream1 = (snd_pcm1_substream_t *) substream->private_data;

	if (hal == NULL2) {
		snd_printk("prepare: no private data?\n");
		return;
	}

	block_size = substream1->block_size;

	if (block_size > PAGE_SIZE)
		block_size = substream1->block_size = PAGE_SIZE;

	blocks = substream1->blocks = substream1->used_size / block_size;

	if (blocks > PAGE_SIZE / sizeof(snd_hal2_ring_t)) {
		snd_printk("too many blocks: %d\n", blocks);
		return;
	}

#ifdef SNDRV_HAL2_DEBUG
	snd_printk("ADC: blocks: %d block_size: %d size: %u\n",
		   blocks, block_size, size);
#endif

	if (hal2->hal2.adc.ringbuf) {
		snd_free_pages((char *) hal2->hal2.adc.ringbuf,
			       hal2->hal2.adc.blocks * sizeof(snd_hal2_ring_t));
	}

	hal2->hal2.adc.ringbuf = (snd_hal2_ring_t *)
		snd_malloc_pages(blocks * sizeof(snd_hal2_ring_t), &pg, 0);
	if (hal == NULL2->hal2.adc.ringbuf) {
		snd_printd("Oops, no memory for DMA descriptors");
		return;
	}
	hal2->hal2.adc.blocks = blocks;

	/* Setup the ring of DMA descriptors to point to their parts of the DMA
	 * buffer and to generate interrrupts.
	 */
	i = 0;
	ring = hal2->hal2.adc.ringbuf;
	ring->desc.pbuf = PHYSADDR(buffer);
	ring->desc.pnext = PHYSADDR(ring + 1);
	ring->desc.cntinfo = block_size | HPCDMA_XIE;
	while (++i < blocks) {
		ring++;
		ring->desc.pbuf = PHYSADDR(buffer + i * block_size);
		ring->desc.pnext = PHYSADDR(ring + 1);
		ring->desc.cntinfo = block_size | HPCDMA_XIE;
	}
	ring->desc.pnext = PHYSADDR(hal2->hal2.adc.ringbuf);
	
	/* The PBUS can prolly not read thes stuff when it's in the cache so we
	 * have to flush it back to main memory
	 */
	dma_cache_wback_inv((unsigned long) hal2->hal2.adc.ringbuf,
			    blocks * sizeof(snd_hal2_ring_t));

	/* Now we set up some PBUS information. The PBUS needs information about
	 * what portion of the fifo it will use. If it's receiving or
	 * transmitting, and finally whether the stream is little endian or big
	 * endian. The information is written later, on the trigger call.
	 */
	
	sample_size = substream1->voices * 2;

	pbus = &hal2->hal2.adc.pbus;
	pbus->highwater = (sample_size * 2) >> 1;	/* halfwords */
	pbus->fifobeg = (4 * 4) >> 3;			/* capture is second */
	pbus->fifoend = (sample_size * 4) >> 3;		/* doublewords */
	pbus->ctrl = HPC3_PDMACTRL_RT | HPC3_PDMACTRL_RCV |
		((substream1->mode & SNDRV_PCM1_MODE_BIG) ? 0 : HPC3_PDMACTRL_SEL);

#ifdef SNDRV_HAL2_DEBUG
	snd_printk("ADC: mode: %x voices: %d\n", substream1->mode, substream1->voices);
#endif

	/* We disable everything before we do anything at all
	 */
	pbus->pbus->pbdma_ctrl = HPC3_PDMACTRL_LD;
	snd_hal2_i_clearbit16(hal2, H2I_DMA_PORT_EN, H2I_DMA_PORT_EN_CODECR);
	snd_hal2_i_clearbit16(hal2, H2I_DMA_DRV, (1 << pbus->pbusnr));

	/* Setup the HAL2 for capture
	 */
	substream1->real_rate = snd_hal2_compute_rate(hal2, substream1->rate);
	snd_hal2_set_rate(hal2);
	snd_hal2_i_write16(hal2, H2I_ADC_C1,
			   (pbus->pbusnr << H2I_ADC_C1_DMA_SHIFT) |
			   (1 << H2I_ADC_C1_CLKID_SHIFT));
	snd_hal2_i_write16(hal2, H2I_DAC_C1,
			   (1 << H2I_DAC_C1_CLKID_SHIFT) |
			   (substream1->voices << H2I_DAC_C1_DATAT_SHIFT));
	snd_hal2_i_write32(hal2, H2I_ADC_C2, 0);	/* disable muting */

	/* The spec says that we should write 0x08248844 but that's WRONG. HAL2
	 * does 8 bit DMA, not 16 bit even if it generates 16 bit audio.
	 */
	hpc3c0->pbus_dmacfgs[pbus->pbusnr][0] = 0x08208844;	/* Magic :-) */
}

static void snd_hal2_playback_trigger(void *private_data,
				      snd_pcm_substream_t *substream, int up)
{
	snd_hal2_card_t *hal2;
	snd_hal2_pbus_t *pbus;

#ifdef SNDRV_HAL2_DEBUG
	snd_printk("Trigger %s\n", up ? "up" : "down");
#endif

	hal2 = (snd_hal2_card_t *) private_data;
	pbus = &hal2->hal2.dac.pbus;

	if (up) {
		unsigned long fifobeg, fifoend, highwater;

		if (hal == NULL2->hal2.dac.ringbuf) {
			snd_printd("Oops, there was no ringbuf to trigger!\n");
			return;
		}

		fifobeg = pbus->fifobeg;
		fifoend = pbus->fifoend;
		highwater = pbus->highwater;

		pbus->pbus->pbdma_dptr = PHYSADDR(hal2->hal2.dac.ringbuf);

		/* We can not activate the PBUS at the same time as we configure
		 * it. We have to write the configuration bits first and then
		 * write both the configuration bits and the activation bits.
		 * If we do not do so, we might get noise in the DMA stream.
		 */
		pbus->ctrl |= ((highwater << 8) | (fifobeg << 16) | (fifoend << 24) | HPC3_PDMACTRL_LD);
		pbus->pbus->pbdma_ctrl = pbus->ctrl;
		pbus->ctrl |= (HPC3_PDMACTRL_ACT);
		pbus->pbus->pbdma_ctrl = pbus->ctrl;

#if 0
		/* Shouldn't this make difference? It works fine without it..
		 */
		if (substream1->mode & SNDRV_PCM1_MODE_BIG)
			snd_hal2_i_clearbit16(hal2, H2I_DMA_END,
					      H2I_DMA_END_CODECTX);
		else
			snd_hal2_i_setbit16(hal2, H2I_DMA_END,
					    H2I_DMA_END_CODECTX);
#endif

		snd_hal2_i_setbit16(hal2, H2I_DMA_DRV, (1 << pbus->pbusnr));
		snd_hal2_i_setbit16(hal2, H2I_DMA_PORT_EN, H2I_DMA_PORT_EN_CODECTX);
#ifdef SNDRV_HAL2_DEBUG
		snd_hal2_dump_regs(hal2);
#endif
	} else {
		pbus->pbus->pbdma_ctrl = HPC3_PDMACTRL_LD;
		/* The HAL2 itself may remain enabled safely */
	}
}

static void snd_hal2_capture_trigger(void *private_data,
				     snd_pcm_substream_t *substream, int up)
{
	snd_hal2_card_t *hal2;
	snd_hal2_pbus_t *pbus;

#ifdef SNDRV_HAL2_DEBUG
	snd_printk("Trigger %s\n", up ? "up" : "down");
#endif

	hal2 = (snd_hal2_card_t *) private_data;
	pbus = &hal2->hal2.adc.pbus;

	if (up) {
		unsigned long fifobeg, fifoend, highwater;

		if (hal == NULL2->hal2.adc.ringbuf) {
			snd_printd("Oops, there was no ringbuf to trigger!\n");
			return;
		}

		fifobeg = pbus->fifobeg;
		fifoend = pbus->fifoend;
		highwater = pbus->highwater;

		pbus->pbus->pbdma_dptr = PHYSADDR(hal2->hal2.adc.ringbuf);

		pbus->ctrl |= ((highwater << 8) | (fifobeg << 16) | (fifoend << 24) | HPC3_PDMACTRL_LD);
		pbus->pbus->pbdma_ctrl = pbus->ctrl;
		pbus->ctrl |= (HPC3_PDMACTRL_ACT);
		pbus->pbus->pbdma_ctrl = pbus->ctrl;

#if 0
		if (substream1->mode & SNDRV_PCM1_MODE_BIG)
			snd_hal2_i_clearbit16(hal2, H2I_DMA_END,
					      H2I_DMA_END_CODECR);
		else
			snd_hal2_i_setbit16(hal2, H2I_DMA_END,
					    H2I_DMA_END_CODECR);
#endif
		snd_hal2_i_setbit16(hal2, H2I_DMA_DRV, (1 << pbus->pbusnr));
		snd_hal2_i_setbit16(hal2, H2I_DMA_PORT_EN, H2I_DMA_PORT_EN_CODECR);
	} else {
		pbus->pbus->pbdma_ctrl = HPC3_PDMACTRL_LD;
		/* The HAL2 itself may safely remain enabled */
	}
}

static snd_pcm_uframes_t snd_hal2_playback_pointer(void *private_data,
					      snd_pcm_substream_t *substream,
					      unsigned int used_size)
{
	snd_hal2_card_t *hal2 = (snd_hal2_card_t *) private_data;

	return hal2->hal2.dac.pbus.pbus->pbdma_bptr;
}

static snd_pcm_uframes_t snd_hal2_capture_pointer(void *private_data,
					     snd_pcm_substream_t *substream,
					     unsigned int used_size)
{
	snd_hal2_card_t *hal2 = (snd_hal2_card_t *) private_data;

	return hal2->hal2.adc.pbus.pbus->pbdma_bptr;
}

static int snd_hal2_playback_dma(void *private_data, snd_pcm_substream_t *substream,
				 unsigned char *buffer, unsigned int offset,
				 unsigned char *user, unsigned int count)
{
	int ret;
	ret = snd_pcm1_playback_dma(private_data, substream, buffer, offset, user,
				    count);
	dma_cache_wback_inv((unsigned long) &buffer[offset], count);
	return ret;
}

static int snd_hal2_capture_dma(void *private_data, snd_pcm_substream_t *substream,
				unsigned char *buffer, unsigned int offset,
				unsigned char *user, unsigned int count)
{
	int ret;
	dma_cache_inv((unsigned long) &buffer[offset], count);
	ret = snd_pcm1_capture_dma(private_data, substream, buffer, offset, user,
				   count);
	return ret;
}

static int snd_hal2_dma_move(void *private_data,
			    snd_pcm_substream_t *substream,
			     unsigned char *dbuffer,
			     unsigned int dest_offset,
			     unsigned char *sbuffer,
			     unsigned int src_offset,
			     unsigned int count)
{
	int ret; 
	ret = snd_pcm1_dma_move(private_data, substream, dbuffer, dest_offset,
				sbuffer, src_offset, count);
	dma_cache_wback_inv((unsigned long) &dbuffer[dest_offset], count);
	return ret;
}

static int snd_hal2_playback_dma_neutral(void *private_data,
					 snd_pcm_substream_t *substream,
					 unsigned char *buffer,
					 unsigned int offset,
					 unsigned int count,
					 unsigned char neutral_byte)
{
	int ret;
	ret = snd_pcm1_playback_dma_neutral(private_data, substream, buffer,
					    offset, count, neutral_byte);
	dma_cache_wback_inv((unsigned long) &buffer[offset], count);
	return ret;
}

struct _snd_pcm1_hardware snd_hal2_playback =
{
	SNDRV_PCM1_HW_16BITONLY | SNDRV_PCM1_HW_AUTODMA,	/* flags */
	SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_S16_LE |
		SNDRV_PCM_FMTBIT_S16_BE,			/* formats */
	SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE,	/* hardware formats */
	0,
	2,	/* XXX */	/* minimal fragment */
	4000,			/* min. rate */
	48000,			/* max. rate */
	2,			/* max. voices */
	snd_hal2_playback_open,
	snd_hal2_playback_close,
	snd_hal2_playback_ioctl,
	snd_hal2_playback_prepare,
	snd_hal2_playback_trigger,
	snd_hal2_playback_pointer,
	snd_hal2_playback_dma,
	snd_hal2_dma_move,
	snd_hal2_playback_dma_neutral
};

struct _snd_pcm1_hardware snd_hal2_capture =
{
	SNDRV_PCM1_HW_16BITONLY | SNDRV_PCM1_HW_AUTODMA,	/* flags */
	SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_S16_LE |
		SNDRV_PCM_FMTBIT_S16_BE,			/* formats */
	SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE,	/* hardware formats */
	0,
	2,	/* XXX */	/* minimal fragment */
	4000,			/* min. rate */
	48000,			/* max. rate */
	2,			/* max. voices */
	snd_hal2_capture_open,
	snd_hal2_capture_close,
	snd_hal2_capture_ioctl,
	snd_hal2_capture_prepare,
	snd_hal2_capture_trigger,
	snd_hal2_capture_pointer,
	snd_hal2_capture_dma,
	snd_hal2_dma_move,
	NULL
};

static void snd_hal2_free_device(void *ptr)
{
	snd_hal2_card_t *hal2 = (snd_hal2_card_t *) ptr;
	snd_kfree(hal2);
}

snd_pcm_t *snd_hal2_new_device(snd_card_t *card,
			       snd_irq_t *irqptr,
			       snd_dma_t *dma1ptr,
			       snd_dma_t *dma2ptr,
			       snd_hal2_ctl_regs_t *ctl_regs,
			       snd_hal2_aes_regs_t *aes_regs,
			       snd_hal2_vol_regs_t *vol_regs,
			       snd_hal2_syn_regs_t *syn_regs)
{
	snd_pcm_t *pcm;
	snd_pcm1_channel_t *pchn1;
	snd_hal2_card_t *hal2;

	pcm = snd_pcm1_new_device(card, "HAL2", 1, 1);
	if (pcm == NULL)
		return NULL;
	hal2 = (snd_hal2_card_t *) snd_kcalloc(sizeof(snd_hal2_card_t), GFP_KERNEL);
	if (hal2 == NULL)
		return NULL;

	hal2->pcm = pcm;
	hal2->card = card;
	hal2->hal2.irqptr = irqptr;
	hal2->hal2.dac.dmaptr = dma1ptr;
	if (dma2ptr)
		hal2->hal2.adc.dmaptr = dma2ptr;
	hal2->hal2.ctl_regs = ctl_regs;
	hal2->hal2.aes_regs = aes_regs;
	hal2->hal2.vol_regs = vol_regs;
	hal2->hal2.syn_regs = syn_regs;
	card->private_data = hal2;
	card->private_free = snd_hal2_free_device;

	pchn1 = (snd_pcm1_channel_t *) pcm->playback.private_data;
	memcpy(&pchn1->hw, &snd_hal2_playback,
	       sizeof(snd_hal2_playback));
	pchn1->private_data = hal2;

	pchn1 = (snd_pcm1_channel_t *) pcm->capture.private_data;
	memcpy(&pchn1->hw, &snd_hal2_capture,
	       sizeof(snd_hal2_capture));
	pchn1->private_data = hal2;

	pcm->info_flags = SNDRV_PCM_INFO_CODEC | /* SNDRV_PCM_INFO_MMAP | */
		SNDRV_PCM_INFO_PLAYBACK | SNDRV_PCM_INFO_CAPTURE |
		SNDRV_PCM_INFO_DUPLEX;
	pcm->private_data = hal2;
	pcm->private_free = snd_hal2_free_device;
	strcpy(pcm->name, "HAL2");

	return pcm;
}

/* XXX proc interface? */

EXPORT_SYMBOL(snd_hal2_create);
EXPORT_SYMBOL(snd_hal2_free);
EXPORT_SYMBOL(snd_hal2_interrupt);
EXPORT_SYMBOL(snd_hal2_pcm);

static int __init alsa_hal2_init(void)
{
	return 0;
}

static void __exit alsa_hal2_exit(void)
{
}

module_init(alsa_hal2_init)
module_exit(alsa_hal2_exit)
