From 1f9f2d2cf1f23baf5215dc8a9261300b6fab625c Mon Sep 17 00:00:00 2001 From: joevt Date: Wed, 6 Dec 2023 17:24:42 -0800 Subject: [PATCH] sixty6: Add support for sixty6 video output. --- devices/common/i2c/saa7187.cpp | 72 ++++++ devices/common/i2c/saa7187.h | 141 +++++++++++ devices/video/control.cpp | 5 + devices/video/control.h | 2 + devices/video/sixty6.cpp | 442 +++++++++++++++++++++++++++++++++ devices/video/sixty6.h | 151 +++++++++++ machines/machinetnt.cpp | 2 +- 7 files changed, 814 insertions(+), 1 deletion(-) create mode 100644 devices/common/i2c/saa7187.cpp create mode 100644 devices/common/i2c/saa7187.h create mode 100644 devices/video/sixty6.cpp create mode 100644 devices/video/sixty6.h diff --git a/devices/common/i2c/saa7187.cpp b/devices/common/i2c/saa7187.cpp new file mode 100644 index 0000000..ea12f6f --- /dev/null +++ b/devices/common/i2c/saa7187.cpp @@ -0,0 +1,72 @@ +/* +DingusPPC - The Experimental PowerPC Macintosh emulator +Copyright (C) 2018-23 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 SAA7187 Digital video encoder emulation. */ + +#include +#include + +Saa7187VideoEncoder::Saa7187VideoEncoder(uint8_t dev_addr) +{ + supports_types(HWCompType::I2C_DEV); + + this->my_addr = dev_addr; +} + +void Saa7187VideoEncoder::start_transaction() +{ + LOG_F(INFO, "Saa7187: start_transaction"); + this->pos = 0; // reset read/write position +} + +bool Saa7187VideoEncoder::send_subaddress(uint8_t sub_addr) +{ + LOG_F(ERROR, "Saa7187: send_subaddress 0x%02x", sub_addr); + //this->pos = sub_addr; + return true; +} + +bool Saa7187VideoEncoder::send_byte(uint8_t data) +{ + if (this->pos == 0) { + this->reg_num = data; + LOG_F(INFO, "Saa7187: write 0x%02x", data); + } + else { + if (this->reg_num < Saa7187Regs::last_reg) { + this->regs[this->reg_num] = data; + LOG_F(INFO, "Saa7187: write 0x%02x = 0x%02x", this->reg_num, data); + } + else { + LOG_F(ERROR, "Saa7187: write 0x%02x = 0x%02x", this->reg_num, data); + } + this->reg_num++; + } + this->pos++; + return true; +} + +bool Saa7187VideoEncoder::receive_byte(uint8_t* p_data) +{ + LOG_F(ERROR, "Saa7187: receive_byte"); + *p_data = 0x00; + return true; +} diff --git a/devices/common/i2c/saa7187.h b/devices/common/i2c/saa7187.h new file mode 100644 index 0000000..80abb3a --- /dev/null +++ b/devices/common/i2c/saa7187.h @@ -0,0 +1,141 @@ +/* +DingusPPC - The Experimental PowerPC Macintosh emulator +Copyright (C) 2018-23 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 SAA7187 Digital video encoder definitions. */ + +#ifndef SAA7187_H +#define SAA7187_H + +#include +#include + +namespace Saa7187Regs { + +enum Saa7187Regs : uint8_t { // i2c address is 0x44; fatman is at 0x46? + IPC = 0x3a, // Input port control + FMT0 = 0x01, + FMT1 = 0x02, + VUV2C = 0x04, + VY2C = 0x08, + CBENB = 0x80, + OSDY0 = 0x42, // OSD LUT, 8 colors + OSDU0 , + OSDV0 , + CHPS = 0x5a, // Chrominance phase + GAINU = 0x5b, // Gain U + GAINV = 0x5c, // Gain V + BLCKL = 0x5d, // Gain U MSB, black level + BLCKL0_5 = 0x3f, + GAINU8 = 0x80, + BLNNL = 0x5e, // Gain V MSB, blanking level + BLNNL0_5 = 0x3f, + GAINV8 = 0x80, + CCRS = 0x60, // Cross-colour select + CCRS0 = 0x40, + CCRS1 = 0x80, + SCTRL = 0x61, // Standard control + FISE = 0x01, + PAL = 0x02, + SCBW = 0x04, + RTCE = 0x08, + YGS = 0x10, + INPI1 = 0x20, + DOWN = 0x40, + BSTA = 0x62, // Burst amplitude + BSTA0_6 = 0x7f, + SQP = 0x80, + FSC0 = 0x63, // Subcarrier 0 + FSC8 = 0x64, // Subcarrier 1 + FSC16 = 0x65, // Subcarrier 2 + FSC24 = 0x66, // Subcarrier 3 + // 0x25555555 = NTSC + // 0x26798c0c = PAL + L21O0 = 0x67, // Line 21 odd 0 + L21O1 = 0x68, // Line 21 odd 1 + L21E0 = 0x69, // Line 21 even 0 + L21E1 = 0x6a, // Line 21 even 1 + SCCLN = 0x6b, // CC line + SCCLN0_4 = 0x1f, + RCTRL = 0x6c, // RCV port control + PRCV2 = 0x01, + ORCV2 = 0x02, + CBLF = 0x04, + PRCV1 = 0x08, + ORCV1 = 0x10, + TRCV2 = 0x20, + SRCV10 = 0x40, + RCV11 = 0x80, + RCM_CC = 0x6d, // RCM, CC mode + CCEN0 = 0x01, + CCEN1 = 0x02, + SRCM10 = 0x04, + SRCM11 = 0x08, + HTRIG0 = 0x6e, // Horizontal trigger + HTRIG8 = 0x6f, // Horizontal trigger + HTRIG8_10 = 0x07, + RES_VTRIG = 0x70, // fsc reset mode, Vertical trigger + VTRIG0_4 = 0x1f, + SBLBN = 0x20, + PHRES0 = 0x40, + HRES1 = 0x80, + BMRQ0 = 0x71, // Begin master request + EMRQ0 = 0x72, // End master request + BMRQ8_EMRQ8 = 0x73, // MSBs master request + BMRQ08_10 = 0x07, + EMRQ08_10 = 0x70, + BRCV0 = 0x77, // Begin RCV2 output + ERCV0 = 0x78, // End RCV2 output + BRCV8_ERCV8 = 0x79, // MSBs RCV2 output + BRCV08_10 = 0x07, + ERCV08_10 = 0x70, + FLEN = 0x7a, // Field length + FAL = 0x7b, // First active line + LAL = 0x7c, // Last active line + FLEN8_FAL8_LAL8 = 0x7d, // MSBs field control + FLEN8_9 = 0x03, + FAL8 = 0x10, + LAL8 = 0x20, + last_reg = 0x7e, +}; + +}; // namespace Saa7187Regs + +class Saa7187VideoEncoder : public I2CDevice, public HWComponent +{ +public: + Saa7187VideoEncoder(uint8_t dev_addr); + ~Saa7187VideoEncoder() = default; + + // I2CDevice methods + void start_transaction(); + bool send_subaddress(uint8_t sub_addr); + bool send_byte(uint8_t data); + bool receive_byte(uint8_t* p_data); + +private: + uint8_t my_addr = 0; + uint8_t reg_num = 0; + int pos = 0; + + uint8_t regs[Saa7187Regs::last_reg] = {0}; +}; + +#endif // SAA7187_H diff --git a/devices/video/control.cpp b/devices/video/control.cpp index 9faca0a..43d6112 100644 --- a/devices/video/control.cpp +++ b/devices/video/control.cpp @@ -395,6 +395,11 @@ void ControlVideo::write(uint32_t rgn_start, uint32_t offset, uint32_t value, in } } +uint8_t* ControlVideo::GetVram() +{ + return &this->vram_ptr[0]; +} + void ControlVideo::enable_display() { int new_width, new_height, clk_divisor; diff --git a/devices/video/control.h b/devices/video/control.h index 7c3d536..5826ea8 100644 --- a/devices/video/control.h +++ b/devices/video/control.h @@ -118,6 +118,8 @@ public: return std::unique_ptr(new ControlVideo()); } + uint8_t* GetVram(); + // 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); diff --git a/devices/video/sixty6.cpp b/devices/video/sixty6.cpp new file mode 100644 index 0000000..71b9b82 --- /dev/null +++ b/devices/video/sixty6.cpp @@ -0,0 +1,442 @@ +/* +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 TNT on-board NTSC/PAL video output emulation. */ + +#include +#include +#include +#include +#include +#include + +namespace loguru { + enum : Verbosity { + Verbosity_SIXTY6 = loguru::Verbosity_9, + Verbosity_SIXTY6_EXTRA = loguru::Verbosity_9, + Verbosity_SIXTY6_INTERRUPT = loguru::Verbosity_9 + }; +} + +Sixty6Video::Sixty6Video() +{ + // initialize the video clock generator + this->saa7187 = std::unique_ptr (new Saa7187VideoEncoder(0x44)); + + // 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(0x44, this->saa7187.get()); + + // get (raw) pointer to the I/O controller + GrandCentral* gc_obj = dynamic_cast(gMachineObj->get_comp_by_name("GrandCentral")); + + // attach IOBus Device #3 0xF301C000 ; sixty6 + gc_obj->attach_iodevice(2, this); + + // attach IOBus Device #5 0xF301E000 ; sixty6 composite/s-video + gMachineObj->add_device("BoardReg66", std::unique_ptr( + new BoardRegister("Board Register 66", + ((GET_BIN_PROP("has_svideo") ^ 1) << 6) | // S-Video connected (active low) + ((GET_BIN_PROP("has_composite") ^ 1) << 7) | // Composite Video connected (active low) + 0xFF3FU // pull up unused bits + ))); + gc_obj->attach_iodevice(4, dynamic_cast(gMachineObj->get_comp_by_name("BoardReg66"))); +} + +uint16_t Sixty6Video::iodev_read(uint32_t address) +{ + uint16_t value; + switch (address & 3) { + + case Sixty6BaseReg::CLUT_DATA: + if (this->comp_index == 0) { + uint8_t a; + get_palette_color(this->clut_addr, clut_color[0], clut_color[1], clut_color[2], a); + } + value = this->clut_color[this->comp_index]; + LOG_F(SIXTY6, "Sixty6: read %d:CLUT_DATA 0x%02x.%c = %02x", address, this->clut_addr, "rgb"[comp_index], value); + this->comp_index++; + if (this->comp_index >= 3) { + this->clut_addr++; // auto-increment CLUT address + this->comp_index = 0; + } + break; + + case Sixty6BaseReg::CLUT_ADDR: + value = this->clut_addr; + LOG_F(SIXTY6, "Sixty6: read %d:CLUT_ADDR = %02x", address, value); + break; + + case Sixty6BaseReg::CONTROL_DATA: + switch (this->control_addr) { + case Sixty6Reg::CONTROL_1: + value = this->control_1; + if (last_control_1_value == value) { + last_control_1_count++; + } else { + if (last_control_1_count) { + LOG_F(SIXTY6_INTERRUPT, "Sixty6: read %d:CONTROL_1 = %02x x %d", address, last_control_1_value, last_control_1_count); + } + last_control_1_value = value; + last_control_1_count = 0; + LOG_F(SIXTY6_INTERRUPT, "Sixty6: read %d:CONTROL_1 = %02x x %d", address, last_control_1_value, last_control_1_count); + } + break; + default: + value = 0; + LOG_F(ERROR, "Sixty6: read %d:CONTROL_DATA 0x%02x = %02x", address, this->control_addr, value); + } + break; + + case Sixty6BaseReg::CONTROL_ADDR: + value = this->control_addr; + LOG_F(SIXTY6_INTERRUPT, "Sixty6: read %d:CONTROL_ADDR = %02x", address, value); + break; + } + return value; +} + +void Sixty6Video::iodev_write(uint32_t address, uint16_t value) +{ + switch (address & 3) { + + case Sixty6BaseReg::CLUT_DATA: + if (this->comp_index == 0) { + uint8_t a; + get_palette_color(this->clut_addr, clut_color[0], clut_color[1], clut_color[2], a); + } + LOG_F(SIXTY6, "Sixty6: write %d:CLUT_DATA 0x%02x.%c = %02x", address, this->clut_addr, "rgb"[comp_index], value); + this->clut_color[this->comp_index++] = value; + if (this->comp_index >= 3) { + this->set_palette_color(this->clut_addr, clut_color[0], + clut_color[1], clut_color[2], 0xFF); + this->clut_addr++; // auto-increment CLUT address + this->comp_index = 0; + } + break; + + case Sixty6BaseReg::CLUT_ADDR: + LOG_F(SIXTY6, "Sixty6: write %d:CLUT_ADDR = %02x", address, value); + this->clut_addr = value; + this->comp_index = 0; + break; + + case Sixty6BaseReg::CONTROL_DATA: + switch (this->control_addr) { + case Sixty6Reg::REG_V1O_L: + this->v1_odd = (this->v1_odd & 0xff00) | (value & 0xff); + this->changed = true; + LOG_F(SIXTY6, "Sixty6: write %d:REG_V1O_L = %02x V1_odd = %d", address, value, this->v1_odd); + break; + case Sixty6Reg::REG_V1O_H: + this->v1_odd = (value << 8) | (this->v1_odd & 0xff); + this->changed = true; + LOG_F(SIXTY6, "Sixty6: write %d:REG_V1O_H = %02x V1_odd = %d", address, value, this->v1_odd); + break; + case Sixty6Reg::REG_V2O_L: + this->v2_odd = (this->v2_odd & 0xff00) | (value & 0xff); + this->changed = true; + LOG_F(SIXTY6, "Sixty6: write %d:REG_V2O_L = %02x V2_odd = %d", address, value, this->v2_odd); + break; + case Sixty6Reg::REG_V2O_H: + this->v2_odd = (value << 8) | (this->v2_odd & 0xff); + this->changed = true; + LOG_F(SIXTY6, "Sixty6: write %d:REG_V2O_H = %02x V2_odd = %d", address, value, this->v2_odd); + break; + case Sixty6Reg::REG_V1E_L: + this->v1_even = (this->v1_even & 0xff00) | (value & 0xff); + this->changed = true; + LOG_F(SIXTY6, "Sixty6: write %d:REG_V1E_L = %02x v1_even = %d", address, value, this->v1_even); + break; + case Sixty6Reg::REG_V1E_H: + this->v1_even = (value << 8) | (this->v1_even & 0xff); + this->changed = true; + LOG_F(SIXTY6, "Sixty6: write %d:REG_V1E_H = %02x v1_even = %d", address, value, this->v1_even); + break; + case Sixty6Reg::REG_V2E_L: + this->v2_even = (this->v2_even & 0xff00) | (value & 0xff); + this->changed = true; + LOG_F(SIXTY6, "Sixty6: write %d:REG_V2E_L = %02x v2_even = %d", address, value, this->v2_even); + break; + case Sixty6Reg::REG_V2E_H: + this->v2_even = (value << 8) | (this->v2_even & 0xff); + this->changed = true; + LOG_F(SIXTY6, "Sixty6: write %d:REG_V2E_H = %02x v2_even = %d", address, value, this->v2_even); + break; + case Sixty6Reg::REG_H1_L: + this->h1 = (this->h1 & 0xff00) | (value & 0xff); + this->changed = true; + LOG_F(SIXTY6, "Sixty6: write %d:REG_H1_L = %02x h1 = %d", address, value, this->h1); + break; + case Sixty6Reg::REG_H1_H: + this->h1 = (value << 8) | (this->h1 & 0xff); + this->changed = true; + LOG_F(SIXTY6, "Sixty6: write %d:REG_H1_H = %02x h1 = %d", address, value, this->h1); + break; + case Sixty6Reg::REG_H2_L: + this->h2 = (this->h2 & 0xff00) | (value & 0xff); + this->changed = true; + LOG_F(SIXTY6, "Sixty6: write %d:REG_H2_L = %02x h2 = %d", address, value, this->h2); + break; + case Sixty6Reg::REG_H2_H: + this->h2 = (value << 8) | (this->h2 & 0xff); + this->changed = true; + LOG_F(SIXTY6, "Sixty6: write %d:REG_H2_H = %02x h2 = %d", address, value, this->h2); + break; + case Sixty6Reg::BASE_ADDR_L: + this->base_addr = (this->base_addr & 0xffff00) | (value & 0xff); + this->changed = true; + LOG_F(SIXTY6, "Sixty6: write %d:BASE_ADDR_L = %02x base_addr = %06x", address, value, this->base_addr); + break; + case Sixty6Reg::BASE_ADDR_M: + this->base_addr = (value << 8) | (this->base_addr & 0xff00ff); + this->changed = true; + LOG_F(SIXTY6, "Sixty6: write %d:BASE_ADDR_M = %02x base_addr = %06x", address, value, this->base_addr); + break; + case Sixty6Reg::BASE_ADDR_H: + this->base_addr = (value << 16) | (this->base_addr & 0x00ffff); + this->changed = true; + LOG_F(SIXTY6, "Sixty6: write %d:BASE_ADDR_H = %02x base_addr = %06x", address, value, this->base_addr); + break; + case Sixty6Reg::PITCH_L: + this->pitch = (this->pitch & 0xff00) | (value & 0xff); + this->changed = true; + LOG_F(SIXTY6, "Sixty6: write %d:PITCH_L = %02x pitch = %d", address, value, this->pitch); + break; + case Sixty6Reg::PITCH_H: + this->pitch = (value << 8) | (this->pitch & 0xff); + this->changed = true; + LOG_F(SIXTY6, "Sixty6: write %d:PITCH_H = %02x pitch = %d", address, value, this->pitch); + break; + case Sixty6Reg::CURSOR_X_L: + this->cursor_x = (this->cursor_x & 0xff00) | (value & 0xff); + LOG_F(SIXTY6, "Sixty6: write %d:CURSOR_X_L = %02x cursor_x = %d", address, value, this->cursor_x); + break; + case Sixty6Reg::CURSOR_X_H: + this->cursor_x = (value << 8) | (this->cursor_x & 0xff); + LOG_F(SIXTY6, "Sixty6: write %d:CURSOR_X_H = %02x cursor_x = %d", address, value, this->cursor_x); + break; + case Sixty6Reg::CURSOR_Y_L: + this->cursor_y = (this->cursor_y & 0xff00) | (value & 0xff); + LOG_F(SIXTY6, "Sixty6: write %d:CURSOR_Y_L = %02x cursor_y = %d", address, value, this->cursor_y); + break; + case Sixty6Reg::CURSOR_Y_H: + this->cursor_y = (value << 8) | (this->cursor_y & 0xff); + LOG_F(SIXTY6, "Sixty6: write %d:CURSOR_Y_H = %02x cursor_y = %d", address, value, this->cursor_y); + break; + case Sixty6Reg::INT_COUNT_L: + this->int_count = (this->int_count & 0xff00) | (value & 0xff); + LOG_F(SIXTY6, "Sixty6: write %d:INT_COUNT_L = %02x int_count = %d", address, value, this->int_count); + break; + case Sixty6Reg::INT_COUNT_H: + this->int_count = (value << 8) | (this->int_count & 0xff); + LOG_F(SIXTY6, "Sixty6: write %d:INT_COUNT_H = %02x int_count = %d", address, value, this->int_count); + break; + case Sixty6Reg::CONTROL_1: + { + if (this->changed) { + if (value & 2) { + LOG_F(SIXTY6, "Sixty6: write %d:CONTROL_1 = %02x -> %02x", address, value, value & ~2); + } else { + LOG_F(SIXTY6, "Sixty6: write %d:CONTROL_1 = %02x", address, value); + } + } + else { + if (value & 2) { + LOG_F(SIXTY6_INTERRUPT, "Sixty6: write %d:CONTROL_1 = %02x -> %02x", address, value, value & ~2); + } else { + LOG_F(SIXTY6_INTERRUPT, "Sixty6: write %d:CONTROL_1 = %02x", address, value); + } + } + this->control_1 = value & ~2; // we don't support hardware cursor + + if (this->control_1 & 0x40) { + this->interrupt_enabled = true; + } + else { + /* + If you disable interrupts then startup may hang which means we + aren't doing interrupts properly. + + We need interrupts to move mouse while it is positioned on the + Sixty6 display. + */ + // this->interrupt_enabled = false; + } + + int new_pixel_format = (this->control_1 >> 4) & 3; + if (new_pixel_format != this->pixel_format) + this->changed = true; + + if (1 || (value & 0x49) == 0x49) { + if (this->changed) { + this->enable_display(); + this->changed = false; + } + } + else { + //this->disable_display(); + } + break; + } + case Sixty6Reg::CONTROL_2: + this->control_2 = value; + LOG_F(SIXTY6, "Sixty6: write %d:CONTROL_2 = %02x", address, value); + break; + default: + LOG_F(ERROR, "Sixty6: write %d:CONTROL_DATA 0x%02x = %02x", address, this->control_addr, value); + } + break; + + case Sixty6BaseReg::CONTROL_ADDR: + LOG_F(SIXTY6_EXTRA, "Sixty6: write %d:CONTROL_ADDR = %02x", address, value); + this->control_addr = value; + break; + } +} + +void Sixty6Video::enable_display() +{ + int new_width, new_height, clk_divisor; + + // get pixel frequency from Saa7187 + this->pixel_clock = 25000000; // this->saa7187->get_dot_freq(); + + // calculate active_width and active_height from video timing parameters + new_width = this->h2 - this->h1; + new_height = (this->v2_odd - this->v1_odd + this->v2_even - this->v1_even + 1) & ~1; + + if (this->control_1 & 4) { + // zoom mode + new_width >>= 1; + new_height >>= 1; + } + + this->active_width = new_width; + this->active_height = new_height; + + // set framebuffer parameters + this->fb_ptr = &control_video->GetVram()[(this->base_addr << 3) & 0x3fffff]; // why does (this->base_addr << 3) == 0x4C00C20? + this->fb_pitch = this->pitch << 3; + this->pixel_format = (this->control_1 >> 4) & 3; + + // get pixel depth + switch (this->pixel_format) { + case 1: + 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); + }; + break; + case 2: + this->pixel_depth = 16; + this->convert_fb_cb = [this](uint8_t *dst_buf, int dst_pitch) { + this->convert_frame_15bpp_BE(dst_buf, dst_pitch); + }; + break; + default: + LOG_F(ERROR, "Sixty6: Invalid pixel format %d!", this->pixel_format); + // fallthrough + case 3: + this->pixel_depth = 32; + this->convert_fb_cb = [this](uint8_t *dst_buf, int dst_pitch) { + this->convert_frame_32bpp_BE(dst_buf, dst_pitch); + }; + break; + } + + // calculate display refresh rate + if (this->v2_odd > 256) + this->refresh_rate = 50.0; + else + this->refresh_rate = 60.0; + + this->hori_blank = this->h1; + this->vert_blank = this->v1_odd + this->v1_even; + + this->hori_total = this->h2; + this->vert_total = this->v2_odd + v2_even; + + this->stop_refresh_task(); + + // set up periodic timer for display updates + if (this->active_width > 0 && this->active_height > 0 && this->pixel_clock > 0) { + LOG_F(INFO, "Sixty6: refresh rate set to %f Hz", this->refresh_rate); + + this->start_refresh_task(); + + this->blank_on = false; + + LOG_F(INFO, "Sixty6: display enabled"); + this->crtc_on = true; + } + else { + LOG_F(INFO, "Sixty6: display not enabled"); + this->blank_on = true; + this->crtc_on = false; + } +} + +void Sixty6Video::disable_display() +{ + this->crtc_on = false; + LOG_F(INFO, "Sixty6: display disabled"); +} + +int Sixty6Video::device_postinit() +{ + this->int_ctrl = dynamic_cast( + gMachineObj->get_comp_by_type(HWCompType::INT_CTRL)); + this->irq_id = this->int_ctrl->register_dev_int(IntSrc::SIXTY6); + + this->vbl_cb = [this](uint8_t irq_line_state) { + if (irq_line_state) + this->control_1 |= 0x80; + else + this->control_1 &= ~0x80; + LOG_F(SIXTY6_INTERRUPT, "Sixty6: interrupt state:%d doit:%d", irq_line_state, ((this->control_1 & 0x40) && this->crtc_on)); + + if (this->interrupt_enabled && this->crtc_on) { + //this->pci_interrupt(irq_line_state); + this->int_ctrl->ack_int(this->irq_id, irq_line_state); + } + }; + + this->control_video = dynamic_cast( + gMachineObj->get_comp_by_name("ControlVideo")); + + return 0; +} + +// ========================== Device registry stuff ========================== + +static const PropMap Sixty6_Properties = { + {"has_composite", + new BinProperty(0)}, + {"has_svideo", + new BinProperty(0)}, +}; + +static const DeviceDescription Sixty6_Descriptor = { + Sixty6Video::create, {}, Sixty6_Properties +}; + +REGISTER_DEVICE(Sixty6Video, Sixty6_Descriptor); diff --git a/devices/video/sixty6.h b/devices/video/sixty6.h new file mode 100644 index 0000000..2528aff --- /dev/null +++ b/devices/video/sixty6.h @@ -0,0 +1,151 @@ +/* +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 TNT on-board NTSC/PAL video output definitions. */ + +#ifndef SIXTY6_VIDEO_H +#define SIXTY6_VIDEO_H + +#include +#include + +#include +#include + +namespace Sixty6BaseReg { + +enum Sixty6BaseReg : uint32_t { + CLUT_DATA = 0x00, // Open Firmware chaos reads PRSNT_BITS from here!? + CLUT_ADDR = 0x01, + + CONTROL_DATA = 0x02, + CONTROL_ADDR = 0x03, +}; + +}; + +namespace Sixty6Reg { + +enum Sixty6Reg : uint32_t { + // registers 0x00..0x7f are writable; unused in sixty6; Hardware Cursor for ninety9 + REG_V1O_L = 0x80, // 8 bits + REG_V1O_H = 0x81, // 2 bits + + REG_V2O_L = 0x82, // 8 bits + REG_V2O_H = 0x83, // 2 bits + + REG_V1E_L = 0x84, // 8 bits + REG_V1E_H = 0x85, // 2 bits + + REG_V2E_L = 0x86, // 8 bits + REG_V2E_H = 0x87, // 2 bits + + REG_H1_L = 0x88, // 8 bits + REG_H1_H = 0x89, // 2 bits + + REG_H2_L = 0x8a, // 8 bits + REG_H2_H = 0x8b, // 2 bits + + BASE_ADDR_L = 0x8c, // 8 bits + BASE_ADDR_M = 0x8d, // 8 bits + BASE_ADDR_H = 0x8e, // 2 bits + + PITCH_L = 0x8f, // 8 bits + PITCH_H = 0x90, // 3 bits + + CURSOR_X_L = 0x91, // 8 bits + CURSOR_X_H = 0x92, // 2 bits + + CURSOR_Y_L = 0x93, // 8 bits + CURSOR_Y_H = 0x94, // 2 bits + + INT_COUNT_L = 0x95, // 8 bits + INT_COUNT_H = 0x96, // 2 bits + + CONTROL_1 = 0x97, // 8 bits // 0x00, 0x02, 0x40, 0x48, 0x49, 0x79 + // 0x01: ? + // 0x02: hardware cursor enable (read-only as 0 for sixty6) + // 0x04: zoom + // 0x08: ? + // 0x30: pixel depth: 0 = 8-bit indexed, 2 = 16 bit, 3 = 32 bit + // 0x40: interrupt enable + // 0x80: interrupt + CONTROL_2 = 0x98, // 8 bits // 0x02, 0x03 + // 0x01: ? + // 0x02: ? + // registers 0x99..0xff are read only and read from registers 0x19..0x7F. +}; + +}; + +class Saa7187VideoEncoder; +class ControlVideo; + +class Sixty6Video: public VideoCtrlBase, public IobusDevice, public HWComponent { + +public: + Sixty6Video(); + + static std::unique_ptr create() { + return std::unique_ptr(new Sixty6Video()); + } + +protected: + + void enable_display(); + void disable_display(); + + // HWComponent methods + int device_postinit(); + + // IobusDevice methods + uint16_t iodev_read(uint32_t address); + void iodev_write(uint32_t address, uint16_t value); + + uint8_t control_addr = 0; + uint8_t clut_addr = 0; + uint8_t clut_color[3]; + uint8_t comp_index = 0; + + uint16_t v1_odd = 0; + uint16_t v2_odd = 0; + uint16_t v1_even = 0; + uint16_t v2_even = 0; + uint16_t h1 = 0; + uint16_t h2 = 0; + uint32_t base_addr = 0; + uint16_t pitch = 0; + uint16_t cursor_x = 0; + uint16_t cursor_y = 0; + uint16_t int_count = 0; + uint8_t control_1 = 0; + bool interrupt_enabled = false; + uint8_t control_2 = 0; + uint8_t last_control_1_value = 0; + uint8_t last_control_1_count = 0; + + bool changed = false; + + std::unique_ptr saa7187; + ControlVideo * control_video = nullptr; +}; + +#endif // SIXTY6_VIDEO_H diff --git a/machines/machinetnt.cpp b/machines/machinetnt.cpp index 77b04e4..efa83b3 100644 --- a/machines/machinetnt.cpp +++ b/machines/machinetnt.cpp @@ -115,7 +115,7 @@ static const PropMap pm7500_settings = { }; static vector pm7500_devices = { - "Hammerhead", "Bandit1", "Chaos", "ScsiMesh", "MeshTnt", "GrandCentral", "ControlVideo" + "Hammerhead", "Bandit1", "Chaos", "ScsiMesh", "MeshTnt", "GrandCentral", "ControlVideo", "Sixty6Video" }; static const MachineDescription pm7300_descriptor = {