/*
 * sound/sco/soundcard.c
 *
 * Soundcard driver for SCO 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"

#define FIX_RETURN(ret) { int cnt = (ret); \
	if (cnt<0) u.u_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 int      timer_running = 0;

static int	soundcard_configured = 0;

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;

  FIX_RETURN(sound_read_sw (dev, &files[dev], buf, count));
}

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

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

  FIX_RETURN (sound_write_sw (dev, &files[dev], buf, count));
}

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

  dev = minor (dev);

  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;

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

  FIX_OK_RETURN (0);
}

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

  dev = minor (dev);

  sound_release_sw (dev, &files[dev]);

  FIX_OK_RETURN (0);
}

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

  FIX_OK_RETURN (sound_ioctl_sw (dev, &files[dev], cmd, (unsigned int) arg));
}

long
sndinit (long mem_start)
{
  int             i;

  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 mem_start;		/* No cards detected */

#ifndef EXCLUDE_AUDIO
  if (num_audiodevs)		/* Audio devices present */
    {
      mem_start = DMAbuf_init (mem_start);
      mem_start = audio_init (mem_start);
      sound_mem_init();
    }
#endif

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

#ifndef EXCLUDE_SEQUENCER
  if (num_midis + num_synths)
    mem_start = sequencer_init (mem_start);
#endif

  return mem_start;
}

#ifndef EXCLUDE_SEQUENCER
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;
}

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

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

#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_audiodevs; dev++)	/* Enumerate devices */
    if (!(dsp_init_mask & (1 << dev)))	/* Not already done */
      if (audio_devs[dev]->buffcount > 0 && audio_devs[dev]->dmachan > 0)
	{
	  dsp_init_mask |= (1 << dev);

#ifndef DMAMODE_AUTO
	  if (audio_devs[dev]->flags & DMA_AUTOMODE)
	    {
	      audio_devs[dev]->flags &= ~DMA_AUTOMODE;	/* Automode not possible with SCO */
	    }						/* ...but MAY be for ISC? */
#endif

	  if (!(audio_devs[dev]->flags & DMA_AUTOMODE))
	  if (audio_devs[dev]->buffcount == 1)
	    {
	      audio_devs[dev]->buffcount = 2;
	      audio_devs[dev]->buffsize /= 2;
	    }

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

	  if (audio_devs[dev]->dmachan > 3 && audio_devs[dev]->buffsize > 65536)
	    dma_pagesize = 131072;	/* 128k */
	  else
	    dma_pagesize = 65536;

	  /* More sanity checks */

	  if (audio_devs[dev]->buffsize > dma_pagesize)
	    audio_devs[dev]->buffsize = dma_pagesize;
	  audio_devs[dev]->buffsize &= 0xfffff000;	/* Truncate to n*4k */
	  if (audio_devs[dev]->buffsize < 4096)
	    audio_devs[dev]->buffsize = 4096;

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

	  for (audio_devs[dev]->dmap->raw_count = 0; audio_devs[dev]->dmap->raw_count < audio_devs[dev]->buffcount; audio_devs[dev]->dmap->raw_count++)
	    {
	      /*
	       * 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 *)ctob(memget(btoc(2*audio_devs[dev]->buffsize)));

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

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

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

	      audio_devs[dev]->dmap->raw_buf[audio_devs[dev]->dmap->raw_count] =
		&tmpbuf[rounded - addr];	/* Compute offset */
	      /*
	       * Convert from virtual address to physical address, since
	       * that is needed from dma.
	       */
	      audio_devs[dev]->dmap->raw_buf_phys[audio_devs[dev]->dmap->raw_count] =
		(unsigned long) kvtophys(audio_devs[dev]->dmap->raw_buf[audio_devs[dev]->dmap->raw_count]);
	    }
	}			/* 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;
}

int
sndintr(int vect)
{
    if (vect<0 || vect > 15) return 0;
    if (!irq_tab[vect]) return 0;

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


#endif
