From 0df1b2c408cc5ae37940149d6c12135db81826a5 Mon Sep 17 00:00:00 2001 From: Maxim Poliakovski Date: Wed, 13 Apr 2022 23:25:28 +0200 Subject: [PATCH] ATI Mach64 GX controller emulation. --- devices/video/atimach64gx.cpp | 358 ++++++++++++++++++++++++++++++++++ devices/video/atimach64gx.h | 84 ++++++++ devices/video/rgb514defs.h | 65 ++++++ 3 files changed, 507 insertions(+) create mode 100644 devices/video/atimach64gx.cpp create mode 100644 devices/video/atimach64gx.h create mode 100644 devices/video/rgb514defs.h diff --git a/devices/video/atimach64gx.cpp b/devices/video/atimach64gx.cpp new file mode 100644 index 0000000..f3795d5 --- /dev/null +++ b/devices/video/atimach64gx.cpp @@ -0,0 +1,358 @@ +/* +DingusPPC - The Experimental PowerPC Macintosh emulator +Copyright (C) 2018-22 divingkatae and maximum + (theweirdo) spatium + +(Contact divingkatae#1017 or powermax#2286 on Discord for more info) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +/** ATI Mach64 GX emulation. + It emulates an ATI88800GX controller with an IBM RGB514 style RAMDAC. + Emulation is limited to a basic framebuffer for now. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +AtiMach64Gx::AtiMach64Gx() + : PCIDevice("ati-mach64-gx"), VideoCtrlBase(1024, 768) +{ + supports_types(HWCompType::MMIO_DEV | HWCompType::PCI_DEV); + + /* set up PCI configuration space header */ + this->vendor_id = PCI_VENDOR_ATI; + this->device_id = ATI_MACH64_GX_DEV_ID; + this->class_rev = (0x030000 << 8) | 3; + this->bars_cfg[0] = 0xFF000000UL; // declare main aperture (16MB) + + this->pci_notify_bar_change = [this](int bar_num) { + this->notify_bar_change(bar_num); + }; + + // declare expansion ROM containing FCode and Mac OS drivers + if (this->attach_exp_rom_image(std::string("113-32900-004_Apple_MACH64.bin"))) { + LOG_F(WARNING, "AtiMach64Gx: could not load ROM - this device won't work properly!"); + } + + // initialize display identification + this->disp_id = std::unique_ptr (new DisplayID(0x07, 0x3A)); + + // allocate video RAM + this->vram_size = 2 << 20; + this->vram_ptr = std::unique_ptr (new uint8_t[this->vram_size]); +} + +void AtiMach64Gx::notify_bar_change(int bar_num) +{ + if (bar_num) // only BAR0 is supported + return; + + if (this->aperture_base != (this->bars[bar_num] & 0xFFFFFFF0UL)) { + if (this->aperture_base) { + LOG_F(WARNING, "AtiMach64Gx: deallocating I/O memory not implemented"); + } + + this->aperture_base = this->bars[0] & 0xFFFFFFF0UL; + this->host_instance->pci_register_mmio_region(this->aperture_base, + APERTURE_SIZE, this); + + // copy aperture address to CONFIG_CNTL:CFG_MEM_AP_LOC + this->config_cntl = (this->config_cntl & 0xFFFFC00FUL) | + ((this->aperture_base >> 18) & 0x3FF0U); + + LOG_F(INFO, "AtiMach64Gx: aperture address set to 0x%08X", this->aperture_base); + } +} + +// map I/O register index to MMIO register offset +static const uint32_t io_idx_to_reg_offset[32] = { + ATI_CRTC_H_TOTAL_DISP, ATI_CRTC_H_SYNC_STRT_WID, + ATI_CRTC_V_TOTAL_DISP, ATI_CRTC_V_SYNC_STRT_WID, + ATI_CRTC_VLINE_CRNT_VLINE, ATI_CRTC_OFF_PITCH, + ATI_CRTC_INT_CNTL, ATI_CRTC_GEN_CNTL, + ATI_OVR_CLR, ATI_OVR_WID_LEFT_RIGHT, + ATI_OVR_WID_TOP_BOTTOM, ATI_CUR_CLR0, + ATI_CUR_CLR1, ATI_CUR_OFFSET, + ATI_CUR_HORZ_VERT_POSN, ATI_CUR_HORZ_VERT_OFF, + ATI_SCRATCH_REG0, ATI_SCRATCH_REG1, + ATI_CLOCK_CNTL, ATI_BUS_CNTL, + ATI_MEM_CNTL, ATI_MEM_VGA_WP_SEL, + ATI_MEM_VGA_RP_SEL, ATI_DAC_REGS, + ATI_DAC_CNTL, ATI_GEN_TEST_CNTL, + ATI_CONFIG_CNTL, ATI_CONFIG_CHIP_ID, + ATI_CONFIG_STAT0, ATI_GX_CONFIG_STAT1, + ATI_INVALID, ATI_INVALID +}; + +bool AtiMach64Gx::pci_io_read(uint32_t offset, uint32_t size, uint32_t* res) +{ + *res = 0; + + // check for valid I/O base and I/O access permission + if ((offset & 0x3FC) != 0x2EC || !(this->command & 1)) { + return false; + } + + // convert ISA-style I/O address to MMIO register offset + offset = io_idx_to_reg_offset[(offset >> 10) & 0x1F] + (offset & 3); + + // CONFIG_CNTL is accessible from I/O space only + if ((offset & ~3) == ATI_CONFIG_CNTL) { + *res = read_mem(((uint8_t *)&this->config_cntl) + (offset & 3), size); + } else { + *res = this->read_reg(offset, size); + } + + return true; +} + +bool AtiMach64Gx::pci_io_write(uint32_t offset, uint32_t value, uint32_t size) +{ + // check for valid I/O base and I/O access permission + if ((offset & 0x3FC) != 0x2EC || !(this->command & 1)) { + return false; + } + + // convert ISA-style I/O address to MMIO register offset + offset = io_idx_to_reg_offset[(offset >> 10) & 0x1F] + (offset & 3); + + // CONFIG_CNTL is accessible from I/O space only + if ((offset & ~3) == ATI_CONFIG_CNTL) { + write_mem(((uint8_t *)&this->config_cntl) + (offset & 3), value, size); + switch (this->config_cntl & 3) { + case 0: + LOG_F(WARNING, "AtiMach64Gx: linear aperture disabled!"); + break; + case 1: + LOG_F(INFO, "AtiMach64Gx: aperture size set to 4MB"); + this->mm_regs_offset = MM_REGS_2_OFF; + break; + case 2: + LOG_F(INFO, "AtiMach64Gx: aperture size set to 8MB"); + this->mm_regs_offset = MM_REGS_0_OFF; + break; + default: + LOG_F(ERROR, "AtiMach64Gx: invalid aperture size in CONFIG_CNTL"); + } + } else { + this->write_reg(offset, value, size); + } + + return true; +} + +uint32_t AtiMach64Gx::read_reg(uint32_t offset, uint32_t size) +{ + LOG_F(INFO, "AtiMach64Gx: read from I/O mapped reg at 0x%X", offset); + + return read_mem(&this->mm_regs[offset], size); +} + +void AtiMach64Gx::write_reg(uint32_t offset, uint32_t value, uint32_t size) +{ + uint8_t gpio_dirs, gpio_levels; + int crtc_en; + + // write value to internal registers with endian conversion + write_mem(&this->mm_regs[offset], value, size); + + switch (offset & ~3) { + case ATI_CRTC_GEN_CNTL: + crtc_en = (this->mm_regs[ATI_CRTC_GEN_CNTL+3] >> 1) & 1; + if (crtc_en != this->crtc_enable) { + if (!crtc_en) { + this->disable_crtc_internal(); + } + } + break; + case ATI_DAC_REGS: + if (size == 1) { // only byte accesses are allowed for DAC registers + int dac_reg_addr = ((this->mm_regs[ATI_DAC_CNTL] & 1) << 2) | (offset & 3); + rgb514_write_reg(dac_reg_addr, value); + } + break; + case ATI_DAC_CNTL: + // monitor ID is usually accessed using 8bit writes + if ((offset & 3) == 3) { + gpio_dirs = (value >> 3) & 7; + gpio_levels = value & 7; + gpio_levels = this->disp_id->read_monitor_sense(gpio_levels, gpio_dirs); + this->mm_regs[ATI_DAC_CNTL+3] = (this->mm_regs[ATI_DAC_CNTL+3] & 0xF8U) | gpio_levels; + } + break; + default: + LOG_F(INFO, "AtiMach64Gx: write 0x%X to I/O mapped reg at 0x%X", value, offset); + } +} + +uint32_t AtiMach64Gx::read(uint32_t rgn_start, uint32_t offset, int size) +{ + if (rgn_start == this->aperture_base) { + if (offset < this->vram_size) { + return read_mem(&this->vram_ptr[offset], size); + } else if (offset >= this->mm_regs_offset) { + return read_reg(offset - this->mm_regs_offset, size); + } + } + + // memory mapped expansion ROM region + if (rgn_start >= this->exp_rom_addr && offset < this->exp_rom_size) { + return read_mem(&this->exp_rom_data[offset], size); + } + + return 0; +} + +void AtiMach64Gx::write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size) +{ + if (rgn_start == this->aperture_base) { + if (offset < this->vram_size) { + write_mem(&this->vram_ptr[offset], value, size); + } else if (offset >= this->mm_regs_offset) { + write_reg(offset - this->mm_regs_offset, value, size); + } + } +} + +void AtiMach64Gx::enable_crtc_internal() +{ + int new_width = (this->mm_regs[ATI_CRTC_H_TOTAL_DISP+2] + 1) * 8; + + int new_height = (READ_WORD_LE_A(&this->mm_regs[ATI_CRTC_V_TOTAL_DISP+2]) & 0x7FFUL) + 1; + + if (new_width != this->active_width || new_height != this->active_height) { + LOG_F(WARNING, "AtiMach64Gx: display window resizing not implemented yet!"); + } + + /* calculate display refresh rate */ + int hori_total = (this->mm_regs[ATI_CRTC_H_TOTAL_DISP] + 1) * 8; + + int vert_total = (READ_WORD_LE_A(&this->mm_regs[ATI_CRTC_V_TOTAL_DISP]) & 0x7FFUL) + 1; + + this->refresh_rate = this->pixel_clock / hori_total / vert_total; + + int32_t src_offset = (READ_DWORD_LE_A(&this->mm_regs[ATI_CRTC_OFF_PITCH]) & 0xFFFFFU) * 8; + + this->fb_pitch = ((READ_DWORD_LE_A(&this->mm_regs[ATI_CRTC_OFF_PITCH])) >> 19) & 0x1FF8U; + + this->fb_ptr = &this->vram_ptr[src_offset]; + + // specify framebuffer converter + switch (this->pixel_depth) { + case 8: + this->convert_fb_cb = [this](uint8_t *dst_buf, int dst_pitch) { + this->convert_frame_8bpp(dst_buf, dst_pitch); + }; + break; + default: + ABORT_F("AtiMach64Gx: unsupported pixel depth %d", this->pixel_depth); + } + + uint64_t refresh_interval = static_cast(1.0f / this->refresh_rate * NS_PER_SEC + 0.5); + TimerManager::get_instance()->add_cyclic_timer( + refresh_interval, + [this]() { + this->update_screen(); + } + ); + + this->update_screen(); + + this->crtc_on = true; + + this->crtc_enable = 1; +} + +void AtiMach64Gx::disable_crtc_internal() +{ + this->crtc_enable = 0; +} + +// ========================== IBM RGB514 related code ========================== +void AtiMach64Gx::rgb514_write_reg(uint8_t reg_addr, uint8_t value) +{ + switch (reg_addr) { + case Rgb514::CLUT_ADDR_WR: + this->clut_index = value; + break; + case Rgb514::CLUT_DATA: + this->clut_color[this->comp_index++] = value; + if (this->comp_index >= 3) { + // TODO: combine separate components into a single ARGB value + this->palette[this->clut_index][0] = this->clut_color[0]; + this->palette[this->clut_index][1] = this->clut_color[1]; + this->palette[this->clut_index][2] = this->clut_color[2]; + this->palette[this->clut_index][3] = 255; + this->clut_index++; + this->comp_index = 0; + } + break; + case Rgb514::CLUT_MASK: + if (value != 0xFF) { + LOG_F(WARNING, "RGB514: pixel mask set to 0x%X", value); + } + break; + case Rgb514::INDEX_LOW: + this->dac_idx_lo = value; + break; + case Rgb514::INDEX_HIGH: + this->dac_idx_hi = value; + break; + case Rgb514::INDEX_DATA: + this->rgb514_write_ind_reg((this->dac_idx_hi << 8) + this->dac_idx_lo, value); + break; + default: + LOG_F(WARNING, "RGB514: access to unimplemented register at 0x%X", reg_addr); + } +} + +void AtiMach64Gx::rgb514_write_ind_reg(uint8_t reg_addr, uint8_t value) +{ + LOG_F(INFO, "RGB514: write 0x%X to register 0x%X", value, reg_addr); + + this->dac_regs[reg_addr] = value; + + switch (reg_addr) { + case Rgb514::MISC_CLK_CNTL: + if (value & PLL_ENAB) { + if ((this->dac_regs[Rgb514::PLL_CTL_1] & 3) != 1) + ABORT_F("RGB514: unsupported PLL source"); + + int m = 8 >> (this->dac_regs[Rgb514::F0_M0] >> 6); + int vco_div = (this->dac_regs[Rgb514::F0_M0] & 0x3F) + 65; + int ref_div = (this->dac_regs[Rgb514::F1_N0] & 0x1F) * m; + this->pixel_clock = ATI_XTAL * vco_div / ref_div; + LOG_F(INFO, "RGB514: dot clock set to %f Hz", this->pixel_clock); + } + break; + case Rgb514::PIX_FORMAT: + if (value == 3) { + this->pixel_depth = 8; + // HACK: not the best place for enabling display output! + this->enable_crtc_internal(); + } else { + ABORT_F("RGB514: unimplemented pixel format %d", value); + } + break; + } +} diff --git a/devices/video/atimach64gx.h b/devices/video/atimach64gx.h new file mode 100644 index 0000000..bda2dfc --- /dev/null +++ b/devices/video/atimach64gx.h @@ -0,0 +1,84 @@ +/* +DingusPPC - The Experimental PowerPC Macintosh emulator +Copyright (C) 2018-22 divingkatae and maximum + (theweirdo) spatium + +(Contact divingkatae#1017 or powermax#2286 on Discord for more info) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +/** ATI Mach64 GX definitions. */ + +#ifndef ATI_MACH64_GX_H +#define ATI_MACH64_GX_H + +#include +#include +#include +#include + +#include +#include + +class AtiMach64Gx : public PCIDevice, public VideoCtrlBase { +public: + AtiMach64Gx(); + ~AtiMach64Gx() = default; + + /* PCI device methods */ + bool supports_io_space(void) { + return true; + }; + + /* I/O space access methods */ + bool pci_io_read(uint32_t offset, uint32_t size, uint32_t* res); + bool pci_io_write(uint32_t offset, uint32_t value, uint32_t size); + + /* MMIODevice methods */ + uint32_t read(uint32_t rgn_start, uint32_t offset, int size); + void write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size); + +protected: + void notify_bar_change(int bar_num); + uint32_t read_reg(uint32_t offset, uint32_t size); + void write_reg(uint32_t offset, uint32_t value, uint32_t size); + void enable_crtc_internal(); + void disable_crtc_internal(); + + void rgb514_write_reg(uint8_t reg_addr, uint8_t value); + void rgb514_write_ind_reg(uint8_t reg_addr, uint8_t value); + +private: + uint8_t mm_regs[2048] = { 0 }; // internal registers + + int vram_size; + uint32_t aperture_base; + uint32_t config_cntl; + uint32_t mm_regs_offset; + int crtc_enable = 0; + + // RGB514 RAMDAC state + uint8_t dac_idx_lo; + uint8_t dac_idx_hi; + uint8_t clut_index; + uint8_t comp_index; + uint8_t clut_color[3]; + uint8_t dac_regs[256]; + + std::unique_ptr disp_id; + std::unique_ptr vram_ptr; +}; + +#endif /* ATI_MACH64_GX_H */ diff --git a/devices/video/rgb514defs.h b/devices/video/rgb514defs.h new file mode 100644 index 0000000..0aabd84 --- /dev/null +++ b/devices/video/rgb514defs.h @@ -0,0 +1,65 @@ +/* +DingusPPC - The Experimental PowerPC Macintosh emulator +Copyright (C) 2018-22 divingkatae and maximum + (theweirdo) spatium + +(Contact divingkatae#1017 or powermax#2286 on Discord for more info) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +/** IBM RGB514 RAMDAC definitions. */ + +#include + +#ifndef IBM_RGB514_DEFS_H +#define IBM_RGB514_DEFS_H + +namespace Rgb514 { + +#define PLL_ENAB 1 + +/** RGB514 direct register addresses. */ +enum { + // standard VGA registers + CLUT_ADDR_WR = 0, + CLUT_DATA = 1, + CLUT_MASK = 2, + CLUT_ADDR_RD = 3, + + // extended registers + INDEX_LOW = 4, + INDEX_HIGH = 5, + INDEX_DATA = 6, + INDEX_CNTL = 7 +}; + +/** RGB514 indirect registers. */ +enum { + MISC_CLK_CNTL = 0x0002, + HOR_SYNC_POS = 0x0004, + PWR_MNMGMT = 0x0005, + PIX_FORMAT = 0x000A, + PLL_CTL_1 = 0x0010, + F0_M0 = 0x0020, + F1_N0 = 0x0021, + MISC_CNTL_1 = 0x0070, + MISC_CNTL_2 = 0x0071, + VRAM_MASK_LO = 0x0090, + VRAM_MASK_HI = 0x0091, +}; + +}; // namespace Rgb514 + +#endif // IBM_RGB514_DEFS_H