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 = {