Initial implementation of C-Media CMI8x38
This commit is contained in:
@@ -155,6 +155,8 @@ extern const device_t mpu401_mca_device;
|
||||
|
||||
|
||||
extern uint8_t MPU401_ReadData(mpu_t *mpu);
|
||||
extern void mpu401_write(uint16_t addr, uint8_t val, void *priv);
|
||||
extern uint8_t mpu401_read(uint16_t addr, void *priv);
|
||||
extern void mpu401_setirq(mpu_t *mpu, int irq);
|
||||
extern void mpu401_change_addr(mpu_t *mpu, uint16_t addr);
|
||||
extern void mpu401_init(mpu_t *mpu, uint16_t addr, int irq, int mode, int receive_input);
|
||||
|
@@ -109,6 +109,8 @@ typedef struct sb_ct1745_mixer_t
|
||||
|
||||
uint8_t index;
|
||||
uint8_t regs[256];
|
||||
|
||||
int output_filter; /* for clones */
|
||||
} sb_ct1745_mixer_t;
|
||||
|
||||
typedef struct sb_t
|
||||
@@ -137,8 +139,13 @@ extern void sb_ct1345_mixer_write(uint16_t addr, uint8_t val, void *p);
|
||||
extern uint8_t sb_ct1345_mixer_read(uint16_t addr, void *p);
|
||||
extern void sb_ct1345_mixer_reset(sb_t* sb);
|
||||
|
||||
extern void sb_ct1745_mixer_write(uint16_t addr, uint8_t val, void *p);
|
||||
extern uint8_t sb_ct1745_mixer_read(uint16_t addr, void *p);
|
||||
extern void sb_ct1745_mixer_reset(sb_t* sb);
|
||||
|
||||
extern void sb_get_buffer_sbpro(int32_t *buffer, int len, void *p);
|
||||
extern void sbpro_filter_cd_audio(int channel, double *buffer, void *p);
|
||||
extern void sb16_awe32_filter_cd_audio(int channel, double *buffer, void *p);
|
||||
extern void sb_close(void *p);
|
||||
extern void sb_speed_changed(void *p);
|
||||
|
||||
|
@@ -120,6 +120,7 @@ extern const device_t sb_pro_mcv_device;
|
||||
extern const device_t sb_pro_compat_device;
|
||||
extern const device_t sb_16_device;
|
||||
extern const device_t sb_16_pnp_device;
|
||||
extern const device_t sb_16_compat_device;
|
||||
extern const device_t sb_32_pnp_device;
|
||||
extern const device_t sb_awe32_device;
|
||||
extern const device_t sb_awe32_pnp_device;
|
||||
@@ -140,6 +141,10 @@ extern const device_t cs4235_onboard_device;
|
||||
extern const device_t cs4236b_device;
|
||||
extern const device_t cs4237b_device;
|
||||
extern const device_t cs4238b_device;
|
||||
|
||||
/* C-Media CMI8x38 */
|
||||
extern const device_t cmi8338_device;
|
||||
extern const device_t cmi8738_device;
|
||||
#endif
|
||||
|
||||
#endif /*EMU_SOUND_H*/
|
||||
|
@@ -16,7 +16,7 @@
|
||||
add_library(snd OBJECT sound.c snd_opl.c snd_opl_nuked.c snd_resid.cc
|
||||
midi.c midi_rtmidi.cpp snd_speaker.c snd_pssj.c snd_lpt_dac.c snd_ac97_codec.c snd_ac97_via.c
|
||||
snd_lpt_dss.c snd_ps1.c snd_adlib.c snd_adlibgold.c snd_ad1848.c snd_audiopci.c
|
||||
snd_azt2316a.c snd_cms.c snd_cs423x.c snd_gus.c snd_sb.c snd_sb_dsp.c
|
||||
snd_azt2316a.c snd_cms.c snd_cmi8x38.c snd_cs423x.c snd_gus.c snd_sb.c snd_sb_dsp.c
|
||||
snd_emu8k.c snd_mpu401.c snd_sn76489.c snd_ssi2001.c snd_wss.c snd_ym7128.c)
|
||||
|
||||
if(OPENAL)
|
||||
|
830
src/sound/snd_cmi8x38.c
Normal file
830
src/sound/snd_cmi8x38.c
Normal file
@@ -0,0 +1,830 @@
|
||||
/*
|
||||
* 86Box A hypervisor and IBM PC system emulator that specializes in
|
||||
* running old operating systems and software designed for IBM
|
||||
* PC systems and compatibles from 1981 through fairly recent
|
||||
* system designs based on the PCI bus.
|
||||
*
|
||||
* This file is part of the 86Box distribution.
|
||||
*
|
||||
* C-Media CMI8x38 PCI audio controller emulation.
|
||||
*
|
||||
*
|
||||
*
|
||||
* Authors: RichardG, <richardg867@gmail.com>
|
||||
*
|
||||
* Copyright 2022 RichardG.
|
||||
*/
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#define HAVE_STDARG_H
|
||||
#include <86box/86box.h>
|
||||
#include <86box/device.h>
|
||||
#include <86box/io.h>
|
||||
#include <86box/mem.h>
|
||||
#include <86box/pic.h>
|
||||
#include <86box/timer.h>
|
||||
#include <86box/pci.h>
|
||||
#include <86box/sound.h>
|
||||
#include <86box/snd_sb.h>
|
||||
|
||||
|
||||
enum {
|
||||
CMEDIA_CMI8338 = 0x00,
|
||||
CMEDIA_CMI8738 = 0x11
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t id, reg, always_run, playback_enabled;
|
||||
struct _cmi8x38_ *dev;
|
||||
|
||||
uint32_t sample_ptr, fifo_pos, fifo_end;
|
||||
int32_t frame_count_dma, frame_count_fragment, sample_count_out;
|
||||
uint8_t fifo[32], restart;
|
||||
|
||||
int16_t out_l, out_r;
|
||||
int vol_l, vol_r, pos;
|
||||
int32_t buffer[SOUNDBUFLEN * 2];
|
||||
uint64_t timer_latch;
|
||||
|
||||
pc_timer_t dma_timer, poll_timer;
|
||||
} cmi8x38_dma_t;
|
||||
|
||||
typedef struct _cmi8x38_ {
|
||||
uint16_t io_base;
|
||||
uint8_t type, pci_regs[256], io_regs[256], mixer_ext_regs[16];
|
||||
int slot;
|
||||
|
||||
sb_t *sb;
|
||||
cmi8x38_dma_t dma[2];
|
||||
|
||||
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;
|
||||
|
||||
static void
|
||||
cmi8x38_log(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
if (cmi8x38_do_log) {
|
||||
va_start(ap, fmt);
|
||||
pclog_ex(fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
}
|
||||
#else
|
||||
#define cmi8x38_log(fmt, ...)
|
||||
#endif
|
||||
|
||||
static const double freqs[] = {5512.0, 11025.0, 22050.0, 44100.0, 8000.0, 16000.0, 32000.0, 48000.0};
|
||||
|
||||
|
||||
static void cmi8x38_dma_process(void *priv);
|
||||
static void cmi8x38_speed_changed(void *priv);
|
||||
|
||||
|
||||
static void
|
||||
cmi8x38_update_irqs(cmi8x38_t *dev)
|
||||
{
|
||||
/* Calculate and use the any interrupt flag. */
|
||||
if (*((uint32_t *) &dev->io_regs[0x10]) & 0x0401c003) {
|
||||
dev->io_regs[0x13] |= 0x80;
|
||||
pci_set_irq(dev->slot, PCI_INTA);
|
||||
cmi8x38_log("CMI8x38: Raising IRQ\n");
|
||||
} else {
|
||||
dev->io_regs[0x13] &= ~0x80;
|
||||
pci_clear_irq(dev->slot, PCI_INTA);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
cmi8x38_start_playback(cmi8x38_t *dev, uint8_t val)
|
||||
{
|
||||
uint8_t i;
|
||||
|
||||
i = !(val & 0x01);
|
||||
if (!dev->dma[0].playback_enabled && i)
|
||||
timer_advance_u64(&dev->dma[0].poll_timer, dev->dma[0].timer_latch);
|
||||
dev->dma[0].playback_enabled = i;
|
||||
|
||||
i = !(val & 0x02);
|
||||
if (!dev->dma[1].playback_enabled && i)
|
||||
timer_advance_u64(&dev->dma[1].poll_timer, dev->dma[1].timer_latch);
|
||||
dev->dma[1].playback_enabled = i;
|
||||
}
|
||||
|
||||
|
||||
static uint8_t
|
||||
cmi8x38_read(uint16_t addr, void *priv)
|
||||
{
|
||||
cmi8x38_t *dev = (cmi8x38_t *) priv;
|
||||
addr &= 0xff;
|
||||
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);
|
||||
break;
|
||||
|
||||
case 0x40 ... 0x4f:
|
||||
if (dev->type == CMEDIA_CMI8338)
|
||||
goto io_reg;
|
||||
else
|
||||
ret = mpu401_read(addr, val, dev->sb->mpu);
|
||||
break;
|
||||
|
||||
case 0x50 ... 0x5f:
|
||||
if (dev->type == CMEDIA_CMI8338)
|
||||
goto io_reg;
|
||||
else
|
||||
ret = opl3_read(addr, &dev->sb->opl);
|
||||
break;
|
||||
|
||||
case 0x80: case 0x88:
|
||||
ret = dev->dma[(addr & 0x78) >> 3].sample_ptr;
|
||||
break;
|
||||
|
||||
case 0x81: case 0x89:
|
||||
ret = dev->dma[(addr & 0x78) >> 3].sample_ptr >> 8;
|
||||
break;
|
||||
|
||||
case 0x82: case 0x8a:
|
||||
ret = dev->dma[(addr & 0x78) >> 3].sample_ptr >> 16;
|
||||
break;
|
||||
|
||||
case 0x83: case 0x8b:
|
||||
ret = dev->dma[(addr & 0x78) >> 3].sample_ptr >> 24;
|
||||
break;
|
||||
|
||||
case 0x84: case 0x8c:
|
||||
ret = dev->dma[(addr & 0x78) >> 3].frame_count_dma;
|
||||
break;
|
||||
|
||||
case 0x85: case 0x8d:
|
||||
ret = dev->dma[(addr & 0x78) >> 3].frame_count_dma >> 8;
|
||||
break;
|
||||
|
||||
case 0x86: case 0x8e:
|
||||
ret = dev->dma[(addr & 0x78) >> 3].sample_count_out >> 2;
|
||||
break;
|
||||
|
||||
case 0x87: case 0x8f:
|
||||
ret = dev->dma[(addr & 0x78) >> 3].sample_count_out >> 10;
|
||||
break;
|
||||
|
||||
default:
|
||||
io_reg: ret = dev->io_regs[addr];
|
||||
break;
|
||||
}
|
||||
|
||||
cmi8x38_log("CMI8x38: read(%02X) = %02X\n", addr, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
cmi8x38_write(uint16_t addr, uint8_t val, void *priv)
|
||||
{
|
||||
cmi8x38_t *dev = (cmi8x38_t *) priv;
|
||||
addr &= 0xff;
|
||||
cmi8x38_log("CMI8x38: write(%02X, %02X)\n", addr, val);
|
||||
|
||||
switch (addr) {
|
||||
case 0x00:
|
||||
val &= 0x0f;
|
||||
|
||||
/* Don't care about recording DMA. */
|
||||
dev->dma[0].always_run = val & 0x01;
|
||||
dev->dma[1].always_run = val & 0x02;
|
||||
|
||||
/* Start playback if requested. */
|
||||
cmi8x38_start_playback(dev, val);
|
||||
break;
|
||||
|
||||
case 0x02:
|
||||
/* Reset DMA channels if requested. */
|
||||
if (val & 0x04)
|
||||
val &= ~0x01;
|
||||
if (val & 0x08)
|
||||
val &= ~0x02;
|
||||
|
||||
val &= 0x03;
|
||||
dev->io_regs[addr] = val;
|
||||
|
||||
/* Start DMA channels if requested. */
|
||||
if (val & 0x01) {
|
||||
cmi8x38_log("CMI8x38: DMA 0 trigger\n");
|
||||
dev->dma[0].restart = 1;
|
||||
cmi8x38_dma_process(&dev->dma[0]);
|
||||
}
|
||||
if (val & 0x02) {
|
||||
cmi8x38_log("CMI8x38: DMA 1 trigger\n");
|
||||
dev->dma[1].restart = 1;
|
||||
cmi8x38_dma_process(&dev->dma[1]);
|
||||
}
|
||||
|
||||
/* Start playback along with DMA channels. */
|
||||
if (val & 0x03)
|
||||
cmi8x38_start_playback(dev, dev->io_regs[0x00]);
|
||||
break;
|
||||
|
||||
case 0x05:
|
||||
dev->io_regs[addr] = val;
|
||||
cmi8x38_speed_changed(dev);
|
||||
break;
|
||||
|
||||
case 0x08:
|
||||
if (dev->type == CMEDIA_CMI8338)
|
||||
val &= 0x0f;
|
||||
break;
|
||||
|
||||
case 0x09:
|
||||
#if 0 /* actual CMI8338 behavior unconfirmed; this register is required for the Windows XP driver which outputs 96K */
|
||||
if (dev->type == CMEDIA_CMI8338)
|
||||
return;
|
||||
#endif
|
||||
cmi8x38_speed_changed(dev);
|
||||
break;
|
||||
|
||||
case 0x0a: case 0x0b:
|
||||
if (dev->type == CMEDIA_CMI8338)
|
||||
return;
|
||||
else
|
||||
val &= 0xe0;
|
||||
|
||||
if (addr == 0x0a)
|
||||
dev->pci_regs[0x0d] = (val & 0x80) ? 0x48 : 0x20; /* clearing SETLAT48 is undefined */
|
||||
break;
|
||||
|
||||
case 0x0e:
|
||||
val &= 0x07;
|
||||
|
||||
/* Clear interrupts. */
|
||||
dev->io_regs[0x10] &= val | 0xfc;
|
||||
if (!(val & 0x04))
|
||||
dev->io_regs[0x11] &= ~0xc0;
|
||||
cmi8x38_update_irqs(dev);
|
||||
break;
|
||||
|
||||
case 0x15:
|
||||
if (dev->type == CMEDIA_CMI8338)
|
||||
return;
|
||||
else
|
||||
val &= 0xf0;
|
||||
break;
|
||||
|
||||
case 0x16:
|
||||
if (dev->type == CMEDIA_CMI8338)
|
||||
val &= 0xa0;
|
||||
break;
|
||||
|
||||
case 0x17:
|
||||
if (dev->type == CMEDIA_CMI8338) {
|
||||
val &= 0xf3;
|
||||
|
||||
/* Force IRQ if requested. Clearing this bit is undefined. */
|
||||
if (val & 0x10)
|
||||
pci_set_irq(dev->slot, PCI_INTA);
|
||||
else if ((dev->io_regs[0x17] & 0x10) && !(val & 0x10))
|
||||
pci_clear_irq(dev->slot, PCI_INTA);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x18:
|
||||
if (dev->type == CMEDIA_CMI8338)
|
||||
val &= 0x0f;
|
||||
else
|
||||
val &= 0xdf;
|
||||
break;
|
||||
|
||||
case 0x19:
|
||||
if (dev->type == CMEDIA_CMI8338)
|
||||
return;
|
||||
else
|
||||
val &= 0xe0;
|
||||
break;
|
||||
|
||||
case 0x1a:
|
||||
val &= 0xfd;
|
||||
break;
|
||||
|
||||
case 0x1b:
|
||||
if (dev->type == CMEDIA_CMI8338)
|
||||
val &= 0xf0;
|
||||
else
|
||||
val &= 0xd7;
|
||||
break;
|
||||
|
||||
case 0x20:
|
||||
/* ??? */
|
||||
break;
|
||||
|
||||
case 0x21:
|
||||
if (dev->type != CMEDIA_CMI8338)
|
||||
val &= 0x07;
|
||||
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);
|
||||
return;
|
||||
|
||||
case 0x24:
|
||||
if (dev->type == CMEDIA_CMI8338)
|
||||
val &= 0xcf;
|
||||
break;
|
||||
|
||||
case 0x27:
|
||||
if (dev->type == CMEDIA_CMI8338)
|
||||
val &= 0x03;
|
||||
else
|
||||
val &= 0x27;
|
||||
break;
|
||||
|
||||
case 0x40 ... 0x4f:
|
||||
if (dev->type == CMEDIA_CMI8338)
|
||||
break;
|
||||
else
|
||||
mpu401_write(addr, val, dev->sb->mpu);
|
||||
return;
|
||||
|
||||
case 0x50 ... 0x5f:
|
||||
if (dev->type == CMEDIA_CMI8338)
|
||||
break;
|
||||
else
|
||||
opl3_write(addr, val, &dev->sb->opl);
|
||||
return;
|
||||
|
||||
case 0x92:
|
||||
if (dev->type == CMEDIA_CMI8338)
|
||||
return;
|
||||
else
|
||||
val &= 0x1f;
|
||||
break;
|
||||
|
||||
case 0x93:
|
||||
if (dev->type == CMEDIA_CMI8338)
|
||||
return;
|
||||
else
|
||||
val &= 0x10;
|
||||
break;
|
||||
|
||||
case 0x04:
|
||||
case 0x25: case 0x26:
|
||||
case 0x70: case 0x71:
|
||||
case 0x80 ... 0x8f:
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
dev->io_regs[addr] = val;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
cmi8x38_remap(cmi8x38_t *dev, uint8_t io_msb, uint8_t enable)
|
||||
{
|
||||
if (dev->io_base)
|
||||
io_removehandler(dev->io_base, 256, cmi8x38_read, NULL, NULL, cmi8x38_write, NULL, NULL, dev);
|
||||
|
||||
if (enable & 0x01)
|
||||
dev->io_base = io_msb << 8;
|
||||
else
|
||||
dev->io_base = 0;
|
||||
cmi8x38_log("CMI8x38: remap(%04X)\n", dev->io_base);
|
||||
|
||||
if (dev->io_base)
|
||||
io_sethandler(dev->io_base, 256, cmi8x38_read, NULL, NULL, cmi8x38_write, NULL, NULL, dev);
|
||||
}
|
||||
|
||||
|
||||
static uint8_t
|
||||
cmi8x38_pci_read(int func, int addr, void *priv)
|
||||
{
|
||||
cmi8x38_t *dev = (cmi8x38_t *) priv;
|
||||
uint8_t ret = 0xff;
|
||||
|
||||
if (!func) {
|
||||
ret = dev->pci_regs[addr];
|
||||
cmi8x38_log("CMI8x38: pci_read(%02X) = %02X\n", addr, ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
cmi8x38_pci_write(int func, int addr, uint8_t val, void *priv)
|
||||
{
|
||||
cmi8x38_t *dev = (cmi8x38_t *) priv;
|
||||
|
||||
if (func)
|
||||
return;
|
||||
|
||||
cmi8x38_log("CMI8x38: pci_write(%02X, %02X)\n", addr, val);
|
||||
|
||||
switch (addr) {
|
||||
case 0x04:
|
||||
val &= 0x05;
|
||||
cmi8x38_remap(dev, dev->pci_regs[0x11], val);
|
||||
break;
|
||||
|
||||
case 0x05:
|
||||
val &= 0x01;
|
||||
break;
|
||||
|
||||
case 0x11:
|
||||
cmi8x38_remap(dev, val, dev->pci_regs[0x04]);
|
||||
break;
|
||||
|
||||
case 0x2c: case 0x2d: case 0x2e: case 0x2f:
|
||||
if (!(dev->io_regs[0x1a] & 0x01))
|
||||
return;
|
||||
break;
|
||||
|
||||
case 0x40:
|
||||
if (dev->type == CMEDIA_CMI8338)
|
||||
val &= 0x0f;
|
||||
else
|
||||
return;
|
||||
break;
|
||||
|
||||
case 0x0c: case 0x0d:
|
||||
case 0x3c:
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
dev->pci_regs[addr] = val;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
cmi8x38_update(cmi8x38_t *dev, cmi8x38_dma_t *dma)
|
||||
{
|
||||
sb_ct1745_mixer_t *mixer = &dev->sb->mixer_sb16;
|
||||
int32_t l = (dma->out_l * mixer->voice_l) * mixer->master_l,
|
||||
r = (dma->out_r * mixer->voice_r) * mixer->master_r;
|
||||
|
||||
for (; dma->pos < sound_pos_global; dma->pos++) {
|
||||
dma->buffer[dma->pos*2] = l;
|
||||
dma->buffer[dma->pos*2 + 1] = r;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
cmi8x38_dma_process(void *priv)
|
||||
{
|
||||
cmi8x38_dma_t *dma = (cmi8x38_dma_t *) priv;
|
||||
cmi8x38_t *dev = dma->dev;
|
||||
|
||||
/* Stop if this DMA channel is not active. */
|
||||
uint8_t dma_bit = 0x01 << dma->id;
|
||||
if (!(dev->io_regs[0x02] & dma_bit)) {
|
||||
cmi8x38_log("CMI8x38: Stopping DMA %d due to inactive channel (%02X)\n", dma->id, dev->io_regs[0x02]);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Schedule next run. */
|
||||
timer_on_auto(&dma->dma_timer, 10.0);
|
||||
|
||||
/* Process DMA if it's active, and the FIFO has room or is disabled. */
|
||||
uint8_t dma_status = dev->io_regs[0x00] >> dma->id;
|
||||
if (!(dma_status & 0x04) && (dma->always_run || ((dma->fifo_end - dma->fifo_pos) <= (sizeof(dma->fifo) - 4)))) {
|
||||
/* Start DMA if requested. */
|
||||
if (dma->restart) {
|
||||
/* Set up base address and counters.
|
||||
I have no idea how sample_count_out is supposed to work,
|
||||
nothing consumes it, so it's implemented as an assumption. */
|
||||
dma->restart = 0;
|
||||
dma->sample_ptr = *((uint32_t *) &dev->io_regs[dma->reg]);
|
||||
dma->frame_count_dma = dma->sample_count_out = *((uint16_t *) &dev->io_regs[dma->reg | 0x4]);
|
||||
dma->frame_count_fragment = *((uint16_t *) &dev->io_regs[dma->reg | 0x6]);
|
||||
|
||||
cmi8x38_log("CMI8x38: Starting DMA %d at %08X\n", dma->id, dma->sample_ptr);
|
||||
}
|
||||
|
||||
if (dma_status & 0x01) {
|
||||
/* Write channel: read data from FIFO. */
|
||||
mem_writel_phys(dma->sample_ptr, *((uint32_t *) &dma->fifo[dma->fifo_end & (sizeof(dma->fifo) - 1)]));
|
||||
} else {
|
||||
/* Read channel: write data to FIFO. */
|
||||
*((uint32_t *) &dma->fifo[dma->fifo_end & (sizeof(dma->fifo) - 1)]) = mem_readl_phys(dma->sample_ptr);
|
||||
}
|
||||
dma->fifo_end += 4;
|
||||
dma->sample_ptr += 4;
|
||||
|
||||
/* Check if the fragment size was reached. */
|
||||
if (--dma->frame_count_fragment <= 0) {
|
||||
cmi8x38_log("CMI8x38: DMA %d fragment size reached at %04X frames left", dma->id, dma->frame_count_dma - 1);
|
||||
|
||||
/* Reset fragment counter. */
|
||||
dma->frame_count_fragment = *((uint16_t *) &dev->io_regs[dma->reg | 0x6]);
|
||||
|
||||
/* Fire interrupt if requested. */
|
||||
if (dev->io_regs[0x0e] & dma_bit) {
|
||||
cmi8x38_log(", firing interrupt\n");
|
||||
|
||||
/* Set channel interrupt flag. */
|
||||
dev->io_regs[0x10] |= dma_bit;
|
||||
|
||||
/* Fire interrupt. */
|
||||
cmi8x38_update_irqs(dev);
|
||||
} else {
|
||||
cmi8x38_log("\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if the buffer's end was reached. */
|
||||
if (--dma->frame_count_dma <= 0) {
|
||||
cmi8x38_log("CMI8x38: DMA %d end reached, restarting\n", dma->id);
|
||||
|
||||
/* Restart DMA on the next run. */
|
||||
dma->restart = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
cmi8x38_poll(void *priv)
|
||||
{
|
||||
cmi8x38_dma_t *dma = (cmi8x38_dma_t *) priv;
|
||||
cmi8x38_t *dev = dma->dev;
|
||||
|
||||
/* Schedule next run if playback is enabled. */
|
||||
if (dev->io_regs[0x00] & (1 << dma->id))
|
||||
dma->playback_enabled = 0;
|
||||
else
|
||||
timer_advance_u64(&dma->poll_timer, dma->timer_latch);
|
||||
|
||||
/* Update audio buffer. */
|
||||
cmi8x38_update(dev, dma);
|
||||
|
||||
/* Feed next sample from the FIFO. */
|
||||
switch ((dev->io_regs[0x08] >> (dma->id << 1)) & 0x03) {
|
||||
case 0x00: /* Mono, 8-bit PCM */
|
||||
if ((dma->fifo_end - dma->fifo_pos) >= 1) {
|
||||
dma->out_l = dma->out_r = (dma->fifo[dma->fifo_pos++ & (sizeof(dma->fifo) - 1)] ^ 0x80) << 8;
|
||||
dma->sample_count_out--;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x01: /* Stereo, 8-bit PCM */
|
||||
if ((dma->fifo_end - dma->fifo_pos) >= 2) {
|
||||
dma->out_l = (dma->fifo[dma->fifo_pos++ & (sizeof(dma->fifo) - 1)] ^ 0x80) << 8;
|
||||
dma->out_r = (dma->fifo[dma->fifo_pos++ & (sizeof(dma->fifo) - 1)] ^ 0x80) << 8;
|
||||
dma->sample_count_out -= 2;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x02: /* Mono, 16-bit PCM */
|
||||
if ((dma->fifo_end - dma->fifo_pos) >= 2) {
|
||||
dma->out_l = dma->out_r = *((uint16_t *) &dma->fifo[dma->fifo_pos & (sizeof(dma->fifo) - 1)]);
|
||||
dma->fifo_pos += 2;
|
||||
dma->sample_count_out -= 2;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x03: /* Stereo, 16-bit PCM */
|
||||
if ((dma->fifo_end - dma->fifo_pos) >= 4) {
|
||||
dma->out_l = *((uint16_t *) &dma->fifo[dma->fifo_pos & (sizeof(dma->fifo) - 1)]);
|
||||
dma->fifo_pos += 2;
|
||||
dma->out_r = *((uint16_t *) &dma->fifo[dma->fifo_pos & (sizeof(dma->fifo) - 1)]);
|
||||
dma->fifo_pos += 2;
|
||||
dma->sample_count_out -= 4;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Feed silence if the FIFO is empty. */
|
||||
dma->out_l = dma->out_r = 0;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
cmi8x38_get_buffer(int32_t *buffer, int len, void *priv)
|
||||
{
|
||||
cmi8x38_t *dev = (cmi8x38_t *) priv;
|
||||
|
||||
/* Update wave playback channels. */
|
||||
cmi8x38_update(dev, &dev->dma[0]);
|
||||
cmi8x38_update(dev, &dev->dma[1]);
|
||||
|
||||
/* Apply wave mute. */
|
||||
if (!(dev->io_regs[0x24] & 0x40)) {
|
||||
/* Fill buffer. */
|
||||
for (int c = 0; c < len * 2; c++) {
|
||||
buffer[c] += dev->dma[0].buffer[c];
|
||||
buffer[c] += dev->dma[1].buffer[c];
|
||||
}
|
||||
}
|
||||
|
||||
dev->dma[0].pos = dev->dma[1].pos = 0;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
cmi8x38_speed_changed(void *priv)
|
||||
{
|
||||
cmi8x38_t *dev = (cmi8x38_t *) priv;
|
||||
double freq;
|
||||
uint8_t dsr = dev->io_regs[0x09], freqreg = dev->io_regs[0x05] >> 2;
|
||||
|
||||
/* CMI8338 claims the frequency controls are for DAC (playback) and ADC (recording)
|
||||
respectively, while CMI8738 claims they're for channel 0 and channel 1. The Linux
|
||||
driver just assumes the latter definition, so that's what we're going to use here. */
|
||||
for (int i = 0; i < (sizeof(dev->dma) / sizeof(dev->dma[0])); i++) {
|
||||
/* More confusion. The Linux driver implies the sample rate doubling
|
||||
bits take precedence over any configured sample rate. It also
|
||||
supports 128K with both doubling bits set, which is undocumented. */
|
||||
switch (dsr & 0x03) {
|
||||
case 0x01: freq = 88200.0; break;
|
||||
case 0x02: freq = 96000.0; break;
|
||||
case 0x03: freq = 128000.0; break;
|
||||
default: freq = freqs[freqreg & 0x07]; break;
|
||||
}
|
||||
dsr >>= 2;
|
||||
freqreg >>= 3;
|
||||
|
||||
/* Set period. */
|
||||
dev->dma[i].timer_latch = (uint64_t) ((double) TIMER_USEC * (1000000.0 / freq));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
cmi8x38_reset(void *priv)
|
||||
{
|
||||
cmi8x38_t *dev = (cmi8x38_t *) priv;
|
||||
|
||||
/* Reset PCI configuration registers. */
|
||||
memset(dev->pci_regs, 0, sizeof(dev->pci_regs));
|
||||
dev->pci_regs[0x00] = 0xf6; dev->pci_regs[0x01] = 0x13;
|
||||
dev->pci_regs[0x02] = dev->type; dev->pci_regs[0x03] = 0x01;
|
||||
dev->pci_regs[0x06] = (dev->type == CMEDIA_CMI8338) ? 0x80 : 0x10; dev->pci_regs[0x07] = 0x02;
|
||||
dev->pci_regs[0x08] = 0x10;
|
||||
dev->pci_regs[0x0a] = 0x01; dev->pci_regs[0x0b] = 0x04;
|
||||
dev->pci_regs[0x0d] = 0x20;
|
||||
dev->pci_regs[0x10] = 0x01;
|
||||
dev->pci_regs[0x2c] = 0xf6; dev->pci_regs[0x2d] = 0x13;
|
||||
if (dev->type == CMEDIA_CMI8338) {
|
||||
dev->pci_regs[0x2e] = 0xff; dev->pci_regs[0x2f] = 0xff;
|
||||
} else {
|
||||
dev->pci_regs[0x2e] = dev->type; dev->pci_regs[0x2f] = 0x01;
|
||||
dev->pci_regs[0x34] = 0x40;
|
||||
}
|
||||
dev->pci_regs[0x3d] = 0x01;
|
||||
dev->pci_regs[0x3e] = 0x02;
|
||||
dev->pci_regs[0x3f] = 0x18;
|
||||
|
||||
/* Reset I/O space registers. */
|
||||
memset(dev->io_regs, 0, sizeof(dev->io_regs));
|
||||
if (dev->type == CMEDIA_CMI8738)
|
||||
dev->io_regs[0x0f] = 0x04; /* chip version 039 with 4-channel support */
|
||||
|
||||
/* Reset DMA channels. */
|
||||
for (int i = 0; i < (sizeof(dev->dma) / sizeof(dev->dma[0])); i++) {
|
||||
dev->dma[i].playback_enabled = 0;
|
||||
|
||||
dev->dma[i].fifo_pos = dev->dma[i].fifo_end = 0;
|
||||
memset(dev->dma[i].fifo, 0, sizeof(dev->dma[i].fifo));
|
||||
}
|
||||
|
||||
/* Reset Sound Blaster 16 mixer. */
|
||||
sb_ct1745_mixer_reset(dev->sb);
|
||||
}
|
||||
|
||||
|
||||
static void *
|
||||
cmi8x38_init(const device_t *info)
|
||||
{
|
||||
cmi8x38_t *dev = malloc(sizeof(cmi8x38_t));
|
||||
memset(dev, 0, sizeof(cmi8x38_t));
|
||||
|
||||
/* Set the chip type. */
|
||||
dev->type = info->local;
|
||||
cmi8x38_log("CMI8x38: init(%02X)\n", dev->type);
|
||||
|
||||
/* Initialize Sound Blaster 16. */
|
||||
dev->sb = device_add_inst(&sb_16_compat_device, 1);
|
||||
dev->sb->opl_enabled = 1; /* let snd_sb.c handle the OPL3 */
|
||||
dev->sb->mixer_sb16.output_filter = 0; /* no output filtering */
|
||||
|
||||
/* Initialize DMA channels. */
|
||||
for (int i = 0; i < (sizeof(dev->dma) / sizeof(dev->dma[0])); i++) {
|
||||
dev->dma[i].id = i;
|
||||
dev->dma[i].reg = 0x80 + (8 * i);
|
||||
dev->dma[i].dev = dev;
|
||||
|
||||
timer_add(&dev->dma[i].dma_timer, cmi8x38_dma_process, &dev->dma[i], 0);
|
||||
timer_add(&dev->dma[i].poll_timer, cmi8x38_poll, &dev->dma[i], 0);
|
||||
}
|
||||
cmi8x38_speed_changed(dev);
|
||||
|
||||
/* Initialize playback handler and CD audio filter. */
|
||||
sound_add_handler(cmi8x38_get_buffer, dev);
|
||||
sound_set_cd_audio_filter(sb16_awe32_filter_cd_audio, dev->sb);
|
||||
|
||||
/* Add PCI card. */
|
||||
dev->slot = pci_add_card((info->local & 0x100) ? PCI_ADD_SOUND : PCI_ADD_NORMAL, cmi8x38_pci_read, cmi8x38_pci_write, dev);
|
||||
|
||||
/* Perform initial reset. */
|
||||
cmi8x38_reset(dev);
|
||||
|
||||
return dev;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
cmi8x38_close(void *priv)
|
||||
{
|
||||
cmi8x38_t *dev = (cmi8x38_t *) priv;
|
||||
|
||||
cmi8x38_log("CMI8x38: close()\n");
|
||||
|
||||
free(dev);
|
||||
}
|
||||
|
||||
|
||||
const device_t cmi8338_device =
|
||||
{
|
||||
"C-Media CMI8338",
|
||||
"cmi8338",
|
||||
DEVICE_PCI,
|
||||
CMEDIA_CMI8338,
|
||||
cmi8x38_init, cmi8x38_close, cmi8x38_reset,
|
||||
{ NULL },
|
||||
cmi8x38_speed_changed,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
const device_t cmi8738_device =
|
||||
{
|
||||
"C-Media CMI8738",
|
||||
"cmi8738",
|
||||
DEVICE_PCI,
|
||||
CMEDIA_CMI8738,
|
||||
cmi8x38_init, cmi8x38_close, cmi8x38_reset,
|
||||
{ NULL },
|
||||
cmi8x38_speed_changed,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
@@ -1213,7 +1213,7 @@ MPU401_ReadData(mpu_t *mpu)
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
void
|
||||
mpu401_write(uint16_t addr, uint8_t val, void *priv)
|
||||
{
|
||||
mpu_t *mpu = (mpu_t *)priv;
|
||||
@@ -1233,7 +1233,7 @@ mpu401_write(uint16_t addr, uint8_t val, void *priv)
|
||||
}
|
||||
|
||||
|
||||
static uint8_t
|
||||
uint8_t
|
||||
mpu401_read(uint16_t addr, void *priv)
|
||||
{
|
||||
mpu_t *mpu = (mpu_t *)priv;
|
||||
|
@@ -384,9 +384,14 @@ sb_get_buffer_sb16_awe32(int32_t *buffer, int len, void *p)
|
||||
in_r = (mixer->input_selector_right & INPUT_MIDI_L) ? ((int32_t) out_l) :
|
||||
0 + (mixer->input_selector_right & INPUT_MIDI_R) ? ((int32_t) out_r) : 0;
|
||||
|
||||
/* We divide by 3 to get the volume down to normal. */
|
||||
out_l += (low_fir_sb16(0, 0, (double) sb->dsp.buffer[c]) * mixer->voice_l) / 3.0;
|
||||
out_r += (low_fir_sb16(0, 1, (double) sb->dsp.buffer[c + 1]) * mixer->voice_r) / 3.0;
|
||||
if (mixer->output_filter) {
|
||||
/* We divide by 3 to get the volume down to normal. */
|
||||
out_l += (low_fir_sb16(0, 0, (double) sb->dsp.buffer[c]) * mixer->voice_l) / 3.0;
|
||||
out_r += (low_fir_sb16(0, 1, (double) sb->dsp.buffer[c + 1]) * mixer->voice_r) / 3.0;
|
||||
} else {
|
||||
out_l += (((double) sb->dsp.buffer[c]) * mixer->voice_l) / 3.0;
|
||||
out_r += (((double) sb->dsp.buffer[c + 1]) * mixer->voice_r) / 3.0;
|
||||
}
|
||||
|
||||
out_l *= mixer->master_l;
|
||||
out_r *= mixer->master_r;
|
||||
@@ -468,7 +473,7 @@ sb_get_buffer_sb16_awe32(int32_t *buffer, int len, void *p)
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
void
|
||||
sb16_awe32_filter_cd_audio(int channel, double *buffer, void *p)
|
||||
{
|
||||
sb_t *sb = (sb_t *)p;
|
||||
@@ -481,7 +486,10 @@ sb16_awe32_filter_cd_audio(int channel, double *buffer, void *p)
|
||||
double bass_treble;
|
||||
double output_gain = (channel ? mixer->output_gain_R : mixer->output_gain_L);
|
||||
|
||||
c = (low_fir_sb16(1, channel, *buffer) * cd) / 3.0;
|
||||
if (mixer->output_filter)
|
||||
c = (low_fir_sb16(1, channel, *buffer) * cd) / 3.0;
|
||||
else
|
||||
c = ((*buffer) * cd) / 3.0;
|
||||
c *= master;
|
||||
|
||||
/* This is not exactly how one does bass/treble controls, but the end result is like it.
|
||||
@@ -691,7 +699,7 @@ sb_ct1345_mixer_reset(sb_t* sb)
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
void
|
||||
sb_ct1745_mixer_write(uint16_t addr, uint8_t val, void *p)
|
||||
{
|
||||
sb_t *sb = (sb_t *) p;
|
||||
@@ -862,7 +870,7 @@ sb_ct1745_mixer_write(uint16_t addr, uint8_t val, void *p)
|
||||
}
|
||||
|
||||
|
||||
static uint8_t
|
||||
uint8_t
|
||||
sb_ct1745_mixer_read(uint16_t addr, void *p)
|
||||
{
|
||||
sb_t *sb = (sb_t *) p;
|
||||
@@ -1013,7 +1021,7 @@ sb_ct1745_mixer_read(uint16_t addr, void *p)
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
void
|
||||
sb_ct1745_mixer_reset(sb_t* sb)
|
||||
{
|
||||
sb_ct1745_mixer_write(4, 0, sb);
|
||||
@@ -1655,6 +1663,7 @@ sb_16_init(const device_t *info)
|
||||
}
|
||||
|
||||
sb->mixer_enabled = 1;
|
||||
sb->mixer_sb16.output_filter = 1;
|
||||
io_sethandler(addr + 4, 0x0002, sb_ct1745_mixer_read, NULL, NULL,
|
||||
sb_ct1745_mixer_write, NULL, NULL, sb);
|
||||
sound_add_handler(sb_get_buffer_sb16_awe32, sb);
|
||||
@@ -1688,6 +1697,7 @@ sb_16_pnp_init(const device_t *info)
|
||||
sb_ct1745_mixer_reset(sb);
|
||||
|
||||
sb->mixer_enabled = 1;
|
||||
sb->mixer_sb16.output_filter = 1;
|
||||
sound_add_handler(sb_get_buffer_sb16_awe32, sb);
|
||||
sound_set_cd_audio_filter(sb16_awe32_filter_cd_audio, sb);
|
||||
|
||||
@@ -1709,6 +1719,29 @@ sb_16_pnp_init(const device_t *info)
|
||||
}
|
||||
|
||||
|
||||
static void *
|
||||
sb_16_compat_init(const device_t *info)
|
||||
{
|
||||
sb_t *sb = malloc(sizeof(sb_t));
|
||||
memset(sb, 0, sizeof(sb_t));
|
||||
|
||||
opl3_init(&sb->opl);
|
||||
|
||||
sb_dsp_init(&sb->dsp, SB16, SB_SUBTYPE_DEFAULT, sb);
|
||||
sb_ct1745_mixer_reset(sb);
|
||||
|
||||
sb->mixer_enabled = 1;
|
||||
sound_add_handler(sb_get_buffer_sb16_awe32, sb);
|
||||
|
||||
sb->mpu = (mpu_t *) malloc(sizeof(mpu_t));
|
||||
memset(sb->mpu, 0, sizeof(mpu_t));
|
||||
mpu401_init(sb->mpu, 0, 0, M_UART, 1);
|
||||
sb_dsp_set_mpu(&sb->dsp, sb->mpu);
|
||||
|
||||
return sb;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
sb_awe32_available()
|
||||
{
|
||||
@@ -1783,6 +1816,7 @@ sb_awe32_init(const device_t *info)
|
||||
}
|
||||
|
||||
sb->mixer_enabled = 1;
|
||||
sb->mixer_sb16.output_filter = 1;
|
||||
io_sethandler(addr + 4, 0x0002, sb_ct1745_mixer_read, NULL, NULL,
|
||||
sb_ct1745_mixer_write, NULL, NULL, sb);
|
||||
sound_add_handler(sb_get_buffer_sb16_awe32, sb);
|
||||
@@ -1820,6 +1854,7 @@ sb_awe32_pnp_init(const device_t *info)
|
||||
sb_ct1745_mixer_reset(sb);
|
||||
|
||||
sb->mixer_enabled = 1;
|
||||
sb->mixer_sb16.output_filter = 1;
|
||||
sound_add_handler(sb_get_buffer_sb16_awe32, sb);
|
||||
sound_set_cd_audio_filter(sb16_awe32_filter_cd_audio, sb);
|
||||
|
||||
@@ -2870,6 +2905,18 @@ const device_t sb_16_pnp_device =
|
||||
sb_16_pnp_config
|
||||
};
|
||||
|
||||
const device_t sb_16_compat_device =
|
||||
{
|
||||
"Sound Blaster 16 (Compatibility)",
|
||||
"sb16_compat",
|
||||
DEVICE_ISA | DEVICE_AT,
|
||||
0,
|
||||
sb_16_compat_init, sb_close, NULL, { NULL },
|
||||
sb_speed_changed,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
const device_t sb_32_pnp_device =
|
||||
{
|
||||
"Sound Blaster 32 PnP",
|
||||
|
@@ -133,6 +133,8 @@ static const SOUND_CARD sound_cards[] =
|
||||
{ &ncr_business_audio_device },
|
||||
{ &sb_mcv_device },
|
||||
{ &sb_pro_mcv_device },
|
||||
{ &cmi8338_device },
|
||||
{ &cmi8738_device },
|
||||
{ &es1371_device },
|
||||
{ &ad1881_device },
|
||||
{ &cs4297a_device },
|
||||
|
@@ -657,7 +657,7 @@ SNDOBJ := sound.o \
|
||||
snd_lpt_dac.o snd_lpt_dss.o \
|
||||
snd_adlib.o snd_adlibgold.o snd_ad1848.o snd_audiopci.o \
|
||||
snd_ac97_codec.o snd_ac97_via.o \
|
||||
snd_azt2316a.o snd_cs423x.o \
|
||||
snd_azt2316a.o snd_cs423x.o snd_cmi8x38.o \
|
||||
snd_cms.o \
|
||||
snd_gus.o \
|
||||
snd_sb.o snd_sb_dsp.o \
|
||||
|
Reference in New Issue
Block a user