// license:BSD-3-Clause // copyright-holders:Aaron Giles /********************************************************************** TI TMS9927 and compatible CRT controller emulation **********************************************************************/ #include "emu.h" #include "tms9927.h" #include "screen.h" static constexpr uint8_t chars_per_row_value[8] = { 20, 32, 40, 64, 72, 80, 96, 132 }; static constexpr uint8_t skew_bits_value[4] = { 0, 1, 2, 2 }; #define HCOUNT (m_reg[0] + 1) #define INTERLACED ((m_reg[1] >> 7) & 0x01) #define HSYNC_WIDTH (((m_reg[1] >> 3) & 0x0f) + 1) #define HSYNC_DELAY (((m_reg[1] >> 0) & 0x07) + 1) #define SCANS_PER_DATA_ROW (((m_reg[2] >> 3) & 0x0f) + 1) #define CHARS_PER_DATA_ROW (chars_per_row_value[(m_reg[2] >> 0) & 0x07]) #define SKEW_BITS (skew_bits_value[(m_reg[3] >> 6) & 0x03]) #define DATA_ROWS_PER_FRAME ((m_reg[3] & 0x3f) + 1) #define SCAN_LINES_PER_FRAME ((m_reg[4] * 2) + 256 + (((m_reg[1] >> 7) & 0x01) * 257)) #define VERTICAL_DATA_START (m_reg[5]) #define LAST_DISP_DATA_ROW (m_reg[6] & 0x3f) #define CURSOR_CHAR_ADDRESS (m_reg[7]) #define CURSOR_ROW_ADDRESS (m_reg[8] & 0x3f) DEFINE_DEVICE_TYPE(TMS9927, tms9927_device, "tms9927", "TMS9927 VTC") DEFINE_DEVICE_TYPE(CRT5027, crt5027_device, "crt5027", "CRT5027 VTAC") DEFINE_DEVICE_TYPE(CRT5037, crt5037_device, "crt5037", "CRT5037 VTAC") DEFINE_DEVICE_TYPE(CRT5057, crt5057_device, "crt5057", "CRT5057 VTAC") tms9927_device::tms9927_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : tms9927_device(mconfig, TMS9927, tag, owner, clock) { } tms9927_device::tms9927_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, type, tag, owner, clock) , device_video_interface(mconfig, *this) , m_write_vsyn(*this) , m_write_hsyn(*this) , m_hpixels_per_column(0) , m_overscan_left(0) , m_overscan_right(0) , m_overscan_top(0) , m_overscan_bottom(0) , m_selfload(*this, finder_base::DUMMY_TAG) , m_reset(false) , m_valid_config(false) , m_custom_visarea(0, 0, 0, 0) { std::fill(std::begin(m_reg), std::end(m_reg), 0x00); } crt5027_device::crt5027_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : tms9927_device(mconfig, CRT5027, tag, owner, clock) { } crt5037_device::crt5037_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : tms9927_device(mconfig, CRT5037, tag, owner, clock) { } crt5057_device::crt5057_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : tms9927_device(mconfig, CRT5057, tag, owner, clock) { } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void tms9927_device::device_start() { assert(clock() > 0); if (!(m_hpixels_per_column > 0)) fatalerror("TMS9927: number of pixels per column must be explicitly set using set_char_width()!\n"); // allocate timers m_vsync_timer = timer_alloc(FUNC(tms9927_device::toggle_vsync), this); m_hsync_timer = timer_alloc(FUNC(tms9927_device::toggle_hsync), this); // register for state saving save_item(NAME(m_reg)); save_item(NAME(m_start_datarow)); save_item(NAME(m_reset)); save_item(NAME(m_vsyn)); save_item(NAME(m_hsyn)); } //------------------------------------------------- // device_reset - device-specific reset //------------------------------------------------- void tms9927_device::device_reset() { m_start_datarow = 0; } //------------------------------------------------- // device_clock_changed - called when the // device clock is altered in any way //------------------------------------------------- void tms9927_device::device_clock_changed() { if (m_valid_config && !m_reset) recompute_parameters(false); } //------------------------------------------------- // device_stop - device-specific stop //------------------------------------------------- void tms9927_device::device_stop() { osd_printf_debug("TMS9927: Final params: (%d, %d, %d, %d, %d, %d, %d)\n", clock() * m_hpixels_per_column, m_total_hpix, 0, m_visible_hpix, m_total_vpix, 0, m_visible_vpix); } //------------------------------------------------- // timer events //------------------------------------------------- TIMER_CALLBACK_MEMBER(tms9927_device::toggle_vsync) { m_vsyn = !m_vsyn; m_write_vsyn(m_vsyn ? 1 : 0); if (m_vsyn) { m_vsync_timer->adjust(screen().time_until_pos(m_vsyn_end, m_hsyn_start)); } else { m_vsync_timer->adjust(screen().time_until_pos(m_vsyn_start, m_hsyn_start)); } } TIMER_CALLBACK_MEMBER(tms9927_device::toggle_hsync) { m_hsyn = !m_hsyn; m_write_hsyn(m_hsyn ? 1 : 0); uint16_t vpos = screen().vpos(); if (m_hsyn) { screen().update_now(); if (screen().hpos() > m_hsyn_end) vpos = (vpos + 1) % m_total_vpix; m_hsync_timer->adjust(screen().time_until_pos(vpos, m_hsyn_end)); } else { if (screen().hpos() > m_hsyn_start) vpos = (vpos + 1) % m_total_vpix; m_hsync_timer->adjust(screen().time_until_pos(vpos, m_hsyn_start)); } } void tms9927_device::device_post_load() { recompute_parameters(true); } void tms9927_device::generic_access(offs_t offset) { switch (offset) { case 0x07: /* Processor Self Load */ case 0x0f: /* Non-processor self-load */ if (m_selfload.found()) { for (int cur = 0; cur < 7; cur++) write(cur, m_selfload[cur]); for (int cur = 0; cur < 1; cur++) write(cur + 0xc, m_selfload[cur + 7]); } else popmessage("tms9927: self-load initiated with no PROM!"); /* processor self-load waits with reset enabled; non-processor just goes ahead */ m_reset = (offset == 0x07); break; case 0x0a: /* Reset */ if (!m_reset) { screen().update_now(); m_reset = true; } break; case 0x0b: /* Up scroll */ screen().update_now(); m_start_datarow = (m_start_datarow + 1) % DATA_ROWS_PER_FRAME; break; case 0x0e: /* Start timing chain */ if (m_reset) { screen().update_now(); m_reset = false; recompute_parameters(false); } break; } } void tms9927_device::write(offs_t offset, uint8_t data) { switch (offset) { case 0x00: /* HORIZONTAL CHARACTER COUNT */ case 0x01: /* INTERLACED / HSYNC WIDTH / HSYNC DELAY */ case 0x02: /* SCANS PER DATA ROW / CHARACTERS PER DATA ROW */ case 0x03: /* SKEW BITS / DATA ROWS PER FRAME */ case 0x04: /* SCAN LINES / FRAME */ case 0x05: /* VERTICAL DATA START */ m_reg[offset] = data; recompute_parameters(false); break; case 0x06: /* LAST DISPLAYED DATA ROW */ // TVI-912 writes to this register frequently if (m_reg[offset] != data) { m_reg[offset] = data; recompute_parameters(false); } break; case 0x0c: /* LOAD CURSOR CHARACTER ADDRESS */ case 0x0d: /* LOAD CURSOR ROW ADDRESS */ m_reg[offset - 0x0c + 7] = data; /* Recomputing parameters here will break the scrollup on the Attachè and probably other machines due to m_start_datarow being reset ! */ //recompute_parameters(false); break; default: generic_access(offset); break; } } uint8_t tms9927_device::read(offs_t offset) { switch (offset) { case 0x08: /* READ CURSOR CHARACTER ADDRESS */ case 0x09: /* READ CURSOR ROW ADDRESS */ return m_reg[offset - 0x08 + 7]; default: if (!machine().side_effects_disabled()) generic_access(offset); break; } return 0xff; } int tms9927_device::bl_r() { return (m_reset || screen().vblank() || screen().hblank()) ? 1 : 0; } bool tms9927_device::cursor_bounds(rectangle &bounds) const { int cursorx = CURSOR_CHAR_ADDRESS; int cursory = (CURSOR_ROW_ADDRESS + DATA_ROWS_PER_FRAME - m_start_datarow) % DATA_ROWS_PER_FRAME; bounds.min_x = cursorx * m_hpixels_per_column; bounds.max_x = bounds.min_x + m_hpixels_per_column - 1; bounds.min_y = cursory * SCANS_PER_DATA_ROW; bounds.max_y = bounds.min_y + SCANS_PER_DATA_ROW - 1; return (cursorx < HCOUNT && cursory <= LAST_DISP_DATA_ROW); } void tms9927_device::recompute_parameters(bool postload) { if (m_reset) return; /* compute the screen sizes */ m_total_hpix = HCOUNT * m_hpixels_per_column; m_total_vpix = SCAN_LINES_PER_FRAME; /* determine the visible area, avoid division by 0 */ m_visible_hpix = CHARS_PER_DATA_ROW * m_hpixels_per_column; m_visible_vpix = DATA_ROWS_PER_FRAME * SCANS_PER_DATA_ROW; m_start_datarow = (LAST_DISP_DATA_ROW + 1) % DATA_ROWS_PER_FRAME; m_hsyn_start = (m_visible_hpix + m_overscan_left + HSYNC_DELAY * m_hpixels_per_column) % m_total_hpix; m_hsyn_end = (m_hsyn_start + HSYNC_WIDTH * m_hpixels_per_column) % m_total_hpix; m_vsyn_start = (m_total_vpix + m_overscan_top - VERTICAL_DATA_START) % m_total_vpix; m_vsyn_end = (m_vsyn_start + 3) % m_total_vpix; /* see if it all makes sense */ m_valid_config = true; if ( (m_visible_hpix > m_total_hpix || m_visible_vpix > m_total_vpix) || (((m_visible_hpix-1)<=0) || ((m_visible_vpix-1)<=0)) || ((m_total_hpix * m_total_vpix) == 0) ) { m_valid_config = false; logerror("tms9927: invalid visible size (%dx%d) versus total size (%dx%d)\n", m_visible_hpix, m_visible_vpix, m_total_hpix, m_total_vpix); } if (clock() == 0) { m_valid_config = false; // TODO: make the screen refresh never, and disable the vblank and odd/even interrupts here! logerror("tms9927: invalid clock rate of zero defined!\n"); } /* update */ if (!m_valid_config) return; /* create a visible area */ rectangle visarea(0, m_overscan_left + m_visible_hpix + m_overscan_right - 1, 0, m_overscan_top + m_visible_vpix + m_overscan_bottom - 1); if (m_custom_visarea.width() > 1 && m_custom_visarea.height() > 1) visarea = m_custom_visarea; attotime refresh = clocks_to_attotime(HCOUNT * m_total_vpix); osd_printf_debug( "TMS9927(%s): Total = %dx%d, Visible = %dx%d, HSync = %d-%d, VSync = %d-%d, Skew=%d, Upscroll=%d, Period=%f Hz\n", tag(), m_total_hpix, m_total_vpix, m_visible_hpix, m_visible_vpix, m_hsyn_start, m_hsyn_end, m_vsyn_start, m_vsyn_end, SKEW_BITS, m_start_datarow, refresh.as_hz()); screen().configure(m_total_hpix, m_total_vpix, visarea, refresh.as_attoseconds()); m_hsyn = false; if (!m_write_hsyn.isunset()) { m_write_hsyn(0); m_hsync_timer->adjust(screen().time_until_pos(m_vsyn_start, m_hsyn_start)); } m_vsyn = false; m_write_vsyn(0); m_vsync_timer->adjust(screen().time_until_pos(m_vsyn_start, m_hsyn_start)); }