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