machine/i2chle.cpp: New mix-in class derived from dimm_spd that allows any device to speak I2C. [R. Belmont]

machine/dimm_spd.cpp: Refactored using the i2chle mix-in. [R. Belmont]

apple/valkyrie.cpp: Collected more information, fixed the display enable, and mixed in i2chle to set the pixel clock over I2C. [R. Belmont]

apple/macquadra630.cpp: Hook I2C up between Cuda and Valkyrie. [R. Belmont]
This commit is contained in:
arbee 2024-05-19 12:34:31 -04:00
parent 340fbec1e4
commit 492b868f03
8 changed files with 505 additions and 299 deletions

View File

@ -1560,6 +1560,17 @@ if (MACHINES["1MB5"]~=null) then
}
end
---------------------------------------------------
--@src/devices/machine/i2cmem.h,MACHINES["I2CHLE"] = true
---------------------------------------------------
if (MACHINES["I2CHLE"]~=null) then
files {
MAME_DIR .. "src/devices/machine/i2chle.cpp",
MAME_DIR .. "src/devices/machine/i2chle.h",
}
end
---------------------------------------------------
--
--@src/devices/machine/i2cmem.h,MACHINES["I2CMEM"] = true

View File

@ -6,8 +6,6 @@
Each DIMM contains a small EEPROM with information about the capacity and
timings of the module. The EEPROM speaks a version of I2C called SMBus.
This does not attempt to be a generalized I2C/SMBus solution.
*/
@ -19,35 +17,12 @@
#define VERBOSE (0)
#include "logmacro.h"
//**************************************************************************
// DEVICE DEFINITIONS
//**************************************************************************
DEFINE_DEVICE_TYPE(DIMM_SPD, dimm_spd_device, "dimm_spd", "DIMM Serial Presence Detect")
constexpr int STATE_IDLE = 0;
constexpr int STATE_GET_ADDRESS = 1;
constexpr int STATE_GET_SUBADDRESS = 2;
constexpr int STATE_READ_DATA = 3;
constexpr int STATE_WAIT_ACK = 4;
//**************************************************************************
// LIVE DEVICE
//**************************************************************************
//-------------------------------------------------
// dimm_spd_device - constructor
//-------------------------------------------------
dimm_spd_device::dimm_spd_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
device_t(mconfig, DIMM_SPD, tag, owner, clock),
write_sda(*this)
i2c_hle_interface(mconfig, *this, 0) // address will be overridden by set_address as before
{
m_data_offset = 0;
m_sda = m_scl = 1;
m_state = m_state_next = STATE_IDLE;
m_last_address = 0;
m_just_acked = false;
}
//-------------------------------------------------
@ -68,21 +43,6 @@ void dimm_spd_device::device_start()
m_data[7] = 0; // data bus width high byte
m_data[11] = 0; // non-ECC (1=parity, 2=ECC)
m_data[62] = 0x12; // SPD version 1.2
m_latch = m_bit = 0;
m_state = STATE_IDLE;
m_sda = m_scl = 1;
m_last_address = 0;
m_data_offset = 0;
save_item(NAME(m_latch));
save_item(NAME(m_bit));
save_item(NAME(m_state));
save_item(NAME(m_state_next));
save_item(NAME(m_data_offset));
save_item(NAME(m_just_acked));
write_sda(1);
}
void dimm_spd_device::set_dimm_size(dimm_size_t size)
@ -138,191 +98,13 @@ void dimm_spd_device::set_dimm_size(dimm_size_t size)
}
}
//-------------------------------------------------
// device_reset - device-specific reset
//-------------------------------------------------
void dimm_spd_device::device_reset()
u8 dimm_spd_device::read_data(u16 offset)
{
}
void dimm_spd_device::sda_write(int state)
{
if (m_size == SIZE_SLOT_EMPTY)
{
return;
}
if (m_sda != state)
{
m_sda = state & 1;
if (m_scl)
{
if (m_sda)
{
LOG("%s: stop\n", tag());
m_state = STATE_IDLE;
m_last_address = 0;
m_just_acked = false;
m_data_offset = 0;
}
else
{
LOG("%s: start\n", tag());
m_state = STATE_GET_ADDRESS;
m_bit = 0;
m_latch = 0;
m_just_acked = false;
}
}
}
}
void dimm_spd_device::scl_write(int state)
{
if (m_size == SIZE_SLOT_EMPTY)
{
return;
}
if (m_scl != state)
{
m_scl = state & 1;
switch (m_state)
{
case STATE_IDLE:
// just ignore everything until a START
break;
case STATE_GET_ADDRESS:
case STATE_GET_SUBADDRESS:
if (m_bit < 8)
{
if (m_scl)
{
m_latch <<= 1;
m_latch |= m_sda;
m_bit++;
}
}
else
{
if (m_scl)
{
m_bit++;
}
else
{
if (m_bit == 8)
{
if (m_state == STATE_GET_ADDRESS)
{
LOG("%s: Got address %02x (ours is %02x r/w %d)\n", tag(), m_latch >> 1, m_address, m_latch & 1);
// check if reading
if (m_latch & 1)
{
if ((m_latch >> 1) == m_address)
{
LOG("%s: address matches, ACKing\n", tag());
write_sda(0);
m_bit = 0;
m_latch = 0;
m_state_next = STATE_READ_DATA;
m_state = STATE_WAIT_ACK;
}
else
{
LOG("%s: address doesn't match, ignoring\n", tag());
m_state = STATE_IDLE;
write_sda(1);
}
}
else
{
LOGMASKED(LOG_DATAOUT, "%s: write: getting subaddress\n", tag());
m_last_address = m_latch >> 1;
write_sda(0);
m_bit = 0;
m_latch = 0;
m_state_next = STATE_GET_SUBADDRESS;
m_state = STATE_WAIT_ACK;
}
}
else if (m_state == STATE_GET_SUBADDRESS)
{
LOGMASKED(LOG_DATAOUT, "%s: subaddress is %02x\n", tag(), m_latch);
m_data_offset = m_latch;
write_sda(0);
m_bit = 0;
m_latch = 0;
m_state_next = STATE_IDLE; // is this correct?
m_state = STATE_WAIT_ACK;
}
}
}
}
break;
case STATE_WAIT_ACK:
if (!m_scl)
{
m_state = m_state_next;
write_sda(1);
}
break;
case STATE_READ_DATA:
if (m_bit < 8)
{
if (!m_scl)
{
m_bit++;
write_sda(1);
}
else
{
if (m_bit == 0)
{
m_latch = m_data[m_data_offset++];
m_data_offset &= 0xff;
LOGMASKED(LOG_DATAOUT, "%s: outputting byte %02x\n", tag(), m_latch);
}
write_sda(BIT(m_latch, 7));
m_latch <<= 1;
}
}
else
{
if (m_scl)
{
// did the master ACK or NACK?
if (m_sda)
{
LOGMASKED(LOG_DATAOUT, "%s: master NACK\n", tag());
m_state = STATE_IDLE;
write_sda(1);
}
else
{
LOGMASKED(LOG_DATAOUT, "%s: master ACK\n", tag());
m_just_acked = true;
}
}
else
{
write_sda(1);
if (m_just_acked)
{
m_bit = 0;
m_just_acked = false;
}
}
}
break;
}
}
// i2c_hle_interface auto-increments the address but doesn't wrap it around
if (offset >= m_size)
{
m_data_offset = 0;
}
return m_data[offset];
}

View File

@ -5,13 +5,8 @@
#pragma once
//**************************************************************************
// TYPE DEFINITIONS
//**************************************************************************
// ======================> dimm_spd_device
class dimm_spd_device : public device_t
#include "machine/i2chle.h"
class dimm_spd_device : public device_t, public i2c_hle_interface
{
public:
// construction/destruction
@ -22,16 +17,6 @@ public:
dimm_spd_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
// inline configuration helpers
void set_address(u16 address) { m_address = address; }
auto sda_callback() { return write_sda.bind(); }
void sda_write(int state);
void scl_write(int state);
devcb_write_line write_sda;
typedef enum
{
SIZE_SLOT_EMPTY = 0,
@ -47,20 +32,14 @@ public:
void set_dimm_size(dimm_size_t size);
protected:
// device-level overrides
// device_t overrides
virtual void device_start() override;
virtual void device_reset() override;
// i2c_hle_interface overrides
virtual u8 read_data(u16 offset) override;
virtual const char *get_tag() override { return tag(); }
private:
u8 m_data[256];
u8 m_latch;
u8 m_bit;
u16 m_address;
u16 m_last_address;
int m_sda, m_scl;
u32 m_state, m_state_next;
u16 m_data_offset;
bool m_just_acked;
dimm_size_t m_size;
};

View File

@ -0,0 +1,276 @@
// license:BSD-3-Clause
// copyright-holders:R. Belmont
/*
I2C HLE mix-in
by R. Belmont
A mix-in to allow devices to speak I2C without reimplementing
the protocol themselves.
If the device returns data over I2C, override read_data.
If the device is written data over I2C, override write_data.
If you want the mix-in's logging to show your device name, override
get_tag() with something that just returns your tag().
This mix-in will auto-increment the address on repeated reads/writes,
it's up to your class that consumes this mix-in
*/
#include "emu.h"
#include "i2chle.h"
#define LOG_DATAOUT (1U << 1)
#define VERBOSE (0)
#define LOG_OUTPUT_FUNC osd_printf_info
#include "logmacro.h"
constexpr int STATE_IDLE = 0;
constexpr int STATE_GET_ADDRESS = 1;
constexpr int STATE_GET_SUBADDRESS = 2;
constexpr int STATE_READ_DATA = 3;
constexpr int STATE_WAIT_ACK = 4;
constexpr int STATE_GET_WRITE_DATA = 5;
i2c_hle_interface::i2c_hle_interface(const machine_config &mconfig, device_t &device, u16 address) :
device_interface(device, "i2chle"),
write_sda(*this),
m_address(address)
{
m_data_offset = 0;
m_sda = m_scl = 1;
m_state = m_state_next = STATE_IDLE;
m_last_address = 0;
m_just_acked = false;
}
i2c_hle_interface::~i2c_hle_interface()
{
}
void i2c_hle_interface::interface_post_start()
{
m_latch = m_bit = 0;
m_state = STATE_IDLE;
m_sda = m_scl = 1;
m_last_address = 0;
m_data_offset = 0;
device().save_item(NAME(m_latch));
device().save_item(NAME(m_bit));
device().save_item(NAME(m_state));
device().save_item(NAME(m_state_next));
device().save_item(NAME(m_data_offset));
device().save_item(NAME(m_just_acked));
write_sda(1);
}
void i2c_hle_interface::sda_write(int state)
{
if (m_sda != state)
{
m_sda = state & 1;
if (m_scl)
{
if (m_sda)
{
LOG("%s: stop\n", get_tag());
m_state = STATE_IDLE;
m_last_address = 0;
m_just_acked = false;
m_data_offset = 0;
}
else
{
LOG("%s: start\n", get_tag());
m_state = STATE_GET_ADDRESS;
m_bit = 0;
m_latch = 0;
m_just_acked = false;
}
}
}
}
void i2c_hle_interface::scl_write(int state)
{
if (m_scl != state)
{
m_scl = state & 1;
switch (m_state)
{
case STATE_IDLE:
// just ignore everything until a START
break;
case STATE_GET_ADDRESS:
case STATE_GET_SUBADDRESS:
case STATE_GET_WRITE_DATA:
if (m_bit < 8)
{
if (m_scl)
{
m_latch <<= 1;
m_latch |= m_sda;
m_bit++;
}
}
else
{
if (m_scl)
{
m_bit++;
}
else
{
if (m_bit == 8)
{
if (m_state == STATE_GET_ADDRESS)
{
LOG("%s: Got address %02x (ours is %02x r/w %d)\n", get_tag(), m_latch >> 1, m_address, m_latch & 1);
// check if reading
if (m_latch & 1)
{
if ((m_latch >> 1) == m_address)
{
LOG("%s: address matches, ACKing\n", get_tag());
write_sda(0);
m_bit = 0;
m_latch = 0;
m_state_next = STATE_READ_DATA;
m_state = STATE_WAIT_ACK;
}
else
{
LOG("%s: address doesn't match, ignoring\n", get_tag());
m_state = STATE_IDLE;
write_sda(1);
}
}
else
{
if ((m_latch >> 1) == m_address)
{
LOGMASKED(LOG_DATAOUT, "%s: write: getting subaddress\n", get_tag());
m_last_address = m_latch >> 1;
write_sda(0);
m_bit = 0;
m_latch = 0;
m_state_next = STATE_GET_SUBADDRESS;
m_state = STATE_WAIT_ACK;
}
else
{
LOG("%s: address doesn't match, ignoring\n", get_tag());
m_state = STATE_IDLE;
write_sda(1);
}
}
}
else if (m_state == STATE_GET_SUBADDRESS)
{
LOGMASKED(LOG_DATAOUT, "%s: subaddress is %02x\n", get_tag(), m_latch);
m_data_offset = m_latch;
write_sda(0);
m_bit = 0;
m_latch = 0;
m_state_next = STATE_GET_WRITE_DATA;
m_state = STATE_WAIT_ACK;
}
else if (m_state == STATE_GET_WRITE_DATA)
{
LOGMASKED(LOG_DATAOUT, "%s: got write data %02x for address %02x\n", get_tag(), m_latch, m_data_offset);
write_data(m_data_offset, m_latch);
m_data_offset++;
write_sda(0);
m_bit = 0;
m_latch = 0;
m_state_next = STATE_GET_WRITE_DATA;
m_state = STATE_WAIT_ACK;
}
}
}
}
break;
case STATE_WAIT_ACK:
if (!m_scl)
{
m_state = m_state_next;
write_sda(1);
}
break;
case STATE_READ_DATA:
if (m_bit < 8)
{
if (!m_scl)
{
m_bit++;
write_sda(1);
}
else
{
if (m_bit == 0)
{
m_latch = read_data(m_data_offset);
m_data_offset++;
m_data_offset &= 0xff;
LOGMASKED(LOG_DATAOUT, "%s: outputting byte %02x\n", get_tag(), m_latch);
}
write_sda(BIT(m_latch, 7));
m_latch <<= 1;
}
}
else
{
if (m_scl)
{
// did the master ACK or NACK?
if (m_sda)
{
LOGMASKED(LOG_DATAOUT, "%s: master NACK\n", get_tag());
m_state = STATE_IDLE;
write_sda(1);
}
else
{
LOGMASKED(LOG_DATAOUT, "%s: master ACK\n", get_tag());
m_just_acked = true;
}
}
else
{
write_sda(1);
if (m_just_acked)
{
m_bit = 0;
m_just_acked = false;
}
}
}
break;
}
}
}
u8 i2c_hle_interface::read_data(u16 offset)
{
return 0xff;
}
void i2c_hle_interface::write_data(u16 offset, u8 data)
{
}
static const char i2chle_name[] = "i2chle";
const char *i2c_hle_interface::get_tag()
{
return i2chle_name;
}

View File

@ -0,0 +1,46 @@
// license:BSD-3-Clause
// copyright-holders:R. Belmont
#ifndef MAME_MACHINE_I2CHLE_H
#define MAME_MACHINE_I2CHLE_H
#pragma once
class i2c_hle_interface : public device_interface
{
public:
// construction/destruction
i2c_hle_interface(const machine_config &mconfig, device_t &device, u16 address);
virtual ~i2c_hle_interface();
void set_address(u16 address) { m_address = address; }
auto sda_callback() { return write_sda.bind(); }
void sda_write(int state);
void scl_write(int state);
devcb_write_line write_sda;
protected:
void interface_post_start() override ATTR_COLD;
// override this to read out data
virtual u8 read_data(u16 offset);
virtual void write_data(u16 offset, u8 data);
// override this to properly identify your device in logging
virtual const char *get_tag();
u16 m_data_offset;
private:
u8 m_latch;
u8 m_bit;
u16 m_address;
u16 m_last_address;
int m_sda, m_scl;
u32 m_state, m_state_next;
bool m_just_acked;
};
#endif // MAME_MACHINE_I2CHLE_H

View File

@ -43,6 +43,7 @@
#include "bus/nubus/cards.h"
#include "bus/nubus/nubus.h"
#include "cpu/m68000/m68040.h"
#include "machine/input_merger.h"
#include "machine/ram.h"
#include "machine/timer.h"
@ -161,7 +162,7 @@ void quadra630_state::macqd630(machine_config &config)
m_primetimeii->set_maincpu_tag("maincpu");
m_primetimeii->set_scsi_tag("f108:scsi:7:ncr53c96");
VALKYRIE(config, m_video, C32M);
valkyrie_device &valkyrie(VALKYRIE(config, m_video, C32M));
m_video->write_irq().set(m_primetimeii, FUNC(primetime_device::via2_irq_w<0x40>));
MACADB(config, m_macadb, C15M);
@ -177,6 +178,15 @@ void quadra630_state::macqd630(machine_config &config)
m_macadb->adb_data_callback().set(m_cuda, FUNC(cuda_device::set_adb_line));
config.set_perfect_quantum(m_maincpu);
input_merger_device &sda_merger(INPUT_MERGER_ALL_HIGH(config, "sda"));
sda_merger.output_handler().append(m_cuda, FUNC(cuda_device::set_iic_sda));
m_cuda->iic_sda_callback().set(sda_merger, FUNC(input_merger_device::in_w<0>));
m_cuda->iic_sda_callback().append(valkyrie, FUNC(valkyrie_device::sda_write));
m_cuda->iic_scl_callback().set(valkyrie, FUNC(valkyrie_device::scl_write));
valkyrie.sda_callback().set(sda_merger, FUNC(input_merger_device::in_w<1>));
m_primetimeii->pb3_callback().set(m_cuda, FUNC(cuda_device::get_treq));
m_primetimeii->pb4_callback().set(m_cuda, FUNC(cuda_device::set_byteack));
m_primetimeii->pb5_callback().set(m_cuda, FUNC(cuda_device::set_tip));

View File

@ -1,12 +1,26 @@
// license:BSD-3-Clause
// copyright-holders:R. Belmont
/*
Apple "Valkyrie" - very low-cost video framebuffer
Emulation by R. Belmont
Apple "Valkyrie" - very low-cost video framebuffer
Emulation by R. Belmont
This was the bonus awfulness in the Quadra 630/LC 580 machines. While the
ROM software and MacOS don't support many monitor types, the Linux driver indicates
that's more of a software problem.
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/video/fbdev/valkyriefb.c?h=v6.2.10
Also valkyrie.c in the Marathon Infinity open source release from Bungie. (They use Valkyrie's video upscaler
to only have to render 320x240!)
TODO:
- YUV video-in support
- Determine if the I2C clock is internal or external
Video in buffer 0 is at 0xf9300000
Video in buffer 1 is at 0xf9340000
Both buffers are 320x240 with a stride of 1024 bytes, data is 16 bits per pixel (YUV?)
This was the bonus awfulness in the infamous Quadra 630/LC 580 machines. Only
a few monitor types are supported and the video mode timings appear to be hardcoded
into the chip.
*/
#include "emu.h"
@ -15,6 +29,7 @@
#define LOG_MODE (1U << 1)
#define LOG_MONSENSE (1U << 2)
#define LOG_RAMDAC (1U << 3)
#define LOG_CLOCKGEN (1U << 4)
#define VERBOSE (0)
#include "logmacro.h"
@ -35,6 +50,7 @@ void valkyrie_device::map(address_map &map)
valkyrie_device::valkyrie_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) :
device_t(mconfig, VALKYRIE, tag, owner, clock),
i2c_hle_interface(mconfig, *this, 0x28),
m_vram_size(0x100000),
m_pixel_clock(31334400),
m_pal_address(0), m_pal_idx(0), m_mode(0),
@ -43,8 +59,9 @@ valkyrie_device::valkyrie_device(const machine_config &mconfig, const char *tag,
m_monitor_config(*this, "monitor"),
m_irq(*this),
m_vram_offset(0), m_monitor_id(0),
m_base(0), m_stride(1024), m_int_status(0), m_hres(0), m_vres(0), m_htotal(0), m_vtotal(0),
m_config(0)
m_base(0), m_stride(1024), m_video_timing(0x80), m_int_status(0), m_hres(0), m_vres(0), m_htotal(0), m_vtotal(0),
m_config(0),
m_M(1), m_N(1), m_P(1)
{
}
@ -55,6 +72,7 @@ void valkyrie_device::device_start()
m_vbl_timer = timer_alloc(FUNC(valkyrie_device::vbl_tick), this);
m_vbl_timer->adjust(attotime::never);
save_item(NAME(m_video_timing));
save_item(NAME(m_vram_offset));
save_item(NAME(m_mode));
save_item(NAME(m_monitor_id));
@ -69,6 +87,9 @@ void valkyrie_device::device_start()
save_item(NAME(m_pixel_clock));
save_item(NAME(m_config));
save_item(NAME(m_int_status));
save_item(NAME(m_M));
save_item(NAME(m_N));
save_item(NAME(m_P));
save_pointer(NAME(m_vram), m_vram_size);
machine().save().register_postload(save_prepost_delegate(FUNC(valkyrie_device::recalc_mode), this));
@ -76,7 +97,15 @@ void valkyrie_device::device_start()
void valkyrie_device::device_reset()
{
m_enable = false;
m_video_timing = 0x80;
// clear the MAME default palette so we don't get a weird red screen on initial startup
for (int i = 0; i < 256; i++)
{
m_palette->set_pen_red_level(i, 0);
m_palette->set_pen_green_level(i, 0);
m_palette->set_pen_blue_level(i, 0);
}
}
void valkyrie_device::device_add_mconfig(machine_config &config)
@ -98,9 +127,10 @@ static INPUT_PORTS_START(monitor_config)
PORT_START("monitor")
PORT_CONFNAME(0x7f, 6, "Monitor type")
PORT_CONFSETTING(0x02, u8"Mac RGB Display (12\" 512\u00d7384)") // "Rubik" (modified IIgs AppleColor RGB)
PORT_CONFSETTING(0x06, u8"Mac Hi-Res Display (12-14\" 640\u00d7480)") // "High Res"
PORT_CONFSETTING(ext(1, 1, 3), "640x480 VGA")
PORT_CONFSETTING(0x06, u8"Mac Hi-Res Display (12-14\" 640\u00d7480)") // "High Res" 640x480 @ 67 Hz
PORT_CONFSETTING(ext(1, 1, 3), "640x480 VGA") // VGA 640x480 @ 60 Hz
PORT_CONFSETTING(ext(2, 3, 1), "832x624 16\" RGB") // "Goldfish" or "16 inch RGB"
INPUT_PORTS_END
ioport_constructor valkyrie_device::device_input_ports() const
@ -113,7 +143,7 @@ u32 valkyrie_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap,
auto const vram8 = util::big_endian_cast<u8 const>(&m_vram[0]) + 0x1000;
const pen_t *pens = m_palette->pens();
if (!m_enable)
if (m_video_timing & 0x80)
{
return 0;
}
@ -209,7 +239,6 @@ u32 valkyrie_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap,
u8 valkyrie_device::regs_r(offs_t offset)
{
// printf("Read regs @ %x\n", offset);
switch (offset)
{
case 0:
@ -263,8 +292,12 @@ void valkyrie_device::regs_w(offs_t offset, u8 data)
{
switch (offset)
{
case 0: // video timing select (apparently from hardcoded values!)
m_video_timing = data;
case 0: // video mode. hardcoded!
m_video_timing = data & 0xff;
if (!(data & 0x80))
{
recalc_mode();
}
break;
case 4: // video depth: 0=1bpp, 1=2bpp, 2=4bpp, 3=8bpp, 4=16bpp
@ -272,7 +305,7 @@ void valkyrie_device::regs_w(offs_t offset, u8 data)
m_mode = data & 7;
break;
case 0xc: // written to lock in the video timing from register 0
case 0xc: // subsystem configuration register
if (data == 0x1)
{
recalc_mode();
@ -292,15 +325,6 @@ void valkyrie_device::regs_w(offs_t offset, u8 data)
// at startup, the screen isn't wanted on until 0x81 is written.
// if the Video Startup extension is installed, it later sets this to 0x02.
if ((data & 0x80) || (data == 0x02))
{
m_enable = true;
}
else
{
m_enable = false;
}
// start VBL interrupts when the screen is enabled
if (m_enable)
{
m_vbl_timer->adjust(m_screen->time_until_pos(m_vres, 0), 0);
}
@ -315,6 +339,33 @@ void valkyrie_device::regs_w(offs_t offset, u8 data)
LOGMASKED(LOG_MONSENSE, "%x to sense drive\n", data & 0xf);
break;
case 0x20: // video in control
break;
case 0x60: // video window X position
case 0x61:
break;
case 0x64: // video window Y position
case 0x65:
break;
case 0x70: // video window width
case 0x71:
break;
case 0x74: // video window height
case 0x75:
break;
case 0x80: // video field starting X pixel
case 0x81:
break;
case 0x84: // video field starting Y pixel
case 0x85:
break;
default:
LOG("Valkyrie: Unk write %08x @ %x\n", data, offset<<2);
break;
@ -396,48 +447,59 @@ void valkyrie_device::ramdac_w(offs_t offset, u32 data)
void valkyrie_device::recalc_mode()
{
// mode parameters taken from the Quadra 630 Developer Note
switch (m_video_timing)
{
case 0x01: // default
case 0x86: // 13" 640x480
m_hres = 640;
m_vres = 480;
m_htotal = 864;
m_vtotal = 525;
m_pixel_clock = 30240000;
m_stride = 80;
break;
// m_video_timing numbers:
// 2 = 512x384, 60 Hz ("Rubik")
// 6 = 640x480, 72 Hz (Mac 13")
// 9 = 832x624, 75 Hz (Mac 16")
// 11 = 640x480, 60 Hz (VGA)
// 12 = 800x600, 60 Hz (SVGA)
// 13 = 800x600, 72 Hz (SVGA)
// 14 = 1024x768, 60 Hz (SVGA)
// 15 = 1024x768, 72 Hz (SVGA)
case 0x82: // Rubik 512x384
if (m_video_timing & 0x80)
{
return;
}
switch (m_video_timing & 0x7f)
{
case 2: // Rubik 512x384
m_hres = 512;
m_vres = 384;
m_htotal = 640;
m_vtotal = 407;
m_pixel_clock = 15670000;
m_stride = 64;
break;
case 0x8b: // VGA 640x480
case 6: // 13" 640x480
m_hres = 640;
m_vres = 480;
m_htotal = 800;
m_htotal = 864;
m_vtotal = 525;
m_pixel_clock = 25180000;
m_stride = 80;
break;
case 0x89: // 16" RGB 832x624?
case 9: // 16" RGB 832x624?
m_hres = 832;
m_vres = 624;
m_htotal = 1072;
m_vtotal = 690;
m_pixel_clock = 50000000;
m_stride = 104;
break;
case 11: // VGA 640x480
m_hres = 640;
m_vres = 480;
m_htotal = 800;
m_vtotal = 525;
m_stride = 80;
break;
}
const double refresh = (double)m_pixel_clock / (double)(m_htotal * m_vtotal);
LOGMASKED(LOG_MODE, "hres %d vres %d htotal %d vtotal %d refresh %f stride %d mode %d\n", m_hres, m_vres, m_htotal, m_vtotal, refresh, m_stride, m_mode);
if ((m_hres != 0) && (m_vres != 0))
{
rectangle visarea(0, m_hres - 1, 0, m_vres - 1);
@ -474,3 +536,38 @@ TIMER_CALLBACK_MEMBER(valkyrie_device::vbl_tick)
m_vbl_timer->adjust(m_screen->time_until_pos(480, 0), 0);
}
void valkyrie_device::write_data(u16 offset, u8 data)
{
switch (offset)
{
case 0:
return;
case 1:
m_M = data;
break;
case 2:
m_N = data;
break;
case 3:
m_P = data;
break;
}
const double clock = (3986400.0f * (double)(1 << m_P) * (double)m_N) / (double)m_M;
m_pixel_clock = (u32)clock;
// TODO: Apple documents these machines as supporting the 512x384 monitor, but selecting it
// programs garbage to the clock generator. Allow that configuration to work until we determine
// if the Apple developer note is lying.
if ((m_M == 0) && (m_N == 0) && (m_P = 98))
{
m_pixel_clock = 15670000;
}
LOGMASKED(LOG_CLOCKGEN, "Valkyrie: M = %d %02x, N = %d %02x P = %d, pixel clock %d\n", m_M, m_M, m_N, m_N, m_P, m_pixel_clock);
recalc_mode();
}

View File

@ -7,11 +7,12 @@
#pragma once
#include "cpu/m68000/m68040.h"
#include "machine/i2chle.h"
#include "emupal.h"
#include "screen.h"
class valkyrie_device : public device_t
class valkyrie_device : public device_t, public i2c_hle_interface
{
public:
valkyrie_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
@ -22,10 +23,14 @@ public:
auto write_irq() { return m_irq.bind(); }
protected:
// device_t overrides
virtual void device_start() override;
virtual void device_reset() override;
virtual void device_add_mconfig(machine_config &config) override;
// i2c_hle_interface overrides
virtual ioport_constructor device_input_ports() const override;
virtual void write_data(u16 offset, u8 data) override;
virtual const char *get_tag() override { return tag(); }
void recalc_ints();
void recalc_mode();
@ -48,7 +53,7 @@ private:
u32 m_base, m_stride, m_video_timing;
s32 m_int_status;
u32 m_hres, m_vres, m_htotal, m_vtotal, m_config;
bool m_enable;
u8 m_M, m_N, m_P;
u32 screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect);