VIA On-board AC'97 Audio: Fix PCI registers, implement PCI Power Manager capability registers, properly separate the modem SGD from the audio SGD, and fix 5-bit volume attenuation, this also fixes audio playback in NT 4.0.

This commit is contained in:
OBattler
2025-03-31 04:35:17 +02:00
parent 512154e4a8
commit 3f0138a58c
3 changed files with 233 additions and 145 deletions

View File

@@ -493,14 +493,28 @@ pipc_reset_hard(void *priv)
}
dev->ac97_regs[i][0x1c] = 0x01;
if (i == 0) {
dev->ac97_regs[i][0x34] = 0xc0;
dev->ac97_regs[i][0xc0] = 0x01;
dev->ac97_regs[i][0xc1] = 0x00;
dev->ac97_regs[i][0xc2] = 0x03;
dev->ac97_regs[i][0xc3] = 0x00;
dev->ac97_regs[i][0xc4] = 0x00;
dev->ac97_regs[i][0xc5] = 0x00;
dev->ac97_regs[i][0xc6] = 0x00;
dev->ac97_regs[i][0xc7] = 0x00;
}
dev->ac97_regs[i][0x3d] = 0x03;
if (i == 0)
if (i == 0) {
dev->ac97_regs[i][0x40] = 0x01;
dev->ac97_regs[i][0x43] = 0x1c;
dev->ac97_regs[i][0x48] = 0x01;
dev->ac97_regs[i][0x4b] = 0x02;
dev->ac97_regs[i][0x43] = 0x1c;
dev->ac97_regs[i][0x48] = 0x01;
dev->ac97_regs[i][0x4b] = 0x00;
}
pipc_sgd_handlers(dev, i);
pipc_codec_handlers(dev, i);
@@ -742,10 +756,12 @@ pipc_codec_handlers(pipc_t *dev, uint8_t modem)
if (!dev->ac97)
return;
uint32_t base = (dev->ac97_regs[modem][0x1d] << 8);
if (modem)
ac97_via_remap_modem_codec(dev->ac97, dev->ac97_regs[1][0x1d] << 8, dev->ac97_regs[1][0x04] & PCI_COMMAND_IO);
ac97_via_remap_modem_codec(dev->ac97, base, dev->ac97_regs[1][0x04] & PCI_COMMAND_IO);
else
ac97_via_remap_audio_codec(dev->ac97, dev->ac97_regs[0][0x1d] << 8, dev->ac97_regs[0][0x04] & PCI_COMMAND_IO);
ac97_via_remap_audio_codec(dev->ac97, base, dev->ac97_regs[0][0x04] & PCI_COMMAND_IO);
}
static uint8_t
@@ -1204,7 +1220,7 @@ pipc_write(int func, int addr, uint8_t val, void *priv)
case 0x77:
if ((dev->local >= VIA_PIPC_686A) && (val & 0x10))
pclog("PIPC: Warning: Internal I/O APIC enabled.\n");
warning("PIPC: Warning: Internal I/O APIC enabled.\n");
nvr_via_wp_set(!!(val & 0x04), 0x32, dev->nvr);
nvr_via_wp_set(!!(val & 0x02), 0x0d, dev->nvr);
break;
@@ -1488,36 +1504,39 @@ pipc_write(int func, int addr, uint8_t val, void *priv)
break;
}
} else if (func <= pm_func + 2) { /* AC97 / MC97 */
/* Read-only addresses. */
if ((addr < 0x4) || ((addr >= 0x6) && (addr < 0x9)) || ((addr >= 0xc) && (addr < 0x11)) || (addr == 0x16) || (addr == 0x17) || (addr == 0x1a) || (addr == 0x1b) || ((addr >= 0x1e) && (addr < 0x2c)) || ((addr >= 0x30) && (addr < 0x34)) || ((addr >= 0x35) && (addr < 0x3c)) || ((addr >= 0x3d) && (addr < 0x41)) || ((addr >= 0x45) && (addr < 0x4a)) || (addr >= 0x4c))
return;
/* Small shortcut. */
func = func - pm_func - 1;
/* Check disable bits and specific read-only addresses for both controllers. */
if ((func == 0) && (((addr >= 0x09) && (addr < 0xc)) || (addr == 0x44) || (dev->pci_isa_regs[0x85] & 0x04)))
/* Check disable bits. */
if ((func == 0) && (dev->pci_isa_regs[0x85] & 0x04))
return;
if ((func == 1) && ((addr == 0x14) || (addr == 0x15) || (addr == 0x18) || (addr == 0x19) || (addr == 0x42) || (addr == 0x43) || (addr == 0x48) || (addr == 0x4a) || (addr == 0x4b) || (dev->pci_isa_regs[0x85] & 0x08)))
if ((func == 1) && (dev->pci_isa_regs[0x85] & 0x08))
return;
switch (addr) {
case 0x04:
dev->ac97_regs[func][addr] = val;
dev->ac97_regs[func][addr] = val & 0x01;
pipc_sgd_handlers(dev, func);
if (func == 0) {
pipc_fmnmi_handlers(dev, func);
pipc_sb_handlers(dev, func);
}
pipc_codec_handlers(dev, func);
pipc_fmnmi_handlers(dev, func);
break;
case 0x09:
case 0x0a:
case 0x0b:
if (dev->ac97_regs[func][0x44] & 0x20)
/* Not writable on audio, only on modem. */
if ((func == 1) && (dev->ac97_regs[func][0x44] & 0x20))
dev->ac97_regs[func][addr] = val;
break;
case 0x10:
/*
The lowest 10 bytes are always 0x01, indicating
a 256-byte I/O space.
*/
case 0x11:
dev->ac97_regs[func][addr] = val;
pipc_sgd_handlers(dev, func);
@@ -1525,21 +1544,26 @@ pipc_write(int func, int addr, uint8_t val, void *priv)
case 0x14:
case 0x15:
if (addr == 0x14)
val = (val & 0xfc) | 1;
dev->ac97_regs[func][addr] = val;
pipc_fmnmi_handlers(dev, func);
/* Not present on modem. */
if (func == 0) {
if (addr == 0x14)
val = (val & 0xfc) | 1;
dev->ac97_regs[func][addr] = val;
pipc_fmnmi_handlers(dev, func);
}
break;
case 0x18:
case 0x19:
if (addr == 0x18)
val = (val & 0xfc) | 1;
dev->ac97_regs[func][addr] = val;
pipc_sb_handlers(dev, func);
/* Not present on modem. */
if (func == 0) {
if (addr == 0x18)
val = (val & 0xfc) | 1;
dev->ac97_regs[func][addr] = val;
pipc_sb_handlers(dev, func);
}
break;
case 0x1c:
case 0x1d:
dev->ac97_regs[func][addr] = val;
pipc_codec_handlers(dev, func);
@@ -1549,39 +1573,84 @@ pipc_write(int func, int addr, uint8_t val, void *priv)
case 0x2d:
case 0x2e:
case 0x2f:
if ((func == 0) && (dev->ac97_regs[func][0x42] & 0x20))
if (((func == 0) && (dev->ac97_regs[func][0x42] & 0x20)) ||
((func == 1) && (dev->ac97_regs[func][0x44] & 0x10)))
dev->ac97_regs[func][addr] = val;
break;
case 0x3c:
dev->ac97_regs[func][addr] = val & 0x0f;
break;
case 0x41:
dev->ac97_regs[func][addr] = val;
ac97_via_write_control(dev->ac97, func, val);
break;
case 0x42:
case 0x4a:
case 0x4b:
dev->ac97_regs[0][addr] = dev->ac97_regs[1][addr] = val;
gameport_remap(dev->gameport, (dev->ac97_regs[0][0x42] & 0x08) ? ((dev->ac97_regs[0][0x4b] << 8) | (dev->ac97_regs[0][0x4a] & 0xf8)) : 0);
if (addr == 0x42)
pipc_sb_handlers(dev, func);
case 0x4a ... 0x4b:
if (func == 0) {
dev->ac97_regs[func][addr] = val;
gameport_remap(dev->gameport, (dev->ac97_regs[func][0x42] & 0x08) ?
((dev->ac97_regs[func][0x4b] << 8) |
(dev->ac97_regs[func][0x4a] & 0xf8)) : 0);
if (addr == 0x42)
pipc_sb_handlers(dev, func);
}
break;
case 0x43:
dev->ac97_regs[0][addr] = dev->ac97_regs[1][addr] = val;
if (func == 0)
dev->ac97_regs[func][addr] = val;
break;
case 0x44:
dev->ac97_regs[0][addr] = dev->ac97_regs[1][addr] = val & 0xf0;
if (func == 1)
dev->ac97_regs[func][addr] = val & 0xf0;
break;
case 0x45:
case 0x48:
dev->ac97_regs[0][addr] = dev->ac97_regs[1][addr] = val & 0x0f;
if (func == 0)
dev->ac97_regs[func][addr] = val & 0x0f;
break;
case 0x80:
case 0x81:
case 0x82:
dev->ac97_regs[func][addr] = val;
break;
case 0x83:
dev->ac97_regs[func][addr] = ((dev->ac97_regs[func][addr] & 0x01) |
(val & 0xc0)) & ~(val & 0x0a);
break;
case 0x88:
case 0x89:
dev->ac97_regs[func][addr] = val;
break;
case 0x8a:
case 0x8b:
dev->ac97_regs[func][addr] &= ~val;
break;
case 0x8e:
case 0x8f:
dev->ac97_regs[func][addr] = val;
break;
case 0xc4:
if (func == 0)
dev->ac97_regs[func][addr] = (dev->ac97_regs[func][addr] & 0x0c) |
(val & 0x03);
break;
case 0xc5:
if (func == 0)
dev->ac97_regs[func][addr] = (dev->ac97_regs[func][addr] & 0x60) |
(val & 0x9f);
break;
default:
dev->ac97_regs[func][addr] = val;
break;
}
}

View File

@@ -539,8 +539,9 @@ ac97_codec_getattn(void *priv, uint8_t reg, int *l, int *r)
*l = codec_attn[0x3f - (l_val & 0x3f)];
*r = codec_attn[0x3f - (r_val & 0x3f)];
} else { /* 5-bit gain */
*l = codec_attn[0x47 - (l_val & 0x1f)];
*r = codec_attn[0x47 - (r_val & 0x1f)];
/* Yes, 0x3f is correct, the 0x47 was most likely a decimal-hex mix-up. */
*l = codec_attn[0x3f - (l_val & 0x1f)];
*r = codec_attn[0x3f - (r_val & 0x1f)];
}
/* Apply per-channel mute and center/LFE powerdowns where applicable. */

View File

@@ -24,6 +24,7 @@
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/io.h>
#include <86box/dma.h>
#include <86box/mem.h>
#include <86box/pci.h>
#include <86box/pic.h>
@@ -31,10 +32,13 @@
#include <86box/sound.h>
#include <86box/timer.h>
#include <86box/plat_unused.h>
#include "cpu.h"
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;
@@ -63,7 +67,7 @@ typedef struct _ac97_via_ {
uint16_t audio_codec_base;
uint16_t modem_sgd_base;
uint16_t modem_codec_base;
uint8_t sgd_regs[256];
uint8_t sgd_regs[2][256];
uint8_t pcm_enabled : 1;
uint8_t fm_enabled : 1;
uint8_t vsr_enabled : 1;
@@ -78,7 +82,7 @@ typedef struct _ac97_via_ {
int irq_pin;
ac97_codec_t *codec[2][2];
ac97_via_sgd_t sgd[6];
ac97_via_sgd_t sgd[2][6];
int master_vol_l;
int master_vol_r;
@@ -105,7 +109,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);
static void ac97_via_update_codec(ac97_via_t *dev, int modem);
static void ac97_via_speed_changed(void *priv);
static void ac97_via_filter_cd_audio(int channel, double *buffer, void *priv);
@@ -160,29 +164,29 @@ ac97_via_write_control(void *priv, uint8_t modem, uint8_t val)
/* 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);
timer_advance_u64(&dev->sgd[0][0].poll_timer, dev->sgd[0][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[2].poll_timer, dev->sgd[2].timer_latch);
timer_advance_u64(&dev->sgd[0][2].poll_timer, dev->sgd[0][2].timer_latch);
dev->fm_enabled = i;
/* Update primary audio codec state. */
if (dev->codec[0][0])
ac97_via_update_codec(dev);
ac97_via_update_codec(dev, 0);
}
}
static void
ac97_via_update_irqs(ac97_via_t *dev)
ac97_via_update_irqs(ac97_via_t *dev, int modem)
{
/* 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[i] & (dev->sgd_regs[i | 0x2] & 0x03)) {
if (dev->sgd_regs[modem][i] & (dev->sgd_regs[modem][i | 0x2] & 0x03)) {
pci_set_irq(dev->pci_slot, dev->irq_pin, &dev->irq_state);
return;
}
@@ -192,15 +196,15 @@ ac97_via_update_irqs(ac97_via_t *dev)
}
static void
ac97_via_update_codec(ac97_via_t *dev)
ac97_via_update_codec(ac97_via_t *dev, int modem)
{
/* Get primary audio codec. */
ac97_codec_t *codec = dev->codec[0][0];
ac97_codec_t *codec = dev->codec[modem][0];
/* Update volumes according to codec registers. */
ac97_codec_getattn(codec, 0x02, &dev->master_vol_l, &dev->master_vol_r);
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, 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, 0x12, &dev->cd_vol_l, &dev->cd_vol_r);
/* Update sample rate according to codec registers and the variable sample rate flag. */
@@ -211,9 +215,9 @@ 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
// #ifdef ENABLE_AC97_VIA_LOG
uint8_t modem = (addr & 0xff00) == dev->modem_sgd_base;
#endif
// #endif
addr &= 0xff;
uint8_t ret;
@@ -221,83 +225,83 @@ ac97_via_sgd_read(uint16_t addr, void *priv)
/* Process SGD channel registers. */
switch (addr & 0xf) {
case 0x4:
ret = dev->sgd[addr >> 4].entry_ptr;
ret = dev->sgd[modem][addr >> 4].entry_ptr;
break;
case 0x5:
ret = dev->sgd[addr >> 4].entry_ptr >> 8;
ret = dev->sgd[modem][addr >> 4].entry_ptr >> 8;
break;
case 0x6:
ret = dev->sgd[addr >> 4].entry_ptr >> 16;
ret = dev->sgd[modem][addr >> 4].entry_ptr >> 16;
break;
case 0x7:
ret = dev->sgd[addr >> 4].entry_ptr >> 24;
ret = dev->sgd[modem][addr >> 4].entry_ptr >> 24;
break;
case 0xc:
ret = dev->sgd[addr >> 4].sample_count;
ret = dev->sgd[modem][addr >> 4].sample_count;
break;
case 0xd:
ret = dev->sgd[addr >> 4].sample_count >> 8;
ret = dev->sgd[modem][addr >> 4].sample_count >> 8;
break;
case 0xe:
ret = dev->sgd[addr >> 4].sample_count >> 16;
ret = dev->sgd[modem][addr >> 4].sample_count >> 16;
break;
default:
ret = dev->sgd_regs[addr];
ret = dev->sgd_regs[modem][addr];
break;
}
} else {
/* Process regular registers. */
switch (addr) {
case 0x84:
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] & 0x01);
ret |= (dev->sgd_regs[modem][0x10] & 0x01) << 1;
ret |= (dev->sgd_regs[modem][0x20] & 0x01) << 2;
ret |= (dev->sgd_regs[0x00] & 0x02) << 3;
ret |= (dev->sgd_regs[0x10] & 0x02) << 4;
ret |= (dev->sgd_regs[0x20] & 0x02) << 5;
ret |= (dev->sgd_regs[modem][0x00] & 0x02) << 3;
ret |= (dev->sgd_regs[modem][0x10] & 0x02) << 4;
ret |= (dev->sgd_regs[modem][0x20] & 0x02) << 5;
break;
case 0x85:
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] & 0x04) >> 2;
ret |= (dev->sgd_regs[modem][0x10] & 0x04) >> 1;
ret |= (dev->sgd_regs[modem][0x20] & 0x04);
ret |= (dev->sgd_regs[0x00] & 0x80) >> 3;
ret |= (dev->sgd_regs[0x10] & 0x80) >> 2;
ret |= (dev->sgd_regs[0x20] & 0x80) >> 1;
ret |= (dev->sgd_regs[modem][0x00] & 0x80) >> 3;
ret |= (dev->sgd_regs[modem][0x10] & 0x80) >> 2;
ret |= (dev->sgd_regs[modem][0x20] & 0x80) >> 1;
break;
case 0x86:
ret = (dev->sgd_regs[0x40] & 0x01);
ret |= (dev->sgd_regs[0x50] & 0x01) << 1;
ret = (dev->sgd_regs[modem][0x40] & 0x01);
ret |= (dev->sgd_regs[modem][0x50] & 0x01) << 1;
ret |= (dev->sgd_regs[0x40] & 0x02) << 3;
ret |= (dev->sgd_regs[0x50] & 0x02) << 4;
ret |= (dev->sgd_regs[modem][0x40] & 0x02) << 3;
ret |= (dev->sgd_regs[modem][0x50] & 0x02) << 4;
break;
case 0x87:
ret = (dev->sgd_regs[0x40] & 0x04) >> 2;
ret |= (dev->sgd_regs[0x50] & 0x04) >> 1;
ret = (dev->sgd_regs[modem][0x40] & 0x04) >> 2;
ret |= (dev->sgd_regs[modem][0x50] & 0x04) >> 1;
ret |= (dev->sgd_regs[0x40] & 0x80) >> 3;
ret |= (dev->sgd_regs[0x50] & 0x80) >> 2;
ret |= (dev->sgd_regs[modem][0x40] & 0x80) >> 3;
ret |= (dev->sgd_regs[modem][0x50] & 0x80) >> 2;
break;
default:
ret = dev->sgd_regs[addr];
ret = dev->sgd_regs[modem][addr];
break;
}
}
ac97_via_log("AC97 VIA %d: sgd_read(%02X) = %02X\n", modem, addr, ret);
ac97_via_log("[%04X:%08X] [%i] AC97 VIA %d: sgd_read(%02X) = %02X\n", CS, cpu_state.pc, msw & 1, modem, addr, ret);
return ret;
}
@@ -311,7 +315,9 @@ ac97_via_sgd_write(uint16_t addr, uint8_t val, void *priv)
ac97_codec_t *codec;
addr &= 0xff;
ac97_via_log("AC97 VIA %d: sgd_write(%02X, %02X)\n", modem, addr, val);
ac97_via_log("[%04X:%08X] [%i] AC97 VIA %d: sgd_write(%02X, %02X)\n", CS, cpu_state.pc, msw & 1, modem, addr, val);
// if ((CS == 0x10000) && (cpu_state.pc == 0x000073d1))
/* Check function-specific read only registers. */
if ((addr >= (modem ? 0x00 : 0x40)) && (addr < (modem ? 0x40 : 0x60)))
@@ -324,42 +330,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[addr] &= ~(val & 0x07);
dev->sgd_regs[modem][addr] &= ~(val & 0x07);
/* Update status interrupts. */
ac97_via_update_irqs(dev);
ac97_via_update_irqs(dev, modem);
return;
case 0x1:
/* Start SGD if requested. */
if (val & 0x80) {
if (dev->sgd_regs[addr & 0xf0] & 0x80) {
if (dev->sgd_regs[modem][addr & 0xf0] & 0x80) {
/* Queue SGD trigger if already running. */
dev->sgd_regs[addr & 0xf0] |= 0x08;
dev->sgd_regs[modem][addr & 0xf0] |= 0x08;
} else {
/* Start SGD immediately. */
dev->sgd_regs[addr & 0xf0] = (dev->sgd_regs[addr & 0xf0] & ~0x47) | 0x80;
dev->sgd_regs[modem][addr & 0xf0] = (dev->sgd_regs[modem][addr & 0xf0] & ~0x47) | 0x80;
/* Start at the specified entry pointer. */
dev->sgd[addr >> 4].entry_ptr = *((uint32_t *) &dev->sgd_regs[(addr & 0xf0) | 0x4]) & 0xfffffffe;
dev->sgd[addr >> 4].restart = 2;
dev->sgd[modem][addr >> 4].entry_ptr = *((uint32_t *) &dev->sgd_regs[modem][(addr & 0xf0) | 0x4]) & 0xfffffffe;
dev->sgd[modem][addr >> 4].restart = 2;
/* Start the actual SGD process. */
ac97_via_sgd_process(&dev->sgd[addr >> 4]);
ac97_via_sgd_process(&dev->sgd[modem][addr >> 4]);
}
}
/* Stop SGD if requested. */
if (val & 0x40)
dev->sgd_regs[addr & 0xf0] &= ~0x88;
dev->sgd_regs[modem][addr & 0xf0] &= ~0x88;
val &= 0x08;
/* (Un)pause SGD if requested. */
if (val & 0x08)
dev->sgd_regs[addr & 0xf0] |= 0x40;
dev->sgd_regs[modem][addr & 0xf0] |= 0x40;
else
dev->sgd_regs[addr & 0xf0] &= ~0x40;
dev->sgd_regs[modem][addr & 0xf0] &= ~0x40;
break;
@@ -387,7 +393,7 @@ ac97_via_sgd_write(uint16_t addr, uint8_t val, void *priv)
case 0x82:
/* Determine the selected codec. */
i = !!(dev->sgd_regs[0x83] & 0x40);
i = !!(dev->sgd_regs[modem][0x83] & 0x40);
codec = dev->codec[modem][i];
/* Keep value in register if this codec is not present. */
@@ -395,20 +401,20 @@ ac97_via_sgd_write(uint16_t addr, uint8_t val, void *priv)
/* Read from or write to codec. */
if (val & 0x80) {
if (val & 1) { /* return 0x0000 on unaligned reads (real 686B behavior) */
dev->sgd_regs[0x80] = dev->sgd_regs[0x81] = 0x00;
dev->sgd_regs[modem][0x80] = dev->sgd_regs[modem][0x81] = 0x00;
} else {
*((uint16_t *) &dev->codec_shadow[modem].regs_codec[i][val & 0x7f]) = *((uint16_t *) &dev->sgd_regs[0x80]) = ac97_codec_readw(codec, val);
*((uint16_t *) &dev->codec_shadow[modem].regs_codec[i][val & 0x7f]) = *((uint16_t *) &dev->sgd_regs[modem][0x80]) = ac97_codec_readw(codec, val);
}
/* Flag data/status/index for this codec as valid. */
dev->sgd_regs[0x83] |= 0x02 << (i << 1);
dev->sgd_regs[modem][0x83] |= 0x02 << (i << 1);
} else if (!(val & 1)) { /* do nothing on unaligned writes */
ac97_codec_writew(codec, val,
*((uint16_t *) &dev->codec_shadow[modem].regs_codec[i][val & 0x7f]) = *((uint16_t *) &dev->sgd_regs[0x80]));
*((uint16_t *) &dev->codec_shadow[modem].regs_codec[i][val & 0x7f]) = *((uint16_t *) &dev->sgd_regs[modem][0x80]));
/* Update primary audio codec state if that codec was written to. */
if (!modem && !i) {
ac97_via_update_codec(dev);
ac97_via_update_codec(dev, 0);
/* 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
@@ -424,9 +430,9 @@ ac97_via_sgd_write(uint16_t addr, uint8_t val, void *priv)
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[addr] & ~(val & 0x0a)) | (val & 0xc0);
val = ((dev->sgd_regs[modem][addr] & 0x3f) & ~(val & 0x0a)) | (val & 0xc0);
#else
val = dev->sgd_regs[addr] | (val & 0xc0);
val = (dev->sgd_regs[modem][addr] & 0x3f) | (val & 0xc0);
#endif
break;
@@ -435,7 +441,7 @@ ac97_via_sgd_write(uint16_t addr, uint8_t val, void *priv)
}
}
dev->sgd_regs[addr] = val;
dev->sgd_regs[modem][addr] = val;
}
void
@@ -479,6 +485,8 @@ ac97_via_codec_read(uint16_t addr, void *priv)
ac97_via_log("AC97 VIA %d: codec_read(%02X) = %02X\n", modem, addr, ret);
ac97_via_log("[%04X:%08X] [%i] AC97 VIA %d: codec_read(%02X) = %02X\n", CS, cpu_state.pc, msw & 1, modem, addr, ret);
return ret;
}
@@ -489,6 +497,8 @@ ac97_via_codec_write(uint16_t addr, uint8_t val, void *priv)
uint8_t modem = (addr & 0xff00) == dev->modem_codec_base;
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);
ac97_via_log("AC97 VIA %d: codec_write(%02X, %02X)\n", modem, addr, val);
/* Unknown behavior, maybe it does write to the shadow registers? */
@@ -501,12 +511,12 @@ ac97_via_remap_audio_codec(void *priv, uint16_t new_io_base, uint8_t enable)
ac97_via_t *dev = (ac97_via_t *) priv;
if (dev->audio_codec_base)
io_removehandler(dev->audio_codec_base, 256, ac97_via_codec_read, NULL, NULL, ac97_via_codec_write, NULL, NULL, dev);
io_removehandler(dev->audio_codec_base, 4, ac97_via_codec_read, NULL, NULL, ac97_via_codec_write, NULL, NULL, dev);
dev->audio_codec_base = new_io_base;
if (dev->audio_codec_base && enable)
io_sethandler(dev->audio_codec_base, 256, ac97_via_codec_read, NULL, NULL, ac97_via_codec_write, NULL, NULL, dev);
io_sethandler(dev->audio_codec_base, 4, ac97_via_codec_read, NULL, NULL, ac97_via_codec_write, NULL, NULL, dev);
}
void
@@ -515,12 +525,12 @@ ac97_via_remap_modem_codec(void *priv, uint16_t new_io_base, uint8_t enable)
ac97_via_t *dev = (ac97_via_t *) priv;
if (dev->modem_codec_base)
io_removehandler(dev->modem_codec_base, 256, ac97_via_codec_read, NULL, NULL, ac97_via_codec_write, NULL, NULL, dev);
io_removehandler(dev->modem_codec_base, 4, ac97_via_codec_read, NULL, NULL, ac97_via_codec_write, NULL, NULL, dev);
dev->modem_codec_base = new_io_base;
if (dev->modem_codec_base && enable)
io_sethandler(dev->modem_codec_base, 256, ac97_via_codec_read, NULL, NULL, ac97_via_codec_write, NULL, NULL, dev);
io_sethandler(dev->modem_codec_base, 4, ac97_via_codec_read, NULL, NULL, ac97_via_codec_write, NULL, NULL, dev);
}
static void
@@ -551,12 +561,12 @@ 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->id] & 0xc4;
uint8_t sgd_status = dev->sgd_regs[sgd->modem][sgd->id] & 0xc4;
if (!(sgd_status & 0x80))
return;
/* Schedule next run. */
timer_on_auto(&sgd->dma_timer, 10.0);
timer_on_auto(&sgd->dma_timer, 1.0);
/* Process SGD if it's active, and the FIFO has room or is disabled. */
if (((sgd_status & 0xc7) == 0x80) && (sgd->always_run || ((sgd->fifo_end - sgd->fifo_pos) <= (sizeof(sgd->fifo) - 4)))) {
@@ -564,13 +574,15 @@ ac97_via_sgd_process(void *priv)
if (sgd->restart) {
/* (Re)load entry pointer if required. */
if (sgd->restart & 2)
sgd->entry_ptr = *((uint32_t *) &dev->sgd_regs[sgd->id | 0x4]) & 0xfffffffe; /* TODO: probe real hardware - does "even addr" actually mean dword aligned? */
sgd->entry_ptr = *((uint32_t *) &dev->sgd_regs[sgd->modem][sgd->id | 0x4]) & 0xfffffffe; /* TODO: probe real hardware - does "even addr" actually mean dword aligned? */
sgd->restart = 0;
/* Read entry. */
sgd->sample_ptr = mem_readl_phys(sgd->entry_ptr);
// sgd->sample_ptr = mem_readl_phys(sgd->entry_ptr);
dma_bm_read(sgd->entry_ptr, (uint8_t *) &sgd->sample_ptr, 4, 4);
sgd->entry_ptr += 4;
sgd->sample_count = mem_readl_phys(sgd->entry_ptr);
// sgd->sample_count = mem_readl_phys(sgd->entry_ptr);
dma_bm_read(sgd->entry_ptr, (uint8_t *) &sgd->sample_count, 4, 4);
sgd->entry_ptr += 4;
#ifdef ENABLE_AC97_VIA_LOG
if (((sgd->sample_ptr == 0xffffffff) && (sgd->sample_count == 0xffffffff)) || ((sgd->sample_ptr == 0x00000000) && (sgd->sample_count == 0x00000000)))
@@ -588,10 +600,12 @@ ac97_via_sgd_process(void *priv)
if (sgd->id & 0x10) {
/* Write channel: read data from FIFO. */
mem_writel_phys(sgd->sample_ptr, *((uint32_t *) &sgd->fifo[sgd->fifo_end & (sizeof(sgd->fifo) - 1)]));
// mem_writel_phys(sgd->sample_ptr, *((uint32_t *) &sgd->fifo[sgd->fifo_end & (sizeof(sgd->fifo) - 1)]));
dma_bm_write(sgd->sample_ptr, &sgd->fifo[sgd->fifo_end & (sizeof(sgd->fifo) - 1)], 4, 4);
} else {
/* Read channel: write data to FIFO. */
*((uint32_t *) &sgd->fifo[sgd->fifo_end & (sizeof(sgd->fifo) - 1)]) = mem_readl_phys(sgd->sample_ptr);
// *((uint32_t *) &sgd->fifo[sgd->fifo_end & (sizeof(sgd->fifo) - 1)]) = mem_readl_phys(sgd->sample_ptr);
dma_bm_read(sgd->sample_ptr, &sgd->fifo[sgd->fifo_end & (sizeof(sgd->fifo) - 1)], 4, 4);
}
sgd->fifo_end += 4;
sgd->sample_ptr += 4;
@@ -608,17 +622,17 @@ ac97_via_sgd_process(void *priv)
ac97_via_log(" with STOP");
/* Raise STOP to pause SGD. */
dev->sgd_regs[sgd->id] |= 0x04;
dev->sgd_regs[sgd->modem][sgd->id] |= 0x04;
}
if (sgd->entry_flags & 0x40) {
ac97_via_log(" with FLAG");
/* Raise FLAG to pause SGD. */
dev->sgd_regs[sgd->id] |= 0x01;
dev->sgd_regs[sgd->modem][sgd->id] |= 0x01;
#ifdef ENABLE_AC97_VIA_LOG
if (dev->sgd_regs[sgd->id | 0x2] & 0x01)
if (dev->sgd_regs[sgd->modem][sgd->id | 0x2] & 0x01)
ac97_via_log(" interrupt");
#endif
}
@@ -627,19 +641,19 @@ ac97_via_sgd_process(void *priv)
ac97_via_log(" with EOL");
/* Raise EOL. */
dev->sgd_regs[sgd->id] |= 0x02;
dev->sgd_regs[sgd->modem][sgd->id] |= 0x02;
#ifdef ENABLE_AC97_VIA_LOG
if (dev->sgd_regs[sgd->id | 0x2] & 0x02)
if (dev->sgd_regs[sgd->modem][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->id] & 0x08) || (dev->sgd_regs[sgd->id | 0x2] & 0x80)) {
if ((dev->sgd_regs[sgd->modem][sgd->id] & 0x08) || (dev->sgd_regs[sgd->modem][sgd->id | 0x2] & 0x80)) {
ac97_via_log(" restart");
/* Un-queue trigger. */
dev->sgd_regs[sgd->id] &= ~0x08;
dev->sgd_regs[sgd->modem][sgd->id] &= ~0x08;
/* Go back to the starting block on the next run. */
sgd->restart = 2;
@@ -647,13 +661,13 @@ ac97_via_sgd_process(void *priv)
ac97_via_log(" finish");
/* Terminate SGD. */
dev->sgd_regs[sgd->id] &= ~0x80;
dev->sgd_regs[sgd->modem][sgd->id] &= ~0x80;
}
}
ac97_via_log("\n");
/* Fire any requested status interrupts. */
ac97_via_update_irqs(dev);
ac97_via_update_irqs(dev, sgd->modem);
}
}
}
@@ -662,7 +676,7 @@ static void
ac97_via_poll_stereo(void *priv)
{
ac97_via_t *dev = (ac97_via_t *) priv;
ac97_via_sgd_t *sgd = &dev->sgd[0]; /* Audio Read */
ac97_via_sgd_t *sgd = &dev->sgd[0][0]; /* Audio Read */
/* Schedule next run if PCM playback is enabled. */
if (dev->pcm_enabled)
@@ -672,7 +686,7 @@ ac97_via_poll_stereo(void *priv)
ac97_via_update_stereo(dev, sgd);
/* Feed next sample from the FIFO. */
switch (dev->sgd_regs[sgd->id | 0x2] & 0x30) {
switch (dev->sgd_regs[0][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;
@@ -718,7 +732,7 @@ static void
ac97_via_poll_fm(void *priv)
{
ac97_via_t *dev = (ac97_via_t *) priv;
ac97_via_sgd_t *sgd = &dev->sgd[2]; /* FM Read */
ac97_via_sgd_t *sgd = &dev->sgd[0][2]; /* FM Read */
/* Schedule next run if FM playback is enabled. */
if (dev->fm_enabled)
@@ -746,15 +760,15 @@ 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]);
ac97_via_update_stereo(dev, &dev->sgd[2]);
ac97_via_update_stereo(dev, &dev->sgd[0][0]);
ac97_via_update_stereo(dev, &dev->sgd[0][2]);
for (int c = 0; c < len * 2; c++) {
buffer[c] += dev->sgd[0].buffer[c] / 2;
buffer[c] += dev->sgd[2].buffer[c] / 2;
buffer[c] += dev->sgd[0][0].buffer[c] / 2;
buffer[c] += dev->sgd[0][2].buffer[c] / 2;
}
dev->sgd[0].pos = dev->sgd[2].pos = 0;
dev->sgd[0][0].pos = dev->sgd[0][2].pos = 0;
}
static void
@@ -780,8 +794,8 @@ ac97_via_speed_changed(void *priv)
else
freq = (double) SOUND_FREQ;
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 */
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 */
}
static void *
@@ -799,19 +813,23 @@ ac97_via_init(UNUSED(const device_t *info))
/* Set up SGD channels. */
for (uint8_t i = 0; i < (sizeof(dev->sgd) / sizeof(dev->sgd[0])); i++) {
dev->sgd[i].id = i << 4;
dev->sgd[i].dev = dev;
for (uint8_t j = 0; j < 2; j++) {
dev->sgd[j][i].id = i << 4;
dev->sgd[j][i].dev = dev;
/* Disable the FIFO on SGDs we don't care about. */
if ((i != 0) && (i != 2))
dev->sgd[i].always_run = 1;
dev->sgd[j][i].modem = j;
timer_add(&dev->sgd[i].dma_timer, ac97_via_sgd_process, &dev->sgd[i], 0);
/* 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);
}
}
/* Set up playback pollers. */
timer_add(&dev->sgd[0].poll_timer, ac97_via_poll_stereo, dev, 0);
timer_add(&dev->sgd[2].poll_timer, ac97_via_poll_fm, dev, 0);
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);
ac97_via_speed_changed(dev);
/* Set up playback handler. */