/* linux/kernel/chr_drv/sound/audio.c

Device file manager for /dev/audio

(C) 1992  Hannu Savolainen (hsavolai@cs.helsinki.fi) */

#include "sound_config.h"

#ifdef CONFIGURE_SOUNDCARD

#include <linux/types.h>
#include <linux/errno.h>
#include <linux/signal.h>
#include <linux/fcntl.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/tty.h>
#include <linux/ctype.h>
#include <asm/io.h>
#include <asm/segment.h>
#include <asm/system.h>
#include <sys/kd.h>
#include <linux/wait.h>
#include <linux/soundcard.h>
#include "sound_calls.h"
#include "ulaw.h"

#define OUTB outb
#define DEB(WHAT)		/* (WHAT) */
#define DEB1(WHAT)		/* (WHAT) */

#define ON		1
#define OFF		0

#ifndef EXCLUDE_AUDIO

static int wr_buff_no = -1;	/* != -1, if there is a incomplete output
				   block */
static int wr_buff_size, wr_buff_ptr;
static char *wr_dma_buf;

int
audio_open (struct inode *inode, struct file *filp)
{
  int dev;
  int mode;

  dev = inode->i_rdev;
  dev = MINOR (dev) >> 4;
  mode = filp->f_flags & O_ACCMODE;

  return DMAbuf_open (dev, mode);
}

void
audio_release (struct inode *inode, struct file *filp)
{
  int dev;
  int mode;

  dev = inode->i_rdev;
  dev = MINOR (dev) >> 4;
  mode = filp->f_flags & O_ACCMODE;

  if (wr_buff_no >= 0)
    {
      DMAbuf_start_output (dev, wr_buff_no, wr_buff_ptr);

      wr_buff_no = -1;
    }

  DMAbuf_release (dev, mode);
}

extern inline void translate_bytes(const void *table, void *buff, unsigned long n)
{
  __asm__("cld\n"
	  "1:\tlodsb\n\t"
	  "xlatb\n\t"
	  "stosb\n\t"
	  "loop 1b\n\t"
	  : :"b" ((long)table), "c" (n), "D" ((long)buff), "S" ((long)buff)
	  :"bx","cx","di","si");
}

int
audio_write (struct inode *inode, struct file *file, char *buf, int count)
{
  int c, p, l;
  int dev, err;

  dev = inode->i_rdev;
  dev = MINOR (dev) >> 4;

  p = 0;
  c = count;

  if (!count)			/* Flush output */
    {
      if (wr_buff_no >= 0)
	{
	  DMAbuf_start_output (dev, wr_buff_no, wr_buff_ptr);

	  wr_buff_no = -1;
	}
      return 0;
    }

  while (c)
    {				/* Perform output blocking */
      if (wr_buff_no < 0)	/* There is no incomplete buffers */
	{
	  if ((wr_buff_no = DMAbuf_getwrbuffer (dev, &wr_dma_buf, &wr_buff_size)) < 0)
	    return wr_buff_no;
	  wr_buff_ptr = 0;
	}

      l = c;
      if (l > (wr_buff_size - wr_buff_ptr))
	l = (wr_buff_size - wr_buff_ptr);

      memcpy_fromfs (&wr_dma_buf[wr_buff_ptr], &buf[p], l);

      /* Insert local processing here */

      __asm__ ("sti");
      translate_bytes(ulaw_dsp, &wr_dma_buf[wr_buff_ptr], l);

      c -= l;
      p += l;
      wr_buff_ptr += l;

      if (wr_buff_ptr > (wr_buff_size >> 1))
	{
	  if ((err = DMAbuf_start_output (dev, wr_buff_no, wr_buff_ptr)) < 0)
	    return err;

	  wr_buff_no = -1;
	}

    }

  return count;
}


int
audio_read (struct inode *inode, struct file *file, char *buf, int count)
{
  int dev, c, p, l;
  char *dmabuf;
  int buff_no;

  dev = inode->i_rdev;
  dev = MINOR (dev) >> 4;
  p = 0;
  c = count;

  while (c)
    {
      if ((buff_no = DMAbuf_getrdbuffer (dev, &dmabuf, &l)) < 0)
	return buff_no;

      if (l > c)
	l = c;

      /* Insert any local processing here. */
      __asm__ ("sti");

      translate_bytes(dsp_ulaw, dmabuf, l);

      memcpy_tofs (buf, dmabuf, l);

      DMAbuf_rmchars (dev, buff_no, l);

      p += l;
      c -= l;
    }

  return count - c;
}

int
audio_ioctl (struct inode *inode, struct file *file,
	     unsigned int cmd, unsigned int arg)
{
  int dev;

  dev = inode->i_rdev;
  dev = MINOR (dev) >> 4;

  switch (cmd)
    {
    case SNDCTL_DSP_SYNC:
      if (wr_buff_no >= 0)
	{
	  DMAbuf_start_output (dev, wr_buff_no, wr_buff_ptr);

	  wr_buff_no = -1;
	}
      return DMAbuf_ioctl (dev, cmd, arg);
      break;

    case SNDCTL_DSP_RESET:
      wr_buff_no = -1;
      return DMAbuf_ioctl (dev, cmd, arg);
      break;

    default:
      return DMAbuf_ioctl (dev, cmd, arg);
    }
}

long
audio_init (long mem_start)
{
  return mem_start;
}
#else
/* Stub versions */

int audio_read (struct inode *inode, struct file *file, char *buf, int count) {return -EIO;}
int audio_write (struct inode *inode, struct file *file, char *buf, int count) {return -EIO;}
int audio_open (struct inode *inode, struct file *filp) {return -ENODEV;}
void audio_release (struct inode *inode, struct file *filp) {return;}
int audio_ioctl (struct inode *inode, struct file *file,
	   unsigned int cmd, unsigned int arg) {return -EIO;}
int audio_lseek (struct inode *inode, struct file *file, off_t offset, int orig) {return -EIO;}
long audio_init (long mem_start) {return mem_start;}
#endif

#endif
