CMI8x38: Implement Sound Blaster emulation

This commit is contained in:
RichardG867
2022-03-06 19:59:14 -03:00
parent 9bbf31a2b4
commit 219129f97e
7 changed files with 502 additions and 141 deletions

2
.ci/Jenkinsfile vendored
View File

@@ -25,7 +25,7 @@ def osArchs = [
]
def osFlags = [
'Windows': '',
'Windows': '-D QT=ON',
'Linux': '-D QT=ON'
]

View File

@@ -35,11 +35,11 @@
dma_t dma[8];
uint8_t dma_e;
uint8_t dma_m;
static uint8_t dmaregs[3][16];
static int dma_wp[2];
static uint8_t dma_m;
static uint8_t dma_stat;
static uint8_t dma_stat_rq;
static uint8_t dma_stat_rq_pc;

View File

@@ -66,6 +66,7 @@ typedef struct {
extern dma_t dma[8];
extern uint8_t dma_e;
extern uint8_t dma_m;
extern void dma_init(void);

View File

@@ -20,6 +20,11 @@ typedef struct sb_dsp_t {
int sb_16_length, sb_16_format, sb_16_autoinit, sb_16_pause, sb_16_enable, sb_16_autolen, sb_16_output;
int sb_16_dmanum;
int sb_pausetime;
int (*dma_readb)(void *priv),
(*dma_readw)(void *priv),
(*dma_writeb)(void *priv, uint8_t val),
(*dma_writew)(void *priv, uint16_t val);
void *dma_priv;
uint8_t sb_read_data[256];
int sb_read_wp, sb_read_rp;
@@ -36,6 +41,8 @@ typedef struct sb_dsp_t {
int midi_in_timestamp;
int sb_irqnum;
void (*irq_update)(void *priv, int set),
*irq_priv;
uint8_t sbe2;
int sbe2count;
@@ -53,7 +60,7 @@ typedef struct sb_dsp_t {
int sbdacpos;
int sbleftright;
int sbleftright, sbleftright_default;
int sbreset;
uint8_t sbreaddat;
@@ -123,4 +130,12 @@ void sb_dsp_set_stereo(sb_dsp_t *dsp, int stereo);
void sb_dsp_update(sb_dsp_t *dsp);
void sb_update_mask(sb_dsp_t *dsp, int irqm8, int irqm16, int irqm401);
void sb_dsp_irq_attach(sb_dsp_t *dsp, void (*irq_update)(void *priv, int set), void *priv);
void sb_dsp_dma_attach(sb_dsp_t *dsp,
int (*dma_readb)(void *priv),
int (*dma_readw)(void *priv),
int (*dma_writeb)(void *priv, uint8_t val),
int (*dma_writew)(void *priv, uint16_t val),
void *priv);
#endif /* SOUND_SND_SB_DSP_H */

View File

@@ -673,7 +673,7 @@ io_trap_remap(void *handle, int enable, uint16_t addr, uint16_t size)
trap->base, trap->base + trap->size - 1, trap->enable, addr, addr + size - 1, enable);
/* Remove old I/O mapping. */
if (trap->enable && trap->base && trap->size) {
if (trap->enable && trap->size) {
io_removehandler(trap->base, trap->size,
io_trap_readb, io_trap_readw, io_trap_readl,
io_trap_writeb, io_trap_writew, io_trap_writel,
@@ -686,8 +686,8 @@ io_trap_remap(void *handle, int enable, uint16_t addr, uint16_t size)
trap->size = size;
/* Add new I/O mapping. */
if (trap->enable && trap->base && trap->size) {
io_sethandler(trap->base, trap->size,
if (trap->enable && trap->size) {
io_sethandler(trap->base, trap->size,
io_trap_readb, io_trap_readw, io_trap_readl,
io_trap_writeb, io_trap_writew, io_trap_writel,
trap);

View File

@@ -26,10 +26,13 @@
#include <86box/mem.h>
#include <86box/pic.h>
#include <86box/timer.h>
#include <86box/dma.h>
#include <86box/pci.h>
#include <86box/sound.h>
#include <86box/snd_sb.h>
#include <86box/snd_sb_dsp.h>
#include <86box/gameport.h>
#include <86box/nmi.h>
#include <86box/ui.h>
@@ -43,6 +46,14 @@ enum {
CMEDIA_CMI8738_6CH = 0x080011 /* chip version 055 with 6-channel output */
};
enum {
TRAP_DMA = 0,
TRAP_PIC,
TRAP_OPL,
TRAP_MPU,
TRAP_MAX
};
typedef struct {
uint8_t id, reg, always_run, playback_enabled, channels;
struct _cmi8x38_ *dev;
@@ -63,17 +74,21 @@ typedef struct {
typedef struct _cmi8x38_ {
uint32_t type;
uint16_t io_base, sb_base, opl_base, mpu_base;
uint8_t pci_regs[256], io_regs[256], mixer_ext_regs[16];
int slot;
uint8_t pci_regs[256], io_regs[256];
int slot, sb_irq;
sb_t *sb;
void *gameport;
void *gameport, *io_traps[TRAP_MAX];
cmi8x38_dma_t dma[2];
uint16_t tdma_base_addr, tdma_base_count;
uint8_t prev_mask;
int tdma_last_8, tdma_last_16, tdma_mask;
int master_vol_l, master_vol_r, cd_vol_l, cd_vol_r;
} cmi8x38_t;
#define ENABLE_CMI8X38_LOG 1
#ifdef ENABLE_CMI8X38_LOG
int cmi8x38_do_log = ENABLE_CMI8X38_LOG;
@@ -104,7 +119,7 @@ static void
cmi8x38_update_irqs(cmi8x38_t *dev)
{
/* Calculate and use the INTR flag. */
if (*((uint32_t *) &dev->io_regs[0x10]) & 0x0401c003) {
if ((*((uint32_t *) &dev->io_regs[0x10]) & 0x0401c003) || dev->sb_irq) {
dev->io_regs[0x13] |= 0x80;
pci_set_irq(dev->slot, PCI_INTA);
cmi8x38_log("CMI8x38: Raising IRQ\n");
@@ -135,6 +150,290 @@ cmi8x38_mpu_irq_pending(void *priv)
}
static void
cmi8x38_sb_irq_update(void *priv, int set)
{
cmi8x38_t *dev = (cmi8x38_t *) priv;
dev->sb_irq = set;
cmi8x38_update_irqs(dev);
}
static int
cmi8x38_sb_dma_post(cmi8x38_t *dev, uint16_t *addr, uint16_t *count, int channel)
{
/* Increment address and decrement count. */
*addr += 1;
*count -= 1;
/* Handle end of DMA. */
if (*count == 0xffff) {
if (dma[channel].mode & 0x10) { /* auto-init */
/* Restart TDMA. */
*addr = dev->tdma_base_addr;
*count = dev->tdma_base_count;
cmi8x38_log("CMI8x38: Restarting TDMA on DMA %d with addr %08X count %04X\n", channel, (dma[channel].ab & 0xffff0000) | *addr, *count);
} else {
/* Mask TDMA. */
dev->tdma_mask |= 1 << channel;
}
return DMA_OVER;
}
return 0;
}
static int
cmi8x38_sb_dma_readb(void *priv)
{
cmi8x38_t *dev = (cmi8x38_t *) priv;
/* Stop if the DMA channel is invalid or if TDMA is masked. */
int channel = dev->sb->dsp.sb_8_dmanum;
if ((channel < 0) || (dev->tdma_mask & (1 << channel)))
return DMA_NODATA;
/* Get 16-bit address and count registers. */
uint16_t *addr = (uint16_t *) &dev->io_regs[0x1c],
*count = (uint16_t *) &dev->io_regs[0x1e];
/* Read data. */
int ret = mem_readb_phys((dma[channel].ab & 0xffff0000) | *addr);
/* Handle address, count and end. */
ret |= cmi8x38_sb_dma_post(dev, addr, count, channel);
return ret;
}
static int
cmi8x38_sb_dma_readw(void *priv)
{
cmi8x38_t *dev = (cmi8x38_t *) priv;
/* Stop if the DMA channel is invalid or if TDMA is masked. */
int channel = dev->sb->dsp.sb_16_dmanum;
if ((channel < 0) || (dev->tdma_mask & (1 << channel)))
return DMA_NODATA;
/* Get 16-bit address and count registers. */
uint16_t *addr = (uint16_t *) &dev->io_regs[0x1c],
*count = (uint16_t *) &dev->io_regs[0x1e];
/* Read data. */
int ret = mem_readw_phys((dma[channel].ab & 0xfffe0000) | ((*addr) << 1));
/* Handle address, count and end. */
ret |= cmi8x38_sb_dma_post(dev, addr, count, channel);
return ret;
}
static int
cmi8x38_sb_dma_writeb(void *priv, uint8_t val)
{
cmi8x38_t *dev = (cmi8x38_t *) priv;
/* Stop if the DMA channel is invalid or if TDMA is masked. */
int channel = dev->sb->dsp.sb_8_dmanum;
if ((channel < 0) || (dev->tdma_mask & (1 << channel)))
return 1;
/* Get 16-bit address and count registers. */
uint16_t *addr = (uint16_t *) &dev->io_regs[0x1c],
*count = (uint16_t *) &dev->io_regs[0x1e];
/* Write data. */
mem_writeb_phys((dma[channel].ab & 0xffff0000) | *addr, val);
/* Handle address, count and end. */
cmi8x38_sb_dma_post(dev, addr, count, channel);
return 0;
}
static int
cmi8x38_sb_dma_writew(void *priv, uint16_t val)
{
cmi8x38_t *dev = (cmi8x38_t *) priv;
/* Stop if the DMA channel is invalid or if TDMA is masked. */
int channel = dev->sb->dsp.sb_16_dmanum;
if ((channel < 0) || (dev->tdma_mask & (1 << channel)))
return 1;
/* Get 16-bit address and count registers. */
uint16_t *addr = (uint16_t *) &dev->io_regs[0x1c],
*count = (uint16_t *) &dev->io_regs[0x1e];
/* Write data. */
mem_writew_phys((dma[channel].ab & 0xfffe0000) | ((*addr) << 1), val);
/* Handle address, count and end. */
cmi8x38_sb_dma_post(dev, addr, count, channel);
return 0;
}
static void
cmi8x38_dma_write(uint16_t addr, uint8_t val, void *priv)
{
cmi8x38_t *dev = (cmi8x38_t *) priv;
/* Keep track of the last DMA channel written to. */
uint8_t channel;
if (addr < 0x08) {
channel = addr >> 1;
dev->tdma_last_8 = channel;
} else {
channel = 4 | ((addr >> 2) & 3);
dev->tdma_last_16 = channel;
}
/* Stop if not autodetecting. See note on cmi8x38_write(0x27). */
if (!(dev->io_regs[0x27] & 0x01))
return;
/* Write TDMA registers if this is a TDMA channel. */
if ((channel == dev->sb->dsp.sb_8_dmanum) || (channel == dev->sb->dsp.sb_16_dmanum)) {
/* Write base address and count. */
uint16_t *addr = (uint16_t *) &dev->io_regs[0x1c],
*count = (uint16_t *) &dev->io_regs[0x1e];
*addr = dev->tdma_base_addr = dma[channel].ab >> !!(channel & 4);
*count = dev->tdma_base_count = dma[channel].cb;
cmi8x38_log("CMI8x38: Starting TDMA on DMA %d with addr %08X count %04X\n", channel, (dma[channel].ab & 0xffff0000) | *addr, *count);
/* Set high channel flag. */
if (channel & 4)
dev->io_regs[0x10] |= 0x20;
else
dev->io_regs[0x10] &= ~0x20;
}
}
static void
cmi8x38_dma_mask_write(uint16_t addr, uint8_t val, void *priv)
{
cmi8x38_t *dev = (cmi8x38_t *) priv;
/* Stop if not autodetecting. See note on cmi8x38_write(0x27). */
if (!(dev->io_regs[0x27] & 0x01))
return;
/* Unmask TDMA on DMA unmasking edge. */
if ((dev->sb->dsp.sb_8_dmanum >= 0) && (dev->prev_mask & (1 << dev->sb->dsp.sb_8_dmanum)) && !(dma_m & (1 << dev->sb->dsp.sb_8_dmanum)))
dev->tdma_mask &= ~(1 << dev->sb->dsp.sb_8_dmanum);
else if ((dev->sb->dsp.sb_16_dmanum >= 0) && (dev->prev_mask & (1 << dev->sb->dsp.sb_16_dmanum)) && !(dma_m & (1 << dev->sb->dsp.sb_16_dmanum)))
dev->tdma_mask &= ~(1 << dev->sb->dsp.sb_16_dmanum);
dev->prev_mask = dma_m;
}
static void
cmi8338_io_trap(int size, uint16_t addr, uint8_t write, uint8_t val, void *priv)
{
cmi8x38_t *dev = (cmi8x38_t *) priv;
#ifdef ENABLE_CMI8X38_LOG
if (write)
cmi8x38_log("CMI8x38: cmi8338_io_trap(%04X, %02X)\n", addr, val);
else
cmi8x38_log("CMI8x38: cmi8338_io_trap(%04X)\n", addr);
#endif
/* Weird offsets, it's best to just treat the register as a big dword. */
uint32_t *lcs = (uint32_t *) &dev->io_regs[0x14];
*lcs &= ~0x0003dff0;
*lcs |= (addr & 0x0f) << 14;
if (write)
*lcs |= 0x1000 | (val << 4);
/* Raise NMI. */
nmi = 1;
}
static uint8_t
cmi8x38_sb_mixer_read(uint16_t addr, void *priv)
{
cmi8x38_t *dev = (cmi8x38_t *) priv;
sb_ct1745_mixer_t *mixer = &dev->sb->mixer_sb16;
uint8_t ret = sb_ct1745_mixer_read(addr, dev->sb);
if (addr & 1) {
if ((mixer->index == 0x0e) || (mixer->index >= 0xf0))
ret = mixer->regs[mixer->index];
cmi8x38_log("CMI8x38: sb_mixer_read(1, %02X) = %02X\n", mixer->index, ret);
} else {
cmi8x38_log("CMI8x38: sb_mixer_read(0) = %02X\n", ret);
}
return ret;
}
static void
cmi8x38_sb_mixer_write(uint16_t addr, uint8_t val, void *priv)
{
cmi8x38_t *dev = (cmi8x38_t *) priv;
sb_ct1745_mixer_t *mixer = &dev->sb->mixer_sb16;
/* Our clone mixer has a few differences. */
if (addr & 1) {
cmi8x38_log("CMI8x38: sb_mixer_write(1, %02X, %02X)\n", mixer->index, val);
switch (mixer->index) {
/* Reset interleaved stereo flag for SBPro mode. */
case 0x00:
mixer->regs[0x0e] = 0x00;
break;
/* No dynamic IRQ and DMA assignment. */
case 0x80: case 0x81:
return;
/* Some extended registers beyond those accepted by the CT1745. */
case 0xf0:
if (dev->type == CMEDIA_CMI8338)
val &= 0xfe;
mixer->regs[mixer->index] = val;
return;
case 0xf8 ... 0xff:
if (dev->type == CMEDIA_CMI8338)
mixer->regs[mixer->index] = val;
/* fall-through */
case 0xf1 ... 0xf7:
return;
}
sb_ct1745_mixer_write(addr, val, dev->sb);
/* No [3F:47] controls. */
mixer->input_gain_L = 0;
mixer->input_gain_R = 0;
mixer->output_gain_L = (double) 1.0;
mixer->output_gain_R = (double) 1.0;
mixer->bass_l = 8;
mixer->bass_r = 8;
mixer->treble_l = 8;
mixer->treble_r = 8;
/* Check interleaved stereo flag for SBPro mode. */
if ((mixer->index == 0x00) || (mixer->index == 0x0e))
sb_dsp_set_stereo(&dev->sb->dsp, mixer->regs[0x0e] & 2);
} else {
cmi8x38_log("CMI8x38: sb_mixer_write(0, %02X)\n", val);
sb_ct1745_mixer_write(addr, val, dev->sb);
}
}
static void
cmi8x38_remap_sb(cmi8x38_t *dev)
{
@@ -143,21 +442,19 @@ cmi8x38_remap_sb(cmi8x38_t *dev)
opl3_write, NULL, NULL, &dev->sb->opl);
io_removehandler(dev->sb_base + 8, 0x0002, opl3_read, NULL, NULL,
opl3_write, NULL, NULL, &dev->sb->opl);
io_removehandler(dev->sb_base + 4, 0x0002, sb_ct1745_mixer_read, NULL, NULL,
sb_ct1745_mixer_write, NULL, NULL, dev->sb);
io_removehandler(dev->sb_base + 4, 0x0002, cmi8x38_sb_mixer_read, NULL, NULL,
cmi8x38_sb_mixer_write, NULL, NULL, dev);
sb_dsp_setaddr(&dev->sb->dsp, 0);
}
if (dev->io_regs[0x04] & 0x08) {
dev->sb_base = 0x220;
if (dev->type == CMEDIA_CMI8338)
dev->sb_base += (dev->io_regs[0x17] & 0x80) >> 2;
else
dev->sb_base += (dev->io_regs[0x17] & 0x0c) << 3;
} else {
dev->sb_base = 0x220;
if (dev->type == CMEDIA_CMI8338)
dev->sb_base += (dev->io_regs[0x17] & 0x80) >> 2;
else
dev->sb_base += (dev->io_regs[0x17] & 0x0c) << 3;
if (!(dev->io_regs[0x04] & 0x08))
dev->sb_base = 0;
}
cmi8x38_log("CMI8x38: remap_sb(%04X)\n", dev->sb_base);
if (dev->sb_base) {
@@ -165,8 +462,8 @@ cmi8x38_remap_sb(cmi8x38_t *dev)
opl3_write, NULL, NULL, &dev->sb->opl);
io_sethandler(dev->sb_base + 8, 0x0002, opl3_read, NULL, NULL,
opl3_write, NULL, NULL, &dev->sb->opl);
io_sethandler(dev->sb_base + 4, 0x0002, sb_ct1745_mixer_read, NULL, NULL,
sb_ct1745_mixer_write, NULL, NULL, dev->sb);
io_sethandler(dev->sb_base + 4, 0x0002, cmi8x38_sb_mixer_read, NULL, NULL,
cmi8x38_sb_mixer_write, NULL, NULL, dev);
sb_dsp_setaddr(&dev->sb->dsp, dev->sb_base);
}
@@ -181,19 +478,16 @@ cmi8x38_remap_opl(cmi8x38_t *dev)
opl3_write, NULL, NULL, &dev->sb->opl);
}
if (dev->io_regs[0x1a] & 0x08) {
if (dev->type == CMEDIA_CMI8338)
dev->opl_base = 0x388;
else
dev->opl_base = opl_ports_cmi8738[dev->io_regs[0x17] & 0x03];
} else {
dev->opl_base = (dev->type == CMEDIA_CMI8338) ? 0x388 : opl_ports_cmi8738[dev->io_regs[0x17] & 0x03];
io_trap_remap(dev->io_traps[TRAP_OPL], dev->io_regs[0x16] & 0x80, dev->opl_base, 4);
if (!(dev->io_regs[0x1a] & 0x08))
dev->opl_base = 0;
}
cmi8x38_log("CMI8x38: remap_opl(%04X)\n", dev->opl_base);
if (dev->opl_base) {
io_sethandler(dev->opl_base, 0x0004, opl3_read, NULL, NULL,
opl3_write, NULL, NULL, &dev->sb->opl);
opl3_write, NULL, NULL, &dev->sb->opl);
}
}
@@ -204,13 +498,13 @@ cmi8x38_remap_mpu(cmi8x38_t *dev)
if (dev->mpu_base)
mpu401_change_addr(dev->sb->mpu, 0);
if (dev->io_regs[0x04] & 0x04) {
/* The CMI8338 datasheet's port range of [300:330] is
inaccurate. Drivers expect [330:300] like CMI8738. */
dev->mpu_base = 0x330 - ((dev->io_regs[0x17] & 0x60) >> 1);
} else {
/* The CMI8338 datasheet's port range of [300:330] is
inaccurate. Drivers expect [330:300] like CMI8738. */
dev->mpu_base = 0x330 - ((dev->io_regs[0x17] & 0x60) >> 1);
io_trap_remap(dev->io_traps[TRAP_MPU], dev->io_regs[0x16] & 0x20, dev->mpu_base, 2);
if (!(dev->io_regs[0x04] & 0x04))
dev->mpu_base = 0;
}
cmi8x38_log("CMI8x38: remap_mpu(%04X)\n", dev->mpu_base);
if (dev->mpu_base)
@@ -219,9 +513,9 @@ cmi8x38_remap_mpu(cmi8x38_t *dev)
static void
cmi8x38_start_playback(cmi8x38_t *dev, uint8_t val)
cmi8x38_start_playback(cmi8x38_t *dev)
{
uint8_t i;
uint8_t i, val = dev->io_regs[0x00];
i = !(val & 0x01);
if (!dev->dma[0].playback_enabled && i)
@@ -243,16 +537,8 @@ cmi8x38_read(uint16_t addr, void *priv)
uint8_t ret;
switch (addr) {
case 0x22:
sb_ct1745_mixer_t *mixer = &dev->sb->mixer_sb16;
if (mixer->index >= 0xf0)
ret = dev->mixer_ext_regs[mixer->index & 0x0f];
else
ret = sb_ct1745_mixer_read(1, dev->sb);
break;
case 0x23:
ret = sb_ct1745_mixer_read(0, dev->sb);
case 0x22: case 0x23:
ret = cmi8x38_sb_mixer_read(addr ^ 1, dev);
break;
case 0x40 ... 0x4f:
@@ -327,7 +613,8 @@ cmi8x38_write(uint16_t addr, uint8_t val, void *priv)
dev->dma[1].always_run = val & 0x02;
/* Start playback if requested. */
cmi8x38_start_playback(dev, val);
dev->io_regs[addr] = val;
cmi8x38_start_playback(dev);
break;
case 0x02:
@@ -354,7 +641,7 @@ cmi8x38_write(uint16_t addr, uint8_t val, void *priv)
/* Start playback along with DMA channels. */
if (val & 0x03)
cmi8x38_start_playback(dev, dev->io_regs[0x00]);
cmi8x38_start_playback(dev);
break;
case 0x04:
@@ -425,8 +712,14 @@ cmi8x38_write(uint16_t addr, uint8_t val, void *priv)
break;
case 0x16:
if (dev->type == CMEDIA_CMI8338)
if (dev->type == CMEDIA_CMI8338) {
val &= 0xa0;
/* Enable or disable I/O traps. */
dev->io_regs[addr] = val;
cmi8x38_remap_opl(dev);
cmi8x38_remap_mpu(dev);
}
break;
case 0x17:
@@ -438,6 +731,10 @@ cmi8x38_write(uint16_t addr, uint8_t val, void *priv)
pci_set_irq(dev->slot, PCI_INTA);
else if ((dev->io_regs[0x17] & 0x10) && !(val & 0x10))
pci_clear_irq(dev->slot, PCI_INTA);
/* Enable or disable I/O traps. */
io_trap_remap(dev->io_traps[TRAP_DMA], val & 0x02, 0x0000, 16);
io_trap_remap(dev->io_traps[TRAP_PIC], val & 0x01, 0x0020, 2);
}
/* Remap the legacy devices. */
@@ -484,43 +781,13 @@ cmi8x38_write(uint16_t addr, uint8_t val, void *priv)
val &= 0xf7;
else
val &= 0x07;
/* Enable or disable SBPro channel swapping. */
dev->sb->dsp.sbleftright_default = !!(val & 0x02);
break;
case 0x22:
sb_ct1745_mixer_t *mixer = &dev->sb->mixer_sb16;
switch (mixer->index) {
case 0xf0:
if (dev->type == CMEDIA_CMI8338)
val &= 0xfe;
dev->mixer_ext_regs[dev->sb->mixer_sb16.index & 0x0f] = val;
break;
case 0xf8 ... 0xff:
if (dev->type == CMEDIA_CMI8338)
dev->mixer_ext_regs[dev->sb->mixer_sb16.index & 0x0f] = val;
/* fall-through */
case 0xf1 ... 0xf7:
break;
default:
sb_ct1745_mixer_write(1, val, dev->sb);
/* Our clone mixer lacks the [3F:47] controls. */
mixer->input_gain_L = 0;
mixer->input_gain_R = 0;
mixer->output_gain_L = (double) 1.0;
mixer->output_gain_R = (double) 1.0;
mixer->bass_l = 8;
mixer->bass_r = 8;
mixer->treble_l = 8;
mixer->treble_r = 8;
break;
}
return;
case 0x23:
sb_ct1745_mixer_write(0, val, dev->sb);
case 0x22: case 0x23:
cmi8x38_sb_mixer_write(addr ^ 1, val, dev);
return;
case 0x24:
@@ -533,6 +800,16 @@ cmi8x38_write(uint16_t addr, uint8_t val, void *priv)
val &= 0x03;
else
val &= 0x27;
if (val & 0x01) {
/* Latch last DMA channels that had address/count registers written to.
Nobody knows how this "autodetection" works, but the CMI8338 TSR
disables it before and reenables it after copying the TDMA base/addr
to the 8237 registers corresponding to the 8-bit SB DMA channel. */
dev->sb->dsp.sb_8_dmanum = dev->tdma_last_8;
if (!(dev->io_regs[0x21] & 0x01))
dev->sb->dsp.sb_16_dmanum = dev->tdma_last_16;
}
break;
case 0x40 ... 0x4f:
@@ -751,9 +1028,11 @@ cmi8x38_poll(void *priv)
int16_t *out_l, *out_r, *out_ol, *out_or; /* o = opposite */
/* Schedule next run if playback is enabled. */
#if 0 /* temporary */
if (dev->io_regs[0x00] & (1 << dma->id))
dma->playback_enabled = 0;
else
#endif
timer_advance_u64(&dma->poll_timer, dma->timer_latch);
/* Update audio buffer. */
@@ -1000,6 +1279,10 @@ cmi8x38_reset(void *priv)
memset(dev->dma[i].fifo, 0, sizeof(dev->dma[i].fifo));
}
/* Reset legacy DMA channel. */
dev->tdma_last_8 = dev->tdma_last_16 = dev->sb->dsp.sb_8_dmanum = dev->sb->dsp.sb_16_dmanum = -1;
dev->tdma_mask = 0;
/* Reset Sound Blaster 16 mixer. */
sb_ct1745_mixer_reset(dev->sb);
}
@@ -1023,8 +1306,15 @@ cmi8x38_init(const device_t *info)
dev->sb->opl_enabled = 1; /* let snd_sb.c handle the OPL3 */
dev->sb->mixer_sb16.output_filter = 0; /* no output filtering */
/* Initialize MPU-401 interrupt handler. */
/* Initialize legacy interrupt and DMA handlers. */
mpu401_irq_attach(dev->sb->mpu, cmi8x38_mpu_irq_update, cmi8x38_mpu_irq_pending, dev);
sb_dsp_irq_attach(&dev->sb->dsp, cmi8x38_sb_irq_update, dev);
sb_dsp_dma_attach(&dev->sb->dsp, cmi8x38_sb_dma_readb, cmi8x38_sb_dma_readw, cmi8x38_sb_dma_writeb, cmi8x38_sb_dma_writew, dev);
dev->sb->dsp.sb_type = SBPRO;
io_sethandler(0x00, 8, NULL, NULL, NULL, cmi8x38_dma_write, NULL, NULL, dev);
io_sethandler(0xc0, 16, NULL, NULL, NULL, cmi8x38_dma_write, NULL, NULL, dev);
io_sethandler(0x08, 8, NULL, NULL, NULL, cmi8x38_dma_mask_write, NULL, NULL, dev);
io_sethandler(0xd0, 16, NULL, NULL, NULL, cmi8x38_dma_mask_write, NULL, NULL, dev);
/* Initialize DMA channels. */
for (int i = 0; i < (sizeof(dev->dma) / sizeof(dev->dma[0])); i++) {
@@ -1044,6 +1334,14 @@ cmi8x38_init(const device_t *info)
/* Initialize game port. */
dev->gameport = gameport_add(&gameport_pnp_device);
/* Initialize I/O traps. */
if (dev->type == CMEDIA_CMI8338) {
dev->io_traps[TRAP_DMA] = io_trap_add(cmi8338_io_trap, dev);
dev->io_traps[TRAP_PIC] = io_trap_add(cmi8338_io_trap, dev);
dev->io_traps[TRAP_OPL] = io_trap_add(cmi8338_io_trap, dev);
dev->io_traps[TRAP_MPU] = io_trap_add(cmi8338_io_trap, dev);
}
/* Add PCI card. */
dev->slot = pci_add_card((info->local & (1 << 13)) ? PCI_ADD_SOUND : PCI_ADD_NORMAL, cmi8x38_pci_read, cmi8x38_pci_write, dev);
@@ -1061,6 +1359,9 @@ cmi8x38_close(void *priv)
cmi8x38_log("CMI8x38: close()\n");
for (int i = 0; i < TRAP_MAX; i++)
io_trap_remove(dev->io_traps[i]);
free(dev);
}

View File

@@ -169,6 +169,16 @@ recalc_sb16_filter(int c, int playback_freq)
low_fir_sb16_coef[c][n] /= gain;
}
static void
sb_irq_update_pic(void *priv, int set)
{
sb_dsp_t *dsp = (sb_dsp_t *) priv;
if (set)
picint(1 << dsp->sb_irqnum);
else
picintc(1 << dsp->sb_irqnum);
}
void
sb_update_mask(sb_dsp_t *dsp, int irqm8, int irqm16, int irqm401)
{
@@ -185,7 +195,7 @@ sb_update_mask(sb_dsp_t *dsp, int irqm8, int irqm16, int irqm401)
dsp->sb_irqm401 = irqm401;
if (clear)
picintc(1 << dsp->sb_irqnum);
dsp->irq_update(dsp->irq_priv, 0);
}
void
@@ -210,9 +220,9 @@ sb_update_status(sb_dsp_t *dsp, int bit, int set)
}
if (set && !masked)
picint(1 << dsp->sb_irqnum);
dsp->irq_update(dsp->irq_priv, 1);
else if (!set)
picintc(1 << dsp->sb_irqnum);
dsp->irq_update(dsp->irq_priv, 0);
}
void
@@ -281,7 +291,7 @@ sb_dsp_reset(sb_dsp_t *dsp)
dsp->record_pos_read = 0;
dsp->record_pos_write = SB_DSP_REC_SAFEFTY_MARGIN;
picintc(1 << dsp->sb_irqnum);
dsp->irq_update(dsp->irq_priv, 0);
dsp->asp_data_len = 0;
}
@@ -350,7 +360,7 @@ sb_start_dma(sb_dsp_t *dsp, int dma8, int autoinit, uint8_t format, int len)
dsp->sb_8_output = 1;
if (!timer_is_enabled(&dsp->output_timer))
timer_set_delay_u64(&dsp->output_timer, dsp->sblatcho);
dsp->sbleftright = 0;
dsp->sbleftright = dsp->sbleftright_default;
dsp->sbdacpos = 0;
} else {
dsp->sb_16_length = len;
@@ -397,29 +407,31 @@ sb_start_dma_i(sb_dsp_t *dsp, int dma8, int autoinit, uint8_t format, int len)
}
int
sb_8_read_dma(sb_dsp_t *dsp)
sb_8_read_dma(void *priv)
{
sb_dsp_t *dsp = (sb_dsp_t *) priv;
return dma_channel_read(dsp->sb_8_dmanum);
}
void
sb_8_write_dma(sb_dsp_t *dsp, uint8_t val)
int
sb_8_write_dma(void *priv, uint8_t val)
{
dma_channel_write(dsp->sb_8_dmanum, val);
sb_dsp_t *dsp = (sb_dsp_t *) priv;
return dma_channel_write(dsp->sb_8_dmanum, val) == DMA_NODATA;
}
int
sb_16_read_dma(sb_dsp_t *dsp)
sb_16_read_dma(void *priv)
{
sb_dsp_t *dsp = (sb_dsp_t *) priv;
return dma_channel_read(dsp->sb_16_dmanum);
}
int
sb_16_write_dma(sb_dsp_t *dsp, uint16_t val)
sb_16_write_dma(void *priv, uint16_t val)
{
int ret = dma_channel_write(dsp->sb_16_dmanum, val);
return (ret == DMA_NODATA);
sb_dsp_t *dsp = (sb_dsp_t *) priv;
return dma_channel_write(dsp->sb_16_dmanum, val) == DMA_NODATA;
}
void
@@ -469,12 +481,12 @@ sb_exec_command(sb_dsp_t *dsp)
sb_start_dma(dsp, 1, 0, 0, dsp->sb_data[0] + (dsp->sb_data[1] << 8));
break;
case 0x17: /* 2-bit ADPCM output with reference */
dsp->sbref = sb_8_read_dma(dsp);
dsp->sbref = dsp->dma_readb(dsp->dma_priv);
dsp->sbstep = 0;
/* Fall through */
case 0x16: /* 2-bit ADPCM output */
sb_start_dma(dsp, 1, 0, ADPCM_2, dsp->sb_data[0] + (dsp->sb_data[1] << 8));
dsp->sbdat2 = sb_8_read_dma(dsp);
dsp->sbdat2 = dsp->dma_readb(dsp->dma_priv);
dsp->sb_8_length--;
if (dsp->sb_command == 0x17)
dsp->sb_8_length--;
@@ -486,7 +498,7 @@ sb_exec_command(sb_dsp_t *dsp)
case 0x1F: /* 2-bit ADPCM autoinit output */
if (dsp->sb_type >= SB15) {
sb_start_dma(dsp, 1, 1, ADPCM_2, dsp->sb_data[0] + (dsp->sb_data[1] << 8));
dsp->sbdat2 = sb_8_read_dma(dsp);
dsp->sbdat2 = dsp->dma_readb(dsp->dma_priv);
dsp->sb_8_length--;
}
break;
@@ -581,23 +593,23 @@ sb_exec_command(sb_dsp_t *dsp)
dsp->sb_8_autolen = dsp->sb_data[0] + (dsp->sb_data[1] << 8);
break;
case 0x75: /* 4-bit ADPCM output with reference */
dsp->sbref = sb_8_read_dma(dsp);
dsp->sbref = dsp->dma_readb(dsp->dma_priv);
dsp->sbstep = 0;
/* Fall through */
case 0x74: /* 4-bit ADPCM output */
sb_start_dma(dsp, 1, 0, ADPCM_4, dsp->sb_data[0] + (dsp->sb_data[1] << 8));
dsp->sbdat2 = sb_8_read_dma(dsp);
dsp->sbdat2 = dsp->dma_readb(dsp->dma_priv);
dsp->sb_8_length--;
if (dsp->sb_command == 0x75)
dsp->sb_8_length--;
break;
case 0x77: /* 2.6-bit ADPCM output with reference */
dsp->sbref = sb_8_read_dma(dsp);
dsp->sbref = dsp->dma_readb(dsp->dma_priv);
dsp->sbstep = 0;
/* Fall through */
case 0x76: /* 2.6-bit ADPCM output */
sb_start_dma(dsp, 1, 0, ADPCM_26, dsp->sb_data[0] + (dsp->sb_data[1] << 8));
dsp->sbdat2 = sb_8_read_dma(dsp);
dsp->sbdat2 = dsp->dma_readb(dsp->dma_priv);
dsp->sb_8_length--;
if (dsp->sb_command == 0x77)
dsp->sb_8_length--;
@@ -605,14 +617,14 @@ sb_exec_command(sb_dsp_t *dsp)
case 0x7D: /* 4-bit ADPCM autoinit output */
if (dsp->sb_type >= SB15) {
sb_start_dma(dsp, 1, 1, ADPCM_4, dsp->sb_data[0] + (dsp->sb_data[1] << 8));
dsp->sbdat2 = sb_8_read_dma(dsp);
dsp->sbdat2 = dsp->dma_readb(dsp->dma_priv);
dsp->sb_8_length--;
}
break;
case 0x7F: /* 2.6-bit ADPCM autoinit output */
if (dsp->sb_type >= SB15) {
sb_start_dma(dsp, 1, 1, ADPCM_26, dsp->sb_data[0] + (dsp->sb_data[1] << 8));
dsp->sbdat2 = sb_8_read_dma(dsp);
dsp->sbdat2 = dsp->dma_readb(dsp->dma_priv);
dsp->sb_8_length--;
}
break;
@@ -757,7 +769,7 @@ sb_exec_command(sb_dsp_t *dsp)
}
dsp->sbe2 += sbe2dat[dsp->sbe2count & 3][8];
dsp->sbe2count++;
sb_8_write_dma(dsp, dsp->sbe2);
dsp->dma_writeb(dsp->dma_priv, dsp->sbe2);
break;
case 0xE3: /* DSP copyright */
if (dsp->sb_type >= SB16) {
@@ -1016,7 +1028,7 @@ sb_read(uint16_t a, void *priv)
}
break;
case 0xE: /* Read data ready */
picintc(1 << dsp->sb_irqnum);
dsp->irq_update(dsp->irq_priv, 0);
dsp->sb_irq8 = dsp->sb_irq16 = 0;
/* Only bit 7 is defined but aztech diagnostics fail if the others are set. Keep the original behavior to not interfere with what's already working. */
if (IS_AZTECH(dsp)) {
@@ -1030,7 +1042,7 @@ sb_read(uint16_t a, void *priv)
case 0xF: /* 16-bit ack */
dsp->sb_irq16 = 0;
if (!dsp->sb_irq8)
picintc(1 << dsp->sb_irqnum);
dsp->irq_update(dsp->irq_priv, 0);
sb_dsp_log("SB 16-bit ACK read 0xFF\n");
ret = 0xff;
break;
@@ -1108,6 +1120,16 @@ sb_dsp_init(sb_dsp_t *dsp, int type, int subtype, void *parent)
dsp->sb_16_dmanum = 5;
dsp->mpu = NULL;
dsp->sbleftright_default = 0;
dsp->irq_update = sb_irq_update_pic;
dsp->irq_priv = dsp;
dsp->dma_readb = sb_8_read_dma;
dsp->dma_readw = sb_16_read_dma;
dsp->dma_writeb = sb_8_write_dma;
dsp->dma_writew = sb_16_write_dma;
dsp->dma_priv = dsp;
sb_doreset(dsp);
timer_add(&dsp->output_timer, pollsb, dsp, 0);
@@ -1149,6 +1171,28 @@ sb_dsp_set_stereo(sb_dsp_t *dsp, int stereo)
dsp->stereo = stereo;
}
void
sb_dsp_irq_attach(sb_dsp_t *dsp, void (*irq_update)(void *priv, int set), void *priv)
{
dsp->irq_update = irq_update;
dsp->irq_priv = priv;
}
void
sb_dsp_dma_attach(sb_dsp_t *dsp,
int (*dma_readb)(void *priv),
int (*dma_readw)(void *priv),
int (*dma_writeb)(void *priv, uint8_t val),
int (*dma_writew)(void *priv, uint16_t val),
void *priv)
{
dsp->dma_readb = dma_readb;
dsp->dma_readw = dma_readw;
dsp->dma_writeb = dma_writeb;
dsp->dma_writew = dma_writew;
dsp->dma_priv = priv;
}
void
pollsb(void *p)
{
@@ -1162,7 +1206,7 @@ pollsb(void *p)
switch (dsp->sb_8_format) {
case 0x00: /* Mono unsigned */
data[0] = sb_8_read_dma(dsp);
data[0] = dsp->dma_readb(dsp->dma_priv);
/* Needed to prevent clicking in Worms, which programs the DSP to
auto-init DMA but programs the DMA controller to single cycle */
if (data[0] == DMA_NODATA)
@@ -1181,7 +1225,7 @@ pollsb(void *p)
dsp->sb_8_length--;
break;
case 0x10: /* Mono signed */
data[0] = sb_8_read_dma(dsp);
data[0] = dsp->dma_readb(dsp->dma_priv);
if (data[0] == DMA_NODATA)
break;
dsp->sbdat = data[0] << 8;
@@ -1198,8 +1242,8 @@ pollsb(void *p)
dsp->sb_8_length--;
break;
case 0x20: /* Stereo unsigned */
data[0] = sb_8_read_dma(dsp);
data[1] = sb_8_read_dma(dsp);
data[0] = dsp->dma_readb(dsp->dma_priv);
data[1] = dsp->dma_readb(dsp->dma_priv);
if ((data[0] == DMA_NODATA) || (data[1] == DMA_NODATA))
break;
dsp->sbdatl = (data[0] ^ 0x80) << 8;
@@ -1207,8 +1251,8 @@ pollsb(void *p)
dsp->sb_8_length -= 2;
break;
case 0x30: /* Stereo signed */
data[0] = sb_8_read_dma(dsp);
data[1] = sb_8_read_dma(dsp);
data[0] = dsp->dma_readb(dsp->dma_priv);
data[1] = dsp->dma_readb(dsp->dma_priv);
if ((data[0] == DMA_NODATA) || (data[1] == DMA_NODATA))
break;
dsp->sbdatl = data[0] << 8;
@@ -1241,7 +1285,7 @@ pollsb(void *p)
if (dsp->sbdacpos >= 2) {
dsp->sbdacpos = 0;
dsp->sbdat2 = sb_8_read_dma(dsp);
dsp->sbdat2 = dsp->dma_readb(dsp->dma_priv);
dsp->sb_8_length--;
}
@@ -1284,7 +1328,7 @@ pollsb(void *p)
dsp->sbdacpos++;
if (dsp->sbdacpos >= 3) {
dsp->sbdacpos = 0;
dsp->sbdat2 = sb_8_read_dma(dsp);
dsp->sbdat2 = dsp->dma_readb(dsp->dma_priv);
dsp->sb_8_length--;
}
@@ -1321,7 +1365,7 @@ pollsb(void *p)
dsp->sbdacpos++;
if (dsp->sbdacpos >= 4) {
dsp->sbdacpos = 0;
dsp->sbdat2 = sb_8_read_dma(dsp);
dsp->sbdat2 = dsp->dma_readb(dsp->dma_priv);
}
if ((dsp->sb_type >= SBPRO) && (dsp->sb_type < SB16) && dsp->stereo) {
@@ -1352,22 +1396,22 @@ pollsb(void *p)
switch (dsp->sb_16_format) {
case 0x00: /* Mono unsigned */
data[0] = sb_16_read_dma(dsp);
data[0] = dsp->dma_readw(dsp->dma_priv);
if (data[0] == DMA_NODATA)
break;
dsp->sbdatl = dsp->sbdatr = data[0] ^ 0x8000;
dsp->sb_16_length--;
break;
case 0x10: /* Mono signed */
data[0] = sb_16_read_dma(dsp);
data[0] = dsp->dma_readw(dsp->dma_priv);
if (data[0] == DMA_NODATA)
break;
dsp->sbdatl = dsp->sbdatr = data[0];
dsp->sb_16_length--;
break;
case 0x20: /* Stereo unsigned */
data[0] = sb_16_read_dma(dsp);
data[1] = sb_16_read_dma(dsp);
data[0] = dsp->dma_readw(dsp->dma_priv);
data[1] = dsp->dma_readw(dsp->dma_priv);
if ((data[0] == DMA_NODATA) || (data[1] == DMA_NODATA))
break;
dsp->sbdatl = data[0] ^ 0x8000;
@@ -1375,8 +1419,8 @@ pollsb(void *p)
dsp->sb_16_length -= 2;
break;
case 0x30: /* Stereo signed */
data[0] = sb_16_read_dma(dsp);
data[1] = sb_16_read_dma(dsp);
data[0] = dsp->dma_readw(dsp->dma_priv);
data[1] = dsp->dma_readw(dsp->dma_priv);
if ((data[0] == DMA_NODATA) || (data[1] == DMA_NODATA))
break;
dsp->sbdatl = data[0];
@@ -1418,27 +1462,27 @@ sb_poll_i(void *p)
if (dsp->sb_8_enable && !dsp->sb_8_pause && dsp->sb_pausetime < 0 && !dsp->sb_8_output) {
switch (dsp->sb_8_format) {
case 0x00: /* Mono unsigned As the manual says, only the left channel is recorded */
sb_8_write_dma(dsp, (dsp->record_buffer[dsp->record_pos_read] >> 8) ^ 0x80);
dsp->dma_writeb(dsp->dma_priv, (dsp->record_buffer[dsp->record_pos_read] >> 8) ^ 0x80);
dsp->sb_8_length--;
dsp->record_pos_read += 2;
dsp->record_pos_read &= 0xFFFF;
break;
case 0x10: /* Mono signed As the manual says, only the left channel is recorded */
sb_8_write_dma(dsp, (dsp->record_buffer[dsp->record_pos_read] >> 8));
dsp->dma_writeb(dsp->dma_priv, (dsp->record_buffer[dsp->record_pos_read] >> 8));
dsp->sb_8_length--;
dsp->record_pos_read += 2;
dsp->record_pos_read &= 0xFFFF;
break;
case 0x20: /* Stereo unsigned */
sb_8_write_dma(dsp, (dsp->record_buffer[dsp->record_pos_read] >> 8) ^ 0x80);
sb_8_write_dma(dsp, (dsp->record_buffer[dsp->record_pos_read + 1] >> 8) ^ 0x80);
dsp->dma_writeb(dsp->dma_priv, (dsp->record_buffer[dsp->record_pos_read] >> 8) ^ 0x80);
dsp->dma_writeb(dsp->dma_priv, (dsp->record_buffer[dsp->record_pos_read + 1] >> 8) ^ 0x80);
dsp->sb_8_length -= 2;
dsp->record_pos_read += 2;
dsp->record_pos_read &= 0xFFFF;
break;
case 0x30: /* Stereo signed */
sb_8_write_dma(dsp, (dsp->record_buffer[dsp->record_pos_read] >> 8));
sb_8_write_dma(dsp, (dsp->record_buffer[dsp->record_pos_read + 1] >> 8));
dsp->dma_writeb(dsp->dma_priv, (dsp->record_buffer[dsp->record_pos_read] >> 8));
dsp->dma_writeb(dsp->dma_priv, (dsp->record_buffer[dsp->record_pos_read + 1] >> 8));
dsp->sb_8_length -= 2;
dsp->record_pos_read += 2;
dsp->record_pos_read &= 0xFFFF;
@@ -1459,31 +1503,31 @@ sb_poll_i(void *p)
if (dsp->sb_16_enable && !dsp->sb_16_pause && (dsp->sb_pausetime < 0LL) && !dsp->sb_16_output) {
switch (dsp->sb_16_format) {
case 0x00: /* Unsigned mono. As the manual says, only the left channel is recorded */
if (sb_16_write_dma(dsp, dsp->record_buffer[dsp->record_pos_read] ^ 0x8000))
if (dsp->dma_writew(dsp->dma_priv, dsp->record_buffer[dsp->record_pos_read] ^ 0x8000))
return;
dsp->sb_16_length--;
dsp->record_pos_read += 2;
dsp->record_pos_read &= 0xFFFF;
break;
case 0x10: /* Signed mono. As the manual says, only the left channel is recorded */
if (sb_16_write_dma(dsp, dsp->record_buffer[dsp->record_pos_read]))
if (dsp->dma_writew(dsp->dma_priv, dsp->record_buffer[dsp->record_pos_read]))
return;
dsp->sb_16_length--;
dsp->record_pos_read += 2;
dsp->record_pos_read &= 0xFFFF;
break;
case 0x20: /* Unsigned stereo */
if (sb_16_write_dma(dsp, dsp->record_buffer[dsp->record_pos_read] ^ 0x8000))
if (dsp->dma_writew(dsp->dma_priv, dsp->record_buffer[dsp->record_pos_read] ^ 0x8000))
return;
sb_16_write_dma(dsp, dsp->record_buffer[dsp->record_pos_read + 1] ^ 0x8000);
dsp->dma_writew(dsp->dma_priv, dsp->record_buffer[dsp->record_pos_read + 1] ^ 0x8000);
dsp->sb_16_length -= 2;
dsp->record_pos_read += 2;
dsp->record_pos_read &= 0xFFFF;
break;
case 0x30: /* Signed stereo */
if (sb_16_write_dma(dsp, dsp->record_buffer[dsp->record_pos_read]))
if (dsp->dma_writew(dsp->dma_priv, dsp->record_buffer[dsp->record_pos_read]))
return;
sb_16_write_dma(dsp, dsp->record_buffer[dsp->record_pos_read + 1]);
dsp->dma_writew(dsp->dma_priv, dsp->record_buffer[dsp->record_pos_read + 1]);
dsp->sb_16_length -= 2;
dsp->record_pos_read += 2;
dsp->record_pos_read &= 0xFFFF;