diff --git a/src/sound/snd_cs423x.c b/src/sound/snd_cs423x.c index f28be9778..d7d99b7b3 100644 --- a/src/sound/snd_cs423x.c +++ b/src/sound/snd_cs423x.c @@ -15,12 +15,13 @@ * Copyright 2021-2022 RichardG. */ #include +#include #include #include #include #include #include - +#define HAVE_STDARG_H #include <86box/86box.h> #include <86box/device.h> #include <86box/dma.h> @@ -51,6 +52,12 @@ enum { CRYSTAL_CS4237B = 0xc8, CRYSTAL_CS4238B = 0xc9 }; +enum { + CRYSTAL_RAM_CMD = 0, + CRYSTAL_RAM_ADDR_LO = 1, + CRYSTAL_RAM_ADDR_HI = 2, + CRYSTAL_RAM_DATA = 3 +}; enum { CRYSTAL_SLAM_NONE = 0, CRYSTAL_SLAM_INDEX = 1, @@ -58,15 +65,31 @@ enum { CRYSTAL_SLAM_BYTE2 = 3 }; +#ifdef ENABLE_CS423X_LOG +int cs423x_do_log = ENABLE_CS423X_LOG; + +static void +cs423x_log(const char *fmt, ...) +{ + va_list ap; + + if (cs423x_do_log) { + va_start(ap, fmt); + pclog_ex(fmt, ap); + va_end(ap); + } +} +#else +# define cs423x_log(fmt, ...) +#endif + static const uint8_t slam_init_key[32] = { 0x96, 0x35, 0x9A, 0xCD, 0xE6, 0xF3, 0x79, 0xBC, 0x5E, 0xAF, 0x57, 0x2B, 0x15, 0x8A, 0xC5, 0xE2, 0xF1, 0xF8, 0x7C, 0x3E, 0x9F, 0x4F, 0x27, 0x13, 0x09, 0x84, 0x42, 0xA1, 0xD0, 0x68, 0x34, 0x1A }; -static const uint8_t cs4236b_eeprom[8224] = { +static const uint8_t cs4236b_default[] = { // clang-format off /* Chip configuration */ - 0x55, 0xbb, /* magic */ - 0x00, 0x00, /* length */ 0x00, 0x03, /* CD-ROM and modem decode */ 0x80, /* misc. config */ 0x80, /* global config */ @@ -77,8 +100,9 @@ static const uint8_t cs4236b_eeprom[8224] = { 0x75, 0xb9, 0xfc, /* IRQ routing */ 0x10, 0x03, /* DMA routing */ - /* PnP resources */ - 0x00 + /* Default PnP resources */ + /* TODO: broken, not picked up by VS440FX BIOS, pending hardware research of the actual defaults */ + 0x0e, 0x63, 0x42, 0x36, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0a, 0x10, 0x00, 0x14, 0x41, 0xd0, 0xff, 0xff, 0x79, 0x00 // clang-format on }; @@ -97,6 +121,7 @@ typedef struct cs423x_t { uint16_t ram_addr; uint16_t eeprom_size : 11; uint16_t pnp_offset; + uint16_t pnp_size; uint8_t type; uint8_t ad1848_type; uint8_t regs[8]; @@ -130,6 +155,8 @@ cs423x_nvram(cs423x_t *dev, uint8_t save) else (void) !fread(dev->eeprom_data, sizeof(dev->eeprom_data), 1, fp); fclose(fp); + } else { + cs423x_log("CS423x: EEPROM data %s failed\n", save ? "save" : "load"); } } @@ -147,14 +174,24 @@ cs423x_read(uint16_t addr, void *priv) ret |= 0x04; break; + case 3: /* Control Indirect Access Register */ + /* Intel VS440FX BIOS tells CS4236 from CS4232 through the upper bits. Setting them is enough. */ + ret |= 0xf0; + break; + case 4: /* Control Indirect Data Register */ ret = dev->indirect_regs[dev->regs[3]]; break; case 5: /* Control/RAM Access */ - /* Reading RAM is undocumented; the Windows drivers do so. */ - if (dev->ram_dl == 3) - ret = dev->ram_data[dev->ram_addr++]; + /* Reading RAM is undocumented, but performed by: + - Windows drivers (unknown purpose) + - Intel VS440FX BIOS (PnP ROM checksum recalculation) */ + if (dev->ram_dl == CRYSTAL_RAM_DATA) { + ret = dev->ram_data[dev->ram_addr]; + cs423x_log("CS423x: RAM read(%04X) = %02X\n", dev->ram_addr, ret); + dev->ram_addr++; + } break; case 7: /* Global Status */ @@ -175,6 +212,8 @@ cs423x_read(uint16_t addr, void *priv) break; } + cs423x_log("CS423x: read(%X) = %02X\n", reg, ret); + return ret; } @@ -182,7 +221,9 @@ static void cs423x_write(uint16_t addr, uint8_t val, void *priv) { cs423x_t *dev = (cs423x_t *) priv; - uint8_t reg = addr & 0x07; + uint8_t reg = addr & 7; + + cs423x_log("CS423x: write(%X, %02X)\n", reg, val); switch (reg) { case 1: /* EEPROM Interface */ @@ -248,7 +289,7 @@ cs423x_write(uint16_t addr, uint8_t val, void *priv) case 5: /* Control/RAM Access */ switch (dev->ram_dl) { - case 0: /* commands */ + case CRYSTAL_RAM_CMD: /* commands */ switch (val) { case 0x55: /* Disable PnP Key */ dev->pnp_enable = 0; @@ -266,7 +307,7 @@ cs423x_write(uint16_t addr, uint8_t val, void *priv) break; case 0xaa: /* Download RAM */ - dev->ram_dl = 1; + dev->ram_dl = CRYSTAL_RAM_ADDR_LO; break; default: @@ -274,17 +315,19 @@ cs423x_write(uint16_t addr, uint8_t val, void *priv) } break; - case 1: /* low address byte */ + case CRYSTAL_RAM_ADDR_LO: /* low address byte */ dev->ram_addr = val; - dev->ram_dl++; + dev->ram_dl = CRYSTAL_RAM_ADDR_HI; break; - case 2: /* high address byte */ - dev->ram_addr |= (val << 8); - dev->ram_dl++; + case CRYSTAL_RAM_ADDR_HI: /* high address byte */ + dev->ram_addr |= val << 8; + dev->ram_dl = CRYSTAL_RAM_DATA; + cs423x_log("CS423x: RAM start(%04X)\n", dev->ram_addr); break; - case 3: /* data */ + case CRYSTAL_RAM_DATA: /* data */ + cs423x_log("CS423x: RAM write(%04X, %02X)\n", dev->ram_addr, val); dev->ram_data[dev->ram_addr++] = val; break; @@ -296,7 +339,8 @@ cs423x_write(uint16_t addr, uint8_t val, void *priv) case 6: /* RAM Access End */ /* TriGem Delhi-III BIOS writes undocumented value 0x40 instead of 0x00. */ if ((val == 0x00) || (val == 0x40)) { - dev->ram_dl = 0; + cs423x_log("CS423x: RAM end\n"); + dev->ram_dl = CRYSTAL_RAM_CMD; /* Update PnP state and resource data. */ cs423x_pnp_enable(dev, 1, 0); @@ -319,6 +363,9 @@ cs423x_slam_write(UNUSED(uint16_t addr), uint8_t val, void *priv) cs423x_t *dev = (cs423x_t *) priv; uint8_t idx; + if ((dev->slam_state != CRYSTAL_SLAM_NONE) || (val == slam_init_key[dev->key_pos])) /* cut down on ISAPnP-related noise */ + cs423x_log("CS423x: slam_write(%02X)\n", val); + switch (dev->slam_state) { case CRYSTAL_SLAM_NONE: /* Not in SLAM: read and compare Crystal key. */ @@ -333,6 +380,7 @@ cs423x_slam_write(UNUSED(uint16_t addr), uint8_t val, void *priv) } /* Enter SLAM. */ + cs423x_log("CS423x: SLAM unlocked\n"); dev->slam_state = CRYSTAL_SLAM_INDEX; } } else { @@ -343,6 +391,8 @@ cs423x_slam_write(UNUSED(uint16_t addr), uint8_t val, void *priv) case CRYSTAL_SLAM_INDEX: /* Intercept the Activate Audio Device command. */ if (val == 0x79) { + cs423x_log("CS423x: Exiting SLAM\n"); + /* Apply the last logical device's configuration. */ if (dev->slam_config) { cs423x_pnp_config_changed(dev->slam_ld, dev->slam_config, dev); @@ -363,6 +413,7 @@ cs423x_slam_write(UNUSED(uint16_t addr), uint8_t val, void *priv) case CRYSTAL_SLAM_BYTE1: case CRYSTAL_SLAM_BYTE2: /* Write register value: two bytes for I/O ports, single byte otherwise. */ + cs423x_log("CS423x: SLAM write(%02X, %02X)\n", dev->slam_reg, val); switch (dev->slam_reg) { case 0x06: /* Card Select Number */ isapnp_set_csn(dev->pnp_card, val); @@ -433,7 +484,7 @@ cs423x_slam_write(UNUSED(uint16_t addr), uint8_t val, void *priv) break; } - /* Prepare for the next register, unless a two-byte read returns above. */ + /* Prepare for the next register, unless a two-byte write returns above. */ dev->slam_state = CRYSTAL_SLAM_INDEX; break; @@ -454,8 +505,11 @@ cs423x_slam_enable(cs423x_t *dev, uint8_t enable) /* Enable SLAM if the CKD bit is not set. */ if (enable && !(dev->ram_data[0x4002] & 0x10)) { + cs423x_log("CS423x: Enabling SLAM\n"); dev->slam_enable = 1; io_sethandler(0x279, 1, NULL, NULL, NULL, cs423x_slam_write, NULL, NULL, dev); + } else { + cs423x_log("CS423x: Disabling SLAM\n"); } } @@ -471,6 +525,7 @@ cs423x_ctxswitch_write(uint16_t addr, UNUSED(uint8_t val), void *priv) /* Flip context bit. */ dev->regs[7] ^= 0x80; ctx ^= 0x80; + cs423x_log("CS423x: Context switch to %s\n", ctx ? "WSS" : "SBPro"); /* Update CD audio filter. FIXME: not thread-safe: filter function TOCTTOU in sound_cd_thread! */ @@ -540,10 +595,12 @@ cs423x_get_music_buffer(int32_t *buffer, int len, void *priv) static void cs423x_pnp_enable(cs423x_t *dev, uint8_t update_rom, uint8_t update_hwconfig) { + cs423x_log("CS423x: Updating PnP ROM=%d hwconfig=%d\n", update_rom, update_hwconfig); + if (dev->pnp_card) { /* Update PnP resource data if requested. */ if (update_rom) - isapnp_update_card_rom(dev->pnp_card, &dev->ram_data[dev->pnp_offset], 384); + isapnp_update_card_rom(dev->pnp_card, &dev->ram_data[dev->pnp_offset], dev->pnp_size); /* Disable PnP key if the PKD bit is set, or if it was disabled by command 0x55. */ /* But wait! The TriGem Delhi-III BIOS sends command 0x55, and its behavior doesn't @@ -697,9 +754,21 @@ cs423x_reset(void *priv) /* Clear RAM. */ memset(dev->ram_data, 0, sizeof(dev->ram_data)); + /* Load default configuration data to RAM. */ + memcpy(&dev->ram_data[0x4000], cs4236b_default, sizeof(cs4236b_default)); + dev->pnp_size = 19; + if (dev->eeprom) { - /* Load EEPROM data to RAM. */ - memcpy(&dev->ram_data[0x4000], &dev->eeprom_data[4], MIN(384, ((dev->eeprom_data[2] << 8) | dev->eeprom_data[3]) - 4)); + /* Load EEPROM data to RAM if the magic bytes are present. */ + if ((dev->eeprom_data[0] == 0x55) && (dev->eeprom_data[1] == 0xbb)) { + cs423x_log("CS423x: EEPROM data valid, loading to RAM\n"); + dev->pnp_size = (dev->eeprom_data[2] << 8) | dev->eeprom_data[3]; + if (dev->pnp_size > 384) + dev->pnp_size = 384; + memcpy(&dev->ram_data[0x4000], &dev->eeprom_data[4], sizeof(dev->eeprom_data) - 4); + } else { + cs423x_log("CS423x: EEPROM data invalid, ignoring\n"); + } /* Save EEPROM contents to file. */ cs423x_nvram(dev, 1); @@ -734,6 +803,7 @@ cs423x_init(const device_t *info) /* Initialize model-specific data. */ dev->type = info->local & 0xff; + cs423x_log("CS423x: init(%02X)\n", dev->type); switch (dev->type) { case CRYSTAL_CS4235: case CRYSTAL_CS4236B: @@ -743,30 +813,41 @@ cs423x_init(const device_t *info) dev->ad1848_type = (dev->type == CRYSTAL_CS4235) ? AD1848_TYPE_CS4235 : AD1848_TYPE_CS4236; dev->pnp_offset = 0x4013; - /* Different Chip Version and ID registers, which shouldn't be reset by ad1848_init */ + /* Different Chip Version and ID registers, which shouldn't be reset by ad1848_init. */ dev->ad1848.xregs[25] = dev->type; if (!(info->local & CRYSTAL_NOEEPROM)) { - /* Load EEPROM contents from template. */ - memcpy(dev->eeprom_data, cs4236b_eeprom, sizeof(cs4236b_eeprom)); + /* Copy default configuration data. */ + memcpy(&dev->eeprom_data[4], cs4236b_default, sizeof(cs4236b_default)); + /* Load PnP resource data ROM. */ FILE *fp = rom_fopen(PNP_ROM_CS4236B, "rb"); if (fp) { - (void) !fread(&(dev->eeprom_data[23]), 1, 8201, fp); + uint16_t eeprom_pnp_offset = (dev->pnp_offset & 0x1ff) + 4; + /* This is wrong. The header field only indicates PnP resource data length, and real chips use + it to locate the firmware patch area, but we don't need any of that, so we can get away + with pretending the whole ROM is PnP data, at least until we can get full EEPROM dumps. */ + dev->pnp_size = fread(&dev->eeprom_data[eeprom_pnp_offset], 1, sizeof(dev->eeprom_data) - eeprom_pnp_offset, fp); fclose(fp); } - /* Set content size. */ - dev->eeprom_data[2] = sizeof(cs4236b_eeprom) >> 8; - dev->eeprom_data[3] = sizeof(cs4236b_eeprom) & 0xff; + /* Populate EEPROM header if the PnP ROM was loaded. */ + if (dev->pnp_size) { + dev->eeprom_data[0] = 0x55; + dev->eeprom_data[1] = 0xbb; + dev->eeprom_data[2] = dev->pnp_size >> 8; + dev->eeprom_data[3] = dev->pnp_size; + } - /* Set PnP card ID and EEPROM file name. */ + /* Patch PnP ROM and set EEPROM file name. */ switch (dev->type) { case CRYSTAL_CS4235: - dev->eeprom_data[8] = 0x05; - dev->eeprom_data[16] = 0x08; - dev->eeprom_data[26] = 0x25; - dev->eeprom_data[44] = '5'; + if (dev->pnp_size) { + dev->eeprom_data[8] = 0x05; + dev->eeprom_data[16] = 0x08; + dev->eeprom_data[26] = 0x25; + dev->eeprom_data[44] = '5'; + } dev->nvr_path = "cs4235.nvr"; break; @@ -775,14 +856,18 @@ cs423x_init(const device_t *info) break; case CRYSTAL_CS4237B: - dev->eeprom_data[26] = 0x37; - dev->eeprom_data[44] = '7'; + if (dev->pnp_size) { + dev->eeprom_data[26] = 0x37; + dev->eeprom_data[44] = '7'; + } dev->nvr_path = "cs4237b.nvr"; break; case CRYSTAL_CS4238B: - dev->eeprom_data[26] = 0x38; - dev->eeprom_data[44] = '8'; + if (dev->pnp_size) { + dev->eeprom_data[26] = 0x38; + dev->eeprom_data[44] = '8'; + } dev->nvr_path = "cs4238b.nvr"; break; @@ -807,8 +892,8 @@ cs423x_init(const device_t *info) /* Initialize I2C bus for the EEPROM. */ dev->i2c = i2c_gpio_init("nvr_cs423x"); - /* Initialize I2C EEPROM if the contents are valid. */ - if ((dev->eeprom_data[0] == 0x55) && (dev->eeprom_data[1] == 0xbb)) + /* Initialize I2C EEPROM if enabled. */ + if (!(info->local & CRYSTAL_NOEEPROM)) dev->eeprom = i2c_eeprom_init(i2c_gpio_get_bus(dev->i2c), 0x50, dev->eeprom_data, sizeof(dev->eeprom_data), 1); /* Initialize ISAPnP. */ @@ -836,6 +921,8 @@ cs423x_close(void *priv) { cs423x_t *dev = (cs423x_t *) priv; + cs423x_log("CS423x: close()\n"); + /* Save EEPROM contents to file. */ if (dev->eeprom) { cs423x_nvram(dev, 1);