VIA AC97: Big rework, with proper sharing of audio/modem registers (again) and codec buses

This commit is contained in:
RichardG867
2025-11-15 23:37:27 -03:00
parent 571855319e
commit fd4d8dc8a0
3 changed files with 210 additions and 172 deletions

View File

@@ -987,7 +987,7 @@ pipc_read(int func, int addr, void *priv)
}
} else if ((func <= (pm_func + 2)) && !(dev->pci_isa_regs[0x85] & ((func == (pm_func + 1)) ? 0x04 : 0x08))) { /* AC97 / MC97 */
if (addr == 0x40)
ret = ac97_via_read_status(dev->ac97, func - pm_func - 1);
ret = ac97_via_read_status(dev->ac97);
else
ret = dev->ac97_regs[func - pm_func - 1][addr];
}
@@ -1583,7 +1583,7 @@ pipc_write(int func, int addr, uint8_t val, void *priv)
case 0x41:
dev->ac97_regs[func][addr] = val;
ac97_via_write_control(dev->ac97, func, val);
ac97_via_write_control(dev->ac97, val);
break;
case 0x42:

View File

@@ -125,8 +125,8 @@ extern uint32_t ac97_codec_getrate(void *priv, uint8_t reg);
extern const device_t *ac97_codec_get(uint32_t id);
extern void ac97_via_set_slot(void *priv, int slot, int irq_pin);
extern uint8_t ac97_via_read_status(void *priv, uint8_t modem);
extern void ac97_via_write_control(void *priv, uint8_t modem, uint8_t val);
extern uint8_t ac97_via_read_status(void *priv);
extern void ac97_via_write_control(void *priv, uint8_t val);
extern void ac97_via_remap_audio_sgd(void *priv, uint16_t new_io_base, uint8_t enable);
extern void ac97_via_remap_modem_sgd(void *priv, uint16_t new_io_base, uint8_t enable);
extern void ac97_via_remap_audio_codec(void *priv, uint16_t new_io_base, uint8_t enable);

View File

@@ -10,7 +10,7 @@
*
* Authors: RichardG, <richardg867@gmail.com>
*
* Copyright 2021 RichardG.
* Copyright 2021-2025 RichardG.
*/
#include <stdarg.h>
#include <stdint.h>
@@ -35,8 +35,6 @@
typedef struct ac97_via_sgd_t {
uint8_t id;
uint8_t always_run;
uint8_t modem;
uint8_t pad;
struct _ac97_via_ *dev;
uint32_t entry_ptr;
@@ -65,22 +63,17 @@ typedef struct _ac97_via_ {
uint16_t audio_codec_base;
uint16_t modem_sgd_base;
uint16_t modem_codec_base;
uint8_t sgd_regs[2][256];
uint8_t sgd_regs[256];
uint8_t pcm_enabled : 1;
uint8_t fm_enabled : 1;
uint8_t vsr_enabled : 1;
struct {
union {
uint8_t regs_codec[2][128];
uint8_t regs_linear[256];
};
} codec_shadow[2];
uint8_t pci_slot;
uint8_t irq_state;
int irq_pin;
uint8_t codec_shadow[256];
uint8_t pci_slot;
uint8_t irq_state;
int irq_pin;
ac97_codec_t *codec[2][2];
ac97_via_sgd_t sgd[2][6];
ac97_codec_t *codec[2];
ac97_via_sgd_t sgd[6];
int master_vol_l;
int master_vol_r;
@@ -107,7 +100,7 @@ ac97_via_log(const char *fmt, ...)
#endif
static void ac97_via_sgd_process(void *priv);
static void ac97_via_update_codec(ac97_via_t *dev, int modem);
static void ac97_via_update_codec(ac97_via_t *dev);
static void ac97_via_speed_changed(void *priv);
static void ac97_via_filter_cd_audio(int channel, double *buffer, void *priv);
@@ -118,73 +111,70 @@ ac97_via_set_slot(void *priv, int slot, int irq_pin)
ac97_via_log("AC97 VIA: set_slot(%d, %d)\n", slot, irq_pin);
dev->pci_slot = slot;
dev->irq_pin = irq_pin;
dev->pci_slot = slot;
dev->irq_pin = irq_pin;
}
uint8_t
ac97_via_read_status(void *priv, uint8_t modem)
ac97_via_read_status(void *priv)
{
const ac97_via_t *dev = (ac97_via_t *) priv;
uint8_t ret = 0x00;
/* Flag each codec as ready if present. */
for (uint8_t i = 0; i <= 1; i++) {
if (dev->codec[modem][i])
for (uint8_t i = 0; i < (sizeof(dev->codec) / sizeof(dev->codec[0])); i++) {
if (dev->codec[i])
ret |= 0x01 << (i << 1);
}
ac97_via_log("AC97 VIA %d: read_status() = %02X\n", modem, ret);
ac97_via_log("AC97 VIA: read_status() = %02X\n", ret);
return ret;
}
void
ac97_via_write_control(void *priv, uint8_t modem, uint8_t val)
ac97_via_write_control(void *priv, uint8_t val)
{
ac97_via_t *dev = (ac97_via_t *) priv;
uint8_t i;
ac97_via_log("AC97 VIA %d: write_control(%02X)\n", modem, val);
ac97_via_log("AC97 VIA: write_control(%02X)\n", val);
/* Reset codecs if requested. */
if (!(val & 0x40)) {
for (i = 0; i <= 1; i++) {
if (dev->codec[modem][i])
ac97_codec_reset(dev->codec[modem][i]);
for (i = 0; i < (sizeof(dev->codec) / sizeof(dev->codec[0])); i++) {
if (dev->codec[i])
ac97_codec_reset(dev->codec[i]);
}
}
if (!modem) {
/* Set the variable sample rate flag. */
dev->vsr_enabled = (val & 0xf8) == 0xc8;
/* Set the variable sample rate flag. */
dev->vsr_enabled = (val & 0xf8) == 0xc8;
/* Start or stop PCM playback. */
i = (val & 0xf4) == 0xc4;
if (i && !dev->pcm_enabled)
timer_advance_u64(&dev->sgd[0][0].poll_timer, dev->sgd[0][0].timer_latch);
dev->pcm_enabled = i;
/* Start or stop PCM playback. */
i = (val & 0xf4) == 0xc4;
if (i && !dev->pcm_enabled)
timer_advance_u64(&dev->sgd[0].poll_timer, dev->sgd[0].timer_latch);
dev->pcm_enabled = i;
/* Start or stop FM playback. */
i = (val & 0xf2) == 0xc2;
if (i && !dev->fm_enabled)
timer_advance_u64(&dev->sgd[0][2].poll_timer, dev->sgd[0][2].timer_latch);
dev->fm_enabled = i;
/* Start or stop FM playback. */
i = (val & 0xf2) == 0xc2;
if (i && !dev->fm_enabled)
timer_advance_u64(&dev->sgd[2].poll_timer, dev->sgd[2].timer_latch);
dev->fm_enabled = i;
/* Update primary audio codec state. */
if (dev->codec[0][0])
ac97_via_update_codec(dev, 0);
}
/* Update audio codec state. */
ac97_via_update_codec(dev);
}
static void
ac97_via_update_irqs(ac97_via_t *dev, int modem)
ac97_via_update_irqs(ac97_via_t *dev)
{
/* Check interrupt flags in all SGDs. */
for (uint8_t i = 0x00; i < ((sizeof(dev->sgd) / sizeof(dev->sgd[0])) << 4); i += 0x10) {
/* Stop immediately if any flag is set. Doing it this way optimizes
rising edges for the playback SGD (0 - first to be checked). */
if (dev->sgd_regs[modem][i] & (dev->sgd_regs[modem][i | 0x2] & 0x03)) {
if (dev->sgd_regs[i] & (dev->sgd_regs[i | 0x2] & 0x03)) {
pci_set_irq(dev->pci_slot, dev->irq_pin, &dev->irq_state);
return;
}
@@ -194,28 +184,26 @@ ac97_via_update_irqs(ac97_via_t *dev, int modem)
}
static void
ac97_via_update_codec(ac97_via_t *dev, int modem)
ac97_via_update_codec(ac97_via_t *dev)
{
/* Get primary audio codec. */
ac97_codec_t *codec = dev->codec[modem][0];
/* Update volumes according to codec registers. */
ac97_codec_t *codec = dev->codec[0]; /* assume primary codec */
ac97_codec_getattn(codec, 0x02, &dev->master_vol_l, &dev->master_vol_r);
ac97_codec_getattn(codec, 0x18, &dev->sgd[modem][0].vol_l, &dev->sgd[modem][0].vol_r);
ac97_codec_getattn(codec, 0x18, &dev->sgd[modem][2].vol_l, &dev->sgd[modem][2].vol_r); /* VIAFMTSR sets Master, CD and PCM volumes to 0 dB */
ac97_codec_getattn(codec, 0x18, &dev->sgd[0].vol_l, &dev->sgd[0].vol_r);
ac97_codec_getattn(codec, 0x18, &dev->sgd[2].vol_l, &dev->sgd[2].vol_r); /* VIAFMTSR sets Master, CD and PCM volumes to 0 dB */
ac97_codec_getattn(codec, 0x12, &dev->cd_vol_l, &dev->cd_vol_r);
/* Update sample rate according to codec registers and the variable sample rate flag. */
ac97_via_speed_changed(dev);
}
uint8_t
static uint8_t
ac97_via_sgd_read(uint16_t addr, void *priv)
{
const ac97_via_t *dev = (ac97_via_t *) priv;
// #ifdef ENABLE_AC97_VIA_LOG
uint8_t modem = (addr & 0xff00) == dev->modem_sgd_base;
// #endif
#ifdef ENABLE_AC97_VIA_LOG
uint8_t modem = (addr & 0xff00) == dev->modem_sgd_base;
#endif
addr &= 0xff;
uint8_t ret;
@@ -223,78 +211,78 @@ ac97_via_sgd_read(uint16_t addr, void *priv)
/* Process SGD channel registers. */
switch (addr & 0xf) {
case 0x4:
ret = dev->sgd[modem][addr >> 4].entry_ptr;
ret = dev->sgd[addr >> 4].entry_ptr;
break;
case 0x5:
ret = dev->sgd[modem][addr >> 4].entry_ptr >> 8;
ret = dev->sgd[addr >> 4].entry_ptr >> 8;
break;
case 0x6:
ret = dev->sgd[modem][addr >> 4].entry_ptr >> 16;
ret = dev->sgd[addr >> 4].entry_ptr >> 16;
break;
case 0x7:
ret = dev->sgd[modem][addr >> 4].entry_ptr >> 24;
ret = dev->sgd[addr >> 4].entry_ptr >> 24;
break;
case 0xc:
ret = dev->sgd[modem][addr >> 4].sample_count;
ret = dev->sgd[addr >> 4].sample_count;
break;
case 0xd:
ret = dev->sgd[modem][addr >> 4].sample_count >> 8;
ret = dev->sgd[addr >> 4].sample_count >> 8;
break;
case 0xe:
ret = dev->sgd[modem][addr >> 4].sample_count >> 16;
ret = dev->sgd[addr >> 4].sample_count >> 16;
break;
default:
ret = dev->sgd_regs[modem][addr];
ret = dev->sgd_regs[addr];
break;
}
} else {
/* Process regular registers. */
switch (addr) {
case 0x84:
ret = (dev->sgd_regs[modem][0x00] & 0x01);
ret |= (dev->sgd_regs[modem][0x10] & 0x01) << 1;
ret |= (dev->sgd_regs[modem][0x20] & 0x01) << 2;
ret = (dev->sgd_regs[0x00] & 0x01);
ret |= (dev->sgd_regs[0x10] & 0x01) << 1;
ret |= (dev->sgd_regs[0x20] & 0x01) << 2;
ret |= (dev->sgd_regs[modem][0x00] & 0x02) << 3;
ret |= (dev->sgd_regs[modem][0x10] & 0x02) << 4;
ret |= (dev->sgd_regs[modem][0x20] & 0x02) << 5;
ret |= (dev->sgd_regs[0x00] & 0x02) << 3;
ret |= (dev->sgd_regs[0x10] & 0x02) << 4;
ret |= (dev->sgd_regs[0x20] & 0x02) << 5;
break;
case 0x85:
ret = (dev->sgd_regs[modem][0x00] & 0x04) >> 2;
ret |= (dev->sgd_regs[modem][0x10] & 0x04) >> 1;
ret |= (dev->sgd_regs[modem][0x20] & 0x04);
ret = (dev->sgd_regs[0x00] & 0x04) >> 2;
ret |= (dev->sgd_regs[0x10] & 0x04) >> 1;
ret |= (dev->sgd_regs[0x20] & 0x04);
ret |= (dev->sgd_regs[modem][0x00] & 0x80) >> 3;
ret |= (dev->sgd_regs[modem][0x10] & 0x80) >> 2;
ret |= (dev->sgd_regs[modem][0x20] & 0x80) >> 1;
ret |= (dev->sgd_regs[0x00] & 0x80) >> 3;
ret |= (dev->sgd_regs[0x10] & 0x80) >> 2;
ret |= (dev->sgd_regs[0x20] & 0x80) >> 1;
break;
case 0x86:
ret = (dev->sgd_regs[modem][0x40] & 0x01);
ret |= (dev->sgd_regs[modem][0x50] & 0x01) << 1;
ret = (dev->sgd_regs[0x40] & 0x01);
ret |= (dev->sgd_regs[0x50] & 0x01) << 1;
ret |= (dev->sgd_regs[modem][0x40] & 0x02) << 3;
ret |= (dev->sgd_regs[modem][0x50] & 0x02) << 4;
ret |= (dev->sgd_regs[0x40] & 0x02) << 3;
ret |= (dev->sgd_regs[0x50] & 0x02) << 4;
break;
case 0x87:
ret = (dev->sgd_regs[modem][0x40] & 0x04) >> 2;
ret |= (dev->sgd_regs[modem][0x50] & 0x04) >> 1;
ret = (dev->sgd_regs[0x40] & 0x04) >> 2;
ret |= (dev->sgd_regs[0x50] & 0x04) >> 1;
ret |= (dev->sgd_regs[modem][0x40] & 0x80) >> 3;
ret |= (dev->sgd_regs[modem][0x50] & 0x80) >> 2;
ret |= (dev->sgd_regs[0x40] & 0x80) >> 3;
ret |= (dev->sgd_regs[0x50] & 0x80) >> 2;
break;
default:
ret = dev->sgd_regs[modem][addr];
ret = dev->sgd_regs[addr];
break;
}
}
@@ -304,7 +292,7 @@ ac97_via_sgd_read(uint16_t addr, void *priv)
return ret;
}
void
static void
ac97_via_sgd_write(uint16_t addr, uint8_t val, void *priv)
{
ac97_via_t *dev = (ac97_via_t *) priv;
@@ -328,42 +316,42 @@ ac97_via_sgd_write(uint16_t addr, uint8_t val, void *priv)
switch (addr & 0xf) {
case 0x0:
/* Clear RWC status bits. */
dev->sgd_regs[modem][addr] &= ~(val & 0x07);
dev->sgd_regs[addr] &= ~(val & 0x07);
/* Update status interrupts. */
ac97_via_update_irqs(dev, modem);
ac97_via_update_irqs(dev);
return;
case 0x1:
/* Start SGD if requested. */
if (val & 0x80) {
if (dev->sgd_regs[modem][addr & 0xf0] & 0x80) {
if (dev->sgd_regs[addr & 0xf0] & 0x80) {
/* Queue SGD trigger if already running. */
dev->sgd_regs[modem][addr & 0xf0] |= 0x08;
dev->sgd_regs[addr & 0xf0] |= 0x08;
} else {
/* Start SGD immediately. */
dev->sgd_regs[modem][addr & 0xf0] = (dev->sgd_regs[modem][addr & 0xf0] & ~0x47) | 0x80;
dev->sgd_regs[addr & 0xf0] = (dev->sgd_regs[addr & 0xf0] & ~0x47) | 0x80;
/* Start at the specified entry pointer. */
dev->sgd[modem][addr >> 4].entry_ptr = AS_U32(dev->sgd_regs[modem][(addr & 0xf0) | 0x4]) & 0xfffffffe;
dev->sgd[modem][addr >> 4].restart = 2;
dev->sgd[addr >> 4].entry_ptr = AS_U32(dev->sgd_regs[(addr & 0xf0) | 0x4]) & 0xfffffffe;
dev->sgd[addr >> 4].restart = 2;
/* Start the actual SGD process. */
ac97_via_sgd_process(&dev->sgd[modem][addr >> 4]);
ac97_via_sgd_process(&dev->sgd[addr >> 4]);
}
}
/* Stop SGD if requested. */
if (val & 0x40)
dev->sgd_regs[modem][addr & 0xf0] &= ~0x88;
dev->sgd_regs[addr & 0xf0] &= ~0x88;
val &= 0x08;
/* (Un)pause SGD if requested. */
if (val & 0x08)
dev->sgd_regs[modem][addr & 0xf0] |= 0x40;
dev->sgd_regs[addr & 0xf0] |= 0x40;
else
dev->sgd_regs[modem][addr & 0xf0] &= ~0x40;
dev->sgd_regs[addr & 0xf0] &= ~0x40;
break;
@@ -391,55 +379,95 @@ ac97_via_sgd_write(uint16_t addr, uint8_t val, void *priv)
case 0x82:
/* Determine the selected codec. */
i = !!(dev->sgd_regs[modem][0x83] & 0x40);
codec = dev->codec[modem][i];
i = !!(dev->sgd_regs[0x83] & 0x40);
codec = dev->codec[i];
/* Keep value in register if this codec is not present. */
if (codec) {
/* Read from or write to codec. */
if (val & 0x80) {
if (val & 1) { /* return 0x0000 on unaligned reads (real 686B behavior) */
dev->sgd_regs[modem][0x80] = dev->sgd_regs[modem][0x81] = 0x00;
} else {
AS_U16(dev->codec_shadow[modem].regs_codec[i][val & 0x7f]) = AS_U16(dev->sgd_regs[modem][0x80]) = ac97_codec_readw(codec, val);
}
if (val & 1) /* return 0x0000 on unaligned reads (real 686B behavior) */
AS_U16(dev->sgd_regs[0x80]) = 0x0000;
else
AS_U16(dev->codec_shadow[(i << 7) | (val & 0x7f)]) = AS_U16(dev->sgd_regs[0x80]) = ac97_codec_readw(codec, val);
/* Flag data/status/index for this codec as valid. */
dev->sgd_regs[modem][0x83] |= 0x02 << (i << 1);
dev->sgd_regs[0x83] |= 0x02 << (i << 1);
} else if (!(val & 1)) { /* do nothing on unaligned writes */
ac97_codec_writew(codec, val,
AS_U16(dev->codec_shadow[modem].regs_codec[i][val & 0x7f]) = AS_U16(dev->sgd_regs[modem][0x80]));
AS_U16(dev->codec_shadow[(i << 7) | val]) = AS_U16(dev->sgd_regs[0x80]));
/* Update primary audio codec state if that codec was written to. */
if (!modem && !i) {
ac97_via_update_codec(dev, 0);
/* Update audio codec state. */
ac97_via_update_codec(dev);
/* Set up CD audio filter if CD volume was written to. Setting it
up at init prevents CD audio from working on other cards, but
this works as the CD channel is muted by default per AC97 spec. */
if (val == 0x12)
sound_set_cd_audio_filter(ac97_via_filter_cd_audio, dev);
}
/* Set up CD audio filter if CD volume was written to. Setting it
up at init prevents CD audio from working on other cards, but
this works as the CD channel is muted by default per AC97 spec. */
if (!i && (val == 0x12))
sound_set_cd_audio_filter(ac97_via_filter_cd_audio, dev);
}
}
break;
case 0x83:
/* Clear RWC status bits. */
#if 0 /* race condition with Linux accessing a register and clearing status bits on the same dword write */
val = ((dev->sgd_regs[modem][addr] & 0x3f) & ~(val & 0x0a)) | (val & 0xc0);
#else
val = (dev->sgd_regs[modem][addr] & 0x3f) | (val & 0xc0);
#endif
val = ((dev->sgd_regs[addr] & 0x3f) & ~(val & 0x0a)) | (val & 0xc0);
break;
case 0x88 ... 0x89:
dev->sgd_regs[addr] = val;
/* Send GPO to codec. */
/*for (uint8_t i = 0; i < (sizeof(dev->codec) / sizeof(dev->codec[0])); i++) {
if (dev->codec[i])
ac97_codec_setgpo(dev->codec[i], AS_U16(dev->sgd_regs[0x88]));
}*/
return;
case 0x8a ... 0x8b:
/* Clear RWC status bits. */
val = dev->sgd_regs[addr] & ~val;
break;
case 0x8c ... 0x8d:
return;
default:
break;
}
}
dev->sgd_regs[modem][addr] = val;
dev->sgd_regs[addr] = val;
}
static void
ac97_via_sgd_writew(uint16_t addr, uint16_t val, void *priv)
{
if ((addr & 0xfe) == 0x82) {
/* Invert order on writes to 82-83 to ensure the correct codec ID is set and
any status bits are cleared before performing the codec register operation. */
ac97_via_sgd_write(addr + 1, val >> 8, priv);
ac97_via_sgd_write(addr, val & 0xff, priv);
} else {
ac97_via_sgd_write(addr, val & 0xff, priv);
ac97_via_sgd_write(addr + 1, val >> 8, priv);
}
}
static void
ac97_via_sgd_writel(uint16_t addr, uint32_t val, void *priv)
{
ac97_via_sgd_write(addr, val & 0xff, priv);
ac97_via_sgd_write(addr + 1, val >> 8, priv);
if ((addr & 0xfc) == 0x80) {
/* Invert order on writes to 82-83 to ensure the correct codec ID is set and
any status bits are cleared before performing the codec register operation. */
ac97_via_sgd_write(addr + 3, val >> 24, priv);
ac97_via_sgd_write(addr + 2, val >> 16, priv);
} else {
ac97_via_sgd_write(addr + 2, val >> 16, priv);
ac97_via_sgd_write(addr + 3, val >> 24, priv);
}
}
void
@@ -448,12 +476,12 @@ ac97_via_remap_audio_sgd(void *priv, uint16_t new_io_base, uint8_t enable)
ac97_via_t *dev = (ac97_via_t *) priv;
if (dev->audio_sgd_base)
io_removehandler(dev->audio_sgd_base, 256, ac97_via_sgd_read, NULL, NULL, ac97_via_sgd_write, NULL, NULL, dev);
io_removehandler(dev->audio_sgd_base, 256, ac97_via_sgd_read, NULL, NULL, ac97_via_sgd_write, ac97_via_sgd_writew, ac97_via_sgd_writel, dev);
dev->audio_sgd_base = new_io_base;
if (dev->audio_sgd_base && enable)
io_sethandler(dev->audio_sgd_base, 256, ac97_via_sgd_read, NULL, NULL, ac97_via_sgd_write, NULL, NULL, dev);
io_sethandler(dev->audio_sgd_base, 256, ac97_via_sgd_read, NULL, NULL, ac97_via_sgd_write, ac97_via_sgd_writew, ac97_via_sgd_writel, dev);
}
void
@@ -462,24 +490,26 @@ ac97_via_remap_modem_sgd(void *priv, uint16_t new_io_base, uint8_t enable)
ac97_via_t *dev = (ac97_via_t *) priv;
if (dev->modem_sgd_base)
io_removehandler(dev->modem_sgd_base, 256, ac97_via_sgd_read, NULL, NULL, ac97_via_sgd_write, NULL, NULL, dev);
io_removehandler(dev->modem_sgd_base, 256, ac97_via_sgd_read, NULL, NULL, ac97_via_sgd_write, ac97_via_sgd_writew, ac97_via_sgd_writel, dev);
dev->modem_sgd_base = new_io_base;
if (dev->modem_sgd_base && enable)
io_sethandler(dev->modem_sgd_base, 256, ac97_via_sgd_read, NULL, NULL, ac97_via_sgd_write, NULL, NULL, dev);
io_sethandler(dev->modem_sgd_base, 256, ac97_via_sgd_read, NULL, NULL, ac97_via_sgd_write, ac97_via_sgd_writew, ac97_via_sgd_writel, dev);
}
uint8_t
ac97_via_codec_read(uint16_t addr, void *priv)
{
const ac97_via_t *dev = (ac97_via_t *) priv;
#ifdef ENABLE_AC97_VIA_LOG
uint8_t modem = (addr & 0xff00) == dev->modem_codec_base;
#endif
uint8_t ret = 0xff;
addr &= 0xff;
ret = dev->codec_shadow[modem].regs_linear[addr];
ret = dev->codec_shadow[addr];
ac97_via_log("AC97 VIA %d: codec_read(%02X) = %02X\n", modem, addr, ret);
@@ -492,7 +522,9 @@ void
ac97_via_codec_write(uint16_t addr, uint8_t val, void *priv)
{
ac97_via_t *dev = (ac97_via_t *) priv;
#ifdef ENABLE_AC97_VIA_LOG
uint8_t modem = (addr & 0xff00) == dev->modem_codec_base;
#endif
addr &= 0xff;
ac97_via_log("[%04X:%08X] [%i] AC97 VIA %d: codec_write(%02X, %02X)\n", CS, cpu_state.pc, msw & 1, modem, addr, val);
@@ -500,7 +532,7 @@ ac97_via_codec_write(uint16_t addr, uint8_t val, void *priv)
ac97_via_log("AC97 VIA %d: codec_write(%02X, %02X)\n", modem, addr, val);
/* Unknown behavior, maybe it does write to the shadow registers? */
dev->codec_shadow[modem].regs_linear[addr] = val;
dev->codec_shadow[addr] = val;
}
void
@@ -564,7 +596,7 @@ ac97_via_sgd_process(void *priv)
ac97_via_t *dev = sgd->dev;
/* Stop if this SGD is not active. */
uint8_t sgd_status = dev->sgd_regs[sgd->modem][sgd->id] & 0xc4;
uint8_t sgd_status = dev->sgd_regs[sgd->id] & 0xc4;
if (!(sgd_status & 0x80))
return;
@@ -577,7 +609,7 @@ ac97_via_sgd_process(void *priv)
if (sgd->restart) {
/* (Re)load entry pointer if required. */
if (sgd->restart & 2)
sgd->entry_ptr = AS_U32(dev->sgd_regs[sgd->modem][sgd->id | 0x4]) & 0xfffffffe; /* TODO: probe real hardware - does "even addr" actually mean dword aligned? */
sgd->entry_ptr = AS_U32(dev->sgd_regs[sgd->id | 0x4]) & 0xfffffffe; /* TODO: probe real hardware - does "even addr" actually mean dword aligned? */
sgd->restart = 0;
/* Read entry. */
@@ -625,17 +657,17 @@ ac97_via_sgd_process(void *priv)
ac97_via_log(" with STOP");
/* Raise STOP to pause SGD. */
dev->sgd_regs[sgd->modem][sgd->id] |= 0x04;
dev->sgd_regs[sgd->id] |= 0x04;
}
if (sgd->entry_flags & 0x40) {
ac97_via_log(" with FLAG");
/* Raise FLAG to pause SGD. */
dev->sgd_regs[sgd->modem][sgd->id] |= 0x01;
dev->sgd_regs[sgd->id] |= 0x01;
#ifdef ENABLE_AC97_VIA_LOG
if (dev->sgd_regs[sgd->modem][sgd->id | 0x2] & 0x01)
if (dev->sgd_regs[sgd->id | 0x2] & 0x01)
ac97_via_log(" interrupt");
#endif
}
@@ -644,19 +676,19 @@ ac97_via_sgd_process(void *priv)
ac97_via_log(" with EOL");
/* Raise EOL. */
dev->sgd_regs[sgd->modem][sgd->id] |= 0x02;
dev->sgd_regs[sgd->id] |= 0x02;
#ifdef ENABLE_AC97_VIA_LOG
if (dev->sgd_regs[sgd->modem][sgd->id | 0x2] & 0x02)
if (dev->sgd_regs[sgd->id | 0x2] & 0x02)
ac97_via_log(" interrupt");
#endif
/* Restart SGD if a trigger is queued or auto-start is enabled. */
if ((dev->sgd_regs[sgd->modem][sgd->id] & 0x08) || (dev->sgd_regs[sgd->modem][sgd->id | 0x2] & 0x80)) {
if ((dev->sgd_regs[sgd->id] & 0x08) || (dev->sgd_regs[sgd->id | 0x2] & 0x80)) {
ac97_via_log(" restart");
/* Un-queue trigger. */
dev->sgd_regs[sgd->modem][sgd->id] &= ~0x08;
dev->sgd_regs[sgd->id] &= ~0x08;
/* Go back to the starting block on the next run. */
sgd->restart = 2;
@@ -664,13 +696,13 @@ ac97_via_sgd_process(void *priv)
ac97_via_log(" finish");
/* Terminate SGD. */
dev->sgd_regs[sgd->modem][sgd->id] &= ~0x80;
dev->sgd_regs[sgd->id] &= ~0x80;
}
}
ac97_via_log("\n");
/* Fire any requested status interrupts. */
ac97_via_update_irqs(dev, sgd->modem);
ac97_via_update_irqs(dev);
}
}
}
@@ -678,8 +710,8 @@ ac97_via_sgd_process(void *priv)
static void
ac97_via_poll_stereo(void *priv)
{
ac97_via_t *dev = (ac97_via_t *) priv;
ac97_via_sgd_t *sgd = &dev->sgd[0][0]; /* Audio Read */
ac97_via_sgd_t *sgd = (ac97_via_sgd_t *) priv;
ac97_via_t *dev = sgd->dev;
/* Schedule next run if PCM playback is enabled. */
if (dev->pcm_enabled)
@@ -689,7 +721,7 @@ ac97_via_poll_stereo(void *priv)
ac97_via_update_stereo(dev, sgd);
/* Feed next sample from the FIFO. */
switch (dev->sgd_regs[0][sgd->id | 0x2] & 0x30) {
switch (dev->sgd_regs[sgd->id | 0x2] & 0x30) {
case 0x00: /* Mono, 8-bit PCM */
if ((sgd->fifo_end - sgd->fifo_pos) >= 1) {
sgd->out_l = sgd->out_r = (sgd->fifo[sgd->fifo_pos++ & (sizeof(sgd->fifo) - 1)] ^ 0x80) << 8;
@@ -734,8 +766,8 @@ ac97_via_poll_stereo(void *priv)
static void
ac97_via_poll_fm(void *priv)
{
ac97_via_t *dev = (ac97_via_t *) priv;
ac97_via_sgd_t *sgd = &dev->sgd[0][2]; /* FM Read */
ac97_via_sgd_t *sgd = (ac97_via_sgd_t *) priv;
ac97_via_t *dev = sgd->dev;
/* Schedule next run if FM playback is enabled. */
if (dev->fm_enabled)
@@ -763,15 +795,17 @@ ac97_via_get_buffer(int32_t *buffer, int len, void *priv)
{
ac97_via_t *dev = (ac97_via_t *) priv;
ac97_via_update_stereo(dev, &dev->sgd[0][0]);
ac97_via_update_stereo(dev, &dev->sgd[0][2]);
ac97_via_update_stereo(dev, &dev->sgd[0]);
ac97_via_update_stereo(dev, &dev->sgd[2]);
ac97_via_update_stereo(dev, &dev->sgd[4]);
for (int c = 0; c < len * 2; c++) {
buffer[c] += dev->sgd[0][0].buffer[c] / 2;
buffer[c] += dev->sgd[0][2].buffer[c] / 2;
buffer[c] += dev->sgd[0].buffer[c] / 2;
buffer[c] += dev->sgd[2].buffer[c] / 2;
buffer[c] += dev->sgd[4].buffer[c] / 2;
}
dev->sgd[0][0].pos = dev->sgd[0][2].pos = 0;
dev->sgd[0].pos = dev->sgd[2].pos = dev->sgd[4].pos = 0;
}
static void
@@ -792,13 +826,20 @@ ac97_via_speed_changed(void *priv)
double freq;
/* Get variable sample rate if enabled. */
if (dev->vsr_enabled && dev->codec[0][0])
freq = ac97_codec_getrate(dev->codec[0][0], 0x2c);
if (dev->vsr_enabled && dev->codec[0])
freq = ac97_codec_getrate(dev->codec[0], 0x2c);
else
freq = (double) SOUND_FREQ;
dev->sgd[0][0].timer_latch = (uint64_t) ((double) TIMER_USEC * (1000000.0 / freq));
dev->sgd[0][2].timer_latch = (uint64_t) ((double) TIMER_USEC * (1000000.0 / 24000.0)); /* FM operates at a fixed 24 KHz */
dev->sgd[0].timer_latch = (uint64_t) ((double) TIMER_USEC * (1000000.0 / freq));
dev->sgd[2].timer_latch = (uint64_t) ((double) TIMER_USEC * (1000000.0 / 24000.0)); /* FM operates at a fixed 24 KHz */
if (dev->codec[1])
freq = ac97_codec_getrate(dev->codec[1], 0x40);
else
freq = (double) SOUND_FREQ;
dev->sgd[4].timer_latch = dev->sgd[5].timer_latch = (uint64_t) ((double) TIMER_USEC * (1000000.0 / freq));
}
static void *
@@ -809,30 +850,27 @@ ac97_via_init(UNUSED(const device_t *info))
ac97_via_log("AC97 VIA: init()\n");
/* Set up codecs. */
ac97_codec = &dev->codec[0][0];
ac97_modem_codec = &dev->codec[1][0];
ac97_codec_count = ac97_modem_codec_count = sizeof(dev->codec[0]) / sizeof(dev->codec[0][0]);
ac97_codec = &dev->codec[0];
ac97_modem_codec = &dev->codec[1];
ac97_codec_count = sizeof(dev->codec) / sizeof(dev->codec[0]);
ac97_modem_codec_count = 1;
ac97_codec_id = ac97_modem_codec_id = 0;
/* Set up SGD channels. */
for (uint8_t i = 0; i < (sizeof(dev->sgd) / sizeof(dev->sgd[0])); i++) {
for (uint8_t j = 0; j < 2; j++) {
dev->sgd[j][i].id = i << 4;
dev->sgd[j][i].dev = dev;
dev->sgd[i].id = i << 4;
dev->sgd[i].dev = dev;
dev->sgd[j][i].modem = j;
/* Disable the FIFO on SGDs we don't care about. */
if ((i != 0) && (i != 2))
dev->sgd[i].always_run = 1;
/* Disable the FIFO on SGDs we don't care about. */
if ((i != 0) && (i != 2))
dev->sgd[j][i].always_run = 1;
timer_add(&dev->sgd[j][i].dma_timer, ac97_via_sgd_process, &dev->sgd[j][i], 0);
}
timer_add(&dev->sgd[i].dma_timer, ac97_via_sgd_process, &dev->sgd[i], 0);
}
/* Set up playback pollers. */
timer_add(&dev->sgd[0][0].poll_timer, ac97_via_poll_stereo, dev, 0);
timer_add(&dev->sgd[0][2].poll_timer, ac97_via_poll_fm, dev, 0);
timer_add(&dev->sgd[0].poll_timer, ac97_via_poll_stereo, &dev->sgd[0], 0);
timer_add(&dev->sgd[2].poll_timer, ac97_via_poll_fm, &dev->sgd[2], 0);
ac97_via_speed_changed(dev);
/* Set up playback handler. */