/*
 * sound/isc/soundcard.c
 *
 * Soundcard driver for INTERACTIVE UNIX.
 *
 * 
 * Copyright by Hannu Savolainen 1993
 *
 * 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"

struct sbc_device
{
  int             usecount;
};

#define FIX_RETURN(ret) { int cnt = (ret); \
	if (cnt<0) u.u_error = RET_ERROR(cnt); else { \
	  u.u_count -= cnt; \
	  u.u_base  += cnt; \
	} return; }

#define FIX_OK_RETURN(ret) { int cnt = (ret); \
	if (cnt<0) u.u_error = RET_ERROR(cnt); \
	return; }

static struct sbc_device sbc_devices[SND_NDEVS];
static int      timer_running = 0;

static int      in_use = 0;	/* Total # of open device files (excluding
				 * minor 0) */

static int      soundcards_installed = 0;	/* Number of installed
						 * soundcards */
static int      soundcard_configured = 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 struct fileinfo files[SND_NDEVS];

int             sndopen (dev_t dev, int flags, int otyp);
int             sndclose (dev_t dev, int flags, int otyp);
int             sndioctl (dev_t dev, int cmd, caddr_t arg, int mode);
void            sndread (dev_t dev);
void            sndwrite (dev_t dev);
static void     sound_mem_init (void);

void
sndread (dev_t dev)
{
  int count;
  char *buf;

  dev = minor (dev);
  count = u.u_count;
  buf = u.u_base;

  DEB (printk ("sndread(dev=%d, count=%d)\n", dev, count));

  switch (dev & 0x0f)
    {
    case SND_DEV_AUDIO:
      FIX_RETURN (audio_read (dev, &files[dev], buf, count));
      break;

    case SND_DEV_DSP:
    case SND_DEV_DSP16:
      FIX_RETURN (dsp_read (dev, &files[dev], buf, count));
      break;

    case SND_DEV_SEQ:
      FIX_RETURN (sequencer_read (dev, &files[dev], buf, count));
      break;

#ifndef EXCLUDE_MPU401
    case SND_DEV_MIDIN:
      FIX_RETURN (MIDIbuf_read (dev, &files[dev], buf, count));
#endif

    default:
      ;
    }

  FIX_RETURN (-EPERM);
}

void
sndwrite (dev_t dev)
{
  char *buf;
  int count;

  dev = minor (dev);
  count = u.u_count;
  buf = u.u_base;

  DEB (printk ("sound_write(dev=%d, count=%d)\n", dev, count));

  switch (dev & 0x0f)
    {

    case SND_DEV_SEQ:
      FIX_RETURN (sequencer_write (dev, &files[dev], buf, count));
      break;

    case SND_DEV_AUDIO:
      FIX_RETURN (audio_write (dev, &files[dev], buf, count));
      break;

    case SND_DEV_DSP:
    case SND_DEV_DSP16:
      FIX_RETURN (dsp_write (dev, &files[dev], buf, count));
      break;

    default:
      FIX_RETURN (-EPERM);
    }

  FIX_RETURN (count);
}

int
sndopen (dev_t dev, int flags, int otyp)
{
  int             retval;
  static int snd_first_time = 0;
  extern long sndinit();

  if (snd_first_time == 0) {
    snd_first_time = 1;
    sndinit((long)snd_raw_buf);
  }

  dev = minor (dev);

  DEB (printk ("sndopen(dev=%d) : usecount=%d\n", dev, sbc_devices[dev].usecount));

  if ((dev >= SND_NDEVS) || (dev < 0))
    {
      printk ("Invalid minor device %d\n", dev);
      FIX_RETURN (-ENODEV);
    }

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

  files[dev].mode = 0;

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

  switch (dev & 0x0f)
    {
    case SND_DEV_CTL:
      if (!soundcards_installed)
	if (soundcard_configured)
	  {
	    printk ("Soundcard not installed\n");
	    FIX_RETURN (-ENODEV);
	  }
      break;

    case SND_DEV_SEQ:
      if ((retval = sequencer_open (dev, &files[dev])) < 0)
	FIX_RETURN (retval);
      break;

#ifndef EXCLUDE_MPU401
    case SND_DEV_MIDIN:
      if ((retval = MIDIbuf_open (dev, &files[dev])) < 0)
	FIX_RETURN (retval);
      break;
#endif

    case SND_DEV_AUDIO:
      if ((retval = audio_open (dev, &files[dev])) < 0)
	FIX_RETURN (retval);
      break;

    case SND_DEV_DSP:
      if ((retval = dsp_open (dev, &files[dev], 8)) < 0)
	FIX_RETURN (retval);
      break;

    case SND_DEV_DSP16:
      if ((retval = dsp_open (dev, &files[dev], 16)) < 0)
	FIX_RETURN (retval);
      break;

    default:
      printk ("Invalid minor device %d\n", dev);
      FIX_RETURN (-ENODEV);
    }

  sbc_devices[dev].usecount++;
  in_use++;

  FIX_OK_RETURN (0);
}

int
sndclose (dev_t dev, int flags, int otyp)
{

  dev = minor (dev);

  DEB (printk ("sound_release(dev=%d)\n", dev));

  switch (dev)
    {
    case SND_DEV_SEQ:
      sequencer_release (dev, &files[dev]);
      break;

#ifndef EXCLUDE_MPU401
    case SND_DEV_MIDIN:
      MIDIbuf_release (dev, &files[dev]);
      break;
#endif

    case SND_DEV_AUDIO:
      audio_release (dev, &files[dev]);
      break;

    case SND_DEV_DSP:
    case SND_DEV_DSP16:
      dsp_release (dev, &files[dev]);
      break;

    default:;
    }

  sbc_devices[dev].usecount--;
  in_use--;			/* If not control port */

  FIX_OK_RETURN (0);
}

int
sndioctl (dev_t dev, int cmd, caddr_t arg, int mode)
{
  dev = minor (dev);

  DEB (printk ("sndioctl(dev=%d, cmd=0x%x, arg=0x%x)\n", dev, cmd, arg));

  switch (dev & 0x0f)
    {

    case SND_DEV_CTL:
      if (!num_mixers)
	FIX_OK_RETURN (-ENODEV);

      if (dev >= num_mixers)
	FIX_OK_RETURN (-ENODEV);

      FIX_OK_RETURN (mixer_devs[dev]->ioctl (dev, cmd, (unsigned int) arg));
      break;

    case SND_DEV_SEQ:
      FIX_OK_RETURN (sequencer_ioctl (dev, &files[dev], cmd, (unsigned int) arg));
      break;

    case SND_DEV_AUDIO:
      FIX_OK_RETURN (audio_ioctl (dev, &files[dev], cmd, (unsigned int) arg));
      break;

    case SND_DEV_DSP:
    case SND_DEV_DSP16:
      FIX_OK_RETURN (dsp_ioctl (dev, &files[dev], cmd, (unsigned int) arg));
      break;

#ifndef EXCLUDE_MPU401
    case SND_DEV_MIDIN:
      FIX_OK_RETURN (MIDIbuf_ioctl (dev, &files[dev], cmd, (unsigned int) arg));
      break;
#endif

    default:
      FIX_OK_RETURN (-EPERM);
      break;
    }

  FIX_OK_RETURN (-EPERM);
}

long
sndinit (long mem_start)
{
  int             i;

  soundcard_configured = 1;

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

  if (!(soundcards_installed = sndtable_get_cardcount ()))
    return mem_start;		/* 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);
      mem_start = dsp_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);

  for (i = 0; i < SND_NDEVS; i++)
    {
      sbc_devices[i].usecount = 0;
    }

  return mem_start;
}

/****  WARNING -- This needs work!  ****/

int
sndintr(int inum)
{
    pasintr(inum);
}

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(int arg)
{
	unsigned long flags;

	DISABLE_INTR(flags);
	if (*arg) wakeup(arg);
	*arg = 0;
	RESTORE_INTR(flags);
}

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


#ifndef EXCLUDE_AUDIO
static char malloc_tmpbuf[262144];

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);

	  if (sound_dma_automode[dev])
	    {
	      sound_dma_automode[dev] = 0;	/* Not possible with 386BSD */
	    }					/* ...but MAY be for ISC? */

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

	  if (sound_buffsizes[dev] > 65536)	/* Larger is 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] &= 0xfffff000;	/* Truncate to n*4k */
	  if (sound_buffsizes[dev] < 4096)
	    sound_buffsizes[dev] = 4096;

	  /* Now allocate the buffers */

	  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 = &malloc_tmpbuf[snd_raw_count[dev] * sound_buffsizes[dev]];
	      unsigned long   addr, rounded;

	      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
	       */
	      rounded = (addr & ~(dma_pagesize - 1)) + dma_pagesize;

	      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

#endif
