Initial Taos emulation.

This commit is contained in:
Maxim Poliakovski 2024-04-09 01:44:26 +02:00
parent b2c342e453
commit e5282f9852
2 changed files with 455 additions and 0 deletions

260
devices/video/taos.cpp Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*/
/** @file Taos video controller emulation. */
#include <core/bitops.h>
#include <devices/common/i2c/athens.h>
#include <devices/deviceregistry.h>
#include <devices/memctrl/memctrlbase.h>
#include <devices/video/taos.h>
#include <machines/machinebase.h>
#include <machines/machineproperties.h>
#include <loguru.hpp>
#include <memaccess.h>
#include <string>
TaosVideo::TaosVideo() : VideoCtrlBase(640, 480) {
set_name("Taos");
supports_types(HWCompType::MMIO_DEV);
// initialize the video clock generator
this->clk_gen = std::unique_ptr<AthensClocks>(new AthensClocks(0x29, 20000000.0f));
// register the video clock generator with the I2C host
I2CBus* i2c_bus = dynamic_cast<I2CBus*>(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<Bt856>(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<MemCtrlBase*>(
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);

195
devices/video/taos.h Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*/
/** @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 <devices/common/i2c/i2c.h>
#include <devices/common/mmiodevice.h>
#include <devices/video/videoctrl.h>
#include <cinttypes>
#include <memory>
#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<HWComponent> create() {
return std::unique_ptr<TaosVideo>(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<AthensClocks> clk_gen = nullptr;
std::unique_ptr<Bt856> 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