#include #include #include #include #include #include #define HAVE_STDARG_H #include "cpu.h" #include <86box/86box.h> #include <86box/device.h> #include <86box/dma.h> #include <86box/filters.h> #include <86box/io.h> #include <86box/midi.h> #include <86box/pic.h> #include <86box/timer.h> #include <86box/pit.h> #include <86box/snd_mpu401.h> #include <86box/sound.h> #include <86box/snd_opl.h> #include <86box/snd_sb.h> #include <86box/snd_sb_dsp.h> #include <86box/plat_unused.h> /* Original PAS uses 2 x OPL2 PIT - sample rate/count LMC835N/LMC1982 - mixer YM3802 - MIDI Control System 9A01 - IO base base >> 2 All below + IO base B89 - interrupt status / clear bit 2 - sample rate bit 3 - PCM bit 4 - MIDI B88 - Audio mixer control register B8A - Audio filter control bit 5 - mute? B8B - interrupt mask / board ID bits 5-7 - board ID (read only on PAS16) F88 - PCM data (low) F89 - PCM data (high) F8A - PCM control? bit 4 - input/output select (1 = output) bit 5 - mono/stereo select bit 6 - PCM enable 1388-138b - PIT clocked at 1193180 Hz 1388 - sample rate 1389 - sample count 178b - 2789 - board revision 8389 - bit 2 - 8/16 bit BF88 - wait states EF8B - bit 3 - 16 bits okay ? F388 - bit 6 - joystick enable F389 - bits 0-2 - DMA F38A - bits 0-3 - IRQ F788 - bit 1 - SB emulation bit 0 - MPU401 emulation F789 - SB base addr bits 0-3 - addr bits 4-7 FB8A - SB IRQ/DMA bits 3-5 - IRQ bits 6-7 - DMA FF88 - board model 3 = PAS16 */ typedef struct pas16_t { uint16_t base; int irq; int dma; uint8_t audiofilt; uint8_t audio_mixer; uint8_t compat; uint8_t compat_base; uint8_t enhancedscsi; uint8_t io_conf_1; uint8_t io_conf_2; uint8_t io_conf_3; uint8_t io_conf_4; uint8_t irq_stat; uint8_t irq_ena; uint8_t pcm_ctrl; uint16_t pcm_dat; uint16_t pcm_dat_l; uint16_t pcm_dat_r; uint8_t sb_irqdma; int stereo_lr; uint8_t sys_conf_1; uint8_t sys_conf_2; uint8_t sys_conf_3; uint8_t sys_conf_4; uint8_t waitstates; uint8_t midi_ctrl; uint8_t midi_stat; uint8_t midi_data; uint8_t fifo_stat; int midi_r; int midi_w; int midi_uart_out; int midi_uart_in; uint8_t midi_queue[256]; int sysex; fm_drv_t opl; sb_dsp_t dsp; mpu_t *mpu; pc_timer_t timer; int16_t pcm_buffer[2][SOUNDBUFLEN]; int pos; pit_t *pit; } pas16_t; static void pas16_update(pas16_t *pas16); static int pas16_dmas[8] = { 4, 1, 2, 3, 0, 5, 6, 7 }; static int pas16_sb_irqs[8] = { 0, 2, 3, 5, 7, 10, 11, 12 }; static int pas16_sb_dmas[8] = { 0, 1, 2, 3 }; enum { PAS16_INT_SAMP = 0x04, PAS16_INT_PCM = 0x08, PAS16_INT_MIDI = 0x10, }; enum { PAS16_PCM_MONO = 0x20, PAS16_PCM_ENA = 0x40, PAS16_PCM_DMA_ENA = 0x80 }; enum { PAS16_SC2_16BIT = 0x04, PAS16_SC2_MSBINV = 0x10 }; enum { PAS16_FILT_MUTE = 0x20 }; #ifdef ENABLE_PAS16_LOG int pas16_do_log = ENABLE_PAS16_LOG; static void pas16_log(const char *fmt, ...) { va_list ap; if (pas16_do_log) { va_start(ap, fmt); pclog_ex(fmt, ap); va_end(ap); } } #else # define pas16_log(fmt, ...) #endif static uint8_t pas16_in(uint16_t port, void *priv); static void pas16_out(uint16_t port, uint8_t val, void *priv); static void pas16_update_irq(pas16_t *pas16) { if (pas16->midi_uart_out && (pas16->midi_stat & 0x18)) { pas16->irq_stat |= PAS16_INT_MIDI; if (pas16->irq_ena & PAS16_INT_MIDI) picint(1 << pas16->irq); } if (pas16->midi_uart_in && (pas16->midi_stat & 0x04)) { pas16->irq_stat |= PAS16_INT_MIDI; if (pas16->irq_ena & PAS16_INT_MIDI) picint(1 << pas16->irq); } } static uint8_t pas16_in(uint16_t port, void *priv) { pas16_t *pas16 = (pas16_t *) priv; uint8_t temp = 0xff; switch (port) { case 0x388: case 0x389: case 0x38a: case 0x38b: temp = pas16->opl.read(port, pas16->opl.priv); break; case 0xb88: temp = pas16->audio_mixer; break; case 0xb89: temp = pas16->irq_stat; break; case 0xb8a: temp = pas16->audiofilt; break; case 0xb8b: temp = pas16->irq_ena | 0x20; pas16_log("IRQ Mask read=%02x.\n", temp); break; case 0xf8a: temp = pas16->pcm_ctrl; break; case 0x1789: case 0x178b: temp = pas16->midi_ctrl; break; case 0x178a: case 0x1b8a: temp = 0; if (pas16->midi_uart_in) { if ((pas16->midi_data == 0xaa) && (pas16->midi_ctrl & 0x04)) temp = pas16->midi_data; else { temp = pas16->midi_queue[pas16->midi_r]; if (pas16->midi_r != pas16->midi_w) { pas16->midi_r++; pas16->midi_r &= 0xff; } } pas16->midi_stat &= ~0x04; pas16_update_irq(pas16); } break; case 0x1b88: temp = pas16->midi_stat; break; case 0x1b89: temp = pas16->fifo_stat; break; case 0x2789: /*Board revision*/ temp = 0x00; break; case 0x7f89: temp = pas16->enhancedscsi & ~0x01; break; case 0x8388: temp = pas16->sys_conf_1; break; case 0x8389: temp = pas16->sys_conf_2; break; case 0x838a: temp = pas16->sys_conf_3; break; case 0x838b: temp = pas16->sys_conf_4; break; case 0xbf88: temp = pas16->waitstates; break; case 0xef8b: temp = 0x00; break; case 0xf388: temp = pas16->io_conf_1; break; case 0xf389: temp = pas16->io_conf_2; pas16_log("pas16_in : set PAS DMA %i\n", pas16->dma); break; case 0xf38a: temp = pas16->io_conf_3; pas16_log("pas16_in : set PAS IRQ %i\n", pas16->irq); break; case 0xf38b: temp = pas16->io_conf_4; break; case 0xf788: temp = pas16->compat; break; case 0xf789: temp = pas16->compat_base; break; case 0xfb8a: temp = pas16->sb_irqdma; break; case 0xff88: /*Board model*/ temp = 0x04; /*PAS16*/ break; case 0xff8b: /*Master mode read*/ temp = 0x20 | 0x10 | 0x01; /*AT bus, XT/AT timing*/ break; default: break; } pas16_log("pas16_in : port %04X return %02X %04X:%04X\n", port, temp, CS, cpu_state.pc); return temp; } static void pas16_out(uint16_t port, uint8_t val, void *priv) { pas16_t *pas16 = (pas16_t *) priv; pit_t *pit = (pit_t *) pas16->pit; pas16_log("pas16_out : port %04X val %02X %04X:%04X\n", port, val, CS, cpu_state.pc); switch (port) { case 0x388: case 0x389: case 0x38a: case 0x38b: pas16->opl.write(port, val, pas16->opl.priv); break; case 0xb88: pas16->audio_mixer = val; break; case 0xb89: pas16->irq_stat &= ~val; break; case 0xb8a: pas16_update(pas16); pas16->audiofilt = val; break; case 0xb8b: pas16->irq_ena = val & 0x1f; break; case 0xf88: pas16_update(pas16); pas16->pcm_dat = (pas16->pcm_dat & 0xff00) | val; break; case 0xf89: pas16_update(pas16); pas16->pcm_dat = (pas16->pcm_dat & 0x00ff) | (val << 8); break; case 0xf8a: if ((val & PAS16_PCM_ENA) && !(pas16->pcm_ctrl & PAS16_PCM_ENA)) /*Guess*/ pas16->stereo_lr = 0; pas16->pcm_ctrl = val; break; case 0x1789: case 0x178b: pas16->midi_ctrl = val; if ((val & 0x60) == 0x60) { pas16->midi_uart_out = 0; pas16->midi_uart_in = 0; } else if (val & 0x18) { pas16->midi_uart_out = 1; } else if (val & 0x04) pas16->midi_uart_in = 1; else pas16->midi_uart_out = 1; pas16_update_irq(pas16); break; case 0x178a: case 0x1b8a: pas16->midi_data = val; pas16_log("UART OUT=%d.\n", pas16->midi_uart_out); if (pas16->midi_uart_out) midi_raw_out_byte(val); break; case 0x1b88: pas16->midi_stat = val; pas16_update_irq(pas16); break; case 0x1b89: pas16->fifo_stat = val; break; case 0x7f89: pas16->enhancedscsi = val; break; case 0x8388: if ((val & 0xc0) && !(pas16->sys_conf_1 & 0xc0)) { pas16_log("Reset.\n"); picintc(1 << pas16->irq); val = 0x00; } pas16->sys_conf_1 = val; break; case 0x8389: pas16->sys_conf_2 = val; break; case 0x838a: pas16->sys_conf_3 = val; break; case 0x838b: pas16->sys_conf_4 = val; break; case 0xbf88: pas16->waitstates = val; break; case 0xf388: pas16->io_conf_1 = val; break; case 0xf389: pas16->io_conf_2 = val; pas16->dma = pas16_dmas[val & 0x7]; pas16_log("pas16_out : set PAS DMA %i\n", pas16->dma); break; case 0xf38a: pas16->io_conf_3 = val; pas16->irq = val & 0x0f; if (pas16->irq <= 6) { pas16->irq++; } else if ((pas16->irq > 6) && (pas16->irq < 0x0b)) pas16->irq += 3; else pas16->irq += 4; pas16_log("pas16_out : set PAS IRQ %i, val=%02x\n", pas16->irq, val & 0x0f); break; case 0xf38b: pas16->io_conf_4 = val; break; case 0xf788: pas16->compat = val; if (pas16->compat & 0x02) sb_dsp_setaddr(&pas16->dsp, ((pas16->compat_base & 0xf) << 4) | 0x200); else sb_dsp_setaddr(&pas16->dsp, 0); if (pas16->compat & 0x01) mpu401_change_addr(pas16->mpu, ((pas16->compat_base & 0xf0) | 0x300)); else mpu401_change_addr(pas16->mpu, 0); break; case 0xf789: pas16->compat_base = val; if (pas16->compat & 0x02) sb_dsp_setaddr(&pas16->dsp, ((pas16->compat_base & 0xf) << 4) | 0x200); if (pas16->compat & 0x01) mpu401_change_addr(pas16->mpu, ((pas16->compat_base & 0xf0) | 0x300)); break; case 0xfb8a: pas16->sb_irqdma = val; sb_dsp_setirq(&pas16->dsp, pas16_sb_irqs[(val >> 3) & 7]); sb_dsp_setdma8(&pas16->dsp, pas16_sb_dmas[(val >> 6) & 3]); pas16_log("pas16_out : set SB IRQ %i DMA %i.\n", pas16_sb_irqs[(val >> 3) & 7], pas16_sb_dmas[(val >> 6) & 3]); break; default: pas16_log("pas16_out : unknown %04X\n", port); } } static uint8_t pas16_readdma(pas16_t *pas16) { return dma_channel_read(pas16->dma); } static void pas16_pcm_poll(void *priv) { pas16_t *pas16 = (pas16_t *) priv; pit_t *pit = (pit_t *) pas16->pit; int data; uint16_t temp = 0x0000; pas16_update(pas16); if (pit->counters[0].m & 0x02) { if (pit->counters[0].l & 0xff) { if (pas16->dma >= 5) timer_advance_u64(&pas16->timer, (pit->counters[0].l & 0xff) * (PITCONST << 1ULL)); else timer_advance_u64(&pas16->timer, (pit->counters[0].l & 0xff) * PITCONST); } else { if (pas16->dma >= 5) timer_advance_u64(&pas16->timer, 0x100 * (PITCONST << 1ULL)); else timer_advance_u64(&pas16->timer, 0x100 * PITCONST); } } pas16_update_irq(pas16); pas16->irq_stat |= PAS16_INT_SAMP; if (pas16->irq_ena & PAS16_INT_SAMP) { pas16_log("INT SAMP.\n"); picint(1 << pas16->irq); } /*Update sample rate counter*/ pas16_log("T1=%d, master bit 1=%x, counter0=%d, counter1=%d, pcm dma ena=%02x 16bit?=%02x.\n", pit->counters[1].enable, pit->counters[0].m & 0x02, pit->counters[0].l, pit->counters[1].l, pas16->pcm_ctrl & 0xc0, pas16->sys_conf_2 & PAS16_SC2_16BIT); if (pit->counters[1].enable) { if ((pas16->pcm_ctrl & (PAS16_PCM_ENA | PAS16_PCM_DMA_ENA))) { if (pas16->sys_conf_2 & PAS16_SC2_16BIT) { data = pas16_readdma(pas16) << 8; data |= pas16_readdma(pas16); temp = data; } else { data = pas16_readdma(pas16); temp = (data ^ 0x80) << 8; } if (pas16->sys_conf_2 & PAS16_SC2_MSBINV) temp ^= 0x8000; if (pas16->pcm_ctrl & PAS16_PCM_MONO) pas16->pcm_dat_l = pas16->pcm_dat_r = temp; else { if (pas16->stereo_lr) pas16->pcm_dat_r = temp; else pas16->pcm_dat_l = temp; pas16->stereo_lr = !pas16->stereo_lr; } } if (pas16->sys_conf_2 & PAS16_SC2_16BIT) { pit->counters[1].lback -= 2; if (!pit->counters[1].lback) { if (pit->counters[1].m & 0x02) { if (pit->counters[1].lback2 & 0xfffe) pit->counters[1].lback = pit->counters[1].lback2 & 0xfffe; else pit->counters[1].lback = 0; } else { pit->counters[1].lback = 0; pit->counters[1].enable = 0; } pas16_log("16-bit: New counter=%d, mode=%x.\n", pit->counters[1].lback, pit->counters[1].m & 0x03); pas16->irq_stat |= PAS16_INT_PCM; if (pas16->irq_ena & PAS16_INT_PCM) { pas16_log("16-bit: INT PCM.\n"); picint(1 << pas16->irq); } } } else { pit->counters[1].lback--; if (!pit->counters[1].lback) { if (pit->counters[1].m & 0x02) { if (pit->counters[1].lback2 & 0xffff) pit->counters[1].lback = pit->counters[1].lback2 & 0xffff; else pit->counters[1].lback = 0; } else { pit->counters[1].lback = 0; pit->counters[1].enable = 0; } pas16_log("8-bit: New counter=%d, mode=%x.\n", pit->counters[1].lback, pit->counters[1].m & 0x03); pas16->irq_stat |= PAS16_INT_PCM; if (pas16->irq_ena & PAS16_INT_PCM) { pas16_log("8-bit: INT PCM.\n"); picint(1 << pas16->irq); } } } } } static void pas16_pit_timer0(int new_out, UNUSED(int old_out), void *priv) { pit_t *pit = (pit_t *)priv; pas16_t *pas16 = (pas16_t *)pit->dev_priv; pas16_log("PAS16 pit timer0 out=%x, cnt0=%d, cnt1=%d.\n", new_out, pit->counters[0].l, pit->counters[1].l); pit_ctr_set_clock(&pit->counters[0], new_out, pit); pit->counters[1].enable = new_out; if (!timer_is_enabled(&pas16->timer)) { if (pas16->dma >= 5) timer_set_delay_u64(&pas16->timer, (pit->counters[0].l & 0xff) * (PITCONST << 1ULL)); else timer_set_delay_u64(&pas16->timer, (pit->counters[0].l & 0xff) * PITCONST); } } static void pas16_out_base(UNUSED(uint16_t port), uint8_t val, void *priv) { pas16_t *pas16 = (pas16_t *) priv; for (uint32_t addr = 0x000; addr < 0x10000; addr += 0x400) { if (addr != 0x1000) { io_removehandler(pas16->base + addr, 0x0004, pas16_in, NULL, NULL, pas16_out, NULL, NULL, pas16); io_sethandler(pas16->base + addr, 0x0004, pas16_in, NULL, NULL, pas16_out, NULL, NULL, pas16); } } pit_handler(0, pas16->base + 0x1000, 0x0004, pas16->pit); pit_handler(1, pas16->base + 0x1000, 0x0004, pas16->pit); pas16->base = val << 2; } static void pas16_input_msg(void *priv, uint8_t *msg, uint32_t len) { pas16_t *pas16 = (pas16_t *) priv; if (pas16->sysex) return; if (pas16->midi_uart_in) { pas16->midi_stat |= 0x04; for (uint32_t i = 0; i < len; i++) { pas16->midi_queue[pas16->midi_w++] = msg[i]; pas16->midi_w &= 0xff; } pas16_update_irq(pas16); } } static int pas16_input_sysex(void *priv, uint8_t *buffer, uint32_t len, int abort) { pas16_t *pas16 = (pas16_t *) priv; if (abort) { pas16->sysex = 0; return 0; } pas16->sysex = 1; for (uint32_t i = 0; i < len; i++) { if (pas16->midi_r == pas16->midi_w) return (len - i); pas16->midi_queue[pas16->midi_w++] = buffer[i]; pas16->midi_w &= 0xff; } pas16->sysex = 0; return 0; } static void pas16_update(pas16_t *pas16) { if (!(pas16->audiofilt & PAS16_FILT_MUTE)) { for (; pas16->pos < sound_pos_global; pas16->pos++) { pas16->pcm_buffer[0][pas16->pos] = 0; pas16->pcm_buffer[1][pas16->pos] = 0; } } else { for (; pas16->pos < sound_pos_global; pas16->pos++) { pas16->pcm_buffer[0][pas16->pos] = (int16_t) pas16->pcm_dat_l; pas16->pcm_buffer[1][pas16->pos] = (int16_t) pas16->pcm_dat_r; } } } void pas16_get_buffer(int32_t *buffer, int len, void *priv) { pas16_t *pas16 = (pas16_t *) priv; sb_dsp_update(&pas16->dsp); pas16_update(pas16); for (int c = 0; c < len * 2; c++) { buffer[c] += (int16_t) (sb_iir(0, c & 1, (double) pas16->dsp.buffer[c]) / 1.3) / 2; buffer[c] += (pas16->pcm_buffer[c & 1][c >> 1] / 2); } pas16->pos = 0; pas16->dsp.pos = 0; } void pas16_get_music_buffer(int32_t *buffer, int len, void *priv) { pas16_t *pas16 = (pas16_t *) priv; const int32_t *opl_buf = pas16->opl.update(pas16->opl.priv); for (int c = 0; c < len * 2; c++) buffer[c] += opl_buf[c]; pas16->opl.reset_buffer(pas16->opl.priv); } static void * pas16_init(UNUSED(const device_t *info)) { pas16_t *pas16 = malloc(sizeof(pas16_t)); memset(pas16, 0, sizeof(pas16_t)); fm_driver_get(FM_YMF262, &pas16->opl); sb_dsp_set_real_opl(&pas16->dsp, 1); sb_dsp_init(&pas16->dsp, SB2, SB_SUBTYPE_DEFAULT, pas16); pas16->mpu = (mpu_t *) malloc(sizeof(mpu_t)); memset(pas16->mpu, 0, sizeof(mpu_t)); mpu401_init(pas16->mpu, 0, 0, M_UART, device_get_config_int("receive_input401")); sb_dsp_set_mpu(&pas16->dsp, pas16->mpu); pas16->pit = device_add(&i8254_ext_io_device); pas16->pit->dev_priv = pas16; pas16->irq = 10; pas16->dma = 3; pas16->base = 0x0388; io_sethandler(0x9a01, 0x0001, NULL, NULL, NULL, pas16_out_base, NULL, NULL, pas16); pit_ctr_set_out_func(pas16->pit, 0, pas16_pit_timer0); pit_ctr_set_using_timer(pas16->pit, 0, 1); pit_ctr_set_using_timer(pas16->pit, 1, 0); pit_ctr_set_using_timer(pas16->pit, 2, 0); timer_add(&pas16->timer, pas16_pcm_poll, pas16, 0); sound_add_handler(pas16_get_buffer, pas16); music_add_handler(pas16_get_music_buffer, pas16); if (device_get_config_int("receive_input")) midi_in_handler(1, pas16_input_msg, pas16_input_sysex, pas16); return pas16; } static void pas16_close(void *priv) { pas16_t *pas16 = (pas16_t *) priv; free(pas16); } static const device_config_t pas16_config[] = { { .name = "receive_input401", .description = "Receive input (MPU-401)", .type = CONFIG_BINARY, .default_string = "", .default_int = 0 }, { .name = "receive_input", .description = "Receive input (PAS16 MIDI)", .type = CONFIG_BINARY, .default_string = "", .default_int = 1 }, { .name = "", .description = "", .type = CONFIG_END } }; const device_t pas16_device = { .name = "Pro Audio Spectrum 16", .internal_name = "pas16", .flags = DEVICE_ISA | DEVICE_AT, .local = 0, .init = pas16_init, .close = pas16_close, .reset = NULL, { .available = NULL }, .speed_changed = NULL, .force_redraw = NULL, .config = pas16_config };