Logo Search packages:      
Sourcecode: faumachine version File versions  Download package

isa_gen_soundblaster16.c

/* $Id: isa_gen_soundblaster16.c,v 1.38 2009-01-28 12:59:20 potyra Exp $ 
 *
 * Copyright (C) 2007-2009 FAUmachine Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

#include "config.h"

#include <assert.h>
#include "fixme.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "qemu/bswap.h"
#include "glue-main.h"
#include "glue-shm.h"

#include "sig_sound.h"
#include "sound_sample.h"

#include "isa_gen_soundblaster16.h"

#define COMP "isa_gen_soundblaster16"

#define EXPECT_TEST123_SAMPLE 0

/*----------------------------DEBUG-------------------------------------------*/

#define DEBUG_INFO 0
#define DEBUG_DMA 0
#define DEBUG_IO 0
#define DEBUG_MIXER 0
#define DEBUG_SAMPLES 0
#define DEBUG_IRQ 0
#define DEBUG_PROCESS   0

#ifndef DEBUGPRINT
#define DEBUGPRINT(class, arg...) \
      if (class) fprintf(stderr, "%20s:% 4d: ", __FUNCTION__, __LINE__); \
      if (class) fprintf(stderr, arg);
#endif
/*----------------------------------------------------------------------------*/

#define ADJUST_SAMPLE_VOLUME 1

#define PROCESS_FREQ 100

#define MAX(x,y) (((x) > (y)) ? (x) : (y))
#define MIN(x,y) (((x) < (y)) ? (x) : (y))
#define ROUND_DIV(x, y) (((x) + (x / y) - 1) / (y))

#define MIN_SAMPLINGRATE 5000
#define MIXER_REG_NUM 0x90
#define MIXER_DEV_NUM 14

#define MAX_SAMPLE_RATE 44100
#define MIN_SAMPLE_RATE 5000
#define MAX_SAMPLE_CHANNELS 2
#define MIN_SAMPLE_CHANNELS 1
#define MAX_SAMPLE_FORMAT_SIZE 2
#define MIN_SAMPLE_FORMAT_SIZE 1

/* Estimate buffer need */
#define INBUF_SIZE ((MAX_SAMPLE_RATE \
      * MAX_SAMPLE_CHANNELS \
      * MAX_SAMPLE_FORMAT_SIZE) \
      / PROCESS_FREQ) * 2

#define OUTBUF_SIZE ((SIG_SOUND_RATE \
      * SIG_SOUND_CHANNELS \
      * sizeof_format(SIG_SOUND_FORMAT)) \
      / PROCESS_FREQ) * 2

#define TMP_BUF_SIZE OUTBUF_SIZE

#define DMA_BUFFER_SIZE (128 * 1024)  /* Maximum buffer size for 16-bit DMA channel */

/* SB16 hwref 4.15
 *
 * CT1345 mixer chip compatibility volume controls
 * Register 0x04 (Voice volume .L/.R)
 * Register 0x22 (Master volume .L/.R)
 * Register 0x26 (MIDI volume .L/.R)
 *
 * 4 bits per channel, giving 16 levels.
 * 0 to 15 = -60dB to 0 dB, in 4dB steps.
 * Default is 12 = -12dB.
 *
 *
 * Register 0x30/0x31 (Master volume .L/.R)
 * Register 0x32/0x33 (Voice volume .L/.R)
 * Register 0x34/0x35 (MIDI volume .L/.R)
 *
 * 5 bits per channel, giving 32 levels.
 * 0 to 31 => -62 to 0 dB, in 2dB steps.
 * Default is 24 => -14 dB.
 *
 */

struct mixer_t{
      /* FIXME JOSEF this mixer regs can be dropped sooner or later */
      unsigned char reg[MIXER_REG_NUM];
      int   reg_index;
      int curDev;
      int curDevChan;
      int device[MIXER_DEV_NUM];
      unsigned int outMask;
      unsigned int recMask;
};

struct opl3_t{
      unsigned char stat_reg;
      unsigned char reg_nr;
      unsigned char reg[256];
};


#define DATABUF_SIZE 0x40
struct databuf{
      unsigned char data[DATABUF_SIZE];
      unsigned char cnt;
      unsigned char read_index;
      unsigned char write_index;
};


enum dma_trans_state {
      DMA_DISABLED,
      DMA_ENABLED,
      DMA_HALTED
};

struct dma_transfer_t{
      enum dma_trans_state state;
      char bits;
      /* Transferlength */
      int transfer_len;
      /* left bytes of current transfer_len */
      int transfer_left;
      /* DMA channel to read from */
      int channel;      
      int mode;
      int total_transf;
      char buffer[DMA_BUFFER_SIZE];
      unsigned int size;
      unsigned int count;
      unsigned int tc;
};
typedef struct dma_transfer_t dma_transfer;


struct mpu_t {
      struct databuf datain;
      struct databuf dataout;
      int cmd_cnt;            
      int cmd_pending;
};

      

struct sb16_dsp {
      unsigned char speaker_status;
      struct databuf datain, dataout;
      int cmd_cnt;            
      int cmd_pending;
      /* states of current DSP transfer */
      dma_transfer dma;
      /* register 0x22E */
      unsigned char reg_send_stat;
      /* register 0x22C */
      unsigned char reg_rec_stat;
      /* Test Register - not cleared by DSP Reset */
      unsigned char test_reg;
      unsigned char reset_reg;
      /* Channels of sample to transfer */
      int channels;     
      /* Format of sample to transfer */
      int format; 
      /* Samplingrate of sample to transfer */
      int rate;   
      int time_const;
      /* read dma buffer */
      char in_buf[INBUF_SIZE];
      int in_fill;
      int in_need;
      /* sample packet output buf */
      char out_buf[OUTBUF_SIZE];
      int out_fill;
      int out_need;
      int total_bytes_in;     
      int total_bytes_out;    
      enum {WAITING, PLAYING} state;
};

struct cpssp {
      /*
       * Config
       */
      unsigned int irq;
      unsigned int ioaddr;
      unsigned int dma8;
      unsigned int dma16;

      /*
       * Ports
       */
      struct sig_boolean *sig_p5V;
      unsigned int state_p5V;
      struct sig_boolean *sig_n_reset;
      struct sig_boolean_or *isa_bus_int[4];
      struct sig_isa_bus_dma *isa_bus_dma[8];
      struct sig_sound *port_speaker;

      /* mixer component */
      struct mixer_t mixer;
      struct opl3_t opl3[2];
      struct mpu_t mpu;

      char tmp_buf[TMP_BUF_SIZE];
      struct sb16_dsp dsp;
      unsigned char byte_counter;
      unsigned long long tsc_passed;
};

#define DSP_RESET  0x6
#define DSP_READ   0xA
#define DSP_WRITE  0xC
#define DSP_DATA_AVAIL   0xE
#define DSP_DATA_AVL16   0xF
#define MIXER_ADDR       0x4
#define MIXER_DATA       0x5
#define OPL3_LEFT  0x0
#define OPL3_RIGHT       0x2
#define OPL3_BOTH  0x8
#define ADLIB_COMP       0x388

/* DSP Commands */
#define DSP_CMD_SPKON         0xD1
#define DSP_CMD_SPKOFF        0xD3
#define DSP_CMD_DMAON         0xD0
#define DSP_CMD_DMAOFF        0xD4
/* JOSEF: */
#define DSP_CMD_IDENTIFICATION      0xE0
#define DSP_CMD_GETVERSION    0xE1
#define DSP_CMD_GETCOPYRIGHT  0xE3
#define DSP_CMD_WRITE_TESTREG 0xE4
#define DSP_CMD_READ_TESTREG  0xE8
#define DSP_CMD_GENIRQ        0xF2
#define DSP_CMD_DMA           0xC0
#define DSP_CMD_DMA16         0xB0

#define DSP_VER_SB16          0x0404
#define DSP_COPYRIGHT_STR     "Copyright (c) Creative Technology"

#define SOUND_MIXER_VOLUME    0
#define SOUND_MIXER_BASS      1
#define SOUND_MIXER_TREBLE    2
#define SOUND_MIXER_SYNTH     3
#define SOUND_MIXER_PCM       4
#define SOUND_MIXER_SPEAKER   5
#define SOUND_MIXER_LINE      6
#define SOUND_MIXER_MIC       7
#define SOUND_MIXER_CD        8
#define SOUND_MIXER_IMIX      9     /*  Recording monitor  */
#define SOUND_MIXER_ALTPCM    10
#define SOUND_MIXER_RECLEV    11    /* Recording level */
#define SOUND_MIXER_IGAIN     12    /* Input gain */
#define SOUND_MIXER_OGAIN     13    /* Output gain */

#define SOUND_MIXER_LEFT      0     /* left volume */
#define SOUND_MIXER_RIGHT     1     /* right volume */

#define IRQ_DMA8  0x01
#define IRQ_SBMIDI      0x02
#define IRQ_DMA16 0x03
#define IRQ_MPU         0x04

#define DMA8_CHANNEL    0x01
#define DMA16_CHANNEL   0x02


struct mixer_portdef {
      unsigned int regno: 8;
      unsigned int bitoffs:4;
      unsigned int nbits:4;
};


static const struct mixer_portdef mixer_ports_table[MIXER_DEV_NUM*2] = {
      {0x30, 7, 5}, {0x31, 7, 5},    /* SOUND_MIXER_VOLUME */
      {0x46, 7, 4}, {0x47, 7, 4},    /* SOUND_MIXER_BASS   */
      {0x44, 7, 4}, {0x45, 7, 4},    /* SOUND_MIXER_TREBLE */
      {0x34, 7, 5}, {0x35, 7, 5},    /* SOUND_MIXER_SYNTH  */
      {0x32, 7, 5}, {0x33, 7, 5},    /* SOUND_MIXER_PCM    */
      {0x3b, 7, 2}, {0x00, 0, 0},    /* SOUND_MIXER_SPEAKER*/
      {0x38, 7, 5}, {0x39, 7, 5},    /* SOUND_MIXER_LINE   */
      {0x3a, 7, 5}, {0x00, 0, 0},    /* SOUND_MIXER_MIC    */
      {0x36, 7, 5}, {0x37, 7, 5},    /* SOUND_MIXER_CD     */
      {0x3c, 0, 1}, {0x00, 0, 0},    /* SOUND_MIXER_IMIX   */
      {0x00, 0, 0}, {0x00, 0, 0},    /* SOUND_MIXER_ALTPCM */
      {0x3f, 7, 2}, {0x40, 7, 2},    /* SOUND_MIXER_IGAIN  */
      {0x41, 7, 2}, {0x42, 7, 2}     /* SOUND_MIXER_OGAIN  */
};

/*----------------------------------------------------------------------------*/
/*----------------------------FUNCTION PROTOTYPES-----------------------------*/
/*----------------------------------------------------------------------------*/
static void
timer_event(void *_cpssp);

/* Start calling timer_event regularly */
static void
start_timer(struct cpssp *cpssp);

/* Initialize Interrupt Setup Register */
static void
init_irq_setup_reg(struct cpssp * _sb, int irq);
/* Initialize DMA Setup Register */
static void
init_dma_setup_reg(struct cpssp * _sb, int dma8, int dma16);

static int
dsp_get_samples(struct cpssp *cpssp, void * buf, int size, int * filled);

/* FIXME rename to SA */

static void
mixer_write_addr(struct cpssp *cpssp, unsigned char addr);

static void
mixer_write_data(struct cpssp *cpssp, unsigned char value);

static int
mixer_read_addr(struct cpssp *cpssp);

static int
mixer_read_data(struct cpssp *cpssp);

/* Returns the IRQ setup for the SB16 */
static int
get_irq_nr(struct cpssp *cpssp);

/* Returns the DMA channels setup for the SB16 */
static void
get_dma_channels(struct cpssp *cpssp, int *dma8, int *dma16);

static void
dsp_write_reset(struct cpssp *cpssp, int value);

static void
dsp_set_rate(struct cpssp *cpssp, int _freq);

static int
dsp_get_rate(struct cpssp *cpssp);

static void
dsp_set_time_const(struct cpssp *cpssp, int _freq);

static void
isa_sb_set_irq(struct cpssp *cpssp, int reason, int value);

static int
isa_sb_is_irq_pending(struct cpssp *cpssp, int reason);

static void
dsp_write(struct cpssp *cpssp, int value);

static int
dsp_write_status(struct cpssp *cpssp);

static int
dsp_read(struct cpssp *cpssp);

static int
dsp_read_status16(struct cpssp *cpssp);

static int
dsp_read_status(struct cpssp *cpssp);

static void
dsp_cmd(struct cpssp *cpssp);

static int
dsp_ack_irq(struct cpssp *cpssp, int reason);

static int
dsp_dma_init(struct cpssp *cpssp, int format, int channels, int length,
            int in_or_out, int dma_mode, int compress, int highspeed);

static int send_sample_data(struct cpssp *cpssp, void * buf, int size);

static int convert_to_out_samples(struct cpssp *cpssp,
            char *in_buf, int in_len, char *out_buf, int out_size);

static void opl3_set_reg_index(struct cpssp *cpssp, int opl_nr, int index_);

static void opl3_write_reg(struct cpssp *cpssp, int opl_nr, int value);

static unsigned char opl3_read_stat(struct cpssp *cpssp, int opl_nr);

static void mpu_cmd(struct cpssp *cpssp, unsigned char cmd);

static void mpu_data_write(struct cpssp *cpssp, unsigned char data);

static int mpu_data_read(struct cpssp *cpssp);

static int mpu_status(struct cpssp *cpssp);

/*----------------------------------------------------------------------------*/
/*----------------------------DATA BUFFER HELPER FUNCTIONS--------------------*/
/*----------------------------------------------------------------------------*/


static void
databuf_newcommand(struct databuf* buf, unsigned char cmd, unsigned char len);

static int
databuf_getW(struct databuf* buf, unsigned short* val, char littleEndian);

static int
databuf_add(struct databuf* buf, unsigned char val);

static void
databuf_add_str(struct databuf *buf, const char *str);

static int
databuf_get(struct databuf* buf, unsigned char* val);

static int
databuf_addW(struct databuf* buf, unsigned short val);

static int
databuf_empty(struct databuf *buf);

static void
databuf_reset(struct databuf* buf)
{
      buf->cnt = 0;
      buf->write_index = 0;
      buf->read_index = 0;
}

static void
databuf_newcommand(struct databuf* buf, unsigned char cmd, unsigned char len)
{
      buf->cnt = len;
      buf->data[0] = cmd;
      buf->write_index = 1;
      buf->read_index = 0;
}

static int
databuf_add(struct databuf* buf, unsigned char val)
{
      assert(buf->write_index < DATABUF_SIZE - 1);
      buf->data[buf->write_index++] = val;
      return 0;
}

static int
databuf_addW(struct databuf* buf, unsigned short val)
{
      assert(buf->write_index < DATABUF_SIZE - 2);
      buf->data[buf->write_index++] = (val >> 8) & 0xff;
      buf->data[buf->write_index++] =  val & 0xff;
      return 0;
}

static void
databuf_add_str(struct databuf *buf, const char *str)
{
      int i;

      /* Write into buffer including \0 */
      for (i = 0; i < strlen(str) + 1; i++) {
            databuf_add(buf, str[i]);
      }
}

static int
databuf_get(struct databuf* buf, unsigned char* val)
{
      if (0 < buf->write_index &&
                  buf->read_index < buf->write_index) {
            *val =      buf->data[buf->read_index];
            buf->read_index++;
            return 0;
      } else {
            /* Overflow */
            *val = 0;
            return -1;
      }
}


static int
databuf_getW(struct databuf* buf, unsigned short* val, char littleEndian)
{
      if (0 < buf->write_index &&
                  buf->read_index < buf->write_index) {
            /* first low then high byte incoming */
            if (littleEndian) {
                  *val = le16_to_cpu(*((uint16_t*) &buf->data[buf->read_index]));
                  buf->read_index += 2;
            } else {
                  *val = be16_to_cpu(*((uint16_t*) &buf->data[buf->read_index]));
                  buf->read_index += 2;
            }
            return 0;
      } else {
            *val = 0;
            return -1;
      }
}

static int
databuf_empty(struct databuf *buf)
{
      if (0 < buf-> write_index &&
                  buf->read_index < buf->write_index) {
            return 0;
      } else {
            return 1;
      }
}

/*----------------------------MIXER FUNCTIONS---------------------------------*/
static void
mixer_reset(struct cpssp *cpssp)
{
      /* Set no irq reasons */
      cpssp->mixer.reg[0x82] = 0;
      /* Default values according to SBHWREF */
      cpssp->mixer.reg[0x04] = 0xCC;
      cpssp->mixer.reg[0x22] = 0xCC;
      cpssp->mixer.reg[0x26] = 0xCC;
      cpssp->mixer.reg[0x28] = 0x00;
      cpssp->mixer.reg[0x2E] = 0x00;
      cpssp->mixer.reg[0x0A] = cpssp->mixer.reg[0x0A] & 0xF8;
      cpssp->mixer.reg[0x30] = (cpssp->mixer.reg[0x30] & 0x07) | (0x18 << 3);
      cpssp->mixer.reg[0x31] = (cpssp->mixer.reg[0x31] & 0x07) | (0x18 << 3);
      cpssp->mixer.reg[0x32] = (cpssp->mixer.reg[0x32] & 0x07) | (0x18 << 3);
      cpssp->mixer.reg[0x33] = (cpssp->mixer.reg[0x33] & 0x07) | (0x18 << 3);
      cpssp->mixer.reg[0x34] = (cpssp->mixer.reg[0x34] & 0x07) | (0x18 << 3);
      cpssp->mixer.reg[0x35] = (cpssp->mixer.reg[0x35] & 0x07) | (0x18 << 3);
      cpssp->mixer.reg[0x36] &= 0x07;
      cpssp->mixer.reg[0x37] &= 0x07;
      cpssp->mixer.reg[0x38] &= 0x07;
      cpssp->mixer.reg[0x39] &= 0x07;
      cpssp->mixer.reg[0x3A] &= 0x07;
      cpssp->mixer.reg[0x3B] &= 0x3F;
      cpssp->mixer.reg[0x3C] |= 0x0F;
      cpssp->mixer.reg[0x3D] &= (cpssp->mixer.reg[0x3D] & 0x80) | 0x15;
      cpssp->mixer.reg[0x3E] &= (cpssp->mixer.reg[0x3E] & 0x80) | 0x0B;
      cpssp->mixer.reg[0x3F] &= 0x3F;
      cpssp->mixer.reg[0x40] &= 0x3F;
      cpssp->mixer.reg[0x41] &= 0x3F;
      cpssp->mixer.reg[0x42] &= 0x3F;
      cpssp->mixer.reg[0x43] &= 0xFE;
      cpssp->mixer.reg[0x44] = (cpssp->mixer.reg[0x44] & 0x0F) | (0x08 << 4);
      cpssp->mixer.reg[0x45] = (cpssp->mixer.reg[0x45] & 0x0F) | (0x08 << 4);
      cpssp->mixer.reg[0x46] = (cpssp->mixer.reg[0x46] & 0x0F) | (0x08 << 4);
      cpssp->mixer.reg[0x47] = (cpssp->mixer.reg[0x47] & 0x0F) | (0x08 << 4);
}

static void
mixer_write_addr(struct cpssp *cpssp, unsigned char addr)
{
      if (addr == 0) {
            /* Reset */
            mixer_reset(cpssp);
      } else {
      }
      cpssp->mixer.reg_index = addr;
      DEBUGPRINT(DEBUG_MIXER, "mixer addr set to 0x%x\n", addr);
}

static void
mixer_write_data(struct cpssp *cpssp, unsigned char value)
{
      cpssp->mixer.reg[cpssp->mixer.reg_index] = value;
      DEBUGPRINT(DEBUG_MIXER, "mixer register 0x%x set to 0x%x\n", cpssp->mixer.reg_index, value);
}

static int
mixer_read_addr(struct cpssp *cpssp)
{
      DEBUGPRINT(DEBUG_MIXER, "returning mixer addr (=0x%x)\n", cpssp->mixer.reg_index);
      return cpssp->mixer.reg_index;
}

static int
mixer_read_data(struct cpssp *cpssp)
{
      DEBUGPRINT(DEBUG_MIXER, "returning %x from mixer register 0x%x\n",
                  cpssp->mixer.reg[cpssp->mixer.reg_index],
                  cpssp->mixer.reg_index);
      return      cpssp->mixer.reg[cpssp->mixer.reg_index];
}


/* Returns volume setting between 0 and 100% */
static int
mixer_get_volume(struct cpssp *cpssp, int mixer_dev, int left_or_right)
{
      unsigned char mask;
      int table_index;
      int reg_index;
      int shift;
      int ret;

      table_index = 2 * mixer_dev + left_or_right;
      reg_index = mixer_ports_table[table_index].regno;
      ret = cpssp->mixer.reg[reg_index];
      /* create mask according to number of relevant bits */
      mask = (1 << mixer_ports_table[table_index].nbits) - 1;
      shift = mixer_ports_table[table_index].bitoffs
            - mixer_ports_table[table_index].nbits + 1;
      /* extract device volume from datum */
      ret &= (mask << shift);
      ret >>= shift;
      /* scaling in respect to a maximum ret of 100 */
      ret = (ret * 100) / mask;
      return ret;
}

/* returns the generic output volume in decibel */
static int
mixer_db_volume(struct cpssp *cpssp, int * decibel, int * mute)
{
      int vol;
      int pcm;
      int chan;
      
      for (chan = 0; chan < 2; chan++) {
            vol = mixer_get_volume(cpssp, SOUND_MIXER_VOLUME, chan);
            pcm = mixer_get_volume(cpssp, SOUND_MIXER_PCM, chan);

            if (vol == 0 || pcm == 0) {
                  mute[chan] = 1;
                  decibel[chan] = 0;
            } else {

                  /* Interpolate between -62 and -0 decibel */
                  vol = (1 -(vol / 100.0)) * -62.0;
                  pcm = (1 - (pcm / 100.0)) * -62.0;

                  /* Default is -14dB, calculate difference */
                  vol -= -14;
                  pcm -= -14;
                  /* Weigh pcm and vol values */
                  mute[chan] = 0;
                  decibel[chan] = (pcm + vol) / 2;
            }
      }
      return 0;
}

static int
get_irq_nr(struct cpssp *cpssp)
{
      int irq;

      switch (cpssp->mixer.reg[0x80] & 0x0f) {
      case 0x1:
            irq = 0;
            break;
      case 0x2:
            irq = 1;
            break;
      case 0x4:
            irq = 2;
            break;
      case 0x8:
            irq = 3;
            break;
      default:
            /* More than one IRQ set */
            assert(0);
      }

      return irq;
}

static void
get_dma_channels(struct cpssp *cpssp, int *dma8, int *dma16)
{
      /* Get 8-bit DMA channel */
      switch(cpssp->mixer.reg[0x81] & 0x0f) {
      case 0x01:
            *dma8 = 0;
            break;
      case 0x02:
            *dma8 = 1;
            break;
      case 0x08:
            *dma8 = 3;
            break;
      default:
            /* No valid value */
            assert(0);
            break;
      }

      /* Get 16-bit DMA channel */
      switch(cpssp->mixer.reg[0x81] & 0xf0) {
      case 0x00:
            /* 8-bit DMA sharing */
            *dma16 = *dma8;
      case 0x20:
            *dma16 = 5;
            break;
      case 0x40:
            *dma16 = 6;
            break;
      case 0x80:
            *dma16 = 7;
            break;
      default:
            /* No valid value */
            assert(0);
            break;
      }
}

/*----------------------------DSP FUNCTIONS-----------------------------------*/
static void
dsp_set_rate(struct cpssp *cpssp, int _freq)
{
      assert(MIN_SAMPLINGRATE <= _freq);
      cpssp->dsp.rate = _freq;
      cpssp->dsp.time_const = -1;
      DEBUGPRINT(DEBUG_INFO, "output freqency set to %d\n", _freq);
}

static void
dsp_set_time_const(struct cpssp *cpssp, int _freq)
{
      cpssp->dsp.rate = -1;
      cpssp->dsp.time_const = _freq;
      DEBUGPRINT(DEBUG_INFO, "time constant set to %d\n", _freq);
}

static int
dsp_get_rate(struct cpssp *cpssp)
{
      int freq;

      if (cpssp->dsp.rate == -1) {
            /* PCINTERN:
             * 8Bit:Time Constant = 256 - (1000000 / (channels * freq))
             * 16Bit: Time Constant = 65536 - (256000000 / (channels * freq))
             */
            if (cpssp->dsp.format == AFMT_U8) {
                  freq = 1000000 / (256 - cpssp->dsp.time_const)
                        * cpssp->dsp.channels;
            } else if (cpssp->dsp.format == AFMT_S16_LE) {
                  freq = 256000000 / (65536 - cpssp->dsp.time_const);
            } else {
                  /* Unsupported Format */
                  assert(0);
            }
      } else if (cpssp->dsp.time_const == -1) {
            freq = cpssp->dsp.rate;
      } else {
            /* No samplingrate set */
            assert(0);
      }
      /* Round to a multiple of 11025 */
      freq = ((freq + 5000) / 11025) * 11025;
      return freq;
}

static void
isa_sb_set_irq(struct cpssp *cpssp, int reason, int value)
{
      unsigned char reason_u8;
      int irq;

      assert(reason == IRQ_DMA8
          || reason == IRQ_DMA16
          || reason == IRQ_MPU
          || reason == IRQ_SBMIDI);
      assert(value == 0
          || value == 1);
      /* set the irq reason: dma8 or dma16 transfer */
      reason_u8 = reason;
      switch (reason) {
      case IRQ_DMA8:
            if (value) {
                  cpssp->mixer.reg[0x82] |= 0x01;
            } else {
                  cpssp->mixer.reg[0x82] &= ~0x01;
            }
            break;
      case IRQ_SBMIDI:
            if (value) {
                  cpssp->mixer.reg[0x82] |= 0x01;
            } else {
                  cpssp->mixer.reg[0x82] &= ~0x01;
            }
            break;
      case IRQ_DMA16:
            if (value) {
                  cpssp->mixer.reg[0x82] |= 0x02;
            } else {
                  cpssp->mixer.reg[0x82] &= ~0x02;
            }
            break;
      case IRQ_MPU:
            if (value) {
                  cpssp->mixer.reg[0x82] |= 0x04;
            } else {
                  cpssp->mixer.reg[0x82] &= ~0x04;
            }
            break;
      }
      irq = get_irq_nr(cpssp);
      if ((cpssp->mixer.reg[0x82] & 0x07) == 0) {
            sig_boolean_or_set(cpssp->isa_bus_int[irq], cpssp, 0);
            DEBUGPRINT(DEBUG_IRQ, "lowered irq line (irq 0x%x)\n", irq);
      } else {
            sig_boolean_or_set(cpssp->isa_bus_int[irq], cpssp, 1);
            DEBUGPRINT(DEBUG_IRQ, "raised irq line (irq 0x%x)\n", irq);
      }
}

static int
isa_sb_is_irq_pending(struct cpssp *cpssp, int reason)
{
      int is_pending;
      switch (reason) {
      case IRQ_DMA8:
            is_pending = cpssp->mixer.reg[0x82] & 0x01;
            break;
      case IRQ_SBMIDI:
            is_pending = cpssp->mixer.reg[0x82] & 0x01;
            break;
      case IRQ_DMA16:
            is_pending = cpssp->mixer.reg[0x82] & 0x02;
            break;
      case IRQ_MPU:
            is_pending = cpssp->mixer.reg[0x82] & 0x04;
            break;
      default:
            assert (0);
      }
      return is_pending;
}

static void
dma_terminate(struct cpssp *cpssp, int dma)
{
      cpssp->dsp.dma.mode = 0;
      if (cpssp->dsp.dma.state == DMA_HALTED) {
            cpssp->dsp.dma.state = DMA_DISABLED;
      }
}

/* SB16 DSP Commands documentation at http://the.earth.li/~tfm/oldpage/sb_dsp.html */
static void
dsp_cmd(struct cpssp *cpssp)
{
      unsigned short tmp;
      int cmd;
      unsigned char mode;
      unsigned short length;
      unsigned char data_u8;
      unsigned char bps, bits;
      unsigned char dmamode;
      unsigned char stereo;
      unsigned char output;
      unsigned char isSigned;
      unsigned char littleEndian;
      unsigned char fifo;
#if 0
      unsigned char highSpeed;
#endif
      int format;

      cmd = 0;
      databuf_get(&cpssp->dsp.datain, (unsigned char*)&cmd);
      DEBUGPRINT(DEBUG_INFO, "process command 0x%x \n", cmd);

      switch (cmd) {
      case 0x0F:
            databuf_get(&cpssp->dsp.datain, &data_u8);
            databuf_reset(&cpssp->dsp.dataout);
            /* Zero for no ASP */
            databuf_add(&cpssp->dsp.dataout, 0);
            break;
      case 0x14:
            databuf_getW(&cpssp->dsp.datain, &length, 1);
            format = AFMT_U8;
            stereo = 0;
            output = 1;
            dmamode = 1;
            dsp_dma_init(cpssp, format, stereo + 1, length + 1,
                        output, dmamode, 0, 0);
            break;
      case 0x40:
            databuf_get(&cpssp->dsp.datain, &data_u8);
            dsp_set_time_const(cpssp, data_u8);
            break;
      case 0x41:
            databuf_getW(&cpssp->dsp.datain, &tmp, 0);
            dsp_set_rate(cpssp, tmp);
            break;
      case 0xB0 ... 0xB0|0x0f:
      case 0xC0 ... 0xC0|0x0f:
            databuf_get(&cpssp->dsp.datain, &mode);
            databuf_getW(&cpssp->dsp.datain, &length, 1);
            /* 0xb? = 16 bit DMA */
            if ((cmd >> 4) == 0xB) {
                  bits = 16;
                  /* 0xc? = 8 bit DMA */
            } else {
                  bits = 8;
            }
            cmd &= 0x0f;
            output = 1 - (cmd >> 3);      // 1=output, 0=input
            dmamode = 1 + ( (cmd >> 2) & 1);// 0=none, 1=normal, 2=auto
            fifo = (cmd >> 1) & 1;
            stereo = (mode >> 5) & 1;
            isSigned = (mode >> 4) & 1;
            littleEndian = 1;
#if 0
            highSpeed = (compress >> 4) & 1;
#endif
            if (bits == 16) {
                  bps = 2;
                  cpssp->dsp.dma.transfer_len = length * 2;
            } else {
                  bps = 1;
                  cpssp->dsp.dma.transfer_len = length * 1;
            }
            if (stereo != 0) {
                  bps *= 2;
            }
            format = (bits == 8) ?
                  (isSigned) ? AFMT_S8 : AFMT_U8
                  : (isSigned) ?
                  (littleEndian) ?
                  AFMT_S16_LE : AFMT_S16_BE
                  : (littleEndian) ?
                  AFMT_U16_LE : AFMT_U16_BE;
            dsp_dma_init(cpssp, format, stereo + 1, length + 1,
                        output, dmamode, 0, 0);
            break;
      case 0xD0:
            /* Halt DMA Operation, 8-bit */
            cpssp->dsp.dma.state = DMA_HALTED;
            break;
      case 0xD1:
            /* Enables speaker output.  Reset of DSP disables speaker.  Speaker state does
               NOT physically change on SoundBlaster 16.  */
            cpssp->dsp.speaker_status = 0xFF;
            break;
      case 0xD3:
            /* Disables speaker output.  Reset of DSP disables speaker.  Speaker state does
               NOT physically change on SoundBlaster 16.  */
            cpssp->dsp.speaker_status = 0x00;
            break;
      case 0xD4:
            /* Continue DMA Operation, 8-bit */
            cpssp->dsp.dma.state = DMA_ENABLED;
            break;
      case 0xD5:
            /* FIXME JOSEF: No differentiation between the two channels */
            /* Halt DMA Operation, 16-bit */
            cpssp->dsp.dma.state = DMA_HALTED;
            break;
      case 0xD6:
            /* Continue DMA Operation, 16-bit */
            cpssp->dsp.dma.state = DMA_ENABLED;
            break;
      case 0xD8:
            /* Determines current status of speaker.
             * 000h = Disabled 0FFh = Enabled
             */
            databuf_reset(&cpssp->dsp.dataout);
            databuf_add(&cpssp->dsp.dataout, cpssp->dsp.speaker_status);
            break;
      case 0xD9:
            /* Terminates auto-initialized 16-bit DMA operation after
             * current block. Halt commands are required for immediate
             * termination and to quiet SB16.
             *
             * JOSEF: if theres currently a irq acknowledge done, then
             * "current block" means the next one
             */
            dma_terminate(cpssp, DMA8_CHANNEL);
            break;
      case 0xDA:
            /* Terminates auto-initialized 8-bit DMA operations after
               current block.  Halt commands are required for immediate
               termination and to quiet SB16.
             *
             * JOSEF: if theres currently a irq acknowledge done, then
             * "current block" means the next one
             */
            dma_terminate(cpssp, DMA16_CHANNEL);
            break;
      case 0xE0:
            databuf_get(&cpssp->dsp.datain, &data_u8);
            databuf_reset(&cpssp->dsp.dataout);
            databuf_add(&cpssp->dsp.dataout, ~data_u8);
            cpssp->dsp.reg_rec_stat |= 0x80;
            break;
      case 0xE1:
            databuf_reset(&cpssp->dsp.dataout);
            databuf_addW(&cpssp->dsp.dataout, DSP_VER_SB16);
            cpssp->dsp.reg_rec_stat |= 0x80;
            break;
      case 0xE3:
            databuf_reset(&cpssp->dsp.dataout);
            databuf_add_str(&cpssp->dsp.dataout, DSP_COPYRIGHT_STR);
            break;
      case 0xE4:
            databuf_get(&cpssp->dsp.datain, &data_u8);
            cpssp->dsp.test_reg = data_u8;
            break;
      case 0xE8:
            databuf_reset(&cpssp->dsp.dataout);
            databuf_add(&cpssp->dsp.dataout, cpssp->dsp.test_reg);
            break;
      case 0xF2:
            isa_sb_set_irq(cpssp, IRQ_DMA8, 1);
            break;
      default:
            DEBUGPRINT(DEBUG_INFO, "not implemented command\n");
            break;
      }
}



static int
dsp_ack_irq(struct cpssp *cpssp, int reason)
{
      int new_in_bytes;
      int was_ack;

      switch (reason) {
      case IRQ_DMA8:
      case IRQ_DMA16:
            was_ack = isa_sb_is_irq_pending(cpssp, reason);
            if (was_ack) {
                  DEBUGPRINT(DEBUG_IRQ, "DMA IRQ acknowledged \n");
                  isa_sb_set_irq(cpssp, reason, 0);
                  if (cpssp->dsp.state == PLAYING) {
                        cpssp->dsp.dma.state = DMA_ENABLED;
                        new_in_bytes = dsp_get_samples(cpssp,
                                    cpssp->dsp.in_buf,
                                    cpssp->dsp.in_need,
                                    &cpssp->dsp.in_fill);
                        if (new_in_bytes == -1) {
                              cpssp->dsp.state = WAITING;
                        }
                  }
            } else {
                  was_ack = -1;
            }
            break;
      case IRQ_MPU:
            was_ack = -1;
            DEBUGPRINT(DEBUG_IRQ, "unimplemented IRQ acknowledge \n");
            break;
      default:
            assert (0);
            break;
      }
      return was_ack;
}

/*----------------------------------------------------------------------------*/
/*----------------------------DMA TRANSFER FUNCTIONS--------------------------*/
/*----------------------------------------------------------------------------*/

static int
dsp_dma_init(
      struct cpssp *cpssp,
      int format,
      int channels,
      int length,
      int in_or_out,
      int dma_mode,
      int compress,
      int highspeed
)
{
      int dma8;
      int dma16;
      int bps;

      cpssp->dsp.format = format;
      if (format == AFMT_S16_LE) {
            cpssp->dsp.dma.bits = 16;
            cpssp->dsp.dma.transfer_len = length * 2;
            bps = 2 * channels;
      } else if (format == AFMT_U8) {
            cpssp->dsp.dma.bits = 8;
            cpssp->dsp.dma.transfer_len = length * 1;
            bps = 1 * channels;
      } else {
            assert(0);
      }
      cpssp->dsp.dma.transfer_left = cpssp->dsp.dma.transfer_len;
      cpssp->dsp.dma.mode = dma_mode;
      cpssp->dsp.dma.state = DMA_ENABLED;
      get_dma_channels(cpssp, &dma8, &dma16);
      cpssp->dsp.dma.channel = (cpssp->dsp.dma.bits == 8) ? dma8 : dma16;
      cpssp->dsp.channels = channels;
      cpssp->dsp.in_fill = 0;
      cpssp->dsp.out_fill = 0;
      cpssp->dsp.dma.total_transf = 0;
      DEBUGPRINT(DEBUG_INFO, "DMA is %db, %dHz, %s %s, mode %d, %s, transfer_len: 0x%x\n",
                  cpssp->dsp.dma.bits, dsp_get_rate(cpssp), (cpssp->dsp.channels == 2) ? "stereo":"mono",
                  (in_or_out == 1) ? "output" : "input",
                  dma_mode,
                  (highspeed == 1) ? "highspeed" : "normal speed",
                  cpssp->dsp.dma.transfer_len);
      if (EXPECT_TEST123_SAMPLE) {
            cpssp->byte_counter = 0;
      }
      cpssp->dsp.state = PLAYING;
      return 0;
}

static int
send_sample_data(struct cpssp *cpssp, void * buf, int size)
{
      assert(size == SIG_SOUND_MTU);
      
      sig_sound_samples_set(cpssp->port_speaker, cpssp, (int16_t *) buf);

      return size;
}

static int
convert_to_out_samples(
      struct cpssp *cpssp,
      char *in_buf,
      int in_len,
      char *out_buf,
      int out_size
)
{
      int ret;
      char *out_ptr;
      char *in_ptr;
      int com_size;
      
      assert(out_size <= sizeof(cpssp->tmp_buf));
      memcpy(cpssp->tmp_buf, in_buf, in_len);
      com_size = MIN(out_size, sizeof(cpssp->tmp_buf));
      in_ptr = cpssp->tmp_buf;
      out_ptr = out_buf;
      ret = in_len;
      /* Performing transformations to default sample
       * format */
      if (cpssp->dsp.format != SIG_SOUND_FORMAT) {
            unsigned char *tmp;

            ret = ss_change_format(in_ptr, ret, out_ptr,
                        com_size,
                        cpssp->dsp.format,
                        SIG_SOUND_FORMAT);
            tmp = in_ptr;
            in_ptr = out_ptr;
            out_ptr = tmp;
            DEBUGPRINT(DEBUG_PROCESS,
                        "Transformed samples to default format resulting %d bytes\n",
                        ret);
      }

      if (cpssp->dsp.channels != SIG_SOUND_CHANNELS) {
            unsigned char *tmp;

            ret = ss_change_channels(in_ptr, ret, out_ptr,
                        com_size,
                        cpssp->dsp.channels,
                        SIG_SOUND_CHANNELS);
            tmp = in_ptr;
            in_ptr = out_ptr;
            out_ptr = tmp;

            DEBUGPRINT(DEBUG_PROCESS,
                        "Transformed samples to default channels resulting %d bytes\n",
                        ret);
      }
      if (dsp_get_rate(cpssp) != SIG_SOUND_RATE) {
            unsigned char *tmp;

            ret = ss_change_rate(in_ptr, ret, out_ptr,
                        com_size,
                        dsp_get_rate(cpssp),
                        SIG_SOUND_RATE, SIG_SOUND_CHANNELS);
            tmp = in_ptr;
            in_ptr = out_ptr;
            out_ptr = tmp;
            DEBUGPRINT(DEBUG_PROCESS,
                        "Transformed samples to default sampling rate resulting %d bytes\n",
                        ret);
      }
      if (in_ptr != out_buf) {
            memcpy(out_buf, in_ptr, ret);
      }
      return ret;
}


/*----------------------------------------------------------------------------*/
/*----------------------VIRTUAL HARDWARE INTERFACE----------------------------*/
/*----------------------------------------------------------------------------*/

static int
isa_sb_inb(void *_cpssp, unsigned char *valuep, unsigned short port)
{
      struct cpssp *cpssp = (struct cpssp *) _cpssp;

      if ((cpssp->ioaddr <= port && port <= cpssp->ioaddr + 0x13)
       || (0x330 <= port && port <= 0x330 + 0x01)
       || (0x388 <= port && port <= 0x388 + 0x01)) {
            /*
             * Don't output io on DSP_DATA_AVAIL because this can
             * blow up output buffer e.g. when running diagnose.exe.
             */
            if (port - cpssp->ioaddr != DSP_DATA_AVAIL) {
                  DEBUGPRINT(DEBUG_IO, "from port 0x%x\n", port);
            }
            switch (port - cpssp->ioaddr) {
            case DSP_READ:
                  *valuep = dsp_read(cpssp);
                  break;
            case DSP_WRITE:
                  *valuep = dsp_write_status(cpssp);
                  break;
            case DSP_DATA_AVAIL:
                  *valuep = dsp_read_status(cpssp);
                  break;
            case DSP_DATA_AVL16:
                  *valuep = dsp_read_status16(cpssp);
                  break;
            case MIXER_ADDR:
                  *valuep = mixer_read_addr(cpssp);
                  break;
            case MIXER_DATA:
                  *valuep = mixer_read_data(cpssp);
                  break;
            case OPL3_BOTH:
                  *valuep = opl3_read_stat(cpssp, 0);
                  break;
            }
            switch (port - 0x330) {
            case 0x00:
                  *valuep = mpu_data_read(cpssp);
                  break;
            case 0x01:
                  *valuep = mpu_status(cpssp);
                  break;
#if 0
            default:
                  DEBUGPRINT(DEBUG_INFO, "\nnot implemented input port 0x%x\n",
                              port);
                  break;
#endif
            }
            if (port - cpssp->ioaddr != DSP_DATA_AVAIL) {
                  DEBUGPRINT(DEBUG_IO, " <- 0x%x\n", *valuep);
            }
            return 0;
      }
      return -1;
}

static int
isa_sb_outb(void *_cpssp, unsigned char value, unsigned short port)
{
      struct cpssp *cpssp = (struct cpssp *) _cpssp;

      if ((cpssp->ioaddr <= port && port <= cpssp->ioaddr + 0x13)
       || (0x330 <= port && port <= 0x330 + 0x01)
       || (0x388 <= port && port <= 0x388 + 0x01)) {
            DEBUGPRINT(DEBUG_IO, "isa_sb_outb: value 0x%x port 0x%x \n", value, port);
            switch (port - cpssp->ioaddr) {
            case DSP_RESET:
                  dsp_write_reset(cpssp, value);
                  break;
            case DSP_WRITE:
                  dsp_write(cpssp, value);
                  break;
            case MIXER_ADDR:
                  mixer_write_addr(cpssp, value);
                  break;
            case MIXER_DATA:
                  mixer_write_data(cpssp, value);
                  break;
            case OPL3_LEFT:
                  opl3_set_reg_index(cpssp, 0, value);
                  break;
            case OPL3_LEFT + 1:
                  opl3_write_reg(cpssp, 0, value);
                  break;
            case OPL3_RIGHT:
                  opl3_set_reg_index(cpssp, 1, value);
                  break;
            case OPL3_RIGHT + 1:
                  opl3_write_reg(cpssp, 1, value);
                  break;
            case OPL3_BOTH:
                  opl3_set_reg_index(cpssp, 0, value);
                  opl3_set_reg_index(cpssp, 1, value);
                  break;
            case OPL3_BOTH + 1:
                  opl3_write_reg(cpssp, 0, value);
                  opl3_write_reg(cpssp, 1, value);
                  break;
            }
            switch (port - 0x330) {
            case 0x00:
                  mpu_data_write(cpssp, value);
                  break;
            case 0x01:
                  mpu_cmd(cpssp, value);
                  break;
#if 0
            default:
                  DEBUGPRINT(DEBUG_INFO, "not implemented output port 0x%x\n",
                              port);
#endif
            }
            return 0;
      }
      return -1;
}

static int
isa_sb_ack_inb(void *_cpssp, unsigned int tc, unsigned char *val)
{
      struct cpssp *cpssp = (struct cpssp *) _cpssp;

      *val = cpssp->dsp.dma.buffer[cpssp->dsp.dma.count++];
      cpssp->dsp.dma.tc = tc;
      return 0;
}

static int
isa_sb_ack_inw(void *_cpssp, unsigned int tc, unsigned short *val)
{
      struct cpssp *cpssp = (struct cpssp *) _cpssp;

      *val = *(unsigned short *) (&cpssp->dsp.dma.buffer[cpssp->dsp.dma.count]);
      cpssp->dsp.dma.count += 2;
      cpssp->dsp.dma.tc = tc;
      return 0;
}


static int
isa_sb_ack_outb(void *_cpssp, unsigned int tc, unsigned char val)
{
      struct cpssp *cpssp = (struct cpssp *) _cpssp;

      cpssp->dsp.dma.buffer[cpssp->dsp.dma.count++] = val;
      cpssp->dsp.dma.tc = tc;
      return 0;
}

static int
isa_sb_ack_outw(void *_cpssp, unsigned int tc, unsigned short val)
{
      struct cpssp *cpssp = (struct cpssp *) _cpssp;

      *(unsigned short *) (&cpssp->dsp.dma.buffer[cpssp->dsp.dma.count]) = val;
      cpssp->dsp.dma.count += 2;
      cpssp->dsp.dma.tc = tc;
      return 0;
}

static void
dsp_reset(struct cpssp *cpssp)
{
      cpssp->dsp.state = WAITING;
      cpssp->dsp.dma.state = DMA_DISABLED;
      cpssp->dsp.in_fill = 0;
      cpssp->dsp.in_need = 0;
      cpssp->dsp.out_fill = 0;
      isa_sb_set_irq(cpssp, IRQ_DMA8, 0);
      isa_sb_set_irq(cpssp, IRQ_DMA16, 0);
      isa_sb_set_irq(cpssp, IRQ_SBMIDI, 0);
      isa_sb_set_irq(cpssp, IRQ_MPU, 0);
      cpssp->dsp.reg_send_stat = 0;
      databuf_reset(&cpssp->dsp.dataout);
      databuf_reset(&cpssp->dsp.datain);
      databuf_add(&cpssp->dsp.dataout, 0xAA);
      DEBUGPRINT(DEBUG_INFO, "DSP reset\n");
}

static void
dsp_write_reset(struct cpssp *cpssp, int value)
{
      if (cpssp->dsp.reset_reg == 0x01
       && value == 0x00) {
            dsp_reset(cpssp);
      }
      cpssp->dsp.reset_reg = value;
}

static int
dsp_read_status(struct cpssp *cpssp)
{
      int retval;

      retval = dsp_ack_irq(cpssp, IRQ_DMA8);
      if (retval == -1) {
            if (databuf_empty(&cpssp->dsp.dataout)) {
                  retval = cpssp->dsp.reg_rec_stat & ~0x80;
            } else {
                  retval = cpssp->dsp.reg_rec_stat | 0x80;
            }
      }
      return retval;
}

static int
dsp_read_status16(struct cpssp *cpssp)
{
      int retval;
      retval = dsp_ack_irq(cpssp, IRQ_DMA16);
      if (retval == -1) {
            if (databuf_empty(&cpssp->dsp.dataout)) {
                  retval = cpssp->dsp.reg_rec_stat & ~0x80;
            } else {
                  retval = cpssp->dsp.reg_rec_stat | 0x80;
            }
      }
      return retval;
}

static int
dsp_write_status(struct cpssp *cpssp)
{
      return cpssp->dsp.reg_send_stat;
}

static int
dsp_read(struct cpssp *cpssp)
{
      int retval;
      int overflow;
      unsigned char tmp_val;

      overflow = databuf_get(&cpssp->dsp.dataout, &tmp_val);
      if (overflow) {
            retval = 0xff;
      } else {
            retval = tmp_val;
      }
      return retval;
}

static void
dsp_write(struct cpssp *cpssp, int value)
{
      cpssp->dsp.cmd_pending = 1;
      switch (cpssp->dsp.cmd_cnt) {
      case 0:
            switch (value) {
                  /* 1 byte commands */
            case 0xE1:
            case 0xF2:
            case 0xE8:
            case 0xE3:
            case 0xD0:
            case 0xD1:
            case 0xD3:
            case 0xD5:
            case 0xD8:
            case 0xD9:
            case 0xDA:
            case 0xDF:
                  cpssp->dsp.cmd_cnt = 0;
                  break;
                  /* 2 byte commands & reset */
            case 0x0F:
            case 0x40:
            case 0xE0:
            case 0xE4:
                  cpssp->dsp.cmd_cnt = 1;
                  break;
                  /* 3 byte commands */
            case 0x14:
            case 0x41:
                  cpssp->dsp.cmd_cnt = 2;
                  break;
            case 0xC0:
            case 0xC0+0x1:
            case 0xC0+0x2:
            case 0xC0+0x3:
            case 0xC0+0x4:
            case 0xC0+0x5:
            case 0xC0+0x6:
            case 0xC0+0x7:
            case 0xC0+0x8:
            case 0xC0+0x9:
            case 0xC0+0xa:
            case 0xC0+0xb:
            case 0xC0+0xc:
            case 0xC0+0xd:
            case 0xC0+0xe:
            case 0xC0+0xf:
            case 0xB0:
            case 0xB0+0x1:
            case 0xB0+0x2:
            case 0xB0+0x3:
            case 0xB0+0x4:
            case 0xB0+0x5:
            case 0xB0+0x6:
            case 0xB0+0x7:
            case 0xB0+0x8:
            case 0xB0+0x9:
            case 0xB0+0xa:
            case 0xB0+0xb:
            case 0xB0+0xc:
            case 0xB0+0xd:
            case 0xB0+0xe:
            case 0xB0+0xf:
                  cpssp->dsp.cmd_cnt = 3;
                  break;
            default:
                  DEBUGPRINT(DEBUG_INFO, "unimplemented DSP command\n");
                  break;
            }
            databuf_newcommand(&cpssp->dsp.datain, value, cpssp->dsp.cmd_cnt);
            break;
      case 1:
      case 2:
      case 3:
            databuf_add(&cpssp->dsp.datain, value);
            cpssp->dsp.cmd_cnt--;
            break;
      default:
            break;
      }

      if (cpssp->dsp.cmd_pending && cpssp->dsp.cmd_cnt == 0) {
            /* Command complete */
            dsp_cmd(cpssp);
            databuf_reset(&cpssp->dsp.datain);
            cpssp->dsp.cmd_pending = 0;
      }
}


/* dma & transfer_len plane */
static int
dsp_read_dma(struct cpssp *cpssp, char *buffer, int len)
{
      int read;

      switch (cpssp->dsp.dma.state) {
      case DMA_ENABLED:
            if (0 < cpssp->dsp.dma.transfer_left) {
                  unsigned int channel;
                  unsigned int to_read;

                  channel = cpssp->dsp.dma.channel;
                  to_read = MIN(len, cpssp->dsp.dma.transfer_left);

                  assert(cpssp->dsp.dma.channel < 8);
                  cpssp->dsp.dma.size = to_read;
                  cpssp->dsp.dma.count = 0;
                  cpssp->dsp.dma.tc = 0;
                  while (cpssp->dsp.dma.count < cpssp->dsp.dma.size
                              && ! cpssp->dsp.dma.tc) {
                        sig_isa_bus_dma_req(cpssp->isa_bus_dma[channel], cpssp);
                  }
                  memcpy(buffer, cpssp->dsp.dma.buffer, cpssp->dsp.dma.count);
                  read = cpssp->dsp.dma.count;
                  DEBUGPRINT(DEBUG_DMA,
                              "channel: %d to_read: 0x%x, read: 0x%x, left_in_dma: 0x%x\n",
                              channel,
                              to_read,
                              read,
                              cpssp->dsp.dma.transfer_left);

                  cpssp->dsp.dma.transfer_left -= read;
                  cpssp->dsp.dma.total_transf += read;
                  if (cpssp->dsp.dma.transfer_left == 0) {
                        int reason;
                        if (cpssp->dsp.dma.bits == 16) {
                              reason = IRQ_DMA16;
                        } else {
                              reason = IRQ_DMA8;
                        }
                        isa_sb_set_irq(cpssp, reason, 1);
                        switch (cpssp->dsp.dma.mode)  {
                        case 0:
                        case 1:
                              cpssp->dsp.dma.state = DMA_DISABLED;
                              break;
                        case 2:
                              cpssp->dsp.dma.transfer_left = cpssp->dsp.dma.transfer_len;
                              cpssp->dsp.dma.state = DMA_HALTED;
                              break;
                        default:
                              break;
                        }
                  }
            } else {
                  /* No more bytes available */
                  read = 0;
            }
            break;
      case DMA_DISABLED:
            DEBUGPRINT(DEBUG_INFO,
                        "total transfered bytes over this dma channel: %d\n",
                        cpssp->dsp.dma.total_transf);
            read = -1;
            break;
      case DMA_HALTED:
            read = 0;
            break;
      default:
            assert (0);
      }
      DEBUGPRINT(DEBUG_INFO, "requested to read: %d, read: %d, available before irq: %d\n",
                  len,
                  read,
                  cpssp->dsp.dma.transfer_left);
      return read;
}

static int
dsp_get_samples(struct cpssp *cpssp, void *buf, int size, int *filled)
{
      int read;
      char * cbuf;

      assert(buf != 0);
      assert(0 <= *filled && *filled <= size);
      if (*filled < size) {
            cbuf = (char *) buf;
            read = dsp_read_dma(cpssp, cbuf + *filled, size - *filled);
            if (read < 0) {
                  /* Failure in DMA read */
                  read = -1;
            } else if (read == 0) {
                  /* No read possible, probably waiting for IRQ */
                  read = 0;
            } else {
                  *filled += read;
            }
      } else {
            read = 0;
      }
      DEBUGPRINT(DEBUG_INFO, "whole bytes to read: %d, read now %d , total bytes read: %d\n",
                  size,
                  read,
                  *filled);
      return read;
}

static int
adequ_in_bytes(int in_rate, int in_channels, int in_format,
            int out_rate, int out_channels, int out_format,
            int  out_bytes)
{
      /* Output sample size in bytes */
      int out_ss;
      /* Input sample size in bytes */
      int in_ss;
      /* Number of out samples */
      int out_s;
      int ratio;
      int in_s;
      int in_bytes;

      assert (out_rate % in_rate == 0);
      assert (0 < out_bytes);
      switch (out_format) {
      case AFMT_U8:
            out_ss = out_channels * 1;
            break;
      case AFMT_S16_NE:
            out_ss = out_channels * 2;
            break;
      default:
            assert (0);
            break;
      }
      switch (in_format) {
      case AFMT_U8:
            in_ss = in_channels * 1;
            break;
      case AFMT_S16_NE:
            in_ss = in_channels * 2;
            break;
      default:
            assert (0);
            break;
      }
      assert (out_bytes % out_ss == 0);
      out_s = out_bytes / out_ss;
      ratio = out_rate / in_rate;
      in_s = (out_s + ratio - 1) / ratio;
      in_bytes = in_s * in_ss;
      return in_bytes;
}

static void
start_timer(struct cpssp *cpssp)
{
      cpssp->tsc_passed = time_virt();
      time_call_at(cpssp->tsc_passed + TIME_HZ / PROCESS_FREQ,
                  timer_event, cpssp);
}

static void
proc_next_block(struct cpssp *cpssp)
{
      const int out_need = (SIG_SOUND_RATE
                  * SIG_SOUND_CHANNELS
                  * sizeof_format(SIG_SOUND_FORMAT))
            / PROCESS_FREQ;

      assert(cpssp->state_p5V);

      if (0 < cpssp->dsp.in_fill) {
            DEBUGPRINT(DEBUG_INFO,
                        "end reading (out_fill: %d, in_fill: %d, in_need: %d)\n",
                        cpssp->dsp.out_fill,
                        cpssp->dsp.in_fill,
                        cpssp->dsp.in_need);
            /* Transform to standard format */
            cpssp->dsp.out_fill += convert_to_out_samples(cpssp,
                        cpssp->dsp.in_buf, cpssp->dsp.in_fill,
                        cpssp->dsp.out_buf + cpssp->dsp.out_fill,
                        sizeof(cpssp->dsp.out_buf));
            cpssp->dsp.in_fill = 0;
      }
      if (cpssp->dsp.out_fill < out_need) {
            /* Add silence */
            int silence;
            silence = ss_gen_silence(cpssp->dsp.out_buf + cpssp->dsp.out_fill,
                        out_need - cpssp->dsp.out_fill,
                        SIG_SOUND_CHANNELS,
                        SIG_SOUND_FORMAT);
            cpssp->dsp.out_fill += silence;
            DEBUGPRINT(DEBUG_INFO, "added %d bytes of silence\n",
                        silence);
      }
      /* Adjust sample volume according to mixer settings
       */
      if (ADJUST_SAMPLE_VOLUME) {
            int decibel[2];
            int mute[2];

            mixer_db_volume(cpssp, decibel, mute);
            ss_change_volume(cpssp->dsp.out_buf, out_need, SIG_SOUND_FORMAT,
                        SIG_SOUND_CHANNELS, decibel, mute);
            DEBUGPRINT(DEBUG_PROCESS, "changed volume by left: %d dB right: %d dB\n",
                        decibel[0], decibel[1]);
      }
      if (out_need <= cpssp->dsp.out_fill) {
            /* Send sample-block */
            send_sample_data(cpssp, cpssp->dsp.out_buf, out_need);
            memcpy(cpssp->dsp.out_buf, cpssp->dsp.out_buf + out_need,
                        cpssp->dsp.out_fill - out_need);
            cpssp->dsp.out_fill -= out_need;
      }
      if (cpssp->dsp.state == PLAYING) {
            int new_in_bytes;
            int new_out;
            new_out = out_need - cpssp->dsp.out_fill;
            cpssp->dsp.in_need = adequ_in_bytes(dsp_get_rate(cpssp),
                        cpssp->dsp.channels,
                        cpssp->dsp.format,
                        SIG_SOUND_RATE,
                        SIG_SOUND_CHANNELS,
                        SIG_SOUND_FORMAT,
                        new_out);
            DEBUGPRINT(DEBUG_INFO,
                        "start reading (out_need: %d, out_fill: %d, in_need: %d\n",
                        out_need,
                        cpssp->dsp.out_fill,
                        cpssp->dsp.in_need);
            new_in_bytes = dsp_get_samples(cpssp, cpssp->dsp.in_buf,
                        cpssp->dsp.in_need, &cpssp->dsp.in_fill);
            if (new_in_bytes == -1) {
                  cpssp->dsp.state = WAITING;
            }
      } else {
      }
}

static void
timer_event(void *_cpssp)
{
      struct cpssp *cpssp = (struct cpssp *) _cpssp;

      if (cpssp->state_p5V) {
            proc_next_block(cpssp);
      }
      cpssp->tsc_passed += TIME_HZ / PROCESS_FREQ;
      time_call_at(cpssp->tsc_passed + TIME_HZ / PROCESS_FREQ,
                  timer_event, cpssp);
}

static void
opl3_set_reg_index(struct cpssp *cpssp, int opl_nr, int index_)
{
      cpssp->opl3[opl_nr].reg_nr = index_;
}

static void
opl3_write_reg(struct cpssp *cpssp, int opl_nr, int value)
{
      unsigned char index_;

      index_ = cpssp->opl3[opl_nr].reg_nr;
      /* Write to corresponding register */
      cpssp->opl3[opl_nr].reg[index_] = value;
      /* Any further action required ? */
      switch(index_) {
      case 0x04:
            if (value == 0x60) {
            /* Reset Timer 1 and 2 */
                  cpssp->opl3[opl_nr].stat_reg &= 0x9f;

            } else if (value == 0x80) {
            /* Reset IRQ */
                  cpssp->opl3[opl_nr].stat_reg &= 0x7f;
            } else if (value == 0x21) {
            /* Unmask and start Timer 1 */
                  cpssp->opl3[opl_nr].stat_reg = 0x0c;
            }
            break;
      case 0x02:
      /* Timer 1 */
            break;
      }
}

static unsigned char
opl3_read_stat(struct cpssp *cpssp, int opl_nr)
{
      return cpssp->opl3[opl_nr].stat_reg;
}

void
mpu_cmd(struct cpssp *cpssp, unsigned char cmd_byte)
{
      if (cpssp->mpu.cmd_pending == 0) {
            /* New command */
            cpssp->mpu.cmd_pending = 1;
            switch (cmd_byte) {
            case 0xff:
                  cpssp->mpu.cmd_cnt = 0;
                  break;
            }
            databuf_newcommand(&cpssp->mpu.datain, cmd_byte, cpssp->mpu.cmd_cnt);
      } else {
            /* More bytes for multi byte command */
      }

      if (cpssp->mpu.cmd_pending && cpssp->mpu.cmd_cnt == 0) {
            unsigned char cmd;

            databuf_get(&cpssp->mpu.datain, &cmd);
            DEBUGPRINT(DEBUG_INFO, "process mpu command 0x%x \n", cmd);
            switch (cmd) {
            case 0xff:
                  /* Reset */
                  databuf_reset(&cpssp->mpu.dataout);
                  databuf_reset(&cpssp->mpu.datain);
                  /* Output Acknowledge */
                  databuf_add(&cpssp->mpu.dataout, 0xfe);
                  break;
            }
            cpssp->mpu.cmd_pending = 0;
      }
}

static void
mpu_data_write(struct cpssp *cpssp, unsigned char data)
{
}

static int
mpu_data_read(struct cpssp *cpssp)
{
      unsigned char read_data;
      unsigned char tmp_val;
      int overflow;

      overflow = databuf_get(&cpssp->mpu.dataout, &tmp_val);
      if (overflow) {
            read_data = 0xff;
      } else {
            read_data = tmp_val;
      }
      return read_data;
}

static int
mpu_status(struct cpssp *cpssp)
{
      unsigned char status;

      status = 0;
      if (databuf_empty(&cpssp->mpu.dataout)) {
            status |= 0x80;

      }
      return status;
}

static void
isa_sb_p5V_set(void *_cpssp, unsigned int val)
{
      struct cpssp *cpssp = (struct cpssp *) _cpssp;

      cpssp->state_p5V = val;
}

static void
isa_sb_n_reset_set(void *_cpssp, unsigned int n_val)
{
      struct cpssp *cpssp = (struct cpssp *) _cpssp;

      cpssp->dsp.dma.state = DMA_DISABLED;
      cpssp->dsp.cmd_cnt = 0;
      cpssp->dsp.cmd_pending = 0;
      cpssp->dsp.total_bytes_in = 0;
      cpssp->dsp.total_bytes_out = 0;
      cpssp->mpu.cmd_pending = 0;
}

void
isa_gen_soundblaster16_init(
      unsigned int nr,
      struct sig_isa_bus *port_isa,
      struct sig_sound *port_speaker
)
{
      static const struct sig_boolean_funcs p5V_funcs = {
            .set = isa_sb_p5V_set,
      };
      static const struct sig_boolean_funcs n_reset_funcs = {
            .set = isa_sb_n_reset_set,
      };
      static const struct sig_isa_bus_main_funcs funcs = {
            inb:  isa_sb_inb,
            outb: isa_sb_outb,
      };
      static const struct sig_isa_bus_dma_funcs dma_funcs = {
            .ack_outb = isa_sb_ack_outb,
            .ack_outw = isa_sb_ack_outw,
            .ack_inb = isa_sb_ack_inb,
            .ack_inw = isa_sb_ack_inw,
      };

      struct cpssp *cpssp;

      cpssp = shm_map(COMP, nr, sizeof(*cpssp), 0);

      cpssp->sig_p5V = port_isa->p5V;
      cpssp->sig_n_reset = port_isa->n_reset;
      cpssp->isa_bus_int[0] = port_isa->int9;
      cpssp->isa_bus_int[1] = port_isa->int5;
      cpssp->isa_bus_int[2] = port_isa->int7;
      cpssp->isa_bus_int[3] = port_isa->int10;
      cpssp->isa_bus_dma[0] = port_isa->dma0;
      cpssp->isa_bus_dma[1] = port_isa->dma1;
      cpssp->isa_bus_dma[2] = (struct sig_isa_bus_dma *) 0; /* Not used. */
      cpssp->isa_bus_dma[3] = port_isa->dma3;
      cpssp->isa_bus_dma[4] = (struct sig_isa_bus_dma *) 0; /* Not used. */
      cpssp->isa_bus_dma[5] = port_isa->dma5;
      cpssp->isa_bus_dma[6] = port_isa->dma6;
      cpssp->isa_bus_dma[7] = port_isa->dma7;
      cpssp->port_speaker = port_speaker;

      DEBUGPRINT(DEBUG_INFO, "sim initialization\n");

      sig_boolean_connect_in(port_isa->p5V, cpssp, &p5V_funcs);
      sig_boolean_connect_in(port_isa->n_reset, cpssp, &n_reset_funcs);
      sig_isa_bus_main_connect(port_isa->main, cpssp, &funcs);
      sig_boolean_or_connect_out(port_isa->int9, cpssp, 0);
      sig_boolean_or_connect_out(port_isa->int5, cpssp, 0);
      sig_boolean_or_connect_out(port_isa->int7, cpssp, 0);
      sig_boolean_or_connect_out(port_isa->int10, cpssp, 0);
      sig_isa_bus_dma_connect(port_isa->dma0, cpssp, &dma_funcs);
      sig_isa_bus_dma_connect(port_isa->dma1, cpssp, &dma_funcs);
      sig_isa_bus_dma_connect(port_isa->dma3, cpssp, &dma_funcs);
      sig_isa_bus_dma_connect(port_isa->dma5, cpssp, &dma_funcs);
      sig_isa_bus_dma_connect(port_isa->dma6, cpssp, &dma_funcs);
      sig_isa_bus_dma_connect(port_isa->dma7, cpssp, &dma_funcs);

      init_irq_setup_reg(cpssp, cpssp->irq);
      init_dma_setup_reg(cpssp, cpssp->dma8, cpssp->dma16);
      mixer_reset(cpssp);
      dsp_reset(cpssp);
      start_timer(cpssp);
}

/* Initialize Interrupt Setup Register */
static void
init_irq_setup_reg(struct cpssp * _sb, int irq)
{
      int irq_setup_reg = 0;

      DEBUGPRINT(DEBUG_INFO, "Setting IRQ to %d\n",
                  irq);
      switch (irq) {
      case 2:
            irq_setup_reg |= 0x1;
            break;
      case 5:
            irq_setup_reg |= 0x2;
            break;
      case 7:
            irq_setup_reg |= 0x4;
            break;
      case 10:
            irq_setup_reg |= 0x8;
            break;
      }
      _sb->mixer.reg[0x80] =  irq_setup_reg;
}

/* Initialize DMA Setup Register */
static void
init_dma_setup_reg(struct cpssp * _sb, int dma8, int dma16)
{
      int dma_setup_reg = 0;

      switch (dma8) {
      case 0:
            dma_setup_reg |= 0x1;
            break;
      case 1:
            dma_setup_reg |= 0x2;
            break;
      case 3:
            dma_setup_reg |= 0x8;
            break;
      default:
            /* No available DMA channel */
            assert(0);
            break;
      }
      switch (dma16) {
      case 5:
            dma_setup_reg |= 0x20;
            break;
      case 6:
            dma_setup_reg |= 0x40;
            break;
      case 7:
            dma_setup_reg |= 0x80;
            break;
      default:
            /* Elsewise if no 8-bit DMA is shared */
            if (! (dma8 == dma16)) {
                  /* No available DMA channel */
                  assert(0);
            }
            break;
      }
      _sb->mixer.reg[0x81] =  dma_setup_reg;
}

void
isa_gen_soundblaster16_create(
      unsigned int nr,
      const char *name,
      const char *irq,
      const char *ioaddr,
      const char *dma8,
      const char *dma16
)
{
      static unsigned int count = 0;
      struct cpssp *cpssp;

      assert(count == 0); /* FIXME */
      count++;

      shm_create(COMP, nr, sizeof(*cpssp));
      cpssp = shm_map(COMP, nr, sizeof(*cpssp), 0);

      if (irq == NULL
       || atoi(irq) == -1) {
            cpssp->irq = 5;
      } else {
            cpssp->irq = strtoul(irq, NULL, 0);
      }
      if (ioaddr == NULL
       || atoi(ioaddr) == -1) {
            cpssp->ioaddr = 0x220;
      } else {
            cpssp->ioaddr = strtoul(ioaddr, NULL, 0);
      }
      if (dma8 == NULL
       || atoi(dma8) == -1) {
            cpssp->dma8 = 1;
      } else {
            cpssp->dma8 = strtoul(dma8, NULL, 0);
      }
      if (dma16 == NULL
       || atoi(dma16) == -1) {
            cpssp->dma16 = 5;
      } else {
            cpssp->dma16 = strtoul(dma16, NULL, 0);
      }

      shm_unmap(cpssp, sizeof(*cpssp));
}

void
isa_gen_soundblaster16_destroy(unsigned int nr)
{
      struct cpssp *cpssp;

      cpssp = shm_map(COMP, nr, sizeof(*cpssp), 0);

      shm_unmap(cpssp, sizeof(*cpssp));
      shm_destroy(COMP, nr);
}

Generated by  Doxygen 1.6.0   Back to index