/* This file contains the driver for a DSP (Digital Sound Processor) on
 * a SoundBlaster 16 (ASP) soundcard.
 *
 * The driver supports the following operations (using message format m2):
 *
 *    m_type      DEVICE    PROC_NR     COUNT    POSITION  ADRRESS
 * ----------------------------------------------------------------
 * |  DEV_OPEN  | device  | proc nr |         |         |         |
 * |------------+---------+---------+---------+---------+---------|
 * |  DEV_CLOSE | device  | proc nr |         |         |         |
 * |------------+---------+---------+---------+---------+---------|
 * |  DEV_READ  | device  | proc nr |  bytes  |         | buf ptr |
 * |------------+---------+---------+---------+---------+---------|
 * |  DEV_WRITE | device  | proc nr |  bytes  |         | buf ptr |
 * |------------+---------+---------+---------+---------+---------|
 * |  DEV_IOCTL | device  | proc nr |func code|         | buf ptr |
 * ----------------------------------------------------------------
 *
 * The file contains one entry point:
 *
 *   dsp_task:	main entry when system is brought up
 *
 *  May 20 1995			Author: Michel R. Prevenier 
 */


#include "kernel.h"
#include <minix/com.h> 
#include <minix/callnr.h> 
#include <sys/ioctl.h>
#if __minix_vmd
#include "proc.h"
#include "config.h"
#endif
#include "sb16.h"

#if ENABLE_SB_AUDIO

/* prototypes */
FORWARD _PROTOTYPE( void init_buffer, (void));
FORWARD _PROTOTYPE( int dsp_init, (void)); 
FORWARD _PROTOTYPE( int dsp_handler, (int irq));
FORWARD _PROTOTYPE( int dsp_open, (message *m_ptr));
FORWARD _PROTOTYPE( int dsp_close, (message *m_ptr));
FORWARD _PROTOTYPE( int dsp_ioctl, (message *m_ptr));
FORWARD _PROTOTYPE( int dsp_write, (message *m_ptr));
FORWARD _PROTOTYPE( int dsp_read, (message *m_ptr));
FORWARD _PROTOTYPE( int dsp_reset, (void)); 
FORWARD _PROTOTYPE( int dsp_command, (int value));
FORWARD _PROTOTYPE( int dsp_set_speed, (unsigned int speed));
FORWARD _PROTOTYPE( int dsp_set_size, (unsigned int size));
FORWARD _PROTOTYPE( int dsp_set_stereo, (unsigned int stereo));
FORWARD _PROTOTYPE( int dsp_set_bits, (unsigned int bits));
FORWARD _PROTOTYPE( int dsp_set_sign, (unsigned int sign));
FORWARD _PROTOTYPE( void dsp_dma_setup, (phys_bytes address, int count));
FORWARD _PROTOTYPE( void dsp_setup, (void));

/* globals */
#if __minix_vmd
PRIVATE int DspTasknr = ANY;
#endif
PRIVATE int DspVersion[2]; 
PRIVATE unsigned int DspStereo = DEFAULT_STEREO;
PRIVATE unsigned int DspSpeed = DEFAULT_SPEED; 
PRIVATE unsigned int DspBits = DEFAULT_BITS;
PRIVATE unsigned int DspSign = DEFAULT_SIGN;
PRIVATE unsigned int DspFragmentSize = DSP_MAX_FRAGMENT_SIZE;
PRIVATE int DspAvail = 0;
PRIVATE int DspBusy = 0;
PRIVATE int DmaBusy = 0;
PRIVATE int DmaDone = 1;
PRIVATE int DmaMode = 0;

PRIVATE char DmaBuffer[(long)2 * DMA_SIZE];
PRIVATE char *DmaPtr;
PRIVATE phys_bytes DmaPhys;


/*=========================================================================*
 *				dsp_task				   *
 *=========================================================================*/
PUBLIC void dsp_task()
{
  message mess;
  int err, caller, proc_nr;

#if __minix_vmd
  DspTasknr = proc_number(proc_ptr);
#endif

  /* initialize the DMA buffer */
  init_buffer();

  /* Here is the main loop of the sound task. It waits for a message, carries
   * it out, and sends a reply.
   */
  while (TRUE) 
  {
	receive(ANY, &mess);

	caller = mess.m_source;
	proc_nr = mess.PROC_NR;

	switch (caller) 
        {
   	  case HARDWARE:
		/* Leftover interrupt. */
		continue;
	  case FS_PROC_NR:
		/* The only legitimate caller. */
		break;
	  default:
		printf("sb16: got message from %d\n", caller);
		continue;
	}

	/* Now carry out the work. */
	switch(mess.m_type) 
        {
	    case DEV_OPEN:      err = dsp_open(&mess);break;	
	    case DEV_CLOSE:     err = dsp_close(&mess);break; 
	    case DEV_IOCTL:     err = dsp_ioctl(&mess);break;
	    case DEV_READ:      err = dsp_read(&mess);break;  
	    case DEV_WRITE:     err = dsp_write(&mess);break;
	    default:		err = EINVAL;break;
	}

	/* Finally, prepare and send the reply message. */
	mess.m_type = TASK_REPLY;
	mess.REP_PROC_NR = proc_nr;

	mess.REP_STATUS = err;	/* #bytes transfered or error code */
	send(caller, &mess);	/* send reply to caller */
  }
}


/*===========================================================================*
 *				init_buffer				     *
 *===========================================================================*/
PRIVATE void init_buffer()
{
/* Select a buffer that can safely be used for dma transfers.  
 * Its absolute address is 'DmaPhys', the normal address is 'DmaPtr'.
 */

  DmaPtr = DmaBuffer;
  DmaPhys = vir2phys(DmaBuffer);

  if (dma_bytes_left(DmaPhys) < DMA_SIZE) {
	/* First half of buffer crosses a 64K boundary, can't DMA into that */
	DmaPtr += DMA_SIZE;
	DmaPhys += DMA_SIZE;
  }
}


/*=========================================================================*
 *				dsp_open				   *	
 *=========================================================================*/
PRIVATE int dsp_open(m_ptr)
message *m_ptr;
{

#if SB_DEBUG
  printf("sb16_open\n");
#endif

  /* try to detect SoundBlaster card */
  if (!DspAvail && dsp_init() != OK) return EIO;

  /* Only one open at a time with soundcards */
  if (DspBusy) return EBUSY;
 
  /* Start with a clean DSP */
  if (dsp_reset() != OK) return EIO;

  /* Setup default values */
  DspStereo = DEFAULT_STEREO;
  DspSpeed = DEFAULT_SPEED;
  DspBits = DEFAULT_BITS;
  DspSign = DEFAULT_SIGN;
  DspFragmentSize = DMA_SIZE;
  
  DspBusy = 1;
  DmaBusy = 0;

  return OK;
}


/*=========================================================================*
 *				dsp_close				   *	
 *=========================================================================*/
PRIVATE int dsp_close(m_ptr)
message *m_ptr;
{

#if SB_DEBUG
  printf("dsp_close\n");
#endif

  DspBusy = 0;                  /* soundcard available again */
  DmaBusy = 0;

  return OK;
}


/*=========================================================================*
 *				dsp_ioctl				   *	
 *=========================================================================*/
PRIVATE int dsp_ioctl(m_ptr)
message *m_ptr;
{
  int status;
  phys_bytes user_phys;
  unsigned int val;

  /* Cannot change parameters during play or recording */
  if (DmaBusy) return EBUSY;

  /* Get user data */
  if (m_ptr->REQUEST != DSPIORESET)
  {
    user_phys = numap(m_ptr->PROC_NR, (vir_bytes) m_ptr->ADDRESS, 
                                                   sizeof(unsigned int));
    if (user_phys == 0) return(EFAULT);
    phys_copy(user_phys, vir2phys(&val), (phys_bytes) sizeof(val));
  }

#if SB_DEBUG
  printf("dsp_ioctl: got ioctl %d, argument: %d\n", m_ptr->REQUEST, val);
#endif

  switch(m_ptr->REQUEST)
  {
    case DSPIORATE:	status = dsp_set_speed(val);break;
    case DSPIOSTEREO:	status = dsp_set_stereo(val);break;
    case DSPIOBITS:	status = dsp_set_bits(val);break;
    case DSPIOSIZE:	status = dsp_set_size(val);break;
    case DSPIOSIGN:	status = dsp_set_sign(val);break;
    case DSPIOMAX:	{ 
                           val = DMA_SIZE;
                           phys_copy(vir2phys(&val), user_phys, 
                                           (phys_bytes) sizeof(val));
                           status = OK;
                        };break;
    case DSPIORESET:    status = dsp_reset();break;
    default:            status = ENOTTY;break;
  }

  return status;
}


/*=========================================================================*
 *				dsp_init				   *
 *=========================================================================*/
PRIVATE int dsp_init()
{
  int i;

  if (dsp_reset () != OK) 
  { 
    printf("sb16: No SoundBlaster card detected\n");
    return -1;
  }

  DspVersion[0] = DspVersion[1] = 0;
  dsp_command(DSP_GET_VERSION);	/* Get DSP version bytes */

  for (i = 1000; i; i--)
  {
    if (in_byte (DSP_DATA_AVL) & 0x80)
    {		
      if (DspVersion[0] == 0)
        DspVersion[0] = in_byte (DSP_READ);
      else
      {
        DspVersion[1] = in_byte (DSP_READ);
        break;
      }
    }
  }

  if (DspVersion[0] < 4)
  {
    printf("sb16: No SoundBlaster 16 compatible card detected\n");
    return -1;
  }
  else
    printf ("sb16: SoundBlaster DSP version %d.%d detected\n", 
          DspVersion[0], DspVersion[1]);

  /* set IRQ and DMA channels */
  mixer_set(MIXER_SET_IRQ, (1 << (SB_IRQ / 2 - 1)));
  mixer_set(MIXER_SET_DMA, (1 << SB_DMA_8 | 1 << SB_DMA_16)); 

  /* register interrupt vector and enable irq */
  put_irq_handler(SB_IRQ, dsp_handler);
  enable_irq(SB_IRQ);

  DspAvail = 1;
  return OK;
}


/*=========================================================================*
 *				dsp_handler				   *
 *=========================================================================*/
PRIVATE int dsp_handler(irq)
int irq;
{

#if SB_DEBUG2
  printf("SoundBlaster interrupt %d\n", irq);
#endif 

  if (DmaDone)     /* Dma transfer is done */
  {
    /* Send DSP command to stop dma */
    dsp_command((DspBits == 8 ? DSP_CMD_DMA8HALT : DSP_CMD_DMA16HALT));

    DmaBusy = 0;  /* Dma available again */
  }

  /* Send interrupt to audio task and enable again */
#if __minix_vmd
  interrupt(DspTasknr);
#else
  interrupt(AUDIO);
#endif

  /* Acknowledge the interrupt on the DSP */
  (void) in_byte((DspBits == 8 ? DSP_DATA_AVL : DSP_DATA16_AVL));

  return 1;
}


/*=========================================================================*
 *				dsp_command				   *
 *=========================================================================*/
PRIVATE int dsp_command(value)
int value;
{
  int             i;

  for (i = 0; i < SB_TIMEOUT; i++)
  {
    if ((in_byte (DSP_STATUS) & 0x80) == 0)
    {
      out_byte (DSP_COMMAND, value);
      return OK;
    }
  }

  printf ("sb16: SoundBlaster: DSP Command(%x) timeout\n", value);
  return -1;
}


/*=========================================================================*
 *				dsp_reset				   *
 *=========================================================================*/
PRIVATE int dsp_reset(void)
{
  int i;

  out_byte (DSP_RESET, 1);
  for(i =0; i<1000; i++); /* wait a while */
  out_byte (DSP_RESET, 0);

  for (i = 0; i < 1000 && !(in_byte (DSP_DATA_AVL) & 0x80); i++);	

  if (in_byte (DSP_READ) != 0xAA) return EIO; /* No SoundBlaster */			

  DmaBusy = 0;
  DmaDone = 1;

  return OK;
}


/*=========================================================================*
 *				dsp_set_speed				   *
 *=========================================================================*/
static int dsp_set_speed(speed)
unsigned int speed;
{
#if SB_DEBUG
  printf("sb16: setting speed to %u, stereo = %d\n", speed, DspStereo);
#endif

  if (speed < DSP_MIN_SPEED || speed > DSP_MAX_SPEED)
    return EPERM;

  /* Soundblaster 16 can be programmed with real sample rates
   * instead of time constants
   *
   * Since you cannot sample and play at the same time
   * we set in- and output rate to the same value 
   */

   lock();   		        		/* disable interrupts */
   dsp_command(DSP_INPUT_RATE);			/* set input rate */
   dsp_command(speed >> 8);			/* high byte of speed */
   dsp_command(speed);			 	/* low byte of speed */
   dsp_command(DSP_OUTPUT_RATE);		/* same for output rate */
   dsp_command(speed >> 8);	
   dsp_command(speed); 
   unlock();					/* enable interrupts */

   DspSpeed = speed;

   return OK;
}


/*=========================================================================*
 *				dsp_set_stereo				   *
 *=========================================================================*/
static int dsp_set_stereo(stereo)
unsigned int stereo;
{
  if (stereo) 
    DspStereo = 1;
  else 
    DspStereo = 0;

  return OK;
}


/*=========================================================================*
 *				dsp_set_bits				   *
 *=========================================================================*/
static int dsp_set_bits(bits)
unsigned int bits;
{
  /* Sanity checks */
  if (bits != 8 && bits != 16) return EINVAL;

  DspBits = bits; 

  return OK;
}


/*=========================================================================*
 *				dsp_set_size				   *
 *=========================================================================*/
static int dsp_set_size(size)
unsigned int size;
{

#if SB_DEBUG
  printf("sb16: set fragment size to %u\n", size);
#endif
 
  /* Sanity checks */
  if (size < DSP_MIN_FRAGMENT_SIZE || 
      size > DSP_MAX_FRAGMENT_SIZE || 
      size % 2 != 0)
    return EINVAL;

  DspFragmentSize = size; 

  return OK;
}


/*=========================================================================*
 *				dsp_set_sign				   *
 *=========================================================================*/
static int dsp_set_sign(sign)
unsigned int sign;
{

#if SB_DEBUG
  printf("sb16: set sign to %u\n", sign);
#endif
 
  DspSign = (sign > 0 ? 1 : 0); 

  return OK;
}


/*===========================================================================*
 *				dsp_dma_setup				     *
 *===========================================================================*/
PRIVATE void dsp_dma_setup(address, count)
phys_bytes address;
int count;

{
#if SB_DEBUG
  printf("Setting up %d bit DMA\n", DspBits);
#endif

  if (DspBits == 8)    /* 8 bit sound */
  {
    count--;     

    lock();
    out_byte(DMA8_MASK, SB_DMA_8 | 0x04);      /* Disable DMA channel */
    out_byte(DMA8_CLEAR, 0x00);		       /* Clear flip flop */

    /* set DMA mode */
    out_byte(DMA8_MODE, 
               (DmaMode == DEV_WRITE ? DMA8_AUTO_PLAY : DMA8_AUTO_REC)); 

    out_byte(DMA8_ADDR, address >>  0);        /* Low_byte of address */
    out_byte(DMA8_ADDR, address >>  8);        /* High byte of address */
    out_byte(DMA8_PAGE, address >> 16);        /* 64K page number */
    out_byte(DMA8_COUNT, count >> 0);          /* Low byte of count */
    out_byte(DMA8_COUNT, count >> 8);          /* High byte of count */
    out_byte(DMA8_MASK, SB_DMA_8);	       /* Enable DMA channel */
    unlock();
  }
  else  /* 16 bit sound */
  {
    count-= 2;

    lock();
    out_byte(DMA16_MASK, (SB_DMA_16 & 3) | 0x04); /* Disable DMA channel */
    out_byte(DMA16_CLEAR, 0x00);                  /* Clear flip flop */

    /* Set dma mode */
    out_byte(DMA16_MODE, 
               (DmaMode == DEV_WRITE ? DMA16_AUTO_PLAY : DMA16_AUTO_REC));        

    out_byte(DMA16_ADDR, (address >> 1) & 0xFF);  /* Low_byte of address */
    out_byte(DMA16_ADDR, (address >> 9) & 0xFF);  /* High byte of address */
    out_byte(DMA16_PAGE, (address >> 16) & 0xFE); /* 128K page number */
    out_byte(DMA16_COUNT, count >> 1);            /* Low byte of count */
    out_byte(DMA16_COUNT, count >> 9);            /* High byte of count */
    out_byte(DMA16_MASK, SB_DMA_16 & 3);          /* Enable DMA channel */
    unlock();
  }
}


/*===========================================================================*
 *				dsp_setup				     *
 *===========================================================================*/
PRIVATE void dsp_setup()
{ 
  /* Set current sample speed */
  dsp_set_speed(DspSpeed);

  /* Put the speaker on */
   if (DmaMode == DEV_WRITE)
   {
     dsp_command (DSP_CMD_SPKON); /* put speaker on */

      /* Program DSP with dma mode */
      dsp_command((DspBits == 8 ? DSP_CMD_8BITAUTO_OUT : DSP_CMD_16BITAUTO_OUT));     
   }
   else
   {
     dsp_command (DSP_CMD_SPKOFF); /* put speaker off */

     /* Program DSP with dma mode */
     dsp_command((DspBits == 8 ? DSP_CMD_8BITAUTO_IN : DSP_CMD_16BITAUTO_IN));     
   }

  /* Program DSP with transfer mode */
  if (!DspSign)
    dsp_command((DspStereo == 1 ? DSP_MODE_STEREO_US : DSP_MODE_MONO_US));
  else
    dsp_command((DspStereo == 1 ? DSP_MODE_STEREO_S : DSP_MODE_MONO_S));

  /* Give length of fragment to DSP */
  if (DspBits == 8) /* 8 bit transfer */
  {
    /* #bytes - 1 */
    dsp_command((DspFragmentSize - 1) >> 0); 
    dsp_command((DspFragmentSize - 1) >> 8);
  }
  else              /* 16 bit transfer */
  {
    /* #words - 1 */
    dsp_command((DspFragmentSize - 1) >> 1);
    dsp_command((DspFragmentSize - 1) >> 9);
  }
}
      

/*===========================================================================*
 *				dsp_write				     *
 *===========================================================================*/
PRIVATE int dsp_write(m_ptr)
message *m_ptr;
{
  phys_bytes user_phys;
  message mess;

  if (m_ptr->COUNT != DspFragmentSize) return EINVAL;

  /* From this user address */
  user_phys = numap(m_ptr->PROC_NR, (vir_bytes)m_ptr->ADDRESS, DspFragmentSize);
  if (user_phys == 0) return EINVAL;

  if (DmaBusy)    /* Dma already started */
  {
    if (DmaMode != m_ptr->m_type) return EBUSY;

    DmaDone = 0;  /* No, we're not done yet */

    /* Wait for next block to become free */
    receive(HARDWARE, &mess); 

    /* Copy first block to dma buffer */
    phys_copy(user_phys, DmaPhys, (phys_bytes) DspFragmentSize);

  }
  else /* A new dma transfer has started */    
  {
    DmaMode = DEV_WRITE;           /* Dma mode is writing */

    /* Copy fragment to dma buffer */    
    phys_copy(user_phys, DmaPhys, (phys_bytes) DspFragmentSize);

    /* Set up the dma chip */
    dsp_dma_setup(DmaPhys, DspFragmentSize);  

    /* Set up the DSP */
    dsp_setup();
      
    DmaBusy = 1;         /* Dma is busy */
  }

  DmaDone = 1;            /* dma done for now */

  return(DspFragmentSize);
}


/*===========================================================================*
 *				dsp_read				     *
 *===========================================================================*/
PRIVATE int dsp_read(m_ptr)
message *m_ptr;
{
  phys_bytes user_phys;
  message mess;
 
  if (m_ptr->COUNT != DspFragmentSize) return EINVAL;

  /* To this user address */
  user_phys = numap(m_ptr->PROC_NR, (vir_bytes)m_ptr->ADDRESS, DspFragmentSize);
  if (user_phys == 0) return EINVAL;

  if (DmaBusy)     /* Dma already started */
  {
    if (DmaMode != m_ptr->m_type) return EBUSY;

    DmaDone = 0;   /* No, we're not done yet */ 

    /* Wait for a full dma buffer */
    receive(HARDWARE, &mess); 

    /* Copy the buffer */
    phys_copy(DmaPhys, user_phys, (phys_bytes) DspFragmentSize);
  }
  else  /* A new dma transfer has started */
  {
    DmaMode = DEV_READ;                /* Dma mode is reading */

    /* Set up the dma chip */
    dsp_dma_setup(DmaPhys, DspFragmentSize);  
 
    /* Set up the DSP */
    dsp_setup();

    DmaBusy = 1;       /* Dma has started */
    DmaDone = 0;       /* Dma not done */

    /* Wait for dma to finish with first block */
    receive(HARDWARE, &mess);

    /* Copy dma buffer to user */
    phys_copy(DmaPhys, user_phys, (phys_bytes) DspFragmentSize);
  }

  DmaDone = 1;   /* dma done for now */

  return(DspFragmentSize);
}
#endif /* ENABLE_AUDIO */
