// license:BSD-3-Clause
// copyright-holders:Patrick Mackinlay
/*
* Silicon Graphics GR1 (aka Eclipse) graphics hardware.
*
* This graphics system was first designed for the Personal Iris 4D/20,
* connecting via a unique graphics port interface. The system was later
* adapted and released under the IrisVision brand, initially with a Micro
* Channel Architecture bus interface and support for IBM RS/6000 and some IBM
* PS/2 systems. A final variant has a 16-bit ISA bus interface enabling use in
* PC-AT and compatible systems.
*
* The TG-V variation fits into the VME chassis and slots of the SGI V20 and
* V30/35, however control and data signals are delivered via a ribbon cable to
* the CPU and I/O boards, and only draws power and ground from the VME slot.
*
* All versions of the hardware consist of two primary boards which mount into
* host system slots, plus several daughter-boards which mount on the two
* primary boards.
*
* The board set consists of:
*
* Name Description Function
* Geometry Engine host interface and geometry engine (HQ1, GE5)
* Display Engine raster video and display subsystem (RE2 + 1-2 Bt431 cursor chips + 8 pixel, 2 overlay/underlay, 2 wid bitplanes)
* Video Driver basic video daughter-board (5 XPC1 ASICs, Bt458 RGB RAMDAC)
* Video Driver enhanced video daughter-board (5 XMAP2 ASICs, 8K color map, 3 Bt457 RAMDACs)
* OP1 Bitplane Board optional bitplane expansion (adds 2 overlay/underlay, 2 wid planes)
* BP4 Bitplane Board optional bitplane expansion (adds 16 pixel, 2 overlay/underlay, 2 wid planes, 4k color map)
* ZB3 Z-buffer Z-buffer option (adds 24 Z-buffer planes, requires BP4)
* GT1/GT2 Turbo Board turbo graphics option (4 TMS320C30 DSPs)
*
* Sources:
* - http://ohlandl.ipv7.net/video/Iris.html
* - https://www.4crawler.com/IrisVision/index.shtml
* - http://archive.irix.cc/sgistuff/hardware/graphics/eclipse.html
*
* TODO:
* - host dma
* - display registers
* - save state
* - slotify (SGI, MCA, ISA)
* - separate raster and display systems?
*/
/*
* Irix 4.0.5 IDE WIP
*
* usage: buffon; ge5load; <test>; gr_exit; buffoff
*
* these diagnostics fail (all others pass):
* bitp - options not detected
* ctl2 - graphics strobe
* gedma - ge5 failed to finish
* re - fails line drawing test
* redma - ge5 failed to finish
*/
#include "emu.h"
#include "sgi_gr1.h"
#define LOG_GENERAL (1U << 0)
#define LOG_READS (1U << 1)
//#define VERBOSE (LOG_GENERAL)
#include "logmacro.h"
DEFINE_DEVICE_TYPE(SGI_GR1, sgi_gr1_device, "sgi_gr1", "SGI GR1 Graphics")
sgi_gr1_device::sgi_gr1_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock)
: device_t(mconfig, SGI_GR1, tag, owner, clock)
, m_bank(*this, "bank")
, m_screen(*this, "screen")
, m_ge(*this, "ge5")
, m_re(*this, "re2")
, m_xmap(*this, "xmap%u", 0U)
, m_cursor(*this, "cursor%u", 0U)
, m_ramdac(*this, "ramdac%u", 0U)
, m_int_fifo_cb(*this)
{
}
static INPUT_PORTS_START(sgi_gr1)
PORT_START("options")
PORT_CONFNAME(0x08, 0x08, "Turbo")
PORT_CONFSETTING(0x00, DEF_STR(Yes))
PORT_CONFSETTING(0x08, DEF_STR(No))
PORT_CONFNAME(0x10, 0x00, "Z Buffer")
PORT_CONFSETTING(0x00, DEF_STR(Yes))
PORT_CONFSETTING(0x10, DEF_STR(No))
INPUT_PORTS_END
void sgi_gr1_device::map(address_map &map)
{
map(0x0000, 0x7fff).m(m_bank, FUNC(address_map_bank_device::amap32));
}
void sgi_gr1_device::map_bank(address_map &map)
{
// bit 15 of the map offset represents mar_msb
map(0x0000, 0x03ff).rw(m_ge, FUNC(sgi_ge5_device::code_r<false>), FUNC(sgi_ge5_device::code_w<false>));
map(0x8000, 0x83ff).rw(m_ge, FUNC(sgi_ge5_device::code_r<true>), FUNC(sgi_ge5_device::code_w<true>));
map(0x8400, 0x841f).rw(m_xmap[0], FUNC(sgi_xmap2_device::reg_r), FUNC(sgi_xmap2_device::reg_w)).umask32(0x000000ff);
map(0x8420, 0x843f).rw(m_xmap[1], FUNC(sgi_xmap2_device::reg_r), FUNC(sgi_xmap2_device::reg_w)).umask32(0x000000ff);
map(0x8440, 0x845f).rw(m_xmap[2], FUNC(sgi_xmap2_device::reg_r), FUNC(sgi_xmap2_device::reg_w)).umask32(0x000000ff);
map(0x8460, 0x847f).rw(m_xmap[3], FUNC(sgi_xmap2_device::reg_r), FUNC(sgi_xmap2_device::reg_w)).umask32(0x000000ff);
map(0x8480, 0x849f).rw(m_xmap[4], FUNC(sgi_xmap2_device::reg_r), FUNC(sgi_xmap2_device::reg_w)).umask32(0x000000ff);
map(0x84a0, 0x84bf).lw8(
[this](offs_t offset, u8 data)
{
for (sgi_xmap2_device *xmap : m_xmap)
xmap->reg_w(offset, data);
}, "xmap_broadcast").umask32(0x000000ff);
map(0x84c0, 0x84c3).rw(FUNC(sgi_gr1_device::dr1_r), FUNC(sgi_gr1_device::dr1_w)).umask32(0xff000000);
map(0x84e0, 0x84e3).rw(FUNC(sgi_gr1_device::dr0_r), FUNC(sgi_gr1_device::dr0_w)).umask32(0xff000000);
map(0x8500, 0x850f).m(m_ramdac[0], FUNC(bt457_device::map)).umask32(0x000000ff);
map(0x8520, 0x852f).m(m_ramdac[1], FUNC(bt457_device::map)).umask32(0x000000ff);
map(0x8540, 0x854f).m(m_ramdac[2], FUNC(bt457_device::map)).umask32(0x000000ff);
map(0x8560, 0x856f).m(m_cursor[0], FUNC(bt431_device::map)).umask32(0x000000ff);
map(0x8580, 0x858f).m(m_cursor[1], FUNC(bt431_device::map)).umask32(0x000000ff);
map(0x85a0, 0x85a3).rw(FUNC(sgi_gr1_device::dr4_r), FUNC(sgi_gr1_device::dr4_w)).umask32(0xff000000);
map(0x85c0, 0x85c3).rw(FUNC(sgi_gr1_device::dr3_r), FUNC(sgi_gr1_device::dr3_w)).umask32(0xff000000);
map(0x85e0, 0x85e3).rw(FUNC(sgi_gr1_device::dr2_r), FUNC(sgi_gr1_device::dr2_w)).umask32(0xff000000);
map(0x8640, 0x8783).w(m_ge, FUNC(sgi_ge5_device::command_w)).umask32(0xffff0000);
map(0x8740, 0x8743).r(m_ge, FUNC(sgi_ge5_device::pc_r)).umask32(0xffff0000);
map(0x0800, 0x0bff).r(m_ge, FUNC(sgi_ge5_device::buffer_r)).mirror(0x8000);
map(0x0800, 0x0bff).w(FUNC(sgi_gr1_device::fifo_w)).mirror(0x8000);
map(0x0c00, 0x0dff).w(m_ge, FUNC(sgi_ge5_device::mar_w)).mirror(0x8000);
map(0x0e00, 0x0e07).lw32([this](offs_t offset, u32 data) { m_bank->set_bank(offset); }, "mar_msb").mirror(0x8000);
map(0x1400, 0x17ff).rw(m_ge, FUNC(sgi_ge5_device::data_r), FUNC(sgi_ge5_device::data_w));
map(0x2000, 0x2007).rw(m_ge, FUNC(sgi_ge5_device::finish_r), FUNC(sgi_ge5_device::finish_w)).mirror(0x8000);
//map(0x207c, 0x207f); // gr1 vs gr2
}
void sgi_gr1_device::device_add_mconfig(machine_config &config)
{
unsigned pixel_clock = 107'352'000;
ADDRESS_MAP_BANK(config, m_bank).set_map(&sgi_gr1_device::map_bank).set_options(ENDIANNESS_NATIVE, 32, 32, 0x8000);
/*
* 1280x1024 @ 60Hz
* 107.352MHz pixel clock
* horizontal sync 63.9kHz, pulse width == 120 pixels
* vertical sync 60Hz, front/sync/back == 3/3/35 lines
*
// SGI GR1 offsets:
// 1280: Cx:D+H-P=0+246-57, Cy:V-32=39-32 (189, 7)
// 1024: Cx:D+H-P=0+126-57, Cy:V-32=43-32
// NTSC: Cx:D+H-P=-6, Cy:V-32=-554
// PAL: Cx:D+H-P=24, Cy:V-32=-457
// 30HZ: Cx:D+H-P=39, Cy:V-32=18
// STEREO: Cx:D+H-P=0+246-57, Cy:V-32=39-32+1
*
*/
SCREEN(config, m_screen, SCREEN_TYPE_RASTER);
m_screen->set_raw(pixel_clock, 1680, 246, 246 + 1280, 1065, 39, 39 + 1024);
m_screen->set_screen_update(m_re.finder_tag(), FUNC(sgi_re2_device::screen_update));
SGI_GE5(config, m_ge, 10_MHz_XTAL);
m_ge->fifo_empty().set([this]() { return int(m_fifo.empty()); });
m_ge->fifo_read().set(FUNC(sgi_gr1_device::fifo_r));
m_ge->re_r().set(m_re, FUNC(sgi_re2_device::reg_r));
m_ge->re_w().set(m_re, FUNC(sgi_re2_device::reg_w));
SGI_RE2(config, m_re, 0);
m_re->out_rdy().set(m_ge, FUNC(sgi_ge5_device::re_rdy_w));
m_re->out_drq().set(m_ge, FUNC(sgi_ge5_device::re_drq_w));
SGI_XMAP2(config, m_xmap[0], pixel_clock / 5);
SGI_XMAP2(config, m_xmap[1], pixel_clock / 5);
SGI_XMAP2(config, m_xmap[2], pixel_clock / 5);
SGI_XMAP2(config, m_xmap[3], pixel_clock / 5);
SGI_XMAP2(config, m_xmap[4], pixel_clock / 5);
BT431(config, m_cursor[0], pixel_clock / 5);
BT431(config, m_cursor[1], pixel_clock / 5);
BT457(config, m_ramdac[0], pixel_clock);
BT457(config, m_ramdac[1], pixel_clock);
BT457(config, m_ramdac[2], pixel_clock);
}
ioport_constructor sgi_gr1_device::device_input_ports() const
{
return INPUT_PORTS_NAME(sgi_gr1);
}
void sgi_gr1_device::device_start()
{
m_int_fifo_cb.resolve_safe();
//save_item(NAME());
m_reset = true;
}
void sgi_gr1_device::device_reset()
{
m_bank->set_bank(0);
m_dr0 = DR0_GRF1EN | DR0_SMALLMON0;
m_dr1 = DR1_TURBO;
//m_dr2 = 0;
//m_dr3 = DR3_FIFOFULL;
m_dr4 = DR4_MEGOPT;
m_fifo.clear();
}
u8 sgi_gr1_device::dr0_r()
{
LOG("dr0_r 0x%02x (%s)\n", m_dr0, machine().describe_context());
return m_dr0;
}
u8 sgi_gr1_device::dr1_r()
{
LOG("dr1_r 0x%02x (%s)\n", m_dr1, machine().describe_context());
return m_dr1;
}
u8 sgi_gr1_device::dr2_r()
{
LOG("dr2_r 0x%02x (%s)\n", m_dr2, machine().describe_context());
return m_dr2;
}
u8 sgi_gr1_device::dr3_r()
{
LOG("dr3_r 0x%02x (%s)\n", m_dr3, machine().describe_context());
return m_dr3;
}
u8 sgi_gr1_device::dr4_r()
{
LOG("dr4_r 0x%02x (%s)\n", m_dr4, machine().describe_context());
return (m_dr4 | (m_ge->suspended() ? 0 : DR4_GESTALL)) & DR4_RM;
}
void sgi_gr1_device::dr0_w(u8 data)
{
LOG("dr0_w 0x%02x (%s)\n", data, machine().describe_context());
m_dr0 = (m_dr0 & ~DR0_WM) | (data & DR0_WM);
}
void sgi_gr1_device::dr1_w(u8 data)
{
LOG("dr1_w 0x%02x (%s)\n", data, machine().describe_context());
m_dr1 = (m_dr1 & ~DR1_WM) | (data & DR1_WM);
m_ge->cwen_w(BIT(data, 1));
}
void sgi_gr1_device::dr2_w(u8 data)
{
LOG("dr2_w 0x%02x (%s)\n", data, machine().describe_context());
m_dr2 = (m_dr2 & ~DR2_WM) | (data & DR2_WM);
}
void sgi_gr1_device::dr3_w(u8 data)
{
LOG("dr3_w 0x%02x (%s)\n", data, machine().describe_context());
m_dr3 = (m_dr3 & ~DR3_WM) | (data & DR3_WM);
}
void sgi_gr1_device::dr4_w(u8 data)
{
LOG("dr4_w 0x%02x (%s)\n", data, machine().describe_context());
m_dr4 = (m_dr4 & ~DR4_WM) | (data & DR4_WM);
for (sgi_xmap2_device *xmap : m_xmap)
xmap->map_select_w(m_dr4 & DR4_MS);
}
u64 sgi_gr1_device::fifo_r()
{
u64 data = m_fifo.dequeue();
if (m_fifo.empty())
m_dr3 &= DR3_FIFOEMPTY;
if (!(m_dr3 & DR3_FIFOFULL) && (m_fifo.queue_length() <= 256))
{
m_dr3 |= DR3_FIFOFULL;
m_int_fifo_cb(CLEAR_LINE);
}
return data;
}
void sgi_gr1_device::fifo_w(offs_t offset, u32 data, u32 mem_mask)
{
LOG("fifo_w 0x%010x (%s)\n", (u64(offset) << 32) | data, machine().describe_context());
m_fifo.enqueue((u64(offset) << 32) | data);
if (!(m_dr3 & DR3_FIFOEMPTY))
m_dr3 |= DR3_FIFOEMPTY;
if ((m_dr3 & DR3_FIFOFULL) && (m_fifo.queue_length() > 256))
{
m_dr3 &= ~DR3_FIFOFULL;
m_int_fifo_cb(ASSERT_LINE);
}
if (m_ge->suspended())
m_ge->resume(SUSPEND_REASON_TRIGGER);
}
void sgi_gr1_device::reset_w(int state)
{
if (!m_reset && !state)
{
LOG("reset_w %d (%s)\n", state, machine().describe_context());
m_ge->pulse_input_line(INPUT_LINE_RESET, attotime::from_ticks(1, 10_MHz_XTAL));
m_re->reset();
}
m_reset = !state;
}