// license:BSD-3-Clause // copyright-holders:R. Belmont /*************************************************************************** RasterOps ColorVue 8LC 1, 2, 4, and 8 bpp at 1024x768 for the Macintosh LC This is the same hardware as the ClearVueGS/LC board, which shows grayscale. Both cards are documented to only work with the original LC due to some shortcuts taken in how they map the address space. Note: the current ROM is a prototype from a former ROPS employee. RasterOps' graphics driver software doesn't recognize it to enable the pan/zoom functionality. ROM dumps from a retail card would resolve several questions. Map: 0xXXX00000: VRAM (768K) 0xXXXC0000: TMS34061 registers 0xXXXD0000: Control register + DIP switches Control register layout: bits 0-1: oscillator select (0 = 80.0 MHz, 1 = 57.2832 MHz, 2/3 unused) bits 2-3: mode select (0 = 1 bpp, 1 = 2 bpp, 2 = 4 bpp, 3 = 8 bpp) bits 4-5: X zoom amount (0 = 1x, 1 = 2x, 2 = 4x, 3 = 8x) bit 6: Vblank status, negative logic (0 = active) bit 7: Display status (0 = disabled, 1 = enabled) bit 8: monitor sense (1 = connected) bit 11: Display enable switch on the 708+/SE this is derived from TODO: - Get real declaration ROMs for 8LC and ClearVueGS/LC and other related cards - Implement pan/zoom functionality if supported, which likely requires VRAM to go through the TMS34061 ***************************************************************************/ #include "emu.h" #include "8lc.h" #include "video/tms34061.h" #include "emupal.h" #include "screen.h" #define LOG_REGISTERS (1U << 1) #define LOG_RAMDAC (1U << 2) #define VERBOSE (0) #include "logmacro.h" namespace { static constexpr u32 VRAM_SIZE = 0xc0000; class lcpds_cv8lc_device : public device_t, public device_nubus_card_interface { public: // construction/destruction lcpds_cv8lc_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock); protected: lcpds_cv8lc_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock); required_device m_tms34061; required_device m_screen; required_device m_palette; required_ioport m_monitor_config; // device-level overrides virtual void device_start() override ATTR_COLD; virtual void device_reset() override ATTR_COLD; // optional information overrides virtual void device_add_mconfig(machine_config &config) override ATTR_COLD; virtual const tiny_rom_entry *device_rom_region() const override ATTR_COLD; virtual ioport_constructor device_input_ports() const override ATTR_COLD; u32 registers_r(offs_t offset, u32 mem_mask); void registers_w(offs_t offset, u32 data, u32 mem_mask); u32 ramdac_r(offs_t offset, u32 mem_mask); void ramdac_w(offs_t offset, u32 data, u32 mem_mask); private: u32 screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect); void vblank_w(int state); std::unique_ptr m_vram; u32 m_mode, m_irq_control; u32 m_pal_idx, m_pal_address; u32 m_hres, m_vres, m_stride, m_htotal, m_vtotal, m_pixel_clock; u32 m_display_enable; u32 m_crtc[12]; }; ROM_START( cb708plc ) ROM_REGION(0x8000, "declrom", 0) ROM_LOAD( "8lc_prototype.bin", 0x000000, 0x008000, CRC(6f47e0f2) SHA1(a8552315d9127aee92416aa22893551851f42e5c) ) ROM_END static INPUT_PORTS_START(monitor_config) PORT_START("monitor") PORT_CONFNAME(0x0100, 0x0000, "Monitor presence") PORT_CONFSETTING(0x0000, u8"19\" 1024\u00d7768 RGB"); PORT_CONFSETTING(0x0100, u8"No monitor") INPUT_PORTS_END void lcpds_cv8lc_device::device_add_mconfig(machine_config &config) { SCREEN(config, m_screen, SCREEN_TYPE_RASTER); m_screen->set_raw(80'000'000, 1312, 0, 1024, 802, 0, 768); m_screen->set_screen_update(FUNC(lcpds_cv8lc_device::screen_update)); PALETTE(config, m_palette).set_entries(256); TMS34061(config, m_tms34061, 0); m_tms34061->set_rowshift(10); // VRAM address is (row << rowshift) | col m_tms34061->set_vram_size(0xc0000); m_tms34061->set_screen(m_screen); m_tms34061->int_callback().set(FUNC(lcpds_cv8lc_device::vblank_w)); } const tiny_rom_entry *lcpds_cv8lc_device::device_rom_region() const { return ROM_NAME( cb708plc ); } ioport_constructor lcpds_cv8lc_device::device_input_ports() const { return INPUT_PORTS_NAME(monitor_config); } //************************************************************************** // LIVE DEVICE //************************************************************************** lcpds_cv8lc_device::lcpds_cv8lc_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) : lcpds_cv8lc_device(mconfig, PDSLC_COLORVUE8LC, tag, owner, clock) { } lcpds_cv8lc_device::lcpds_cv8lc_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock) : device_t(mconfig, type, tag, owner, clock), device_nubus_card_interface(mconfig, *this), m_tms34061(*this, "tms34061"), m_screen(*this, "screen"), m_palette(*this, "palette"), m_monitor_config(*this, "monitor"), m_mode(0), m_irq_control(0), m_pal_idx(0), m_pal_address(0), m_hres(1024), m_vres(768), m_stride(1024), m_htotal(0), m_vtotal(0), m_pixel_clock(80'000'000), m_display_enable(0) { std::fill(std::begin(m_crtc), std::end(m_crtc), 0); } void lcpds_cv8lc_device::device_start() { set_pds_slot(0xe); const u32 slotspace = get_slotspace(); install_declaration_rom("declrom"); m_vram = std::make_unique(VRAM_SIZE/4); save_item(NAME(m_mode)); save_item(NAME(m_irq_control)); save_item(NAME(m_pal_idx)); save_item(NAME(m_pal_address)); save_item(NAME(m_stride)); save_item(NAME(m_hres)); save_item(NAME(m_vres)); save_item(NAME(m_htotal)); save_item(NAME(m_vtotal)); save_item(NAME(m_display_enable)); save_pointer(NAME(m_vram), VRAM_SIZE/4); install_bank(0xe00000 + slotspace, 0xe00000 + slotspace + VRAM_SIZE - 1, &m_vram[0]); nubus().install_device(slotspace + 0xec0000, slotspace + 0xedffff, emu::rw_delegate(*this, FUNC(lcpds_cv8lc_device::registers_r)), emu::rw_delegate(*this, FUNC(lcpds_cv8lc_device::registers_w))); nubus().install_device(slotspace + 0xee0000, slotspace + 0xee00ff, emu::rw_delegate(*this, FUNC(lcpds_cv8lc_device::ramdac_r)), emu::rw_delegate(*this, FUNC(lcpds_cv8lc_device::ramdac_w))); // The real card appears to ignore A31 (reminder: on the LC and LC II, A24-A30 are not decoded) so we need this extra // mapping as well. install_bank(0xe00000, 0xe00000 + VRAM_SIZE - 1, &m_vram[0]); nubus().install_device(0xec0000, 0xedffff, emu::rw_delegate(*this, FUNC(lcpds_cv8lc_device::registers_r)), emu::rw_delegate(*this, FUNC(lcpds_cv8lc_device::registers_w))); nubus().install_device(0xee0000, 0xee00ff, emu::rw_delegate(*this, FUNC(lcpds_cv8lc_device::ramdac_r)), emu::rw_delegate(*this, FUNC(lcpds_cv8lc_device::ramdac_w))); } void lcpds_cv8lc_device::device_reset() { m_pal_idx = 0; m_pal_address = 0; m_irq_control = 0; m_mode = 0; m_display_enable = 0; } u32 lcpds_cv8lc_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect) { auto const vram8 = util::big_endian_cast(&m_vram[0]); const pen_t *pens = m_palette->pens(); m_tms34061->get_display_state(); if (m_tms34061->blanked()) { bitmap.fill(rgb_t::black(), cliprect); return 0; } switch (m_mode) { case 0: // 1bpp for (int y = 0; y < m_vres; y++) { u32 *scanline = &bitmap.pix(y); for (int x = 0; x < m_hres/8; x++) { u8 const pixels = vram8[(y * m_stride) + x]; *scanline++ = pens[pixels&0x80]; *scanline++ = pens[(pixels<<1)&0x80]; *scanline++ = pens[(pixels<<2)&0x80]; *scanline++ = pens[(pixels<<3)&0x80]; *scanline++ = pens[(pixels<<4)&0x80]; *scanline++ = pens[(pixels<<5)&0x80]; *scanline++ = pens[(pixels<<6)&0x80]; *scanline++ = pens[(pixels<<7)&0x80]; } } break; case 1: // 2bpp for (int y = 0; y < m_vres; y++) { u32 *scanline = &bitmap.pix(y); for (int x = 0; x < m_hres / 4; x++) { u8 const pixels = vram8[(y * m_stride) + x]; *scanline++ = pens[(pixels & 0xc0)]; *scanline++ = pens[((pixels << 2) & 0xc0)]; *scanline++ = pens[((pixels << 4) & 0xc0)]; *scanline++ = pens[(pixels << 6) & 0xc0]; } } break; case 2: // 4bpp for (int y = 0; y < m_vres; y++) { u32 *scanline = &bitmap.pix(y); for (int x = 0; x < m_hres/2; x++) { u8 const pixels = vram8[(y * m_stride) + x]; *scanline++ = pens[((pixels << 0) & 0xf0)]; *scanline++ = pens[((pixels << 4) & 0xf0)]; } } break; case 3: // 8bpp for (int y = 0; y < m_vres; y++) { u32 *scanline = &bitmap.pix(y); for (int x = 0; x < m_hres; x++) { u8 const pixels = vram8[(y * m_stride) + x]; *scanline++ = pens[pixels]; } } break; } return 0; } void lcpds_cv8lc_device::vblank_w(int state) { if (state) { raise_slot_irq(); } else { lower_slot_irq(); } } u32 lcpds_cv8lc_device::registers_r(offs_t offset, u32 mem_mask) { if (offset != 0x34) { LOGMASKED(LOG_REGISTERS, "Read @ Cxxxx: offset %x (mask %x)\n", offset, mem_mask); } if (offset == 0x4000) { u32 result = m_monitor_config->read(); result |= (m_screen->vblank() ? 0 : 0x0040); return result; } else if (offset < 0x40) { return m_tms34061->read(offset, 0, 0)<<24; } else if (offset == 0x44) { return m_screen->vpos() & 0xff; } else if (offset == 0x46) { return (m_screen->vpos() >> 8) & 0xff; } return 0; } void lcpds_cv8lc_device::registers_w(offs_t offset, u32 data, u32 mem_mask) { LOGMASKED(LOG_REGISTERS, "Write @ Cxxxx: %x to reg %x (mask %x)\n", data, offset, mem_mask); if (offset == 0x4000) { m_mode = (data >> 2) & 3; u32 new_clock; switch (data & 3) { case 0: new_clock = 80'000'000; break; case 1: new_clock = 57'283'200; break; default: fatalerror("colorboard708: invalid crystal select %d\n", data & 3); break; } if (m_pixel_clock != new_clock) { m_pixel_clock = new_clock; m_hres = m_tms34061->hvisible(); m_htotal = m_tms34061->htotal(); m_vres = m_tms34061->vvisible(); m_vtotal = m_tms34061->vtotal(); if ((m_hres != 0) && (m_vres != 0) && (m_htotal != 0) && (m_vtotal != 0)) { rectangle visarea(0, m_hres - 1, 0, m_vres - 1); m_screen->configure(m_htotal, m_vtotal, visarea, attotime::from_ticks(m_htotal * m_vtotal, m_pixel_clock).as_attoseconds()); } } } else { if (mem_mask == 0xff000000) { m_tms34061->write(offset, 0, 0, data >> 24); } else { m_tms34061->write(offset, 0, 0, data & 0xff); } } } u32 lcpds_cv8lc_device::ramdac_r(offs_t offset, u32 mem_mask) { LOGMASKED(LOG_RAMDAC, "Read DAC @ %08x (mask %08x)\n", offset, mem_mask); return 0; } void lcpds_cv8lc_device::ramdac_w(offs_t offset, u32 data, u32 mem_mask) { u8 writedata = (mem_mask == 0xff000000) ? (data >> 24) : (data & 0xff); LOGMASKED(LOG_RAMDAC, "%08x (write %02x) to DAC at %x (mask %08x)\n", data, writedata, offset, mem_mask); switch (offset) { case 0: m_pal_address = writedata; m_pal_idx = 0; break; case 1: switch (m_pal_idx) { case 0: m_palette->set_pen_red_level(m_pal_address, writedata); break; case 1: m_palette->set_pen_green_level(m_pal_address, writedata); break; case 2: m_palette->set_pen_blue_level(m_pal_address, writedata); break; } m_pal_idx++; if (m_pal_idx == 3) { m_pal_address++; m_pal_address &= 0xff; m_pal_idx = 0; } break; default: LOGMASKED(LOG_REGISTERS, "%08x to DAC @ %x\n", data & mem_mask, offset); break; } } } // anonymous namespace DEFINE_DEVICE_TYPE_PRIVATE(PDSLC_COLORVUE8LC, device_nubus_card_interface, lcpds_cv8lc_device, "cv8lc", "RasterOps ColorVue 8LC video card")