// license:BSD-3-Clause
// copyright-holders:Ryan Holtz
/*********************************************************************
vino.cpp
Silicon Graphics VINO (Video-In, No Out) controller emulation
*********************************************************************/
#include "emu.h"
#include "vino.h"
#define LOG_UNKNOWN (1 << 0)
#define LOG_READS (1 << 1)
#define LOG_WRITES (1 << 2)
#define LOG_DESCS (1 << 3)
#define LOG_DMA (1 << 4)
#define LOG_DMA_DATA (1 << 5)
#define LOG_FIFO (1 << 6)
#define LOG_FIELDS (1 << 7)
#define LOG_COORDS (1 << 8)
#define LOG_INPUTS (1 << 9)
#define LOG_INTERRUPTS (1 << 10)
#define LOG_INDICES (1 << 11)
#define LOG_DEFAULT (LOG_WRITES | LOG_FIELDS | LOG_DMA | LOG_DESCS | LOG_READS | LOG_INTERRUPTS | LOG_INDICES | LOG_COORDS)
#define VERBOSE (0)
#include "logmacro.h"
DEFINE_DEVICE_TYPE(VINO, vino_device, "vino", "SGI VINO Controller")
vino_device::vino_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, VINO, tag, owner, clock)
, m_i2c_data_out(*this)
, m_i2c_data_in(*this)
, m_i2c_stop(*this)
, m_interrupt_cb(*this)
, m_picture(*this, "srcimg")
, m_avivideo(*this, "srcavi")
, m_space(*this, finder_base::DUMMY_TAG, -1)
, m_input_bitmap(nullptr)
{
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void vino_device::device_start()
{
m_channels[0].m_fetch_timer = timer_alloc(TIMER_FETCH_CHA);
m_channels[1].m_fetch_timer = timer_alloc(TIMER_FETCH_CHB);
save_item(NAME(m_rev_id));
save_item(NAME(m_control));
save_item(NAME(m_int_status));
save_item(NAME(m_i2c_ctrl));
save_item(NAME(m_i2c_data));
for (uint32_t i = CHAN_A; i < CHAN_COUNT; i++)
{
save_item(NAME(m_channels[i].m_alpha), i);
save_item(NAME(m_channels[i].m_clip_start), i);
save_item(NAME(m_channels[i].m_clip_end), i);
save_item(NAME(m_channels[i].m_frame_rate), i);
save_item(NAME(m_channels[i].m_field_counter), i);
save_item(NAME(m_channels[i].m_line_size), i);
save_item(NAME(m_channels[i].m_line_counter), i);
save_item(NAME(m_channels[i].m_page_index), i);
save_item(NAME(m_channels[i].m_next_desc_ptr), i);
save_item(NAME(m_channels[i].m_start_desc_ptr), i);
save_item(NAME(m_channels[i].m_descriptors[0]), i);
save_item(NAME(m_channels[i].m_descriptors[1]), i);
save_item(NAME(m_channels[i].m_descriptors[2]), i);
save_item(NAME(m_channels[i].m_descriptors[3]), i);
save_item(NAME(m_channels[i].m_fifo_threshold), i);
save_item(NAME(m_channels[i].m_fifo_gio_ptr), i);
save_item(NAME(m_channels[i].m_fifo_video_ptr), i);
save_item(NAME(m_channels[i].m_fifo), i);
save_item(NAME(m_channels[i].m_active_alpha), i);
save_item(NAME(m_channels[i].m_curr_line), i);
save_item(NAME(m_channels[i].m_frame_mask_shift), i);
save_item(NAME(m_channels[i].m_frame_mask_shifter), i);
save_item(NAME(m_channels[i].m_pixel_size), i);
save_item(NAME(m_channels[i].m_next_fifo_word), i);
save_item(NAME(m_channels[i].m_word_pixel_counter), i);
save_item(NAME(m_channels[i].m_pixels_per_even_field), i);
save_item(NAME(m_channels[i].m_pixels_per_odd_field), i);
save_item(NAME(m_channels[i].m_field_pixels_remaining[0]), i);
save_item(NAME(m_channels[i].m_field_pixels_remaining[1]), i);
save_item(NAME(m_channels[i].m_end_of_field), i);
m_channels[i].m_fetch_timer->adjust(attotime::never);
}
m_i2c_data_out.resolve_safe();
m_i2c_data_in.resolve_safe(0x00);
m_i2c_stop.resolve_safe();
m_interrupt_cb.resolve_safe();
}
void vino_device::device_reset()
{
m_rev_id = 0xb0;
m_control = 0;
m_int_status = 0;
m_i2c_ctrl = 0;
m_i2c_data = 0;
for (uint32_t i = CHAN_A; i < CHAN_COUNT; i++)
{
m_channels[i].m_alpha = 0;
m_channels[i].m_clip_start = 0;
m_channels[i].m_clip_end = 0;
m_channels[i].m_frame_rate = 0;
m_channels[i].m_field_counter = 0;
m_channels[i].m_line_size = 0;
m_channels[i].m_line_counter = 0;
m_channels[i].m_page_index = 0;
m_channels[i].m_next_desc_ptr = 0;
m_channels[i].m_start_desc_ptr = 0;
m_channels[i].m_fifo_threshold = 0;
m_channels[i].m_fifo_gio_ptr = 0;
m_channels[i].m_fifo_video_ptr = 0;
m_channels[i].m_active_alpha = 0;
m_channels[i].m_curr_line = 0;
m_channels[i].m_frame_mask_shift = 0;
m_channels[i].m_frame_mask_shifter = 0;
m_channels[i].m_pixel_size = 0;
m_channels[i].m_next_fifo_word = 0;
m_channels[i].m_word_pixel_counter = 0;
for (uint32_t j = 0; j < 4; j++)
{
m_channels[i].m_descriptors[j] = 0ULL;
}
m_channels[i].m_fetch_timer->adjust(attotime::never);
}
}
void vino_device::device_add_mconfig(machine_config &config)
{
IMAGE_PICTURE(config, m_picture);
IMAGE_AVIVIDEO(config, m_avivideo);
}
void vino_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
if (id == TIMER_FETCH_CHA || id == TIMER_FETCH_CHB)
fetch_pixel((int)id);
}
uint32_t vino_device::read(offs_t offset, uint32_t mem_mask)
{
switch (offset & ~1)
{
case 0x0000/4: // Rev/ID
LOGMASKED(LOG_READS, "%s: Rev/ID read: %08x & %08x\n", machine().describe_context(), m_rev_id, mem_mask);
return m_rev_id;
case 0x0008/4: // Control
LOGMASKED(LOG_READS, "%s: Control read: %08x & %08x\n", machine().describe_context(), m_control, mem_mask);
return m_control;
case 0x0010/4: // Interrupt Status
LOGMASKED(LOG_READS, "%s: Interrupt Status read: %08x & %08x\n", machine().describe_context(), m_int_status, mem_mask);
return m_int_status;
case 0x0018/4: // I2C Control
LOGMASKED(LOG_READS, "%s: I2C Control read: %08x & %08x\n", machine().describe_context(), m_i2c_ctrl, mem_mask);
return m_i2c_ctrl;
case 0x0020/4: // I2C Data
LOGMASKED(LOG_READS, "%s: I2C Data read: %08x & %08x\n", machine().describe_context(), m_i2c_data, mem_mask);
return m_i2c_data;
case 0x0028/4: // ChA Alpha
case 0x00b0/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_READS, "%s: Ch%c Alpha read: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', m_channels[channel].m_alpha, mem_mask);
return m_channels[channel].m_alpha;
}
case 0x0030/4: // ChA Clipping Start
case 0x00b8/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_READS, "%s: Ch%c Clipping Start read: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', m_channels[channel].m_clip_start, mem_mask);
return m_channels[channel].m_clip_start;
}
case 0x0038/4: // ChA Clipping End
case 0x00c0/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_READS, "%s: Ch%c Clipping End read: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', m_channels[channel].m_clip_end, mem_mask);
return m_channels[channel].m_clip_end;
}
case 0x0040/4: // ChA Frame Rate
case 0x00c8/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_READS, "%s: Ch%c Frame Rate read: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', m_channels[channel].m_frame_rate, mem_mask);
return m_channels[channel].m_frame_rate;
}
case 0x0048/4: // ChA Field Counter
case 0x00d0/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_READS, "%s: Ch%c Field Counter read: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', m_channels[channel].m_field_counter, mem_mask);
return m_channels[channel].m_field_counter;
}
case 0x0050/4: // ChA Line Size
case 0x00d8/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_READS, "%s: Ch%c Line Size read: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', m_channels[channel].m_line_size, mem_mask);
return m_channels[channel].m_line_size;
}
case 0x0058/4: // ChA Line Counter
case 0x00e0/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_READS, "%s: Ch%c Line Counter read: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', m_channels[channel].m_line_counter, mem_mask);
return m_channels[channel].m_line_counter;
}
case 0x0060/4: // ChA Page Index
case 0x00e8/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_READS, "%s: Ch%c Page Index read: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', m_channels[channel].m_page_index, mem_mask);
return m_channels[channel].m_page_index;
}
case 0x0068/4: // ChA Pointer to Next Four Descriptors
case 0x00f0/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_READS, "%s: Ch%c Pointer to Next Four Descriptors read: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', m_channels[channel].m_next_desc_ptr, mem_mask);
return m_channels[channel].m_next_desc_ptr;
}
case 0x0070/4: // ChA Pointer to Start of Descriptor Table
case 0x00f8/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_READS, "%s: Ch%c Pointer to Start of Descriptor Table read: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', m_channels[channel].m_start_desc_ptr, mem_mask);
return m_channels[channel].m_start_desc_ptr;
}
case 0x0078/4: // ChA Descriptor 0
case 0x0100/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_READS, "%s: Ch%c Descriptor 0 Data read: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', m_channels[channel].m_descriptors[0], mem_mask);
return m_channels[channel].m_descriptors[0];
}
case 0x0080/4: // ChA Descriptor 1
case 0x0108/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_READS, "%s: Ch%c Descriptor 1 Data read: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', m_channels[channel].m_descriptors[1], mem_mask);
return m_channels[channel].m_descriptors[1];
}
case 0x0088/4: // ChA Descriptor 2
case 0x0110/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_READS, "%s: Ch%c Descriptor 2 Data read: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', m_channels[channel].m_descriptors[2], mem_mask);
return m_channels[channel].m_descriptors[2];
}
case 0x0090/4: // ChA Descriptor 3
case 0x0118/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_READS, "%s: Ch%c Descriptor 3 Data read: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', m_channels[channel].m_descriptors[3], mem_mask);
return m_channels[channel].m_descriptors[3];
}
case 0x0098/4: // ChA FIFO Threshold
case 0x0120/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_READS, "%s: Ch%c FIFO Threshold read: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', m_channels[channel].m_fifo_threshold, mem_mask);
return m_channels[channel].m_fifo_threshold;
}
case 0x00a0/4: // ChA FIFO Read Pointer
case 0x0128/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_READS, "%s: Ch%c FIFO Read Pointer read: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', m_channels[channel].m_fifo_gio_ptr, mem_mask);
return m_channels[channel].m_fifo_gio_ptr;
}
case 0x00a8/4: // ChA FIFO Write Pointer
case 0x0130/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_READS, "%s: Ch%c FIFO Write Pointer read: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', m_channels[channel].m_fifo_video_ptr, mem_mask);
return m_channels[channel].m_fifo_video_ptr;
}
default:
LOGMASKED(LOG_READS | LOG_UNKNOWN, "%s: Unknown VINO read: %08x & %08x\n", machine().describe_context(), 0x00080000 + offset*4, mem_mask);
return 0;
}
return 0;
}
void vino_device::write(offs_t offset, uint32_t data, uint32_t mem_mask)
{
switch (offset & ~1)
{
case 0x0000/4: // Rev/ID
LOGMASKED(LOG_WRITES, "%s: Rev/ID write (ignored): %08x & %08x\n", machine().describe_context(), data, mem_mask);
break;
case 0x0008/4: // Control
LOGMASKED(LOG_WRITES, "%s: Control write: %08x & %08x\n", machine().describe_context(), data, mem_mask);
control_w(data & CTRL_MASK);
break;
case 0x0010/4: // Interrupt Status
LOGMASKED(LOG_WRITES, "%s: Interrupt Status write: %08x & %08x\n", machine().describe_context(), data, mem_mask);
for (uint32_t bit = 0; bit < 6; bit++)
{
if (!BIT(data, bit))
{
m_int_status &= ~(1 << bit);
}
}
interrupts_w(m_int_status);
break;
case 0x0018/4: // I2C Control
LOGMASKED(LOG_WRITES, "%s: I2C Control write: %08x & %08x\n", machine().describe_context(), data, mem_mask);
m_i2c_ctrl = data & I2C_CTRL_MASK;
break;
case 0x0020/4: // I2C Data
LOGMASKED(LOG_WRITES, "%s: I2C Data write: %08x & %08x\n", machine().describe_context(), data, mem_mask);
m_i2c_data = data & I2C_DATA_MASK;
break;
case 0x0028/4: // ChA Alpha
case 0x00b0/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_WRITES, "%s: Ch%c Alpha write: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', data, mem_mask);
m_channels[channel].m_alpha = data & ALPHA_MASK;
break;
}
case 0x0030/4: // ChA Clipping Start
case 0x00b8/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_WRITES, "%s: Ch%c Clipping Start write: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', data, mem_mask);
m_channels[channel].m_clip_start = data & CLIP_REG_MASK;
break;
}
case 0x0038/4: // ChA Clipping End
case 0x00c0/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_WRITES, "%s: Ch%c Clipping End write: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', data, mem_mask);
m_channels[channel].m_clip_end = data & CLIP_REG_MASK;
break;
}
case 0x0040/4: // ChA Frame Rate
case 0x00c8/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_WRITES, "%s: Ch%c Frame Rate write: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', data, mem_mask);
frame_rate_w(channel, data);
break;
}
case 0x0048/4: // ChA Field Counter
case 0x00d0/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_WRITES, "%s: Ch%c Field Counter write (ignored): %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', data, mem_mask);
break;
}
case 0x0050/4: // ChA Line Size
case 0x00d8/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_WRITES, "%s: Ch%c Line Size write: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', data, mem_mask);
m_channels[channel].m_line_size = data & LINE_SIZE_MASK;
break;
}
case 0x0058/4: // ChA Line Counter
case 0x00e0/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_WRITES, "%s: Ch%c Line Counter write: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', data, mem_mask);
m_channels[channel].m_line_counter = data & LINE_COUNTER_MASK;
break;
}
case 0x0060/4: // ChA Page Index
case 0x00e8/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_WRITES, "%s: Ch%c Page Index write: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', data, mem_mask);
page_index_w(channel, data & PAGE_INDEX_MASK);
break;
}
case 0x0068/4: // ChA Pointer to Next Four Descriptors
case 0x00f0/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_WRITES, "%s: Ch%c Pointer to Next Four Descriptors write: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', data, mem_mask);
next_desc_w(channel, data & DESC_PTR_MASK);
break;
}
case 0x0070/4: // ChA Pointer to Start of Descriptor Table
case 0x00f8/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_WRITES, "%s: Ch%c Pointer to Start of Descriptor Table write: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', data, mem_mask);
m_channels[channel].m_start_desc_ptr = data & DESC_PTR_MASK;
break;
}
case 0x0078/4: // ChA Descriptor 0
case 0x0100/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_WRITES, "%s: Ch%c Descriptor 0 Data write: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', data, mem_mask);
m_channels[channel].m_descriptors[0] = (data & DESC_PTR_MASK);
m_channels[channel].m_descriptors[0] |= DESC_VALID_BIT;
break;
}
case 0x0080/4: // ChA Descriptor 1
case 0x0108/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_WRITES, "%s: Ch%c Descriptor 1 Data write: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', data, mem_mask);
m_channels[channel].m_descriptors[1] = (data & DESC_PTR_MASK);
m_channels[channel].m_descriptors[1] |= DESC_VALID_BIT;
break;
}
case 0x0088/4: // ChA Descriptor 2
case 0x0110/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_WRITES, "%s: Ch%c Descriptor 2 Data write: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', data, mem_mask);
m_channels[channel].m_descriptors[2] = (data & DESC_PTR_MASK);
m_channels[channel].m_descriptors[2] |= DESC_VALID_BIT;
break;
}
case 0x0090/4: // ChA Descriptor 3
case 0x0118/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_WRITES, "%s: Ch%c Descriptor 3 Data write: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', data, mem_mask);
m_channels[channel].m_descriptors[3] = (data & DESC_PTR_MASK);
m_channels[channel].m_descriptors[3] |= DESC_VALID_BIT;
break;
}
case 0x0098/4: // ChA FIFO Threshold
case 0x0120/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_WRITES, "%s: Ch%c FIFO Threshold write: %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', data, mem_mask);
m_channels[channel].m_fifo_threshold = data & FIFO_MASK;
break;
}
case 0x00a0/4: // ChA FIFO Read Pointer
case 0x0128/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_WRITES, "%s: Ch%c FIFO Read Pointer write (ignored): %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', data, mem_mask);
break;
}
case 0x00a8/4: // ChA FIFO Write Pointer
case 0x0130/4: // ChB ...
{
const uint32_t channel = (offset < 0x00b0/4) ? 0 : 1;
LOGMASKED(LOG_WRITES, "%s: Ch%c FIFO Write Pointer write (ignored): %08x & %08x\n", machine().describe_context(), channel ? 'B' : 'A', data, mem_mask);
break;
}
default:
LOGMASKED(LOG_WRITES | LOG_UNKNOWN, "%s: Unknown VINO write: %08x = %08x & %08x\n", machine().describe_context(), 0x00080000 + offset*4, data, mem_mask);
break;
}
}
bool vino_device::is_even_field(int channel)
{
return BIT(m_channels[channel].m_field_counter, 0);
}
bool vino_device::is_interleaved(int channel)
{
static const uint32_t s_masks[2] = { CTRL_CHA_INTERLEAVE_EN, CTRL_CHB_INTERLEAVE_EN };
return (m_control & s_masks[channel]) != 0;
}
void vino_device::end_of_field(int channel)
{
LOGMASKED(LOG_FIELDS, "End of field for channel %c\n", channel ? 'B' : 'A');
channel_t &chan = m_channels[channel];
if (BIT(chan.m_frame_mask_shifter, 0))
{
do_dma_transfer(channel);
if (!is_interleaved(channel))
{
page_index_w(channel, 0);
}
else if (is_even_field(channel))
{
LOGMASKED(LOG_FIELDS, "Even field ended, setting page index to %08x\n", chan.m_line_size + 8);
line_count_w(channel, 0);
page_index_w(channel, chan.m_line_size + 8);
next_desc_w(channel, chan.m_start_desc_ptr);
}
else // odd field
{
LOGMASKED(LOG_FIELDS, "Odd field ended, setting page index to %08x\n", 0);
line_count_w(channel, 0);
page_index_w(channel, 0);
chan.m_start_desc_ptr = chan.m_next_desc_ptr;
}
static const uint32_t s_eof_masks[2] = { ISR_CHA_EOF, ISR_CHB_EOF };
interrupts_w(m_int_status | s_eof_masks[channel]);
}
chan.m_field_pixels_remaining[0] = chan.m_pixels_per_odd_field;
chan.m_field_pixels_remaining[1] = chan.m_pixels_per_even_field;
chan.m_field_x = 0;
chan.m_field_y = 0;
chan.m_field_counter++;
chan.m_frame_mask_shifter >>= 1;
chan.m_frame_mask_shift--;
if (chan.m_frame_mask_shift == 0)
load_frame_mask_shifter(channel);
chan.m_active_alpha = chan.m_alpha;
}
void vino_device::do_dma_transfer(int channel)
{
channel_t &chan = m_channels[channel];
LOGMASKED(LOG_DMA, "Transferring %08x words via DMA\n", chan.m_fifo_video_ptr - chan.m_fifo_gio_ptr);
while (chan.m_fifo_gio_ptr != chan.m_fifo_video_ptr && !(chan.m_descriptors[0] & DESC_STOP_BIT))
{
const uint32_t address = (chan.m_descriptors[0] & 0x3ffff000) | (chan.m_page_index & 0x00000ff8);
const uint64_t word = chan.m_fifo[chan.m_fifo_gio_ptr >> 3];
LOGMASKED(LOG_DMA_DATA, "Writing %08x%08x to %08x\n", (uint32_t)(word >> 32), (uint32_t)word, address);
m_space->write_qword(address, word);
page_index_w(channel, chan.m_page_index + 8);
if (is_interleaved(channel))
{
line_count_w(channel, chan.m_line_counter + 8);
}
chan.m_fifo_gio_ptr += 8;
}
chan.m_fifo_gio_ptr = 0;
chan.m_fifo_video_ptr = 0;
}
void vino_device::push_fifo(int channel)
{
channel_t &chan = m_channels[channel];
LOGMASKED(LOG_FIFO, "Pushing %08x%08x onto FIFO, new ptr %08x, remaining (%d/%d)\n",
(uint32_t)(chan.m_next_fifo_word >> 32),
(uint32_t)chan.m_next_fifo_word,
chan.m_fifo_video_ptr + 8,
chan.m_field_pixels_remaining[0],
chan.m_field_pixels_remaining[1]);
if (chan.m_fifo_video_ptr < 0x400)
{
chan.m_fifo[chan.m_fifo_video_ptr >> 3] = chan.m_next_fifo_word;
chan.m_fifo_video_ptr += 8;
if (chan.m_fifo_video_ptr >= chan.m_fifo_threshold)
{
do_dma_transfer(channel);
}
}
chan.m_next_fifo_word = 0;
chan.m_word_pixel_counter = 0;
}
void vino_device::argb_to_yuv(uint32_t argb, int32_t &y, int32_t &u, int32_t &v)
{
const int32_t r = (argb >> 16) & 0xff;
const int32_t g = (argb >> 8) & 0xff;
const int32_t b = (argb >> 0) & 0xff;
y = (int32_t)(0.299f * r + 0.587f * g + 0.114f * b);
u = (int32_t)(0.492f * (b - y));
v = (int32_t)(0.877f * (r - y));
}
uint32_t vino_device::yuv_to_abgr(int channel, int32_t y, int32_t u, int32_t v)
{
int32_t r = y + 1.14f * v;
int32_t g = y - 0.395f * u - 0.581f * v;
int32_t b = y + 2.032f * u;
r = (r > 255) ? 255 : (r < 0 ? 0 : r);
g = (g > 255) ? 255 : (g < 0 ? 0 : g);
b = (b > 255) ? 255 : (b < 0 ? 0 : b);
LOGMASKED(LOG_INPUTS, "%d,%d,%d in yuv is %08x in rgb\n", y, u, v, (b << 16) | (g << 8) | r);
return (m_channels[channel].m_alpha << 24) | (b << 16) | (g << 8) | r;
}
bool vino_device::merge_pixel(int channel, int32_t y, int32_t u, int32_t v, pixel_format_t format)
{
channel_t &chan = m_channels[channel];
switch (format)
{
case FORMAT_RGBA32:
{
const uint32_t shift = 32 - (chan.m_word_pixel_counter << 5);
chan.m_next_fifo_word &= ~(0xffffffffULL << shift);
chan.m_next_fifo_word |= (uint64_t)yuv_to_abgr(channel, y, u, v) << shift;
chan.m_word_pixel_counter++;
count_pixel(channel);
return (chan.m_word_pixel_counter == 2);
}
case FORMAT_YUV422:
{
const uint8_t uy = (uint8_t)y;
const uint8_t uu = (uint8_t)u;
const uint8_t uv = (uint8_t)v;
const uint32_t y_shift = (3 - chan.m_word_pixel_counter) * 16;
const uint64_t y_mask = 0xffULL << y_shift;
chan.m_next_fifo_word &= ~y_mask;
chan.m_next_fifo_word |= (uy << y_shift);
if (!(chan.m_word_pixel_counter & 1))
{
chan.m_next_fifo_word &= 0xffffff << (y_shift - 8);
chan.m_next_fifo_word |= (uu << (y_shift + 8));
chan.m_next_fifo_word |= (uv << (y_shift - 8));
}
chan.m_word_pixel_counter++;
count_pixel(channel);
return (chan.m_word_pixel_counter == 4);
}
case FORMAT_RGBA8:
{
const uint32_t abgr = yuv_to_abgr(channel, y, u, v);
const uint8_t b = (abgr >> 16) & 0xc0;
const uint8_t g = ((abgr >> 8) & 0xe0) >> 2;
const uint8_t r = ((abgr >> 0) & 0xe0) >> 5;
const uint8_t bgr = b | g | r;
const uint32_t shift = 56 - (chan.m_word_pixel_counter << 3);
chan.m_next_fifo_word &= ~(0xffULL << shift);
chan.m_next_fifo_word |= (uint64_t)bgr << shift;
chan.m_word_pixel_counter++;
count_pixel(channel);
return (chan.m_word_pixel_counter == 8);
}
case FORMAT_Y8:
{
const uint8_t y8 = (uint8_t)y;
const uint32_t shift = 56 - (chan.m_word_pixel_counter << 3);
chan.m_next_fifo_word &= ~(0xffULL << shift);
chan.m_next_fifo_word |= (uint64_t)y8 << shift;
chan.m_word_pixel_counter++;
count_pixel(channel);
return (chan.m_word_pixel_counter == 8);
}
}
return false;
}
void vino_device::count_pixel(int channel)
{
channel_t &chan = m_channels[channel];
const uint32_t even_or_odd = BIT(chan.m_field_counter, 0);
int32_t pixels_consumed = chan.m_decimation;
chan.m_field_x += chan.m_decimation;
if (chan.m_field_x >= chan.m_field_width)
{
chan.m_field_x = 0;
chan.m_field_y++;
if (is_interleaved(channel))
{
//pixels_consumed += chan.m_field_width * chan.m_decimation;
}
}
chan.m_field_pixels_remaining[even_or_odd] -= pixels_consumed;
if (chan.m_field_pixels_remaining[even_or_odd] == 0)
{
chan.m_field_x = 0;
chan.m_field_y = 0;
chan.m_end_of_field = true;
}
}
vino_device::pixel_format_t vino_device::get_current_format(int channel)
{
static const uint32_t rgb_masks[2] = { CTRL_CHA_COLOR_SPACE_RGB, CTRL_CHB_COLOR_SPACE_RGB };
static const uint32_t luma_masks[2] = { CTRL_CHA_LUMA_ONLY, CTRL_CHB_LUMA_ONLY };
static const uint32_t dither_masks[2] = { CTRL_CHA_DITHER_EN, CTRL_CHB_DITHER_EN };
const bool rgb_mode = (m_control & rgb_masks[channel]) != 0;
const bool luma_mode = (m_control & luma_masks[channel]) != 0;
const bool dither_mode = (m_control & dither_masks[channel]) != 0;
if (rgb_mode)
return dither_mode ? FORMAT_RGBA8 : FORMAT_RGBA32;
else
return luma_mode ? FORMAT_Y8 : FORMAT_YUV422;
}
void vino_device::process_pixel(int channel, int32_t y, int32_t u, int32_t v)
{
if (merge_pixel(channel, y, u, v, get_current_format(channel)))
{
push_fifo(channel);
}
if (m_channels[channel].m_end_of_field)
{
end_of_field(channel);
m_channels[channel].m_end_of_field = false;
}
}
uint32_t vino_device::linear_rgb(uint32_t a, uint32_t b, float f)
{
const int32_t ra = (a >> 16) & 0xff;
const int32_t ga = (a >> 8) & 0xff;
const int32_t ba = (a >> 0) & 0xff;
const int32_t rb = (b >> 16) & 0xff;
const int32_t gb = (b >> 8) & 0xff;
const int32_t bb = (b >> 0) & 0xff;
const float inv_f = 1.0f - f;
int32_t rc = (int32_t)(ra * inv_f + rb * f);
int32_t gc = (int32_t)(ga * inv_f + gb * f);
int32_t bc = (int32_t)(ba * inv_f + bb * f);
rc = (rc > 255 ? 255 : (rc < 0 ? 0 : rc));
gc = (gc > 255 ? 255 : (gc < 0 ? 0 : gc));
bc = (bc > 255 ? 255 : (bc < 0 ? 0 : bc));
return (rc << 16) | (gc << 8) | bc;
}
uint32_t vino_device::bilinear_pixel(float s, float t)
{
if (m_input_bitmap == nullptr)
return 0xff000000;
uint32_t width = m_input_bitmap->width();
uint32_t height = m_input_bitmap->height();
if (width == 0 || height == 0)
return 0xff000000;
width--;
height--;
int32_t s0 = (int32_t)floorf(s * width);
int32_t s1 = (int32_t)floorf(s * width + 1);
int32_t t0 = (int32_t)floorf(t * height);
int32_t t1 = (int32_t)floorf(t * height + 1);
s0 = (s0 < 0 ? 0 : (s0 > width ? width : s0));
s1 = (s1 < 0 ? 0 : (s1 > width ? width : s1));
t0 = (t0 < 0 ? 0 : (t0 > height ? height : t0));
t1 = (t1 < 0 ? 0 : (t1 > height ? height : t1));
LOGMASKED(LOG_COORDS, "lerping from %d,%d to %d,%d\n", s0, t0, s1, t1);
const uint32_t p00 = m_input_bitmap->pix(t0, s0);
const uint32_t p01 = m_input_bitmap->pix(t0, s1);
const uint32_t p10 = m_input_bitmap->pix(t1, s0);
const uint32_t p11 = m_input_bitmap->pix(t1, s1);
LOGMASKED(LOG_INPUTS, "%08x, %08x, %08x, %08x\n", p00, p01, p10, p11);
float ip = 0.0f;
const float sf = modff(s, &ip);
const float tf = modff(t, &ip);
const uint32_t top = linear_rgb(p00, p01, sf);
const uint32_t bot = linear_rgb(p10, p11, sf);
LOGMASKED(LOG_INPUTS, "%08x, %08x\n", top, bot);
const uint32_t mixed = linear_rgb(top, bot, tf);
LOGMASKED(LOG_INPUTS, "%08x\n", mixed);
return 0xff000000 | mixed;
}
void vino_device::input_pixel(int channel, int32_t &y, int32_t &u, int32_t &v)
{
m_input_bitmap = &m_avivideo->get_frame();
if (m_input_bitmap)
{
channel_t &chan = m_channels[channel];
if (is_interleaved(channel))
{
const uint32_t even_or_odd = BIT(chan.m_field_counter, 0);
const uint32_t even_or_odd_offset = (even_or_odd ? 0 : 1);
float s = (float)chan.m_field_x / chan.m_field_width;
float t = (float)chan.m_field_y / chan.m_field_height[even_or_odd];
LOGMASKED(LOG_COORDS, "%d, %d coords: %f, %f\n", chan.m_field_x, chan.m_field_y * 2 + even_or_odd_offset, s, t);
const uint32_t argb = bilinear_pixel(s, t);
argb_to_yuv(argb, y, u, v);
LOGMASKED(LOG_INPUTS, "%08x in yuv is %d,%d,%d\n", argb, y, u, v);
}
else
{
float s = (float)chan.m_field_x / chan.m_field_width;
float t = (float)chan.m_field_y / chan.m_field_height[0];
LOGMASKED(LOG_COORDS, "%d, %d coords: %f, %f\n", chan.m_field_x, chan.m_field_y, s, t);
const uint32_t argb = bilinear_pixel(s, t);
argb_to_yuv(argb, y, u, v);
LOGMASKED(LOG_INPUTS, "%08x in yuv is %d,%d,%d\n", argb, y, u, v);
}
}
}
void vino_device::fetch_pixel(int channel)
{
channel_t &chan = m_channels[channel];
if (chan.m_decimation > 1 && (chan.m_field_x % chan.m_decimation) != 0)
{
count_pixel(channel);
return;
}
if (BIT(chan.m_frame_mask_shifter, 0))
{
int32_t y = 0, u = 0, v = 0;
input_pixel(channel, y, u, v);
process_pixel(channel, y, u, v);
}
else
{
count_pixel(channel);
if (chan.m_end_of_field)
{
end_of_field(channel);
chan.m_end_of_field = false;
}
}
}
attotime vino_device::calculate_field_rate(int channel)
{
channel_t &chan = m_channels[channel];
const uint32_t fields_per_second = (BIT(chan.m_frame_rate, 0) == FRAME_RATE_PAL) ? 50 : 60;
return attotime::from_hz(fields_per_second);
}
attotime vino_device::calculate_fetch_rate(int channel)
{
channel_t &chan = m_channels[channel];
const uint32_t frames_per_second = (BIT(chan.m_frame_rate, 0) == FRAME_RATE_PAL) ? 25 : 30;
const uint32_t x_end = chan.m_clip_end & CLIP_X_MASK;
const uint32_t y_end_even = (chan.m_clip_end >> CLIP_YEVEN_SHIFT) & CLIP_YEVEN_MASK;
const uint32_t y_end_odd = (chan.m_clip_end >> CLIP_YODD_SHIFT) & CLIP_YODD_MASK;
const uint32_t x_start = chan.m_clip_start & CLIP_X_MASK;
const uint32_t y_start_even = (chan.m_clip_start >> CLIP_YEVEN_SHIFT) & CLIP_YEVEN_MASK;
const uint32_t y_start_odd = (chan.m_clip_start >> CLIP_YODD_SHIFT) & CLIP_YODD_MASK;
const uint32_t width = (x_end - x_start) + 1;
const uint32_t height_even = (y_end_even - y_start_even) + 1;
const uint32_t height_odd = (y_end_odd - y_start_odd) + 1;
const uint32_t field_size_even = (height_even * width) / chan.m_decimation;
const uint32_t field_size_odd = (height_odd * width) / chan.m_decimation;
const uint32_t frame_size = field_size_even + field_size_odd;
LOGMASKED(LOG_DMA, "Frames per second: %d\n", frames_per_second);
LOGMASKED(LOG_DMA, "Frame width: %d - %d = %d\n", x_end, x_start, width);
LOGMASKED(LOG_DMA, "Even field height: %d - %d = %d\n", y_end_even, y_start_even, height_even);
LOGMASKED(LOG_DMA, "Odd field height: %d - %d = %d\n", y_end_odd, y_start_odd, height_odd);
LOGMASKED(LOG_DMA, "Even field pixels: %d\n", field_size_even);
LOGMASKED(LOG_DMA, "Odd field pixels: %d\n", field_size_odd);
chan.m_pixels_per_even_field = (int32_t)field_size_even;
chan.m_pixels_per_odd_field = (int32_t)field_size_odd;
chan.m_field_height[1] = height_even / chan.m_decimation;
chan.m_field_height[0] = height_odd / chan.m_decimation;
chan.m_field_width = width;
chan.m_field_x = 0;
chan.m_field_y = 0;
return attotime::from_hz(frames_per_second * frame_size);
}
bool vino_device::page_index_w(int channel, uint32_t data)
{
channel_t &chan = m_channels[channel];
const uint32_t old = chan.m_page_index;
chan.m_page_index = data;
LOGMASKED(LOG_INDICES, "Page Index write: %08x\n", data);
while (chan.m_page_index >= 0x1000)
{
chan.m_page_index -= 0x1000;
}
if (chan.m_page_index < old)
{
shift_dma_descriptors(channel);
return true;
}
return false;
}
void vino_device::interrupts_w(uint32_t new_int)
{
const uint32_t old = m_int_status;
m_int_status = (new_int & (m_control >> 1)) & ISR_MASK;
const uint32_t raised_ints = (~old & m_int_status);
if (raised_ints != 0)
{
LOGMASKED(LOG_INTERRUPTS, "Interrupt status %08x, raising interrupt\n", m_int_status);
m_interrupt_cb(1);
}
else if (m_int_status == 0)
{
LOGMASKED(LOG_INTERRUPTS, "All interrupts clear, lowering interrupt\n");
m_interrupt_cb(0);
}
}
void vino_device::shift_dma_descriptors(int channel)
{
const uint32_t even_or_odd = BIT(m_channels[channel].m_field_counter, 0);
LOGMASKED(LOG_DESCS, "Shifting descriptors, remaining pixels %d\n", m_channels[channel].m_field_pixels_remaining[even_or_odd]);
channel_t &chan = m_channels[channel];
for (int i = 0; i < 3; i++)
{
chan.m_descriptors[i] = chan.m_descriptors[i + 1];
}
if (!(chan.m_descriptors[0] & DESC_VALID_BIT))
{
LOGMASKED(LOG_DESCS, "Shifted in a descriptor without a valid bit; loading a new set\n");
load_dma_descriptors(channel, chan.m_next_desc_ptr);
chan.m_next_desc_ptr += 16;
}
else if (chan.m_descriptors[0] & DESC_JUMP_BIT)
{
LOGMASKED(LOG_DESCS, "Shifted in a descriptor with a jump bit; loading a new set from %08x\n", chan.m_descriptors[0] & 0x3fffffff);
load_dma_descriptors(channel, chan.m_descriptors[0] & 0x3fffffff);
}
}
void vino_device::load_dma_descriptors(int channel, uint32_t addr)
{
channel_t &chan = m_channels[channel];
for (int i = 0; i < 4; i++)
{
chan.m_descriptors[i] = (uint64_t)m_space->read_dword(addr + (i << 2)) | DESC_VALID_BIT;
LOGMASKED(LOG_DESCS, "Descriptor %d: %08x\n", i, (uint32_t)chan.m_descriptors[i]);
}
if (chan.m_descriptors[0] & DESC_STOP_BIT)
{
LOGMASKED(LOG_DESCS, "Shifted in a descriptor with a stop bit; stopping DMA and flagging an interrupt\n");
static const uint32_t s_stop_masks[2] = { ISR_CHA_DESC, ISR_CHB_DESC };
interrupts_w(m_int_status | s_stop_masks[channel]);
static const uint32_t s_dma_mask[2] = { CTRL_CHA_DMA_EN, CTRL_CHB_DMA_EN };
m_control &= ~s_dma_mask[channel];
chan.m_fetch_timer->adjust(attotime::never);
}
}
void vino_device::invalidate_dma_descriptors(int channel)
{
LOGMASKED(LOG_DESCS, "Invalidating descriptors\n");
for (int i = 0; i < 4; i++)
m_channels[channel].m_descriptors[i] &= ~DESC_VALID_BIT;
}
void vino_device::next_desc_w(int channel, uint32_t data)
{
m_channels[channel].m_next_desc_ptr = data;
invalidate_dma_descriptors(channel);
load_dma_descriptors(channel, m_channels[channel].m_next_desc_ptr);
}
bool vino_device::line_count_w(int channel, uint32_t data)
{
channel_t &chan = m_channels[channel];
chan.m_line_counter = data & LINE_COUNTER_MASK;
LOGMASKED(LOG_INDICES, "Line Counter write: %08x\n", data);
if (chan.m_line_counter == chan.m_line_size)
{
chan.m_line_counter = 0xff8;
return page_index_w(channel, chan.m_page_index + (chan.m_line_size + 8));
}
return false;
}
void vino_device::frame_rate_w(int channel, uint32_t data)
{
m_channels[channel].m_frame_rate = data & FRAME_RATE_REG_MASK;
load_frame_mask_shifter(channel);
}
void vino_device::load_frame_mask_shifter(int channel)
{
channel_t &chan = m_channels[channel];
chan.m_frame_mask_shift = (BIT(chan.m_frame_rate, 0) == FRAME_RATE_PAL) ? 10 : 12;
chan.m_frame_mask_shifter = chan.m_frame_rate >> 1;
}
void vino_device::control_w(uint32_t data)
{
const uint32_t old = m_control;
m_control = data;
if (m_control & CTRL_CHA_DECIMATE_EN)
{
m_channels[0].m_decimation = ((m_control >> CTRL_CHA_DECIMATION_SHIFT) & CTRL_CHA_DECIMATION_MASK) + 1;
}
else
{
m_channels[0].m_decimation = 1;
}
if (m_control & CTRL_CHB_DECIMATE_EN)
{
m_channels[1].m_decimation = ((m_control >> CTRL_CHB_DECIMATION_SHIFT) & CTRL_CHB_DECIMATION_MASK) + 1;
}
else
{
m_channels[1].m_decimation = 1;
}
const uint32_t changed = old ^ m_control;
if (changed == 0)
return;
interrupts_w(m_int_status);
static const uint32_t s_dma_mask[2] = { CTRL_CHA_DMA_EN, CTRL_CHB_DMA_EN };
for (int channel = 0; channel < 2; channel++)
{
if (changed & s_dma_mask[channel])
{
if (data & s_dma_mask[channel])
{
channel_t &chan = m_channels[channel];
LOGMASKED(LOG_DMA, "Enabling DMA on channel %c\n", channel ? 'B' : 'A');
chan.m_field_counter = 0;
attotime fetch_rate = calculate_fetch_rate(channel);
chan.m_fetch_timer->adjust(fetch_rate, 0, fetch_rate);
chan.m_field_pixels_remaining[0] = chan.m_pixels_per_odd_field;
chan.m_field_pixels_remaining[1] = chan.m_pixels_per_even_field;
chan.m_field_x = 0;
chan.m_field_y = 0;
}
else
{
LOGMASKED(LOG_DMA, "Disabling DMA on channel %c\n", channel ? 'B' : 'A');
m_channels[channel].m_fetch_timer->adjust(attotime::never);
}
}
}
}