/*
 *  Copyright (c) by Martin Pahl <pahl@tnt.uni-hannover.de>
 *  Routines for Zefiro ZA2
 *
 *
 *   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.
 *
 */

#define __SND_OSS_COMPAT__
#include "driver.h"
#include "za2.h"

static const unsigned char fpga_bit_stream[] = {
#ifdef CONFIG_ZA2_XZA2R1
#include "xza2r1.dcl"
#else
#ifdef CONFIG_ZA2_XZA2R1N
#include "xza2r1n.dcl"
#else
#ifdef CONFIG_ZA2_XZA2R2
#include "xza2r2.dcl"
#else
#ifdef CONFIG_ZA2_XZA2R2N
#include "xza2r2n.dcl"
#else
/*#include "xza2rd.dcl"*/
#include "za2_xza2rdx3.dcl"
#endif
#endif
#endif
#endif
};

unsigned char prg1[] = {
#include "za2_dspos.inc"
};

struct snd_stru_za2_dspos dspos_default =
{
	prg1,
	sizeof(prg1),
	"dspos"
};

#if 1
static void snd_za2_interrupt(snd_pcm_t *pcm)
{
	snd_pcm1_t *pcm1;
        za2_t *codec;
   	int residue, transfered;

        pcm1 = (snd_pcm1_t *) pcm->private_data;
        codec = (za2_t *) pcm1->private_data;
	
	if (codec->trigger_value & 1) {
	     	if (codec->play_block_size) {
		   	residue=snd_dma_residue(codec->dma_out16);
		   	if (residue>codec->play_residue)
		     		transfered=codec->play_residue
		   		  	+codec->play_dma_size
		     			-residue;
			else
		     		transfered=codec->play_residue-residue;
#ifdef TEST_ZA2
printk("interrupt: residue=%05x oldres=%05x trans=%d, bs=%d dma_size=%ld\n",
       residue,codec->play_residue,transfered,
       codec->play_block_size,codec->dma_out16ptr->size);
#endif		   
		     	if (transfered >= codec->play_block_size) {
		     		pcm1->playback.ack(pcm1);
			   	transfered-=codec->play_block_size;
			}
		     	if (transfered >= codec->play_block_size) {
		     		pcm1->playback.ack(pcm1);
			   	transfered-=codec->play_block_size;
			}
			codec->play_residue=residue+transfered;
#ifdef TEST_ZA2	   
printk("interrup2: residue=%05x oldres=%05x trans=%d, bs=%d dma_size=%d (%d)\n",
       residue,codec->play_residue,transfered,
       codec->play_block_size,codec->dma_out16ptr->size,
       transfered>=codec->play_block_size);
#endif
		}
	}
   
	if (codec->trigger_value & 2) {
	     	if (codec->rec_block_size) {
		   	residue=snd_dma_residue(codec->dma_out16);
		   	if (residue>codec->rec_residue)
		     		transfered=codec->rec_residue
		   		  	+codec->rec_dma_size
		     			-residue;
			else
		     		transfered=codec->rec_residue-residue;
printk("interrupt: residue=%05x oldres=%05x trans=%d, bs=%d dma_size=%ld\n",
       residue,codec->rec_residue,transfered,
       codec->rec_block_size,codec->dma_out16ptr->size);
#ifdef TEST_ZA2
#endif		   
		     	if (transfered >= codec->rec_block_size) {
		     		pcm1->record.ack(pcm1);
			   	transfered-=codec->rec_block_size;
			}
		     	if (transfered >= codec->rec_block_size) {
		     		pcm1->record.ack(pcm1);
			   	transfered-=codec->rec_block_size;
			}
			codec->rec_residue=residue+transfered;
#ifdef TEST_ZA2	   
printk("interrup2: residue=%05x oldres=%05x trans=%d, bs=%d dma_size=%d (%d)\n",
       residue,codec->rec_residue,transfered,
       codec->rec_block_size,codec->dma_out16ptr->size,
       transfered>=codec->rec_block_size);
#endif
		}
	}

   	codec->irq_count++;
#if TEST_ZA2
	printk("ZA2: interrupt done (blk_size=%d, trigger_value=%d)\n",
	       codec->rec_block_size, codec->trigger_value);
#endif
}
#endif

#if 0
static void snd_za2_interrupt(snd_pcm_t *pcm)
{
	snd_pcm1_t *pcm1;
        za2_t *codec;

        pcm1 = (snd_pcm1_t *) pcm->private_data;
        codec = (za2_t *) pcm1->private_data;
	
	if (codec->trigger_value & 1)
		pcm1->playback.ack(pcm1);
	if (codec->trigger_value & 2)
		pcm1->record.ack(pcm1);
   	codec->irq_count++;
#if 0
	printk("ZA2: interrupt done\n");
#endif
}
#endif

static int snd_za2_init(snd_pcm_t *pcm, int enable)
{
	snd_pcm1_t *pcm1;
	za2_t *codec;
	int err;
	pcm1=(snd_pcm1_t*)pcm->private_data;
	codec=(za2_t*)pcm1->private_data;
	if (enable) {
		printk("ZA2: uploading fpga mask...");
		if ((err=snd_za2_upbit(codec, fpga_bit_stream))!=0) {
			if (err!=ZA2_ERR_ALREADY_INITIALIZED) {
				printk("failed\nZA2: error(%d) uploading fpga mask\n",err);
				return -EIO;
			}
		   	printk("already loaded - ");
		}
		printk("Ok\n");
		printk("ZA2: uploading micro-code ...");
		if ((err=snd_za2_upsim(codec))!=0) {
			printk("failed\nZA2: error %d uploading micro-code\n",err);
			return -EIO;
		}
		printk("Ok\n");
	}
	return 0;
}

static int snd_za2_playback_open(snd_pcm1_t *pcm1)
{
	snd_card_t *card;
	za2_t *codec;
	int err;
	
	card = pcm1->card;
	codec = (za2_t*)pcm1->private_data;
   	if (codec->state != ZA2_STATE_CLOSE)
     		return -EBUSY;
	printk("ZA2: playback_open: pcm1 mode=0x%x, format=0x%x, real_rate=%ld\n",
	       pcm1->playback.mode,
	       pcm1->playback.format,
	       codec->srate);
    	snd_za2_setenv(codec);
	snd_za2_numode(codec,ZA2_MODE_FREE_PLAY);
	if ((err=snd_pcm1_dma_alloc(pcm1, SND_PCM1_PLAYBACK, 
				    codec->dma_out16ptr, "ZA2 (playback)"))<0)
		return err;
   	codec->state=ZA2_STATE_PLAYBACK_PREPARE;
	return 0;
}

static void snd_za2_playback_close(snd_pcm1_t *pcm1)
{
	za2_t *codec;
	
	codec=(za2_t*)pcm1->private_data;
   	printk("za2_playback_close: interrupts=%d\n",codec->irq_count);
	snd_pcm1_dma_free(pcm1, SND_PCM1_PLAYBACK, codec->dma_out16ptr);
   	codec->state=ZA2_STATE_CLOSE;
}

static int snd_za2_playback_ioctl(snd_pcm1_t *pcm1,
				  unsigned int cmd, unsigned long *arg)
{
	za2_t *codec;

	codec = (za2_t*)pcm1->private_data;
	printk("snd_za2_playback_ioctl: cmd=%d\n",cmd);
	switch (cmd) {
	case SND_PCM1_IOCTL_RATE:
		pcm1->playback.real_rate = pcm1->playback.rate;
		if (pcm1->playback.real_rate < pcm1->playback.hw.min_rate)
			pcm1->playback.real_rate = pcm1->playback.hw.min_rate;
		if (pcm1->playback.real_rate > pcm1->playback.hw.max_rate)
			pcm1->playback.real_rate = pcm1->playback.hw.max_rate;
#ifdef TEST_ZA2
	     	codec->srate=48000;
		pcm1->playback.real_rate=codec->srate;
#endif
	        codec->srate=pcm1->playback.real_rate;
	        printk("snd_za2_playback_ioctl: realrate=%d rate=%d\n",
		       
		       pcm1->playback.real_rate,pcm1->playback.rate);
		return 0;
	}
	return -ENXIO;
		
}

static void snd_za2_playback_prepare(snd_pcm1_t *pcm1,
 				     unsigned char *buffer,
				     unsigned int size,
				     unsigned int offset,
				     unsigned int count)
{
	za2_t *codec;
	snd_pcm1_channel_t *pchn;
	long irq_rate;
	snd_pcm_t *pcm;

	codec = (za2_t*) pcm1->private_data;
	pchn = &pcm1->playback;
	printk("ZA2: playback_prepare: pcm1 mode=0x%x, format=%d, real_rate=%d\n",
	       pcm1->playback.mode,
	       pcm1->playback.format,
	       pcm1->playback.real_rate);
	printk("ZA2: playback_prepare: buffer=%lx, size=%x, offset=%x, count=%x(%x)\n",
	       (long)buffer,size,offset,count,pchn->block_size);
	disable_dma(codec->dma_out16);
#if 0
   	codec->srate=44100;
#endif
	snd_za2_set_samplerate(codec);
	if (!snd_za2_initialized(codec))
		snd_za2_upsim(codec);
	snd_za2_setenv(codec);

	pcm = codec->pcm;
   	irq_rate = count/4;
#if 0
#endif   
   	printk("ZA2: terminate_setza %d\n",snd_za2_terminate_setza(codec));
    	snd_za2_dsp_open(codec);
	snd_za2_dsp_send(codec, ZA2_REG_SYNC_OUTPUT,0);
	snd_za2_dsp_send(codec, ZA2_REG_DAC_STATE, 0xb001);
	snd_za2_dsp_send(codec, ZA2_REG_IRQ_RATE, irq_rate);
	printk("ZA2: dsp_close=%d, irq_rate=0x%lx(%d) \n",
	       snd_za2_dsp_close(codec),
	       irq_rate,
	       pchn->block_size);

	printk("ZA2: playback_prepare command=0x%x(0x%x)\n",
	       codec->command,codec->command&ZA2_NBITS_IDMA);
#if 1
   	outw(codec->command&ZA2_BITS_INIT,ZA2P(codec,COMMAND));
#endif
	snd_dma_program(codec->dma_out16, buffer, size,
			DMA_MODE_WRITE | DMA_MODE_AUTOINIT | DMA_MODE_NO_ENABLE);
      	codec->play_block_size=count;
   	codec->play_residue=snd_dma_residue(codec->dma_out16);
   	codec->play_dma_size=size;
   	codec->play_dma_buf=buffer;

	printk("ZA2: playback_prepare (Done) dma=%d, buffer=0x%lx, size=0x%x, residue=%05x\n",
	       codec->dma_out16, 
	       virt_to_bus((void*)buffer), size, codec->play_residue);
	
}

static unsigned int snd_za2_playback_pointer(snd_pcm1_t *pcm1, 
					     unsigned int used_size)
{
	za2_t *codec;
	
	codec=(za2_t*)pcm1->private_data;
	printk("ZA2: playback_pointer\n");
	
	return used_size-snd_dma_residue(codec->dma_out16);
}

static void snd_za2_playback_trigger(snd_pcm1_t * pcm1, int up)
{
	za2_t *codec;
	unsigned long flags;
   	int offset;
#if 0   
   	int k;
#endif   
	
	codec = (za2_t*) pcm1->private_data;
	codec->trigger_value=(up?1:0);
	printk("ZA2: playback_trigger (up=%d, command=0x%x)\n",
	       up,codec->command&ZA2_NBITS_IDMA);
	if (up) {
		snd_cli(&flags);
	   	offset=snd_dma_residue(codec->dma_out16);
	   	offset= offset==0 ? 0 : codec->play_dma_size-offset;
	   	codec->play_dma_buf[offset  ]=0x00;
	   	codec->play_dma_buf[offset+1]=0x00;
	   	offset=(offset+2)&0xfffffffc;
	   	codec->play_dma_buf[offset  ]=0x00;
	   	codec->play_dma_buf[offset+1]=0x00;
	   	codec->play_dma_buf[offset+2]=0x61;
	   	codec->play_dma_buf[offset+3]=0x7a;
#if 1
	   	printk("ZA2: playback_trigger (offset=0x%05x)\n",offset);
#endif	   
	   	outw(codec->command&ZA2_NBITS_IDMA,ZA2P(codec,COMMAND));
 		enable_dma(codec->dma_out16);
		/*! Disable DMA-IN, no duplex yet !*/
		snd_sti(&flags);
	   	codec->state=ZA2_STATE_PLAYBACK_RUNNING;
	   	/*dma_outb(0, DMA2_CMD_REG);*/ /* enable controller */

	}
	else {
		snd_cli(&flags);
	   	codec->play_block_size=0;
		outw(codec->command & ZA2_BITS_INIT,ZA2P(codec,COMMAND));
	   	disable_dma(codec->dma_out16);
		snd_za2_dsp_open(codec);
#ifdef OLD	   
		/* set R=R and L=L again */
		snd_za2_dsp_send(codec,ZA2_REG_SYNC_OUTPUT,1);
#endif	   
	   	/* Wait for sync signal */
		snd_za2_dsp_send(codec,ZA2_REG_SYNC_OUTPUT,0);
		printk("ZA2: dsp_close=%d\n",snd_za2_dsp_close(codec));
		outw(codec->command & ZA2_BITS_INIT,ZA2P(codec,COMMAND));
		outw(0,ZA2P(codec,SAMPLES));
	   	codec->state=ZA2_STATE_PLAYBACK_PREPARE;
 		snd_sti(&flags);
	}
	printk("ZA2: playback_trigger (Done)\n");
}

void snd_za2_playback_dma_ulaw(snd_pcm1_t * pcm1,
			        unsigned char *buffer, unsigned int offset,
				unsigned char *user, unsigned int count)
{
#if 0
   	if (pcm1->playback.mode & SND_PCM1_MODE_ULAW)
		snd_translate_from_user(snd_ulaw_dsp, &buffer[offset],
				        user, count);
	else
#endif     
		copy_from_user(&buffer[offset], user, count);
}

static int snd_za2_record_open(snd_pcm1_t *pcm1)
{
	snd_card_t *card;
	za2_t *codec;
	int err;

	card = pcm1->card;
	codec = (za2_t*)pcm1->private_data;
      	if (codec->state != ZA2_STATE_CLOSE)
     		return -EBUSY;
	printk("snd_za2_record_open: pcm1 mode=0x%x, format=0x%x, real_rate=%ld\n",
	       pcm1->record.mode,
	       pcm1->record.format,
	       codec->srate);
    	snd_za2_setenv(codec);
	snd_za2_numode(codec,ZA2_MODE_PASS_THRU);
	if ((err=snd_pcm1_dma_alloc(pcm1, SND_PCM1_RECORD, 
				    codec->dma_in16ptr, "ZA2 (record)"))<0)
		return err;
      	codec->state=ZA2_STATE_RECORD_PREPARE;
	return 0;
}

static void snd_za2_record_close(snd_pcm1_t *pcm1)
{
	za2_t *codec;
	
	codec=(za2_t*)pcm1->private_data;
	snd_pcm1_dma_free(pcm1, SND_PCM1_RECORD, codec->dma_in16ptr);
   	codec->state=ZA2_STATE_CLOSE;
}

static int snd_za2_record_ioctl(snd_pcm1_t *pcm1,
				unsigned int cmd, unsigned long *arg)
{
	switch (cmd) {
	case SND_PCM1_IOCTL_RATE:
		pcm1->record.real_rate = pcm1->record.rate;
		if (pcm1->record.real_rate < pcm1->record.hw.min_rate)
			pcm1->record.real_rate = pcm1->record.hw.min_rate;
		if (pcm1->record.real_rate > pcm1->record.hw.max_rate)
			pcm1->record.real_rate = pcm1->record.hw.max_rate;
		return 0;
	}
	return -ENXIO;
		
}
static void snd_za2_record_prepare(snd_pcm1_t *pcm1,
				   unsigned char *buffer,
				   unsigned int size,
				   unsigned int offset,
				   unsigned int count)
{
	za2_t *codec;
	snd_pcm1_channel_t *pchn;
	snd_pcm_t *pcm;

	pchn = &pcm1->record;
	/*! set sampling-rate !*/
	codec = (za2_t*) pcm1->private_data;
	if (!snd_za2_initialized(codec))
		snd_za2_upsim(codec);
	snd_za2_setenv(codec);

	pcm = codec->pcm;
   	codec->irq_rate = count/4;
	snd_dma_program(codec->dma_in16, buffer, size, 
			DMA_MODE_READ | DMA_MODE_AUTOINIT | DMA_MODE_NO_ENABLE);
}

static unsigned int snd_za2_record_pointer(snd_pcm1_t *pcm1, 
					   unsigned int used_size)
{
	za2_t *codec;
	
	codec=(za2_t*)pcm1->private_data;
	
	return used_size-snd_dma_residue(codec->dma_in16);
}

static void snd_za2_record_trigger(snd_pcm1_t *pcm1, int up)
{
	unsigned long flags;
	int k;
	za2_t *codec;
	
	codec = (za2_t*) pcm1->private_data;
	if (up) {
		snd_cli(&flags);
		snd_za2_dsp_open(codec);
		snd_za2_dsp_send(codec, ZA2_REG_IRQ_RATE, codec->irq_rate);
		if (snd_za2_dsp_close(codec)) {
			snd_sti(&flags);
			printk("snd_za2_record_prepare: PLL is unable to lock\n");
			/*! Error handling !*/
			return;
		}
		
		k=65536;
		while( (((unsigned short) inw(ZA2P(codec,SAMPLES))) == ZA2_ZA_ID) 
	       		&& --k ) 
			;

		/*! Disable DMA-OUT, no duplex yet !*/
		outw(codec->command&ZA2_NBITS_ODMA,ZA2P(codec,COMMAND));
	   	enable_dma(codec->dma_in16);
		snd_sti(&flags);

		if (!k) {
			/*! Error handling (no sync received) !*/
		   	disable_dma(codec->dma_in16);
			outw(0,ZA2P(codec,SAMPLES));
			outw(codec->command&ZA2_BITS_INIT,ZA2P(codec,COMMAND));
			return;
		}
      		codec->state=ZA2_STATE_RECORD_RUNNING;
		codec->trigger_value=2;
	}
	else {

		codec->trigger_value=0;
		outw(0,ZA2P(codec,SAMPLES));
		outw(codec->command & ZA2_BITS_INIT,ZA2P(codec,COMMAND));
      		codec->state=ZA2_STATE_RECORD_PREPARE;
	}
}

void snd_za2_record_dma_ulaw(snd_pcm1_t * pcm1,
			      unsigned char *buffer, unsigned int offset,
			      unsigned char *user, unsigned int count)
{
#if 0
	if (pcm1->record.mode & SND_PCM1_MODE_ULAW)
		snd_translate_to_user(snd_dsp_ulaw, user,
				      &buffer[offset], count);
	else
#endif     
		copy_to_user(user, &buffer[offset], count);
}

static void snd_za2_free(void *private_data);

static struct snd_stru_pcm1_hardware snd_za2_playback =
{
	NULL, /* private data */
	NULL, /* private free */
	SND_PCM1_HW_AUTODMA | SND_PCM1_HW_16BITONLY, /* flags */
	SND_PCM_FMT_MU_LAW | SND_PCM_FMT_S16_LE, /* formats */
	SND_PCM_FMT_S16_LE, /* hardware formats */
	0,     /* align value */
	6,     /* minimal fragment */
	20000, /* min. rate */
	60000, /* max. rate */
	2,
	snd_za2_playback_open,
	snd_za2_playback_close,
	snd_za2_playback_ioctl,
	snd_za2_playback_prepare,
	snd_za2_playback_trigger,
	snd_za2_playback_pointer,
	snd_za2_playback_dma_ulaw,
	snd_pcm1_dma_move,
	snd_pcm1_playback_dma_neutral
};

static struct snd_stru_pcm1_hardware snd_za2_record =
{
	NULL, /* private data */
	NULL, /* private free */
	SND_PCM1_HW_AUTODMA | SND_PCM1_HW_16BITONLY, /* flags */
	SND_PCM_FMT_MU_LAW | SND_PCM_FMT_S16_LE, /* formats */
	SND_PCM_FMT_S16_LE, /* hardware formats */
	0,     /* align value */
	6,    /* minimal fragment */
	20000, /* min. rate */
	60000, /* max. rate */
	2,
	snd_za2_record_open,
	snd_za2_record_close,
	snd_za2_record_ioctl,
	snd_za2_record_prepare,
	snd_za2_record_trigger,
	snd_za2_record_pointer,
	snd_za2_record_dma_ulaw,
	snd_pcm1_dma_move,
	NULL
};

static void snd_za2_free(void *private_data)
{
	snd_free(private_data, sizeof(za2_t));
}

snd_pcm_t *snd_za2_new_device(snd_card_t *card,
			      unsigned short port,
			      snd_irq_t *irqptr,
			      snd_dma_t *dma_in16ptr,
			      snd_dma_t *dma_out16ptr,
			      unsigned short digin,
			      unsigned short za2mode)
{
	snd_pcm_t *pcm;
        snd_pcm1_t *pcm1;
        za2_t *codec;
 
	pcm = snd_pcm1_new_device(card, "ZA2");
	if (!pcm)
		return NULL;
	pcm1 = (snd_pcm1_t *) pcm->private_data;
	codec = (za2_t*) snd_calloc(sizeof(za2_t));
	if (!codec)
		return NULL;
	snd_spin_prepare(codec,reg);
	codec->pcm = pcm;
	codec->card = pcm1->card;
	codec->port = port;
	codec->irqptr = irqptr;
	codec->irq = irqptr->irq;
	codec->dma_in16ptr = dma_in16ptr;
	codec->dma_in16 = dma_in16ptr->dma;
	codec->dma_out16ptr = dma_out16ptr;
	codec->dma_out16 = dma_out16ptr->dma;
	codec->digin = digin;
	codec->aeso = 0;
	codec->emph = 0;
	codec->scms = 0;
	codec->za2mode = za2mode;
	codec->isok = 0;
	codec->resetflag = 0;
	codec->cmdcnt    = 0;
	codec->cbitflag  = 0;
   	codec->irq_count = 0;
	codec->dspos = &dspos_default;
   	codec->state=ZA2_STATE_CLOSE;
   	codec->irq_rate=0;
   	codec->play_block_size = 0;
   	codec->play_residue = 0;
   	codec->play_dma_size = 0;
   	codec->play_dma_buf = 0;
   	codec->rec_block_size = 0;
   	codec->rec_residue = 0;
   	codec->rec_dma_size = 0;
   	codec->rec_dma_buf = 0;

	memcpy(&pcm1->playback.hw, &snd_za2_playback, sizeof(snd_za2_playback));
	memcpy(&pcm1->record.hw, &snd_za2_record, sizeof(snd_za2_record));
	pcm1->private_data = codec;
	pcm1->private_free = snd_za2_free;
	pcm->info_flags = SND_PCM_INFO_CODEC | SND_PCM_INFO_MMAP |
            SND_PCM_INFO_PLAYBACK | SND_PCM_INFO_RECORD;
	sprintf(pcm->name,"ZA2");
#ifdef USELESS
	if (snd_za2_probe(pcm1) < 0) {
		snd_pcm_free(pcm);
		return NULL;
	}
#endif
	return pcm;
}

