summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--scripts/src/sound.lua12
-rw-r--r--src/devices/cpu/nec/nec.cpp5
-rw-r--r--src/devices/cpu/nec/necinstr.hxx2
-rw-r--r--src/devices/cpu/nec/v25.cpp5
-rw-r--r--src/devices/sound/upd65043gfu01.cpp249
-rw-r--r--src/devices/sound/upd65043gfu01.h56
-rw-r--r--src/mame/casio/zoomer.cpp700
-rw-r--r--src/mame/casio/zoomer_rtc.cpp150
-rw-r--r--src/mame/casio/zoomer_rtc.h72
-rw-r--r--src/mame/layout/zoomer.lay309
-rw-r--r--src/mame/mame.lst3
11 files changed, 1562 insertions, 1 deletions
diff --git a/scripts/src/sound.lua b/scripts/src/sound.lua
index 431783ccb74..054d87a266f 100644
--- a/scripts/src/sound.lua
+++ b/scripts/src/sound.lua
@@ -1765,3 +1765,15 @@ if (SOUNDS["CF61909"]~=null) then
MAME_DIR .. "src/devices/sound/cf61909.h",
}
end
+
+---------------------------------------------------
+-- NEC uPD65043GF-U01
+--@src/devices/sound/upd65043gfu01.h,SOUNDS["UPD65043GFU01"] = true
+---------------------------------------------------
+
+if (SOUNDS["UPD65043GFU01"]~=null) then
+ files {
+ MAME_DIR .. "src/devices/sound/upd65043gfu01.cpp",
+ MAME_DIR .. "src/devices/sound/upd65043gfu01.h",
+ }
+end
diff --git a/src/devices/cpu/nec/nec.cpp b/src/devices/cpu/nec/nec.cpp
index 49138a745c6..afa45f7dc17 100644
--- a/src/devices/cpu/nec/nec.cpp
+++ b/src/devices/cpu/nec/nec.cpp
@@ -108,6 +108,11 @@
#include "nec.h"
#include "necdasm.h"
+#define LOG_BUSLOCK (1 << 1)
+//#define VERBOSE (...)
+
+#include "logmacro.h"
+
typedef uint8_t BOOLEAN;
typedef uint8_t BYTE;
typedef uint16_t WORD;
diff --git a/src/devices/cpu/nec/necinstr.hxx b/src/devices/cpu/nec/necinstr.hxx
index 11abacbedb0..ae1c1d50b98 100644
--- a/src/devices/cpu/nec/necinstr.hxx
+++ b/src/devices/cpu/nec/necinstr.hxx
@@ -643,7 +643,7 @@ OP( 0xed, i_inaxdx ) { Wreg(AW) = read_port_word(Wreg(DW)); CLKW(12,12,7,12,8,
OP( 0xee, i_outdxal ) { write_port_byte(Wreg(DW), Breg(AL)); CLKS(8,8,3); }
OP( 0xef, i_outdxax ) { write_port_word(Wreg(DW), Wreg(AW)); CLKW(12,12,5,12,8,3,Wreg(DW)); }
-OP( 0xf0, i_lock ) { logerror("%06x: Warning - BUSLOCK\n",PC()); m_no_interrupt=1; CLK(2); }
+OP( 0xf0, i_lock ) { LOGMASKED(LOG_BUSLOCK, "%06x: Warning - BUSLOCK\n",PC()); m_no_interrupt=1; CLK(2); }
OP( 0xf2, i_repne ) { uint32_t next = fetchop(); uint16_t c = Wreg(CW);
switch(next) { /* Segments */
case 0x26: m_seg_prefix=true; m_prefix_base=Sreg(DS1)<<4; next = fetchop(); CLK(2); break;
diff --git a/src/devices/cpu/nec/v25.cpp b/src/devices/cpu/nec/v25.cpp
index 6d96206f0ed..4374da5170b 100644
--- a/src/devices/cpu/nec/v25.cpp
+++ b/src/devices/cpu/nec/v25.cpp
@@ -38,6 +38,11 @@
#include "v25.h"
#include "necdasm.h"
+#define LOG_BUSLOCK (1 << 1)
+//#define VERBOSE (...)
+
+#include "logmacro.h"
+
typedef uint8_t BOOLEAN;
typedef uint8_t BYTE;
typedef uint16_t WORD;
diff --git a/src/devices/sound/upd65043gfu01.cpp b/src/devices/sound/upd65043gfu01.cpp
new file mode 100644
index 00000000000..7ef6c9d98de
--- /dev/null
+++ b/src/devices/sound/upd65043gfu01.cpp
@@ -0,0 +1,249 @@
+// license: BSD-3-Clause
+// copyright-holders: Devin Acker
+
+/*
+ NEC uPD65043GF-U01
+
+ This is the custom sound chip used in the Zoomer PDA.
+ It features 3 square wave generators and 1 noise generator configurable as either white or
+ "metal" noise, as well as an interrupt-driven 8-bit PCM stream.
+
+ TODO:
+ - Verify noise generator behavior (the current implementation is based on presumed similarities
+ with the SN76489)
+ - Verify PCM interrupt behavior (currently only tested with the "About" dialog in Palm-developed
+ Zoomer system apps, which play a short .wav file)
+*/
+
+#include "emu.h"
+#include "upd65043gfu01.h"
+
+#include <algorithm>
+
+
+namespace {
+
+constexpr s8 OUTPUT_LEVEL[16] =
+{
+ 0, 5, 6, 8, 10, 12, 15, 20, 25, 31, 40, 50, 63, 80, 100, 127
+};
+
+} // anonymous namespace
+
+
+DEFINE_DEVICE_TYPE(UPD65043GFU01, upd65043gfu01_device, "upd65043gfu01", "NEC uPD65043GF-U01")
+
+//**************************************************************************
+upd65043gfu01_device::upd65043gfu01_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
+ : device_t(mconfig, UPD65043GFU01, tag, owner, clock)
+ , device_sound_interface(mconfig, *this)
+ , m_irq_cb(*this)
+{
+}
+
+//**************************************************************************
+void upd65043gfu01_device::device_start()
+{
+ m_stream = stream_alloc(0, 1, clock() / 8);
+ m_irq_timer = timer_alloc(FUNC(upd65043gfu01_device::irq_timer), this);
+
+ std::fill(std::begin(m_period), std::end(m_period), 0);
+ std::fill(std::begin(m_count), std::end(m_count), 0);
+ std::fill(std::begin(m_volume), std::end(m_volume), 0);
+ std::fill(std::begin(m_output), std::end(m_output), 0);
+
+ m_noise_mode = 0;
+
+ m_pcm_period = m_pcm_count = 0;
+ m_pcm_buffer_read = 0;
+ m_pcm_buffer_write = 1;
+
+ std::fill(std::begin(m_pcm_buffer), std::end(m_pcm_buffer), 0);
+
+ save_item(NAME(m_control));
+ save_item(NAME(m_period));
+ save_item(NAME(m_count));
+ save_item(NAME(m_volume));
+ save_item(NAME(m_output));
+ save_item(NAME(m_noise_mode));
+ save_item(NAME(m_pcm_period));
+ save_item(NAME(m_pcm_count));
+ save_item(NAME(m_pcm_buffer));
+ save_item(NAME(m_pcm_buffer_read));
+ save_item(NAME(m_pcm_buffer_write));
+}
+
+//**************************************************************************
+void upd65043gfu01_device::device_reset()
+{
+ m_control = 0xff; // disable everything?
+
+ m_irq_timer->adjust(attotime::never);
+ m_irq_cb(0);
+}
+
+//**************************************************************************
+void upd65043gfu01_device::device_clock_changed()
+{
+ m_stream->set_sample_rate(clock() / 8);
+ update_irq();
+}
+
+//**************************************************************************
+u8 upd65043gfu01_device::read(offs_t offset)
+{
+ switch (offset & 0xf)
+ {
+ case 0x0: case 0x2: case 0x4:
+ return m_period[offset >> 1];
+
+ case 0x1: case 0x3: case 0x5:
+ return m_period[offset >> 1] >> 8;
+
+ case 0x6:
+ return m_noise_mode;
+
+ case 0x7: case 0x8: case 0x9: case 0xa:
+ return m_volume[offset - 0x7];
+
+ case 0xc:
+ return m_control;
+
+ case 0xd:
+ return m_pcm_period;
+
+ default:
+ if (!machine().side_effects_disabled())
+ logerror("%s: unknown register read 0x%x\n", machine().describe_context(), offset & 0xf);
+ return 0;
+ }
+}
+
+//**************************************************************************
+void upd65043gfu01_device::write(offs_t offset, u8 data)
+{
+ m_stream->update();
+
+ switch (offset)
+ {
+ case 0x0: case 0x2: case 0x4:
+ m_period[offset >> 1] &= 0x0f00;
+ m_period[offset >> 1] |= data;
+
+ if (offset == 0x4)
+ update_noise();
+ break;
+
+ case 0x1: case 0x3: case 0x5:
+ m_period[offset >> 1] &= 0x00ff;
+ m_period[offset >> 1] |= (data << 8);
+
+ if (offset == 0x5)
+ update_noise();
+ break;
+
+ case 0x6:
+ m_noise_mode = data;
+ update_noise();
+ break;
+
+ case 0x7: case 0x8: case 0x9: case 0xa:
+ m_volume[offset - 0x7] = data & 0xf;
+ break;
+
+ case 0xb:
+ m_pcm_buffer[m_pcm_buffer_write & 0x1ff] = s8(u8(data - 0x80));
+ m_pcm_buffer_write++;
+ break;
+
+ case 0xc:
+ m_control = data;
+ update_irq();
+ break;
+
+ case 0xd:
+ m_pcm_period = data << 1;
+ update_irq();
+ break;
+
+ default:
+ logerror("%s: unknown register write %x = %02x\n", machine().describe_context(), offset, data);
+ break;
+ }
+}
+
+//**************************************************************************
+TIMER_CALLBACK_MEMBER(upd65043gfu01_device::irq_timer)
+{
+ m_irq_cb(1);
+}
+
+//**************************************************************************
+void upd65043gfu01_device::update_irq()
+{
+ if (!BIT(m_control, 2) || BIT(m_control, 4))
+ {
+ // IRQ disabled or PCM stopped
+ m_irq_timer->adjust(attotime::never);
+ m_irq_cb(0);
+ }
+ else
+ {
+ // recalculate timer so the IRQ fires when there are 128 samples or fewer in the buffer
+ // (GEOS will push up to 128 more samples at this point)
+ const u16 samples_left = std::min<u16>(128, (m_pcm_buffer_write - m_pcm_buffer_read) & 0x1ff);
+ const u16 ticks_left = (samples_left * m_pcm_period) - m_pcm_count;
+ m_irq_timer->adjust(m_stream->sample_period() * ticks_left);
+ }
+}
+
+//**************************************************************************
+void upd65043gfu01_device::update_noise()
+{
+ // TODO: this behavior is a complete guess
+ m_period[3] = BIT(m_control, 0) ? (m_period[2] << 1) : (1 << 5);
+}
+
+//**************************************************************************
+void upd65043gfu01_device::sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs)
+{
+ for (int i = 0; i < outputs[0].samples(); i++)
+ {
+ s16 sample = 0;
+
+ for (int i = 0; i < 4; i++)
+ {
+ m_count[i]++;
+ if (m_count[i] >= m_period[i])
+ {
+ m_count[i] = 0;
+
+ if (i < 3)
+ m_output[i] ^= 1;
+ else
+ m_output[i] = machine().rand() & 1;
+ }
+
+ if (!BIT(m_control, 3))
+ {
+ if (BIT(m_output[i], 0))
+ sample += OUTPUT_LEVEL[m_volume[i] & 0xf];
+ else
+ sample -= OUTPUT_LEVEL[m_volume[i] & 0xf];
+ }
+ }
+
+ m_pcm_count++;
+ if (m_pcm_count >= m_pcm_period)
+ {
+ m_pcm_count = 0;
+ if (((m_pcm_buffer_read + 1) ^ m_pcm_buffer_write) & 0x1ff)
+ m_pcm_buffer_read++;
+ }
+
+ if (!BIT(m_control, 4))
+ sample += m_pcm_buffer[m_pcm_buffer_read & 0x1ff];
+
+ outputs[0].put_int_clamp(i, sample, 1 << 10);
+ }
+}
diff --git a/src/devices/sound/upd65043gfu01.h b/src/devices/sound/upd65043gfu01.h
new file mode 100644
index 00000000000..4bcaf6ebf69
--- /dev/null
+++ b/src/devices/sound/upd65043gfu01.h
@@ -0,0 +1,56 @@
+// license: BSD-3-Clause
+// copyright-holders: Devin Acker
+
+#ifndef MAME_SOUND_UPD65043GFU01_H
+#define MAME_SOUND_UPD65043GFU01_H
+
+#pragma once
+
+class upd65043gfu01_device : public device_t, public device_sound_interface
+{
+public:
+ upd65043gfu01_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock = 0);
+
+ static constexpr feature_type imperfect_features() { return feature::SOUND; }
+
+ auto irq_cb() { return m_irq_cb.bind(); }
+
+ u8 read(offs_t offset);
+ void write(offs_t offset, u8 data);
+
+protected:
+ virtual void device_start() override;
+ virtual void device_reset() override;
+ virtual void device_clock_changed() override;
+
+ virtual void sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs) override;
+
+private:
+ TIMER_CALLBACK_MEMBER(irq_timer);
+ void update_irq();
+
+ void update_noise();
+
+ sound_stream *m_stream;
+ emu_timer *m_irq_timer;
+
+ devcb_write_line m_irq_cb;
+
+ u8 m_control;
+
+ u16 m_period[4];
+ u16 m_count[4];
+ u8 m_volume[4];
+ u8 m_output[4];
+ u8 m_noise_mode;
+
+ u8 m_pcm_period;
+ u8 m_pcm_count;
+
+ s8 m_pcm_buffer[0x200];
+ u16 m_pcm_buffer_read, m_pcm_buffer_write;
+};
+
+DECLARE_DEVICE_TYPE(UPD65043GFU01, upd65043gfu01_device)
+
+#endif // MAME_SOUND_UPD65043GFU01_H
diff --git a/src/mame/casio/zoomer.cpp b/src/mame/casio/zoomer.cpp
new file mode 100644
index 00000000000..31d7f99c93a
--- /dev/null
+++ b/src/mame/casio/zoomer.cpp
@@ -0,0 +1,700 @@
+// license: BSD-3-Clause
+// copyright-holders: Devin Acker
+
+/*
+ Casio/Tandy "Zoomer" PDA (1993)
+
+ This early pen-based PDA was created jointly by Casio (who created the hardware and BIOS)
+ and Tandy, with the built-in software provided by GeoWorks and Palm Computing.
+ It was sold by both Casio (as the Z-7000) and Tandy (as the Z-PDA), as well as AST Research
+ as the GRiDPad 2390.
+
+ Main board:
+ LSI101: Casio/NEC uPD95130GD (V20-based CPU)
+ LSI102: Casio/Fujitsu MBCG25173-5104 (I/O controller)
+ LSI201: Toshiba TC35083 (10-bit ADC for touch screen)
+ LSI401: Analog Devices MAX223 (RS232 interface)
+ LSI601: NEC uPD65043GF-U01 (sound chip, SN76489-like with PCM streaming)
+
+ Memory daughterboard:
+ LSI1, LSI2: Fujitsu MB838200AL (8Mbit mask ROM, 16.4mm TSOP)
+ LSI3, LSI4: Fujitsu MB838200AL (8Mbit mask ROM, 16.4mm TSOP, reverse pinout)
+ LSI5, LSI6: Hitachi HM65V8512 (4Mbit SRAM)
+
+ TODO:
+ - PCMCIA (requires 8-bit SRAM or flash cards with specific attribute memory contents)
+ See other TODOs for where/how it should be hooked up, including the card lock switch
+ - Serial and infrared ports
+ - Make suspended unit state persist after pressing the power button and then quitting MAME?
+ (ports 70, 90-af and c0-c7 must be saved as nvram for this to work)
+*/
+
+#include "emu.h"
+
+#include "zoomer_rtc.h"
+
+#include "cpu/nec/nec.h"
+#include "machine/bankdev.h"
+#include "machine/clock.h"
+#include "machine/nvram.h"
+#include "sound/upd65043gfu01.h"
+
+#include "crsshair.h"
+#include "emupal.h"
+#include "screen.h"
+#include "speaker.h"
+
+#include "zoomer.lh"
+
+namespace {
+
+class zoomer_state : public driver_device
+{
+public:
+ zoomer_state(const machine_config &mconfig, device_type type, const char *tag) :
+ driver_device(mconfig, type, tag),
+ m_maincpu(*this, "maincpu"),
+ m_bank_2000(*this, "bank_2000_%u", 0),
+ m_bank_a000(*this, "bank_a000_%u", 0),
+ m_wp_view{
+ { *this, "wp_view0" }, { *this, "wp_view1" }, { *this, "wp_view2" }, { *this, "wp_view3" },
+ { *this, "wp_view4" }, { *this, "wp_view5" }, { *this, "wp_view6" }, { *this, "wp_view7" },
+ { *this, "wp_view8" }, { *this, "wp_view9" }, { *this, "wp_view10" }, { *this, "wp_view11" },
+ { *this, "wp_view12" }, { *this, "wp_view13" }, { *this, "wp_view14" }, {*this, "wp_view15" } },
+ m_rtc(*this, "rtc"),
+ m_psg(*this, "psg"),
+ m_nvram(*this, "nvram", 0x100000, endianness_t::little),
+ m_pen(*this, "PEN"),
+ m_pen_x(*this, "PEN_X"),
+ m_pen_y(*this, "PEN_Y")
+ { }
+
+ void zoomer(machine_config &config) ATTR_COLD;
+
+ template <int Num>
+ void irq_set(int state);
+
+ void power_w(int state);
+
+protected:
+ virtual void machine_start() override ATTR_COLD;
+ virtual void machine_reset() override ATTR_COLD;
+
+private:
+ static inline constexpr unsigned TIMER_RATE = (32768 / 8);
+
+ void maincpu_map(address_map &map) ATTR_COLD;
+ void maincpu_ems_map(address_map &map) ATTR_COLD;
+ void maincpu_io_map(address_map &map) ATTR_COLD;
+
+ u32 screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect);
+
+ void apo_w(u8 data);
+
+ u8 ems_2000_bank_r(offs_t offset);
+ void ems_2000_bank_w(offs_t offset, u8 data);
+ u8 ems_a000_bank_r(offs_t offset);
+ void ems_a000_bank_w(offs_t offset, u8 data);
+
+ template <int Num>
+ TIMER_CALLBACK_MEMBER(timer_irq) { irq_set<Num>(1); }
+
+ u16 timer_count(u8 timer) const;
+ u8 timer_r(offs_t offset);
+ void timer_w(offs_t offset, u8 data);
+
+ u8 irq_status_r(offs_t offset);
+ void irq_ack_w(offs_t offset, u8 data);
+
+ void update_irq();
+
+ u16 pen_x_scaled();
+ u16 pen_y_scaled();
+ u8 pen_value_r(offs_t offset);
+ void pen_select_w(u8 data);
+
+ u8 lcd_ctrl_r(offs_t offset);
+ void lcd_ctrl_w(offs_t offset, u8 data);
+
+ u8 lcdram_latch_r();
+ void lcdram_w(offs_t offset, u8 data);
+
+ required_device<v20_device> m_maincpu;
+ required_device_array<address_map_bank_device, 4> m_bank_2000; // 128kb windows (banked w/ 64kb granularity) at 20000-9ffff
+ required_device_array<address_map_bank_device, 16> m_bank_a000; // 16kb windows at a0000-dffff
+ memory_view m_wp_view[16]; // view used for enabling write-protect trap in a0000-dffff area
+ required_device<zoomer_rtc_device> m_rtc;
+ required_device<upd65043gfu01_device> m_psg;
+
+ memory_share_creator<u8> m_nvram;
+
+ required_ioport m_pen, m_pen_x, m_pen_y;
+
+ u8 m_power;
+
+ emu_timer *m_timer[3];
+ u16 m_timer_rate[3];
+
+ u16 m_irq_status;
+ u16 m_pending_irq;
+
+ u16 m_bank_2000_num[4];
+ u16 m_bank_a000_num[16];
+
+ u8 m_pen_select;
+
+ u8 m_lcdram_latch;
+ u16 m_lcd_ctrl;
+};
+
+enum
+{
+ IRQ_TIMER0,
+ IRQ_TIMER1,
+ IRQ_TIMER2,
+ IRQ_KEYPAD,
+ IRQ_SERIAL, // TODO
+ IRQ_RTC_ALARM,
+ IRQ_RTC_TICK,
+ IRQ_PEN,
+ IRQ_PCMCIA, // TODO
+ IRQ_SOUND,
+ IRQ_POWER,
+ IRQ_PCMCIA_LOCK, // TODO: PCMCIA card lock switch
+ IRQ_PCMCIA_UNLOCK, // TODO: PCMCIA card lock switch
+ IRQ_EMS
+};
+
+
+//**************************************************************************
+// ADDRESS MAPS
+//**************************************************************************
+
+void zoomer_state::maincpu_map(address_map &map)
+{
+ for (int i = 0; i < 4; i++)
+ {
+ const offs_t start = 0x20000 + (i << 17);
+ map(start, start | 0x1ffff).rw(m_bank_2000[i], FUNC(address_map_bank_device::read8), FUNC(address_map_bank_device::write8));
+ }
+ for (int i = 0; i < 16; i++)
+ {
+ const offs_t start = 0xa0000 + (i << 14);
+ map(start, start | 0x3fff).rw(m_bank_a000[i], FUNC(address_map_bank_device::read8), FUNC(address_map_bank_device::write8));
+ map(start, start | 0x3fff).view(m_wp_view[i]);
+ m_wp_view[i][0](start, start | 0x3fff).lw8(NAME([this](u8 data) { irq_set<IRQ_EMS>(1); }));
+ }
+
+ map(0xd8000, 0xdffff).w(FUNC(zoomer_state::lcdram_w));
+ map(0xe0000, 0xfffff).rom().region("maincpu", 0);
+}
+
+void zoomer_state::maincpu_ems_map(address_map &map)
+{
+ map(0x0000000, 0x00fffff).ram().share("nvram");
+ map(0x0400000, 0x07fffff).rom().region("maincpu", 0);
+// map(0x1000000, 0x1ffffff) - TODO: PCMCIA
+}
+
+void zoomer_state::maincpu_io_map(address_map &map)
+{
+ map(0x0020, 0x0023).ram(); // TODO: irq related
+ map(0x0024, 0x0025).r(FUNC(zoomer_state::irq_status_r));
+ map(0x0026, 0x0027).w(FUNC(zoomer_state::irq_ack_w));
+ map(0x0040, 0x004b).rw(FUNC(zoomer_state::timer_r), FUNC(zoomer_state::timer_w));
+ map(0x004d, 0x004d).nopw(); // unknown, pen related
+ map(0x0069, 0x0069).lr8(NAME([] { return 0x80; })); // suppress PCMCIA power usage warning
+ map(0x0070, 0x0070).ram(); // suspend flag, used when pressing power switch or automatically powering off
+ map(0x0071, 0x0071).lr8(NAME([] { return 0x07; })); // unknown, needed for BIOS to start up
+ map(0x0072, 0x0072).portr("PORT72");
+ map(0x0073, 0x0073).lr8(NAME([] { return 0x06; })); // PCMCIA slot voltage?
+ map(0x0075, 0x0076).noprw(); // unknown, pen related
+ map(0x0077, 0x0077).w(FUNC(zoomer_state::apo_w));
+ map(0x0090, 0x00af).rw(FUNC(zoomer_state::ems_a000_bank_r), FUNC(zoomer_state::ems_a000_bank_w));
+ map(0x00c0, 0x00c7).rw(FUNC(zoomer_state::ems_2000_bank_r), FUNC(zoomer_state::ems_2000_bank_w));
+ map(0x00d0, 0x00d1).noprw(); // unknown, input related
+ map(0x00d2, 0x00d2).portr("KEYPAD");
+ map(0x00d3, 0x00d3).nopw(); // unknown, input related
+ map(0x00d4, 0x00d4).portr("POWER");
+ map(0x00d5, 0x00d5).nopw(); // unknown, pen related
+ map(0x00d7, 0x00d7).lr8(NAME([] { return 0x01; })); // unknown, pen related
+ map(0x00d8, 0x00d9).r(FUNC(zoomer_state::pen_value_r));
+ map(0x00da, 0x00da).w(FUNC(zoomer_state::pen_select_w));
+ map(0x00db, 0x00db).nopr(); // pen ADC ready
+ map(0x00dc, 0x00dd).nopw(); // unknown, pen related
+ map(0x00e0, 0x00ef).rw(m_rtc, FUNC(zoomer_rtc_device::read), FUNC(zoomer_rtc_device::write));
+ map(0x0110, 0x011f).rw(m_psg, FUNC(upd65043gfu01_device::read), FUNC(upd65043gfu01_device::write));
+ map(0x0204, 0x0205).rw(FUNC(zoomer_state::lcd_ctrl_r), FUNC(zoomer_state::lcd_ctrl_w));
+ map(0x0213, 0x0213).r(FUNC(zoomer_state::lcdram_latch_r));
+ /*
+ TODO: port 414 = PCMCIA card status
+ bit 0: ready
+ bit 1: write protect
+ bit 2: BVD1 (or 2?)
+ bit 3: BVD2 (or 1?)
+ bit 6: CD1 (or 2?)
+ bit 7: CD2 (or 1?)
+ */
+ map(0x0414, 0x0414).lr8(NAME([] { return 0xff; }));
+ map(0x0800, 0x0fff).noprw(); // TODO: PCMCIA attribute memory?
+}
+
+
+//**************************************************************************
+// INPUT PORT DEFINITIONS
+//**************************************************************************
+
+static INPUT_PORTS_START( zoomer )
+ PORT_START("PORT72")
+ PORT_CONFNAME( 0x01, IP_ACTIVE_HIGH, "Main Battery")
+ PORT_CONFSETTING(0x00, "Normal")
+ PORT_CONFSETTING(0x01, "Low")
+ PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_UNKNOWN) // needs to be low to boot
+ PORT_CONFNAME( 0x04, IP_ACTIVE_HIGH, "Backup Battery")
+ PORT_CONFSETTING(0x00, "Normal")
+ PORT_CONFSETTING(0x04, "Low")
+ PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_UNKNOWN)
+ PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_UNKNOWN) // needs to be high to boot
+ PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_OTHER) PORT_NAME("PCMCIA Lock Switch") // TODO: PCMCIA lock switch (generates IRQ_PCMCIA_LOCK and IRQ_PCMCIA_UNLOCK)
+ PORT_BIT(0xc0, IP_ACTIVE_HIGH, IPT_UNKNOWN)
+
+ PORT_START("KEYPAD")
+ PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_BUTTON3) PORT_NAME("Button B") PORT_WRITE_LINE_MEMBER(zoomer_state, irq_set<IRQ_KEYPAD>)
+ PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_BUTTON2) PORT_NAME("Button A") PORT_WRITE_LINE_MEMBER(zoomer_state, irq_set<IRQ_KEYPAD>)
+ PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_JOYSTICK_UP) PORT_NAME("Cursor Up") PORT_WRITE_LINE_MEMBER(zoomer_state, irq_set<IRQ_KEYPAD>)
+ PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_JOYSTICK_LEFT) PORT_NAME("Cursor Left") PORT_WRITE_LINE_MEMBER(zoomer_state, irq_set<IRQ_KEYPAD>)
+ PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_JOYSTICK_DOWN) PORT_NAME("Cursor Down") PORT_WRITE_LINE_MEMBER(zoomer_state, irq_set<IRQ_KEYPAD>)
+ PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_JOYSTICK_RIGHT) PORT_NAME("Cursor Right") PORT_WRITE_LINE_MEMBER(zoomer_state, irq_set<IRQ_KEYPAD>)
+ PORT_BIT(0xc0, IP_ACTIVE_HIGH, IPT_UNKNOWN)
+
+ PORT_START("POWER")
+ PORT_BIT(0x7f, IP_ACTIVE_HIGH, IPT_UNKNOWN )
+ PORT_BIT(0x80, IP_ACTIVE_HIGH, IPT_POWER_OFF) PORT_NAME("Power") PORT_WRITE_LINE_MEMBER(zoomer_state, power_w)
+
+ PORT_START("PEN")
+ PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_BUTTON1) PORT_NAME("Pen") PORT_WRITE_LINE_MEMBER(zoomer_state, irq_set<IRQ_PEN>)
+
+ PORT_START("PEN_X")
+ PORT_BIT(0x3ff, 128, IPT_LIGHTGUN_X) PORT_NAME("Pen X") PORT_CROSSHAIR(X, 1.0, 0.0, 0) PORT_MINMAX(0, 255) PORT_SENSITIVITY(45) PORT_KEYDELTA(30)
+
+ PORT_START("PEN_Y")
+ PORT_BIT(0x3ff, 179, IPT_LIGHTGUN_Y) PORT_NAME("Pen Y") PORT_CROSSHAIR(Y, 358.0 / 320, 0.0, 0) PORT_MINMAX(0, 357) PORT_SENSITIVITY(45) PORT_KEYDELTA(30)
+INPUT_PORTS_END
+
+
+//**************************************************************************
+// MACHINE EMULATION
+//**************************************************************************
+
+void zoomer_state::machine_start()
+{
+ m_irq_status = 0;
+ m_pending_irq = 0;
+
+ std::fill(std::begin(m_timer_rate), std::end(m_timer_rate), 0);
+ std::fill(std::begin(m_bank_2000_num), std::end(m_bank_2000_num), 0);
+ std::fill(std::begin(m_bank_a000_num), std::end(m_bank_a000_num), 0);
+
+ m_pen_select = 0;
+
+ m_lcdram_latch = 0;
+ m_lcd_ctrl = 0;
+
+ m_maincpu->space(AS_PROGRAM).install_ram(0x00000, 0x1ffff, m_nvram.begin());
+
+ m_timer[0] = timer_alloc(FUNC(zoomer_state::timer_irq<IRQ_TIMER0>), this);
+ m_timer[1] = timer_alloc(FUNC(zoomer_state::timer_irq<IRQ_TIMER1>), this);
+ m_timer[2] = timer_alloc(FUNC(zoomer_state::timer_irq<IRQ_TIMER2>), this);
+
+ save_item(NAME(m_power));
+ save_item(NAME(m_irq_status));
+ save_item(NAME(m_pending_irq));
+ save_item(NAME(m_timer_rate));
+ save_item(NAME(m_bank_2000_num));
+ save_item(NAME(m_bank_a000_num));
+ save_item(NAME(m_pen_select));
+ save_item(NAME(m_lcdram_latch));
+ save_item(NAME(m_lcd_ctrl));
+}
+
+//**************************************************************************
+void zoomer_state::machine_reset()
+{
+ m_power = 1;
+ m_psg->set_output_gain(ALL_OUTPUTS, 1.0);
+}
+
+//**************************************************************************
+u32 zoomer_state::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
+{
+ auto &crosshair = machine().crosshair().get_crosshair(0);
+ crosshair.set_screen(crosshair.y() < 1.0 ? &screen : CROSSHAIR_SCREEN_NONE);
+
+ if (!m_power || !BIT(m_lcd_ctrl, 7))
+ {
+ bitmap.fill(0, cliprect);
+ }
+ else
+ {
+ for (int y = cliprect.top(); y <= cliprect.bottom(); y++)
+ {
+ for (int x = cliprect.left(); x <= cliprect.right(); x++)
+ {
+ const u8 data = m_nvram[0xf8000 + (y << 6) + (x >> 3)];
+ bitmap.pix(y, x) = BIT(data, 7 - (x & 7));
+ }
+ }
+ }
+
+ return 0;
+}
+
+//**************************************************************************
+void zoomer_state::power_w(int state)
+{
+ if (state)
+ {
+ irq_set<IRQ_POWER>(1);
+ m_power = 1;
+ m_maincpu->set_input_line(INPUT_LINE_RESET, CLEAR_LINE);
+ m_psg->set_output_gain(ALL_OUTPUTS, 1.0);
+ }
+}
+
+//**************************************************************************
+void zoomer_state::apo_w(u8 data)
+{
+ if (BIT(data, 7))
+ {
+ m_power = 0;
+ m_maincpu->set_input_line(INPUT_LINE_RESET, ASSERT_LINE);
+ m_psg->set_output_gain(ALL_OUTPUTS, 0.0);
+ }
+}
+
+//**************************************************************************
+u8 zoomer_state::ems_2000_bank_r(offs_t offset)
+{
+ return m_bank_2000_num[offset >> 1] >> (BIT(offset, 0) * 8);
+}
+
+//**************************************************************************
+void zoomer_state::ems_2000_bank_w(offs_t offset, u8 data)
+{
+ const offs_t bank = offset >> 1;
+
+ if (BIT(offset, 0))
+ {
+ m_bank_2000_num[bank] &= 0x00ff;
+ m_bank_2000_num[bank] |= (data << 8);
+ }
+ else
+ {
+ m_bank_2000_num[bank] &= 0xff00;
+ m_bank_2000_num[bank] |= data;
+ }
+
+ m_bank_2000[bank]->set_bank(m_bank_2000_num[bank]);
+}
+
+//**************************************************************************
+u8 zoomer_state::ems_a000_bank_r(offs_t offset)
+{
+ return m_bank_a000_num[offset >> 1] >> (BIT(offset, 0) * 8);
+}
+
+//**************************************************************************
+void zoomer_state::ems_a000_bank_w(offs_t offset, u8 data)
+{
+ const offs_t bank = offset >> 1;
+
+ if (BIT(offset, 0))
+ {
+ m_bank_a000_num[offset >> 1] &= 0x00ff;
+ m_bank_a000_num[offset >> 1] |= (data << 8);
+
+ if (BIT(data, 7))
+ m_wp_view[bank].select(0);
+ else
+ m_wp_view[bank].disable();
+ }
+ else
+ {
+ m_bank_a000_num[offset >> 1] &= 0xff00;
+ m_bank_a000_num[offset >> 1] |= data;
+ }
+
+ m_bank_a000[bank]->set_bank(m_bank_a000_num[bank]);
+}
+
+//**************************************************************************
+u16 zoomer_state::timer_count(u8 timer) const
+{
+ return m_timer_rate[timer] - m_timer[timer]->remaining().as_ticks(TIMER_RATE);
+}
+
+//**************************************************************************
+u8 zoomer_state::timer_r(offs_t offset)
+{
+ const u8 timer = offset >> 2;
+
+ switch (offset & 3)
+ {
+ case 0: return m_timer_rate[timer];
+ case 1: return m_timer_rate[timer] >> 8;
+ case 2: return timer_count(timer);
+ case 3: return timer_count(timer) >> 8;
+ }
+
+ return 0;
+}
+
+//**************************************************************************
+void zoomer_state::timer_w(offs_t offset, u8 data)
+{
+ const u8 timer = offset >> 2;
+
+ switch (offset & 3)
+ {
+ case 0:
+ m_timer_rate[timer] &= 0x0f00;
+ m_timer_rate[timer] |= data;
+ break;
+ case 1:
+ m_timer_rate[timer] &= 0x00ff;
+ m_timer_rate[timer] |= ((data & 0xf) << 8);
+ break;
+ default:
+ break;
+ }
+
+ if (!m_timer_rate[timer])
+ {
+ m_timer[timer]->adjust(attotime::never);
+ }
+ else
+ {
+ /*
+ period needs to be based on value+1 for the GEOS system clock to not run fast
+ (for some reason it reads the RTC only on boot, and then uses an OS-level software timer to keep time after that)
+ */
+ const attotime period = attotime::from_ticks(m_timer_rate[timer] + 1, TIMER_RATE);
+
+ /*
+ DOS allows performing a factory reset by holding A+B on boot, which it checks by enabling input polling,
+ waiting briefly in a loop, and then disabling input polling. in order for the input poll/debounce code
+ to be called the expected number of times before polling is disabled again, the timer IRQ needs to go off
+ immediately when set
+ */
+ if (m_timer[timer]->expire().is_never())
+ m_timer[timer]->adjust(attotime::zero, 0, period);
+ else
+ m_timer[timer]->adjust(m_timer[timer]->remaining(), 0, period);
+ }
+}
+
+//**************************************************************************
+u8 zoomer_state::irq_status_r(offs_t offset)
+{
+ return m_irq_status >> (BIT(offset, 0) * 8);
+}
+
+//**************************************************************************
+void zoomer_state::irq_ack_w(offs_t offset, u8 data)
+{
+ const u16 clear = data << (BIT(offset, 0) * 8);
+
+ if (BIT(clear, 15))
+ {
+ // clear current irq only (used by default handler)
+ m_irq_status &= ~m_pending_irq;
+ m_pending_irq = 0;
+ }
+ else
+ {
+ m_irq_status &= ~clear;
+ m_pending_irq &= ~clear;
+ }
+
+ update_irq();
+}
+
+//**************************************************************************
+void zoomer_state::update_irq()
+{
+ if (m_pending_irq) return;
+
+ int state = CLEAR_LINE;
+ int vector = 0;
+
+ for (int i = 0; i < 14; i++)
+ {
+ if (BIT(m_irq_status, i))
+ {
+ state = ASSERT_LINE;
+ vector = 0x70 + i;
+ m_pending_irq = 1 << i;
+ break;
+ }
+ }
+
+ m_maincpu->set_input_line_and_vector(INPUT_LINE_IRQ0, state, vector);
+}
+
+//**************************************************************************
+template <int Num>
+void zoomer_state::irq_set(int state)
+{
+ if (state)
+ {
+ m_irq_status |= (1 << Num);
+ update_irq();
+ }
+}
+
+//**************************************************************************
+u16 zoomer_state::pen_x_scaled()
+{
+ // 0...255 scaled to 0x069..396
+ static constexpr float scale = 0x32d / 255.0;
+ return 0x69 + (m_pen_x->read() * scale);
+}
+
+//**************************************************************************
+u16 zoomer_state::pen_y_scaled()
+{
+ // 0...357 scaled to 0x044..3d4
+ static constexpr float scale = 0x390 / 357.0;
+ return 0x44 + (m_pen_y->read() * scale);
+}
+
+//**************************************************************************
+u8 zoomer_state::pen_value_r(offs_t offset)
+{
+ u16 value = 0;
+
+ if (m_pen->read() & 1)
+ {
+ switch (m_pen_select)
+ {
+ case 0x9: value = pen_x_scaled() ^ 0x3ff; break;
+ case 0xb: value = pen_x_scaled(); break;
+ case 0xd: value = pen_y_scaled() ^ 0x3ff; break;
+ case 0xf: value = pen_y_scaled(); break;
+ }
+ }
+
+ return value >> (BIT(offset, 0) * 8);
+}
+
+//**************************************************************************
+void zoomer_state::pen_select_w(u8 data)
+{
+ m_pen_select = data;
+}
+
+//**************************************************************************
+u8 zoomer_state::lcd_ctrl_r(offs_t offset)
+{
+ return m_lcd_ctrl >> (BIT(offset, 0) * 8);
+}
+
+//**************************************************************************
+void zoomer_state::lcd_ctrl_w(offs_t offset, u8 data)
+{
+ if (BIT(offset, 0))
+ {
+ m_lcd_ctrl &= 0x00ff;
+ m_lcd_ctrl |= (data << 8);
+ }
+ else
+ {
+ m_lcd_ctrl &= 0xff00;
+ m_lcd_ctrl |= data;
+ }
+}
+
+//**************************************************************************
+u8 zoomer_state::lcdram_latch_r()
+{
+ return m_lcdram_latch;
+}
+
+//**************************************************************************
+void zoomer_state::lcdram_w(offs_t offset, u8 data)
+{
+ offset |= 0xf8000; // TODO: can VRAM be relocated?
+
+ switch (BIT(m_lcd_ctrl, 10, 2))
+ {
+ case 0: m_nvram[offset] = data; break;
+ case 1: m_nvram[offset] |= data; break;
+ case 2: m_nvram[offset] &= data; break;
+ // GEOS seems to read VRAM freely via EMS, but the BIOS does it this way instead
+ case 3: m_lcdram_latch = m_nvram[offset]; break;
+ }
+}
+
+
+//**************************************************************************
+// MACHINE DEFINTIONS
+//**************************************************************************
+
+void zoomer_state::zoomer(machine_config &config)
+{
+ V20(config, m_maincpu, 7.3728_MHz_XTAL);
+ m_maincpu->set_addrmap(AS_PROGRAM, &zoomer_state::maincpu_map);
+ m_maincpu->set_addrmap(AS_IO, &zoomer_state::maincpu_io_map);
+
+ for (int i = 0; i < 4; i++)
+ ADDRESS_MAP_BANK(config, m_bank_2000[i]).set_map(&zoomer_state::maincpu_ems_map).set_options(ENDIANNESS_LITTLE, 8, 25, 0x10000);
+ for (int i = 0; i < 16; i++)
+ ADDRESS_MAP_BANK(config, m_bank_a000[i]).set_map(&zoomer_state::maincpu_ems_map).set_options(ENDIANNESS_LITTLE, 8, 25, 0x4000);
+
+ ZOOMER_RTC(config, m_rtc, 32.768_kHz_XTAL);
+ m_rtc->alarm_cb().set(FUNC(zoomer_state::irq_set<IRQ_RTC_ALARM>));
+ m_rtc->tick_cb().set(FUNC(zoomer_state::irq_set<IRQ_RTC_TICK>));
+
+ NVRAM(config, "nvram");
+
+ screen_device &screen(SCREEN(config, "screen", SCREEN_TYPE_LCD));
+ screen.set_refresh_hz(60);
+ screen.set_vblank_time(ATTOSECONDS_IN_USEC(2500)); /* not accurate */
+ screen.set_size(256, 320);
+ screen.set_visarea_full();
+ screen.set_palette("palette");
+ screen.set_screen_update(FUNC(zoomer_state::screen_update));
+
+ PALETTE(config, "palette", palette_device::MONOCHROME_INVERTED);
+
+ SPEAKER(config, "speaker").front_center();
+
+ UPD65043GFU01(config, m_psg, 7.3728_MHz_XTAL);
+ m_psg->add_route(0, "speaker", 1.0);
+ m_psg->irq_cb().set(FUNC(zoomer_state::irq_set<IRQ_SOUND>));
+
+ config.set_default_layout(layout_zoomer);
+}
+
+
+//**************************************************************************
+// ROM DEFINITIONS
+//**************************************************************************
+
+ROM_START( zoomer )
+ ROM_REGION(0x400000, "maincpu", 0)
+ ROM_LOAD("mb838200al700.lsi1", 0x000000, 0x100000, CRC(5fe48af6) SHA1(790adb436d80409b216996ee02f78164832b5de6))
+ ROM_LOAD("mb838200al701.lsi2", 0x100000, 0x100000, CRC(3104492b) SHA1(9c752c381d5506d7175e3c9645b5d2c731cc0990))
+ ROM_LOAD("mb838200al702.lsi3", 0x200000, 0x100000, CRC(6013e531) SHA1(955c8c0bdf4bc3e262e5f2fe8b0f7d224a855c96))
+ ROM_LOAD("mb838200al703.lsi4", 0x300000, 0x100000, CRC(64038705) SHA1(81ce3e8564d0579cf57a670517ebd2dbf35996c5))
+ROM_END
+
+} // anonymous namespace
+
+
+//**************************************************************************
+// SYSTEM DRIVERS
+//**************************************************************************
+
+// YEAR NAME PARENT COMPAT MACHINE INPUT CLASS INIT COMPANY FULLNAME FLAGS
+COMP( 1993, zoomer, 0, 0, zoomer, zoomer, zoomer_state, empty_init, "Casio / Tandy", "Zoomer (Z-PDA / Z-7000)", MACHINE_SUPPORTS_SAVE )
diff --git a/src/mame/casio/zoomer_rtc.cpp b/src/mame/casio/zoomer_rtc.cpp
new file mode 100644
index 00000000000..73cb203325e
--- /dev/null
+++ b/src/mame/casio/zoomer_rtc.cpp
@@ -0,0 +1,150 @@
+// license:BSD-3-Clause
+// copyright-holders:Devin Acker
+
+#include "emu.h"
+#include "zoomer_rtc.h"
+
+#include <algorithm>
+
+DEFINE_DEVICE_TYPE(ZOOMER_RTC, zoomer_rtc_device, "zoomer_rtc", "Zoomer Real Time Clock")
+
+//**************************************************************************
+zoomer_rtc_device::zoomer_rtc_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, ZOOMER_RTC, tag, owner, clock)
+ , device_rtc_interface(mconfig, *this)
+ , device_nvram_interface(mconfig, *this)
+ , m_tick_cb(*this)
+ , m_alarm_cb(*this)
+{
+}
+
+//**************************************************************************
+void zoomer_rtc_device::device_start()
+{
+ m_month = m_day = m_year = 0;
+
+ m_tick_timer = timer_alloc(FUNC(zoomer_rtc_device::tick_timer), this);
+ m_sec_timer = timer_alloc(FUNC(zoomer_rtc_device::sec_timer), this);
+
+ save_item(NAME(m_data));
+ save_item(NAME(m_month));
+ save_item(NAME(m_day));
+ save_item(NAME(m_year));
+}
+
+//**************************************************************************
+void zoomer_rtc_device::device_reset()
+{
+}
+
+//**************************************************************************
+void zoomer_rtc_device::device_clock_changed()
+{
+ const attotime period = attotime::from_hz(clock() / 32768.0);
+ m_tick_timer->adjust(period / TICKS_PER_SEC, 0, period / TICKS_PER_SEC);
+ m_sec_timer->adjust(period, 0, period);
+}
+
+//**************************************************************************
+u8 zoomer_rtc_device::read(offs_t offset)
+{
+ return m_data[offset & 0xf];
+}
+
+//**************************************************************************
+void zoomer_rtc_device::write(offs_t offset, u8 data)
+{
+ offset &= 0xf;
+
+ m_data[offset] = data;
+
+ if (offset >= REG_DATE_LOW && offset <= REG_DATE_HIGH)
+ update_date();
+
+ if (offset >= REG_SECOND && offset <= REG_DATE_HIGH)
+ {
+ set_time(1, m_year, m_month, m_day, 0,
+ bcd_to_integer(m_data[REG_HOUR]),
+ bcd_to_integer(m_data[REG_MINUTE]),
+ bcd_to_integer(m_data[REG_SECOND]));
+ }
+}
+
+//**************************************************************************
+void zoomer_rtc_device::rtc_clock_updated(int year, int month, int day, int day_of_week, int hour, int minute, int second)
+{
+ m_data[REG_HOUR] = convert_to_bcd(hour);
+ m_data[REG_MINUTE] = convert_to_bcd(minute);
+ m_data[REG_SECOND] = convert_to_bcd(second);
+
+ if (year != m_year || month != m_month || day != m_day)
+ {
+ struct tm date = {};
+ date.tm_year = year - 1900;
+ date.tm_mon = month - 1;
+ date.tm_mday = day;
+
+ const time_t timediff = (mktime(&date) - RTC_EPOCH) / (60 * 60 * 24);
+ m_data[REG_DATE_LOW] = convert_to_bcd(timediff % 100);
+ m_data[REG_DATE_MID] = convert_to_bcd((timediff / 100) % 100);
+ m_data[REG_DATE_HIGH] = convert_to_bcd((timediff / 10000) % 100);
+ }
+}
+
+//**************************************************************************
+void zoomer_rtc_device::update_date()
+{
+ const time_t date = RTC_EPOCH +
+ (bcd_to_integer(m_data[REG_DATE_LOW]) +
+ bcd_to_integer(m_data[REG_DATE_MID]) * 100 +
+ bcd_to_integer(m_data[REG_DATE_HIGH]) * 10000)
+ * 60 * 60 * 24;
+
+ const struct tm *timenow = localtime(&date);
+ if (timenow)
+ {
+ m_year = timenow->tm_year + 1900;
+ m_month = timenow->tm_mon + 1;
+ m_day = timenow->tm_mday;
+ }
+}
+
+//**************************************************************************
+TIMER_CALLBACK_MEMBER(zoomer_rtc_device::tick_timer)
+{
+ m_tick_cb(1);
+ m_tick_cb(0);
+}
+
+//**************************************************************************
+TIMER_CALLBACK_MEMBER(zoomer_rtc_device::sec_timer)
+{
+ advance_seconds();
+
+ if (!memcmp(m_data + REG_SECOND, m_data + REG_ALARM_SECOND, REG_DATE_HIGH - REG_SECOND + 1))
+ m_alarm_cb(1);
+ else
+ m_alarm_cb(0);
+}
+
+//**************************************************************************
+void zoomer_rtc_device::nvram_default()
+{
+ std::fill(std::begin(m_data), std::end(m_data), 0);
+ update_date();
+}
+
+//**************************************************************************
+bool zoomer_rtc_device::nvram_read(util::read_stream &file)
+{
+ auto const [err, actual] = util::read(file, m_data, sizeof(m_data));
+ update_date();
+ return !err;
+}
+
+//**************************************************************************
+bool zoomer_rtc_device::nvram_write(util::write_stream &file)
+{
+ auto const [err, actual] = util::write(file, m_data, sizeof(m_data));
+ return !err;
+}
diff --git a/src/mame/casio/zoomer_rtc.h b/src/mame/casio/zoomer_rtc.h
new file mode 100644
index 00000000000..482f80ee4b3
--- /dev/null
+++ b/src/mame/casio/zoomer_rtc.h
@@ -0,0 +1,72 @@
+// license:BSD-3-Clause
+// copyright-holders:Devin Acker
+
+#ifndef MAME_CASIO_ZOOMER_RTC_H
+#define MAME_CASIO_ZOOMER_RTC_H
+
+#pragma once
+
+#include "dirtc.h"
+
+class zoomer_rtc_device : public device_t,
+ public device_rtc_interface,
+ public device_nvram_interface
+{
+public:
+ zoomer_rtc_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ auto alarm_cb() { return m_alarm_cb.bind(); }
+ auto tick_cb() { return m_tick_cb.bind(); }
+
+ u8 read(offs_t offset);
+ void write(offs_t offset, u8 data);
+
+protected:
+ virtual void device_start() override;
+ virtual void device_reset() override;
+ virtual void device_clock_changed() override;
+
+ virtual bool rtc_feature_leap_year() const override { return true; }
+ virtual bool rtc_feature_y2k() const override { return true; }
+ virtual void rtc_clock_updated(int year, int month, int day, int day_of_week, int hour, int minute, int second) override;
+
+ virtual void nvram_default() override;
+ virtual bool nvram_read(util::read_stream &file) override;
+ virtual bool nvram_write(util::write_stream &file) override;
+
+private:
+ static constexpr int TICKS_PER_SEC = 8;
+ static constexpr s64 RTC_EPOCH = -2203959600LL; // 2/28/1900
+
+ enum
+ {
+ REG_SECOND = 0x1,
+ REG_MINUTE,
+ REG_HOUR,
+ REG_DATE_LOW,
+ REG_DATE_MID,
+ REG_DATE_HIGH,
+ REG_ALARM_SECOND = 0x9,
+ REG_ALARM_MINUTE,
+ REG_ALARM_HOUR,
+ REG_ALARM_DATE_LOW,
+ REG_ALARM_DATE_MID,
+ REG_ALARM_DATE_HIGH
+ };
+
+ void update_date();
+
+ TIMER_CALLBACK_MEMBER(tick_timer);
+ TIMER_CALLBACK_MEMBER(sec_timer);
+
+ emu_timer *m_tick_timer, *m_sec_timer;
+ devcb_write_line m_tick_cb, m_alarm_cb;
+
+ u8 m_data[16];
+ u8 m_day, m_month;
+ u16 m_year;
+};
+
+DECLARE_DEVICE_TYPE(ZOOMER_RTC, zoomer_rtc_device)
+
+#endif // MAME_CASIO_ZOOMER_RTC_H
diff --git a/src/mame/layout/zoomer.lay b/src/mame/layout/zoomer.lay
new file mode 100644
index 00000000000..52118a84147
--- /dev/null
+++ b/src/mame/layout/zoomer.lay
@@ -0,0 +1,309 @@
+<?xml version="1.0"?>
+<!--
+license:CC0-1.0
+-->
+<mamelayout version="2">
+ <element name="pencursor">
+ <image state="1">
+ <data><![CDATA[
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="1" height="1" viewBox="0 0 1 1">
+ <g fill="none" stroke="#6666ff" stroke-width="0.06" stroke-opacity="1">
+ <circle cx="0.5" cy="0.5" r="0.1" />
+ <circle cx="0.5" cy="0.5" r="0.47" />
+ <line x1="0.03" y1="0.5" x2="0.4" y2="0.5" />
+ <line x1="0.6" y1="0.5" x2="0.97" y2="0.5" />
+ <line x1="0.5" y1="0.03" x2="0.5" y2="0.4" />
+ <line x1="0.5" y1="0.6" x2="0.5" y2="0.97" />
+ </g>
+ </svg>
+ ]]></data>
+ </image>
+ </element>
+
+ <element name="panel">
+ <rect>
+ <color red="0.3" green="0.3" blue="0.3" />
+ <bounds x="0" y="0" width="10" height="1.5" />
+ </rect>
+
+ <rect>
+ <color red="0.8" green="0.8" blue="0.8" />
+ <bounds x="0.05" y="0.05" width="0.9" height="1.4" />
+ </rect>
+ <text string="Launcher">
+ <color red="0" green="0" blue="0" />
+ <bounds xc="0.5" yc="0.75" width="0.9" height="0.3" />
+ </text>
+
+ <rect>
+ <color red="0.8" green="0.8" blue="0.8" />
+ <bounds x="1.05" y="0.05" width="0.9" height="1.4" />
+ </rect>
+ <text string="Address">
+ <color red="0" green="0" blue="0" />
+ <bounds xc="1.5" yc="0.60" width="0.9" height="0.3" />
+ </text>
+ <text string="Book">
+ <color red="0" green="0" blue="0" />
+ <bounds xc="1.5" yc="0.90" width="0.9" height="0.3" />
+ </text>
+
+ <rect>
+ <color red="0.8" green="0.8" blue="0.8" />
+ <bounds x="2.05" y="0.05" width="0.9" height="1.4" />
+ </rect>
+ <text string="Date">
+ <color red="0" green="0" blue="0" />
+ <bounds xc="2.5" yc="0.60" width="0.9" height="0.3" />
+ </text>
+ <text string="Book">
+ <color red="0" green="0" blue="0" />
+ <bounds xc="2.5" yc="0.90" width="0.9" height="0.3" />
+ </text>
+
+ <rect>
+ <color red="0.8" green="0.8" blue="0.8" />
+ <bounds x="3.05" y="0.05" width="0.9" height="1.4" />
+ </rect>
+ <text string="Note">
+ <color red="0" green="0" blue="0" />
+ <bounds xc="3.5" yc="0.60" width="0.9" height="0.3" />
+ </text>
+ <text string="Book">
+ <color red="0" green="0" blue="0" />
+ <bounds xc="3.5" yc="0.90" width="0.9" height="0.3" />
+ </text>
+
+ <rect>
+ <color red="0.8" green="0.8" blue="0.8" />
+ <bounds x="4.05" y="0.05" width="0.9" height="1.4" />
+ </rect>
+ <text string="Pocket">
+ <color red="0" green="0" blue="0" />
+ <bounds xc="4.5" yc="0.60" width="0.9" height="0.3" />
+ </text>
+ <text string="Quicken">
+ <color red="0" green="0" blue="0" />
+ <bounds xc="4.5" yc="0.90" width="0.9" height="0.3" />
+ </text>
+
+ <rect>
+ <color red="0.8" green="0.8" blue="0.8" />
+ <bounds x="5.05" y="0.05" width="0.9" height="1.4" />
+ </rect>
+ <text string="World">
+ <color red="0" green="0" blue="0" />
+ <bounds xc="5.5" yc="0.60" width="0.9" height="0.3" />
+ </text>
+ <text string="Clock">
+ <color red="0" green="0" blue="0" />
+ <bounds xc="5.5" yc="0.90" width="0.9" height="0.3" />
+ </text>
+
+ <rect>
+ <color red="0.8" green="0.8" blue="0.8" />
+ <bounds x="6.05" y="0.05" width="0.9" height="1.4" />
+ </rect>
+ <text string="Calculator">
+ <color red="0" green="0" blue="0" />
+ <bounds xc="6.5" yc="0.75" width="0.9" height="0.3" />
+ </text>
+
+ <rect>
+ <color red="0.8" green="0.8" blue="0.8" />
+ <bounds x="7.05" y="0.05" width="0.9" height="1.4" />
+ </rect>
+ <text string="Menu">
+ <color red="0" green="0" blue="0" />
+ <bounds xc="7.5" yc="0.75" width="0.9" height="0.3" />
+ </text>
+
+ <rect>
+ <color red="0.8" green="0.8" blue="0.8" />
+ <bounds x="8.05" y="0.05" width="0.9" height="1.4" />
+ </rect>
+ <text string="Keyboard">
+ <color red="0" green="0" blue="0" />
+ <bounds xc="8.5" yc="0.75" width="0.9" height="0.3" />
+ </text>
+
+ <rect>
+ <color red="0.8" green="0.8" blue="0.8" />
+ <bounds x="9.05" y="0.05" width="0.9" height="1.4" />
+ </rect>
+ <text string="Help">
+ <color red="0" green="0" blue="0" />
+ <bounds xc="9.5" yc="0.75" width="0.9" height="0.3" />
+ </text>
+ </element>
+
+ <view name="Default View">
+ <screen id="screen" index="0">
+ <bounds x="0" y="0" width="256" height="320" />
+ </screen>
+
+ <element id="panel" ref="panel">
+ <bounds x="0" y="320" width="256" height="38" />
+ </element>
+
+ <element id="cursor" ref="pencursor">
+ <!-- will be positioned by script -->
+ <bounds x="0" y="320" width="20" height="20" />
+ <color alpha="0.7" />
+ </element>
+ </view>
+
+ <view name="Touch-Enabled" showpointers="yes">
+ <screen id="screen" index="0">
+ <bounds x="0" y="0" width="256" height="320" />
+ </screen>
+
+ <element id="panel" ref="panel">
+ <bounds x="0" y="320" width="256" height="38" />
+ </element>
+ </view>
+
+ <script>
+ <![CDATA[
+ local function init_default()
+ local view = file.views['Default View']
+ local cursor = view.items['cursor']
+
+ local penx = file.device:ioport('PEN_X')
+ local peny = file.device:ioport('PEN_Y')
+
+ -- recompute target pen cursor size and area when necessary
+ local curxoffs, curyoffs, curxscale, curyscale, curwidth, curheight
+ view:set_recomputed_callback(
+ function ()
+ local bounds = view.items['panel'].bounds
+
+ curwidth = bounds.height * 2 / 3
+ curheight = curwidth
+ curxoffs = bounds.x0 - (curwidth * 0.5)
+ curyoffs = bounds.y0 - (curheight * 0.5)
+ curxscale = bounds.width / penx:field(0x3ff).maxvalue
+ curyscale = bounds.height / (peny:field(0x3ff).maxvalue - 320)
+ end)
+
+ -- animate the position of the pen cursor
+ cursor:set_element_state_callback(
+ function ()
+ if peny:read() >= 320 then return 1 end
+ return 0
+ end)
+ cursor:set_bounds_callback(
+ function ()
+ local x = curxoffs + (penx:read() * curxscale)
+ local y = curyoffs + ((peny:read() - 320) * curyscale)
+ return emu.render_bounds(x, y, x + curwidth, y + curheight)
+ end)
+ end
+
+ local function init_touch()
+ -- get the touchscreen I/O port fields
+ local btn_field = file.device:ioport('PEN'):field(0x01)
+ local x_field = file.device:ioport('PEN_X'):field(0x3ff)
+ local y_field = file.device:ioport('PEN_Y'):field(0x3ff)
+
+ local x_min = x_field.minvalue
+ local x_max = x_field.maxvalue
+ local y_min = y_field.minvalue
+ local y_max = y_field.maxvalue
+
+ -- for mapping coordinates
+ local view = file.views['Touch-Enabled']
+ local screen = view.items['screen']
+ local panel = view.items['panel']
+
+ local floor = math.floor
+ local bounds = emu.render_bounds()
+ local x_scale, y_scale
+
+ -- pointer state
+ local ptr_id = nil
+ local inside = false
+
+ local function release_touch()
+ btn_field:clear_value()
+ x_field:clear_value()
+ y_field:clear_value()
+ end
+
+ local function recomputed()
+ bounds.x0 = screen.bounds.x0
+ bounds.y0 = screen.bounds.y0
+ bounds.x1 = panel.bounds.x1
+ bounds.y1 = panel.bounds.y1
+
+ x_scale = (x_max - x_min + 1) / bounds.width
+ y_scale = (y_max - y_min + 1) / bounds.height
+ end
+
+ local function check_pointer(x, y)
+ if bounds:includes(x, y) then
+ inside = true
+ local ix = x_min + floor(x * x_scale)
+ if ix < x_min then
+ ix = x_min
+ elseif ix > x_max then
+ ix = x_max
+ end
+ local iy = y_min + floor(y * y_scale)
+ if iy < y_min then
+ iy = y_min
+ elseif iy > y_max then
+ iy = y_max
+ end
+ btn_field:set_value(1)
+ x_field:set_value(ix)
+ y_field:set_value(iy)
+ elseif inside then
+ inside = false
+ release_touch()
+ end
+ end
+
+ local function forget_pointer()
+ if inside then
+ release_touch()
+ end
+ ptr_id = nil
+ inside = false
+ end
+
+ local function pointer_updated(type, id, dev, x, y, btn, dn, up, cnt)
+ if ptr_id == id then
+ if (btn & 0x01) == 0 then
+ forget_pointer()
+ else
+ check_pointer(x, y)
+ end
+ elseif (not ptr_id) and ((dn & 0x01) ~= 0) then
+ ptr_id = id
+ check_pointer(x, y)
+ end
+ end
+
+ local function pointer_lost(type, id, dev, x, y, up, cnt)
+ if ptr_id == id then
+ forget_pointer()
+ end
+ end
+
+ -- attach callbacks to view
+ view:set_recomputed_callback(recomputed)
+ view:set_pointer_updated_callback(pointer_updated)
+ view:set_pointer_left_callback(pointer_lost)
+ view:set_pointer_aborted_callback(pointer_lost)
+ view:set_forget_pointers_callback(forget_pointer)
+ end
+
+ file:set_resolve_tags_callback(
+ function ()
+ init_default()
+ init_touch()
+ end)
+ ]]>
+ </script>
+</mamelayout>
diff --git a/src/mame/mame.lst b/src/mame/mame.lst
index f715fbebb03..ab0681c6ba9 100644
--- a/src/mame/mame.lst
+++ b/src/mame/mame.lst
@@ -16129,6 +16129,9 @@ sk10 //
@source:casio/sx1000.cpp
sx1010 // Casio SX1010
+@source:casio/zoomer.cpp
+zoomer // 1993 Casio/Tandy
+
@source:cave/cavepc.cpp
deathsm2 //