/*
 * sound/SVR4.2/soundcard.c
 *
 * Soundcard driver for SVR4.2 UNIX.
 *
 * Copyright by Ian Hartas 1994
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer. 2.
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "sound_config.h"

#ifdef CONFIGURE_SOUNDCARD

#include "dev_table.h"

#define FIX_RETURN(ret) return (ret < 0 ? -ret : 0) 

static int      timer_running = 0;

extern char    *snd_raw_buf[MAX_DSP_DEV][DSP_BUFFCOUNT];
extern unsigned long snd_raw_buf_phys[MAX_DSP_DEV][DSP_BUFFCOUNT];
extern int      snd_raw_count[MAX_DSP_DEV];
static int	soundcard_configured = 0;

static struct fileinfo files[SND_NDEVS];

int         sndopen (dev_t *devp, int flags, int otyp, cred_t *credp);
int         sndclose (dev_t dev, int flags, int otyp, cred_t *credp);
int         sndioctl (dev_t dev, int cmd, void *arg, int mode, cred_t *cred_p);
int         sndread (dev_t dev, uio_t *uiop, cred_t *credp);
int         sndwrite (dev_t dev, uio_t *uiop, cred_t *credp);
static void sound_mem_init (void);

int snddevflag = D_NEW | D_DMA;

int
sndread(dev_t dev, uio_t *uiop, cred_t *credp)
{
	int ret;
	dev_t minor_dev = geteminor(dev);
	ret = sound_read_sw (minor_dev, &files[minor_dev], uiop, uiop->uio_resid);
	FIX_RETURN(ret);
}

int
sndwrite(dev_t dev, uio_t *uiop, cred_t *credp)
{
	int ret;
	dev_t minor_dev = geteminor(dev);

	ret = sound_write_sw (minor_dev, &files[minor_dev], uiop, uiop->uio_resid);
	FIX_RETURN(ret);
}

int
sndopen(dev_t *devp, int flags, int otyp, cred_t *credp)
{
  int             retval;
  static int snd_first_time = 0;

  dev_t minor_dev = geteminor (*devp);

  if (!soundcard_configured && minor_dev)
    {
      printk ("SoundCard Error: The soundcard system has not been configured\n");
      return (ENODEV);
    }

  files[minor_dev].mode = 0;

  if (flags & FREAD && flags & FWRITE)
    files[minor_dev].mode = OPEN_READWRITE;
  else if (flags & FREAD)
    files[minor_dev].mode = OPEN_READ;
  else if (flags & FWRITE)
    files[minor_dev].mode = OPEN_WRITE;

  if ((retval = sound_open_sw (minor_dev, &files[minor_dev])) < 0)
     FIX_RETURN(retval);

  return (0);
}

int
sndclose(dev_t dev, int flags, int otyp, cred_t *credp)
{

  dev_t minor_dev = geteminor(dev);

  sound_release_sw (minor_dev, &files[minor_dev]);

  return (0);
}

int
sndioctl(dev_t dev, int cmd, void *arg, int mode, cred_t *credp)
{
  int ret;
  dev_t minor_dev = geteminor(dev);

  ret = sound_ioctl_sw (minor_dev, &files[minor_dev], cmd, (unsigned int) arg);
  FIX_RETURN(ret);
}

void
sndinit()
{
  int             i;
  int	mem_start = 0xefffffff;

  struct cfg_tab {
		int unit, addr, irq;
	};

  extern struct cfg_tab snd_cfg_tab[];

/*
 * First read the config info from the Space.c
 */

  i = 0;
  while (i<20 && snd_cfg_tab[i].unit != -1)
  {
  	int card_type, dma;

  	card_type = snd_cfg_tab[i].unit;

  	dma = card_type % 10;	/* The last digit */
  	card_type /= 10;

  	sound_chconf(card_type, snd_cfg_tab[i].addr, snd_cfg_tab[i].irq, dma);

  	i++;
  } 

  soundcard_configured = 1;

  mem_start = sndtable_init (mem_start);	/* Initialize call tables and
						 * detect cards */

  if (sndtable_get_cardcount () == 0)
    return ;		/* No cards detected */

  sound_mem_init();		/* Don't know where to call this -- AW */
  if (num_dspdevs)		/* Audio devices present */
    {
      mem_start = DMAbuf_init (mem_start);
      mem_start = audio_init (mem_start);
    }

#ifndef EXCLUDE_MPU401
  if (num_midis)
    mem_start = MIDIbuf_init (mem_start);
#endif

  if (num_midis + num_synths)
    mem_start = sequencer_init (mem_start);

  return ;
}

void
request_sound_timer (int count)
{
  static int      current = 0;
  int             tmp = count;

  if (count < 0)
    timeout (sequencer_timer, 0, -count);
  else
    {

      if (count < current)
	current = 0;		/* Timer restarted */

      count = count - current;

      current = tmp;

      if (!count)
	count = 1;

      timeout (sequencer_timer, 0, count);
    }
  timer_running = 1;
}

int
isc_sound_timeout(caddr_t  arg)
{
	unsigned long flags;

	DISABLE_INTR(flags);
	if (*arg & WK_SLEEP) wakeup(arg);
	*arg = WK_TIMEOUT;
	RESTORE_INTR(flags);
}

void
sound_stop_timer (void)
{
  if (timer_running)
    untimeout (sequencer_timer);
  timer_running = 0;
}


#ifndef EXCLUDE_AUDIO

static void
sound_mem_init (void)
{
  int             i, dev;
  unsigned long   dma_pagesize;
  static unsigned long dsp_init_mask = 0;

  for (dev = 0; dev < num_dspdevs; dev++)	/* Enumerate devices */
    if (!(dsp_init_mask & (1 << dev)))	/* Not already done */
      if (sound_buffcounts[dev] > 0 && sound_dsp_dmachan[dev] > 0)
	{
	  dsp_init_mask |= (1 << dev);

#ifndef DMAMODE_AUTO
	  if (sound_dma_automode[dev])
	    {
	      sound_dma_automode[dev] = 0;	/* Automode not possible with SCO */
	    }					/* ...but MAY be for ISC? */
#endif

	  if (!sound_dma_automode[dev])
	  if (sound_buffcounts[dev] == 1)
	    {
	      sound_buffcounts[dev] = 2;
	      sound_buffsizes[dev] /= 2;
	    }

	  if (sound_buffsizes[dev] > 65536)	/* Larger not possible yet */
 	     sound_buffsizes[dev] = 65536;

	  if (sound_dsp_dmachan[dev] > 3 && sound_buffsizes[dev] > 65536)
	    dma_pagesize = 131072;	/* 128k */
	  else
	    dma_pagesize = 65536;

	  /* More sanity checks */

	  if (sound_buffsizes[dev] > dma_pagesize)
	    sound_buffsizes[dev] = dma_pagesize;
	  sound_buffsizes[dev] &= ~0x0fff;	/* Portably truncate to n*4k */
	  if (sound_buffsizes[dev] < 4096)
	    sound_buffsizes[dev] = 4096;

	/* Allocate the buffers now. Not later at open time */

	  for (snd_raw_count[dev] = 0; snd_raw_count[dev] < sound_buffcounts[dev]; snd_raw_count[dev]++)
	    {
	      /*
	       * The DMA buffer allocation algorithm hogs memory. We allocate
	       * a memory area which is two times the requires size. This
	       * guarantees that it contains at least one valid DMA buffer.
	       * 
	       * This really needs some kind of finetuning.
	       */
	      char *tmpbuf;
	      unsigned long   addr, rounded, start, end;
	      tmpbuf = (char *)kmem_alloc(2 * sound_buffsizes[dev], KM_NOSLEEP);

	      if (tmpbuf == NULL)
		{
		  printk ("snd: Unable to allocate %d bytes of buffer\n",
			  2 * sound_buffsizes[dev]);
		  return;
		}

	      addr = kvtophys (tmpbuf);
	      /*
	       * Align the start address if required
	       */
	      start = (addr & ~(dma_pagesize - 1));
	      end = ((addr+sound_buffsizes[dev]) & ~(dma_pagesize - 1));

	      if (start != end)
	         rounded = end;
	      else
                 rounded = addr;	/* Fits to the same DMA page */

	      snd_raw_buf[dev][snd_raw_count[dev]] =
		&tmpbuf[rounded - addr];	/* Compute offset */
	      /*
	       * Convert from virtual address to physical address, since
	       * that is needed from dma.
	       */
	      snd_raw_buf_phys[dev][snd_raw_count[dev]] =
		(unsigned long) kvtophys(snd_raw_buf[dev][snd_raw_count[dev]]);
	    }
	}			/* for dev */
}
#endif

#if 0
int
snd_ioctl_return (int *addr, int value)
{
  if (value < 0)
    return value;		/* Error */
  suword (addr, value);
  return 0;
}
#endif
typedef void (*irq_entry)(int);

irq_entry irq_tab[16] = {NULL};

int
snd_set_irq_handler (int vect, irq_entry hndlr)
{
  if (irq_tab[vect])
     printk("Sound Warning: IRQ#%d was already in use.\n", vect);

  irq_tab[vect] = hndlr;
  return 1;
}

void
snd_release_irq(int vect)
{
  irq_tab[vect] = (irq_entry)NULL;
}


void
sndintr(int vect)
{
    if (vect<0 || vect > 15) 
		return ;

    if (!irq_tab[vect]) 
		return ;

    irq_tab[vect](vect);	/* Call the actual handler */
    return ;
}

#endif /* CONFIGURE_SOUNDCARD */
