From e5282f9852a2ac0c9fae339210255aa6f65b126a Mon Sep 17 00:00:00 2001 From: Maxim Poliakovski Date: Tue, 9 Apr 2024 01:44:26 +0200 Subject: [PATCH] Initial Taos emulation. --- devices/video/taos.cpp | 260 +++++++++++++++++++++++++++++++++++++++++ devices/video/taos.h | 195 +++++++++++++++++++++++++++++++ 2 files changed, 455 insertions(+) create mode 100644 devices/video/taos.cpp create mode 100644 devices/video/taos.h diff --git a/devices/video/taos.cpp b/devices/video/taos.cpp new file mode 100644 index 0000000..70fd0ce --- /dev/null +++ b/devices/video/taos.cpp @@ -0,0 +1,260 @@ +/* +DingusPPC - The Experimental PowerPC Macintosh emulator +Copyright (C) 2018-24 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 . +*/ + +/** @file Taos video controller emulation. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +TaosVideo::TaosVideo() : VideoCtrlBase(640, 480) { + set_name("Taos"); + + supports_types(HWCompType::MMIO_DEV); + + // initialize the video clock generator + this->clk_gen = std::unique_ptr(new AthensClocks(0x29, 20000000.0f)); + + // register the video clock generator with the I2C host + I2CBus* i2c_bus = dynamic_cast(gMachineObj->get_comp_by_type( + HWCompType::I2C_HOST)); + i2c_bus->register_device(0x29, this->clk_gen.get()); + + // initialize video encoder + this->vid_enc = std::unique_ptr(new Bt856(0x44)); + + // register the video encoder with the I2C host + i2c_bus->register_device(0x44, this->vid_enc.get()); + + MemCtrlBase* mem_ctrl = dynamic_cast( + gMachineObj->get_comp_by_type(HWCompType::MEM_CTRL)); + + // add MMIO region for VRAM + mem_ctrl->add_ram_region(TAOS_VRAM_REGION_BASE, DRAM_CAP_1MB); + this->vram_ptr = mem_ctrl->get_region_hostmem_ptr(TAOS_VRAM_REGION_BASE); + + // add MMIO region for the configuration and status registers + mem_ctrl->add_mmio_region(TAOS_IOREG_BASE, 0x800, this); + + // add MMIO region for the CLUT + mem_ctrl->add_mmio_region(TAOS_CLUT_BASE, 0x400, this); + + this->vbl_cb = [this](uint8_t irq_line_state) { + this->vsync_active = irq_line_state; + }; + + std::string video_out = GET_STR_PROP("video_out"); + if (video_out == "VGA") + this->mon_id = MON_ID_VGA; + else if (video_out == "PAL") + this->mon_id = MON_ID_PAL; + else + this->mon_id = MON_ID_NTSC; // NTSC is set by default in the driver +} + +uint32_t TaosVideo::read(uint32_t rgn_start, uint32_t offset, int size) { + if (rgn_start == TAOS_CLUT_BASE) { + uint8_t r, g, b, a; + get_palette_color(offset >> 2, r, g, b, a); + return (r << 24) | (g << 16) | (b << 8); + } + + int reg_num = offset >> 2; + + switch(reg_num) { + case CRT_CTRL: + return this->crt_ctrl; + case GPIO_IN: + return ((this->mon_id << 29) | (vsync_active << 25)) & ~this->gpio_cfg; + case INT_ENABLES: + return this->int_enables; + case TAOS_VERSION: + return TAOS_CHIP_VERSION; + default: + LOG_F(WARNING, "%s: reading register at 0x%X", this->name.c_str(), offset); + } + + return 0; +} + +void TaosVideo::write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size) { + if (rgn_start == TAOS_CLUT_BASE) { + set_palette_color(offset >> 2, value >> 24, (value >> 16) & 0xFF, + (value >> 8) & 0xFF, 0xFF); + return; + } + + int reg_num = offset >> 2; + + switch(reg_num) { + case FB_BASE: + this->fb_base = value; + break; + case ROW_WORDS: + this->row_words = value; + break; + case COLOR_MODE: + this->color_mode = value; + break; + case VIDEO_MODE: + this->video_mode = value >> 30; + break; + case CRT_CTRL: + if (bit_changed(this->crt_ctrl, value, ENABLE_VIDEO_OUT)) { + if (bit_set(value, ENABLE_VIDEO_OUT)) + enable_display(); + } + this->crt_ctrl = value; + break; + case HEQ: + case HBWAY: + case HAL: + case HSERR: + case HFP: + case HPIX: + case HSP: + case HLFLN: + case VBPEQ: + case VBP: + case VAL: + case VFP: + case VFPEQ: + case VSYNC: + case VHLINE: + this->swatch_regs[REG_TO_INDEX(reg_num)] = value; + break; + case GPIO_CONFIG: + this->gpio_cfg = value >> 24; + break; + case GPIO_OUT: + LOG_F(INFO, "%s: value 0x%X written into GPIO pins, GPIO_CONFIG=0x%X", + this->name.c_str(), value >> 24, this->gpio_cfg); + break; + case INT_ENABLES: + this->int_enables = value; + if (bit_set(value, 29)) + LOG_F(WARNING, "%s: VBL interrupt enabled", this->name.c_str()); + for (int gpio_pin = 0; gpio_pin < 8; gpio_pin++) { + if (bit_set(value, 27 - gpio_pin)) + LOG_F(INFO, "%s: GPIO %d interrupt enabled", this->name.c_str(), + gpio_pin); + } + break; + default: + LOG_F(WARNING, "%s: register at 0x%X set to 0x%X", this->name.c_str(), offset, value); + } +} + +void TaosVideo::enable_display() { + int new_width, new_height; + + // get pixel frequency from Athens + this->pixel_clock = this->clk_gen->get_dot_freq(); + + // calculate active_width and active_height from video timing parameters + new_width = (swatch_regs[REG_TO_INDEX(HFP)] >> 20) - (swatch_regs[REG_TO_INDEX(HAL)] >> 20); + new_height = (swatch_regs[REG_TO_INDEX(VFP)] >> 20) - (swatch_regs[REG_TO_INDEX(VAL)] >> 20); + + LOG_F(INFO, "%s: width=%d, height=%d", this->name.c_str(), new_width, new_height); + + // calculate display refresh rate + this->hori_blank = (swatch_regs[REG_TO_INDEX(HAL)] >> 20) + + ((swatch_regs[REG_TO_INDEX(HSP)] >> 20) - (swatch_regs[REG_TO_INDEX(HFP)] >> 20)); + + this->vert_blank = (swatch_regs[REG_TO_INDEX(VAL)] >> 20) + + ((swatch_regs[REG_TO_INDEX(VSYNC)] >> 20) - (swatch_regs[REG_TO_INDEX(VFP)] >> 20)); + + this->hori_total = this->hori_blank + new_width; + this->vert_total = this->vert_blank + new_height; + + this->stop_refresh_task(); + + this->refresh_rate = (double)(this->pixel_clock) / (this->hori_total * this->vert_total); + + LOG_F(INFO, "%s: refresh rate set to %f Hz", this->name.c_str(), this->refresh_rate); + + // set framebuffer parameters + this->fb_ptr = &this->vram_ptr[this->fb_base >> 20]; + this->fb_pitch = this->row_words >> 20; + + if (bit_set(this->color_mode, 31)) { + this->pixel_depth = 16; + this->convert_fb_cb = [this](uint8_t *dst_buf, int dst_pitch) { + this->convert_frame_15bpp_indexed(dst_buf, dst_pitch); + }; + } else { + this->pixel_depth = 8; + this->convert_fb_cb = [this](uint8_t *dst_buf, int dst_pitch) { + this->convert_frame_8bpp_indexed(dst_buf, dst_pitch); + }; + } + + this->start_refresh_task(); + + this->blank_on = false; + this->crtc_on = true; +} + +void TaosVideo::convert_frame_15bpp_indexed(uint8_t *dst_buf, int dst_pitch) { + uint8_t *src_row, *dst_row; + uint16_t c; + uint32_t pix; + int src_pitch; + + src_row = this->fb_ptr; + dst_row = dst_buf; + + src_pitch = this->fb_pitch - 2 * this->active_width; + dst_pitch = dst_pitch - 4 * this->active_width; + + for (int h = this->active_height; h > 0; h--) { + for (int x = this->active_width; x > 0; x--) { + c = READ_WORD_BE_A(src_row); + pix = (this->palette[(c >> 10) & 0x1F] & 0x00FF0000) | + (this->palette[(c >> 5) & 0x1F] & 0x0000FF00) | + (this->palette[ c & 0x1F] & 0xFF0000FF); + WRITE_DWORD_LE_A(dst_row, pix); + src_row += 2; + dst_row += 4; + } + src_row += src_pitch; + dst_row += dst_pitch; + } +} + +// ========================== Device registry stuff ========================== +static const PropMap Taos_Properties = { + {"video_out", new StrProperty("NTSC", {"PAL", "NTSC", "VGA"})}, +}; + +static const DeviceDescription Taos_Descriptor = { + TaosVideo::create, {}, Taos_Properties +}; + +REGISTER_DEVICE(TaosVideo, Taos_Descriptor); diff --git a/devices/video/taos.h b/devices/video/taos.h new file mode 100644 index 0000000..911d37d --- /dev/null +++ b/devices/video/taos.h @@ -0,0 +1,195 @@ +/* +DingusPPC - The Experimental PowerPC Macintosh emulator +Copyright (C) 2018-24 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 . +*/ + +/** @file Taos video controller definitions. */ + +/** + Kudos to Keith Kaisershot @ blitter.net for his precious technical help! +*/ + +#ifndef TAOS_VIDEO_H +#define TAOS_VIDEO_H + +#include +#include +#include + +#include +#include + +#define TAOS_IOREG_BASE 0xF0800000 +#define TAOS_VRAM_REGION_BASE 0xF0000000 +#define TAOS_CLUT_BASE 0xF1000000 +#define TAOS_CHIP_VERSION 0xA5000000 + +/** Taos register definitions. */ +// The implemented bits always start with the most significant bit (bit 31). +// Unimplemented bits return zeroes. +enum { + // framebuffer configuration and control + FB_BASE = 0x00, // 0x00, frame buffer base address, 19 bits, 32 byte aligned + ROW_WORDS = 0x01, // 0x04, frame buffer pitch 12 bits, 32 byte aligned + COLOR_MODE = 0x02, // 0x08, color mode 1 bit (MSB) + VIDEO_MODE = 0x03, // 0x0C, current video mode, 2 bits (MSB) + VEFS = 0x04, // 0x10, vertical even field start, 12 bits + VOFS = 0x05, // 0x14, vertical odd field start, 12 bits + HSTART = 0x06, // 0x18, horizontal active start, 12 bits + VCONV_END = 0x07, // 0x1C, vertical convolution end, 12 bits + CRT_CTRL = 0x08, // 0x20, frame buffer control, 11 bits + CRT_TEST = 0x09, // 0x24, enables test features, 18 bits + + // timing generator parameters + HEQ = 0x0B, // 0x2C, horizontal equalization, 8 bits + HBWAY = 0x0C, // 0x30, horizontal breezeway, 12 bits + HAL = 0x0D, // 0x34, horizontal active line, 12 bits + HSERR = 0x0E, // 0x38, horizontal serration, 12 bits + HFP = 0x0F, // 0x3C, horizontal front porch, 12 bits + HPIX = 0x10, // 0x40, horizontal pixel count, 12 bits + HSP = 0x11, // 0x44, horizontal sync pulse, 12 bits + HLFLN = 0x12, // 0x48, horizontal half line, 12 bits + VBPEQ = 0x13, // 0x4C, vertical back porch with EQ, 12 bits + VBP = 0x14, // 0x50, vertical back porch, 12 bits + VAL = 0x15, // 0x54, vertical active line, 12 bits + VFP = 0x16, // 0x58, vertical front porch, 12 bits + VFPEQ = 0x17, // 0x5C, vertical front porch with EQ, 12 bits + VSYNC = 0x18, // 0x60, vertical sync starting point, 12 bits + VHLINE = 0x19, // 0x64, vertical half line, 12 bits + + // GPIO and interrupts + GPIO_IN = 0x1A, // 0x68, reads GPIO pins, 8 bits + GPIO_CONFIG = 0x1B, // 0x6C, configure GPIO pins, 8 bits + GPIO_OUT = 0x1C, // 0x70, outputs to GPIO pins, 8 bits + GPIO_INT_CTRL = 0x1D, // 0x74, controls GPIO interrupts, 8 bits + INT_LEVELS = 0x1E, // 0x78, interrupt levels, 12 bits (read-only) + INT_ENABLES = 0x1F, // 0x7C, enable/disable interrupts, 12 bits + INT_CLEAR = 0x20, // 0x80, clear pending interrupts, 12 bits (write-only) + TAOS_VERSION = 0x23, // 0x8C, chip version, 8 bits (read-only) + INT_SET = 0x24, // 0x90, allow setting interrupts 12 bits (write-only) + HEB = 0x25, // 0x94, horizontal early blank, 12 bits +}; + +/** Taos video modes (see VIDEO_MODE register above). */ +enum { + Progressive_Scan = 0, // Progressive scan + Interlace, // Interlaced scan, no convolution + Interlace_Conv, // Interlaced scan, convolution, no scaling + Interlace_Conv_Scale // Interlaced scan, convolution, and scaling +}; + +/** CRT_CTRL bit definitions. */ +enum { + ENABLE_VIDEO_OUT = 24, +}; + +#define REG_TO_INDEX(reg) ((reg) - HEQ) + +/** Monitor ID definitions used internally by the Taos driver. */ +// Those values are obtained by reading GPIO 0...2 pins that are +// directly connected to the three-position mechanical switch +// on the backside of the console. All signals are active low. +enum { + MON_ID_PAL = 3, // GPIO_0 = "0", GPIO_1 = "1", GPIO_2 = "1" + MON_ID_NTSC = 5, // GPIO_0 = "1", GPIO_1 = "0", GPIO_2 = "1" + MON_ID_VGA = 6 // GPIO_0 = "1", GPIO_1 = "1", GPIO_2 = "0" +}; + +/** Broktree Bt856 digital video encoder. */ +class Bt856 : public I2CDevice, public HWComponent { +public: + Bt856(uint8_t dev_addr) { + supports_types(HWCompType::I2C_DEV); + + this->my_addr = dev_addr; + }; + + ~Bt856() = default; + + // I2CDevice methods + void start_transaction() { + this->pos = 0; // reset read/write position + }; + + bool send_subaddress(uint8_t sub_addr) { + return true; + }; + + bool send_byte(uint8_t data) { + switch (this->pos) { + case 0: + this->reg_num = data; + this->pos++; + break; + case 1: + LOG_F(INFO, "Bt856: reg 0x%X set to 0x%X", this->reg_num, data); + break; + default: + LOG_F(WARNING, "Bt856: too much data received!"); + return false; // return NACK + } + return true; + }; + + bool receive_byte(uint8_t* p_data) { + *p_data = 0x60; // return my device ID + return true; + }; + +private: + uint8_t my_addr = 0; + uint8_t reg_num = 0; + int pos = 0; +}; + + +class TaosVideo : public VideoCtrlBase, public MMIODevice { +public: + TaosVideo(); + ~TaosVideo() = default; + + static std::unique_ptr create() { + return std::unique_ptr(new TaosVideo()); + } + + // MMIODevice methods + uint32_t read(uint32_t rgn_start, uint32_t offset, int size) override; + void write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size) override; + +private: + void enable_display(); + void convert_frame_15bpp_indexed(uint8_t *dst_buf, int dst_pitch); + + std::unique_ptr clk_gen = nullptr; + std::unique_ptr vid_enc = nullptr; + + uint8_t gpio_cfg = 0; + uint8_t mon_id = MON_ID_NTSC; + uint8_t vsync_active = 0; + uint8_t video_mode = 0; + uint8_t *vram_ptr = nullptr; + uint32_t fb_base = 0; + uint32_t row_words = 0; + uint32_t color_mode = 0; + uint32_t crt_ctrl = 0; + uint32_t int_enables = 0; + uint32_t swatch_regs[15] = {}; +}; + +#endif // TAOS_VIDEO_H