/*
 * sound/sound_switch.c
 * 
 * The system call switch
 * 
 * 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

struct sbc_device
{
  int             usecount;
};

static struct sbc_device sbc_devices[SND_NDEVS] = {{0}};

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

/*
 * /dev/sndstatus -device
 */
static char    *status_buf = NULL;
static int      status_len, status_ptr;
static int      status_busy = 0;

static int
put_status (char *s)
{
  int             l;

  for (l=0;l<256,s[l];l++);	/* l=strlen(s); */

  if (status_len + l >= 4000)
    return 0;

  memcpy (&status_buf[status_len], s, l);
  status_len += l;

  return 1;
}

static int
put_status_int (unsigned int val, int radix)
{
  int             l, v;

  static char hx[] = "0123456789abcdef";
  char buf[11];

  if (!val) return put_status("0");

  l = 0;
  buf[10]=0;

  while (val)
  {
  	v = val % radix;
  	val = val / radix;

  	buf[9-l] = hx[v];
  	l++;
  }

  if (status_len + l >= 4000)
    return 0;

  memcpy (&status_buf[status_len], &buf[10-l], l);
  status_len += l;

  return 1;
}

static void
init_status (void)
{
  /*
   * Write the status information to the status_buf and update status_len.
   * There is a limit of 4000 bytes for the data.
   */

  int             i;

  status_ptr = 0;

  put_status ("Sound Driver:" SOUND_VERSION_STRING
	      " (" SOUND_CONFIG_DATE " " SOUND_CONFIG_BY "@"
	      SOUND_CONFIG_HOST "." SOUND_CONFIG_DOMAIN ")"
	      "\n");

  if (!put_status ("Config options: "))
    return;
  if (!put_status_int(SELECTED_SOUND_OPTIONS, 16))
     return;

  if (!put_status ("\n\nHW config: \n"))
    return;

  for (i = 0; i < (num_sound_drivers - 1); i++)
    {
      if (!supported_drivers[i].enabled) 
      if (!put_status ("("))
	return;

      if (!put_status ("Type ")) return;
      if (!put_status_int(supported_drivers[i].card_type, 10)) return;
      if (!put_status (": ")) return;
      if (!put_status (supported_drivers[i].name)) return;
      if (!put_status (" at 0x")) return;
      if (!put_status_int(supported_drivers[i].config.io_base, 16)) return;
      if (!put_status (" irq ")) return;
      if (!put_status_int(supported_drivers[i].config.irq, 10)) return;
      if (!put_status (" drq ")) return;
      if (!put_status_int(supported_drivers[i].config.dma, 10)) return;

      if (!supported_drivers[i].enabled) 
      if (!put_status (")"))
	return;

      if (!put_status ("\n"))
	return;
    }

  if (!put_status ("\nPCM devices:\n"))
    return;

  for (i = 0; i < num_dspdevs; i++)
    {
      if (!put_status_int(i, 10)) return;
      if (!put_status(": "))return;
      if (!put_status(dsp_devs[i]->name))return;
      if (!put_status("\n"))return;
    }

  if (!put_status ("\nSynth devices:\n"))
    return;

  for (i = 0; i < num_synths; i++)
    {
      if (!put_status_int(i, 10)) return;
      if (!put_status(": "))return;
      if (!put_status(synth_devs[i]->info->name))return;
      if (!put_status("\n"))return;
    }

  if (!put_status ("\nMidi devices:\n"))
    return;

  for (i = 0; i < num_midis; i++)
    {
      if (!put_status_int(i, 10)) return;
      if (!put_status(": "))return;
      if (!put_status(midi_devs[i]->info.name))return;
      if (!put_status("\n"))return;
    }

  if (num_mixers)
    {
      if (!put_status ("\nMixer(s) installed\n"))
	return;
    }
  else
    {
      if (!put_status ("\nNo mixers installed\n"))
	return;
    }
}

static int
read_status (snd_rw_buf *buf, int count)
{
  /*
   * Return at most 'count' bytes from the status_buf.
   */
  int             l, c;

  l = count;
  c = status_len - status_ptr;

  if (l > c)
    l = c;
  if (l <= 0)
    return 0;

  COPY_TO_USER(buf, 0, &status_buf[status_ptr], l);
  status_ptr += l;

  return l;
}

int
sound_read_sw (int dev, struct fileinfo *file, snd_rw_buf *buf, int count)
{
  DEB (printk ("sound_read_sw(dev=%d, count=%d)\n", dev, count));

  switch (dev & 0x0f)
    {
    case SND_DEV_STATUS:
      return read_status (buf, count);
      break;

    case SND_DEV_DSP:
    case SND_DEV_DSP16:
    case SND_DEV_AUDIO:
      return audio_read (dev, file, buf, count);
      break;

    case SND_DEV_SEQ:
      return sequencer_read (dev, file, buf, count);
      break;

#ifndef EXCLUDE_MPU401
    case SND_DEV_MIDIN:
      return MIDIbuf_read (dev, file, buf, count);
#endif

    default:
      printk ("Sound: Undefined minor device %d\n", dev);
    }

  return RET_ERROR (EPERM);
}

int
sound_write_sw (int dev, struct fileinfo *file, snd_rw_buf *buf, int count)
{

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

  switch (dev & 0x0f)
    {

    case SND_DEV_SEQ:
      return sequencer_write (dev, file, buf, count);
      break;

    case SND_DEV_DSP:
    case SND_DEV_DSP16:
    case SND_DEV_AUDIO:
      return audio_write (dev, file, buf, count);
      break;

    default:
      return RET_ERROR (EPERM);
    }

  return count;
}

int
sound_open_sw (int dev, struct fileinfo *file)
{
  int             retval;
  DEB (printk ("sound_open_sw(dev=%d) : usecount=%d\n", dev, sbc_devices[dev].usecount));

  if ((dev >= SND_NDEVS) || (dev < 0))
    {
      printk ("Invalid minor device %d\n", dev);
      return RET_ERROR (ENXIO);
    }

  switch (dev & 0x0f)
    {
    case SND_DEV_STATUS:
      if (status_busy)
	return RET_ERROR (EBUSY);
      status_busy = 1;
      if ((status_buf = (char *) KERNEL_MALLOC (4000)) == NULL)
	return RET_ERROR (EIO);
      status_len = status_ptr = 0;
      init_status ();
      break;

    case SND_DEV_CTL:
      return 0;
      break;

    case SND_DEV_SEQ:
      if ((retval = sequencer_open (dev, file)) < 0)
	return retval;
      break;

#ifndef EXCLUDE_MPU401
    case SND_DEV_MIDIN:
      if ((retval = MIDIbuf_open (dev, file)) < 0)
	return retval;
      break;
#endif

    case SND_DEV_DSP:
    case SND_DEV_DSP16:
    case SND_DEV_AUDIO:
      if ((retval = audio_open (dev, file)) < 0)
	return retval;
      break;

    default:
      printk ("Invalid minor device %d\n", dev);
      return RET_ERROR (ENXIO);
    }

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

  return 0;
}

void
sound_release_sw (int dev, struct fileinfo *file)
{

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

  switch (dev & 0x0f)
    {
    case SND_DEV_STATUS:
      if (status_buf)
	KERNEL_FREE (status_buf);
      status_buf = NULL;
      status_busy = 0;
      break;

    case SND_DEV_CTL:
      break;

    case SND_DEV_SEQ:
      sequencer_release (dev, file);
      break;

#ifndef EXCLUDE_MPU401
    case SND_DEV_MIDIN:
      MIDIbuf_release (dev, file);
      break;
#endif

    case SND_DEV_DSP:
    case SND_DEV_DSP16:
    case SND_DEV_AUDIO:
      audio_release (dev, file);
      break;

    default:
      printk ("Sound error: Releasing unknown device 0x%02x\n", dev);
    }

  sbc_devices[dev].usecount--;
  in_use--;
}

int
sound_ioctl_sw (int dev, struct fileinfo *file,
	     unsigned int cmd, unsigned long arg)
{
  DEB (printk ("sound_ioctl_sw(dev=%d, cmd=0x%x, arg=0x%x)\n", dev, cmd, arg));

  switch (dev & 0x0f)
    {

    case SND_DEV_CTL:

      if (!num_mixers)
	return RET_ERROR (ENXIO);

      if ((dev >> 4) >= num_mixers)
	return RET_ERROR (ENXIO);

      return mixer_devs[dev >> 4]->ioctl (dev >> 4, cmd, arg);
      break;

    case SND_DEV_SEQ:
      return sequencer_ioctl (dev, file, cmd, arg);
      break;

    case SND_DEV_DSP:
    case SND_DEV_DSP16:
    case SND_DEV_AUDIO:
      return audio_ioctl (dev, file, cmd, arg);
      break;

#ifndef EXCLUDE_MPU401
    case SND_DEV_MIDIN:
      return MIDIbuf_ioctl (dev, file, cmd, arg);
      break;
#endif

    default:
      return RET_ERROR (EPERM);
      break;
    }

  return RET_ERROR (EPERM);
}
#endif
