summaryrefslogtreecommitdiffstatshomepage
path: root/src/mame/machine/hp48.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mame/machine/hp48.c')
-rw-r--r--src/mame/machine/hp48.c1176
1 files changed, 1176 insertions, 0 deletions
diff --git a/src/mame/machine/hp48.c b/src/mame/machine/hp48.c
new file mode 100644
index 00000000000..6c6ad253001
--- /dev/null
+++ b/src/mame/machine/hp48.c
@@ -0,0 +1,1176 @@
+// license:BSD-3-Clause
+// copyright-holders:Antoine Mine
+/**********************************************************************
+
+ Copyright (C) Antoine Mine' 2008
+
+ Hewlett Packard HP48 S/SX & G/GX/G+ and HP49 G
+
+**********************************************************************/
+
+#include "emu.h"
+#include "sound/dac.h"
+#include "cpu/saturn/saturn.h"
+
+#include "machine/nvram.h"
+
+#include "includes/hp48.h"
+
+
+/***************************************************************************
+ DEBUGGING
+***************************************************************************/
+
+
+#define VERBOSE 0
+#define VERBOSE_SERIAL 0
+
+#define LOG(x) do { if (VERBOSE) logerror x; } while (0)
+#define LOG_SERIAL(x) do { if (VERBOSE_SERIAL) logerror x; } while (0)
+
+
+
+/***************************************************************************
+ TYPE DEFINITIONS
+***************************************************************************/
+
+/* state field in hp48_module */
+#define HP48_MODULE_UNCONFIGURED 0
+#define HP48_MODULE_MASK_KNOWN 1
+#define HP48_MODULE_CONFIGURED 2
+
+/***************************************************************************
+ GLOBAL VARIABLES & CONSTANTS
+***************************************************************************/
+
+/* current HP48 model */
+
+#define HP48_G_SERIES ((m_model==HP48_G) || (m_model==HP48_GX) || (m_model==HP48_GP))
+#define HP48_S_SERIES ((m_model==HP48_S) || (m_model==HP48_SX))
+#define HP48_X_SERIES ((m_model==HP48_SX) || (m_model==HP48_GX))
+#define HP48_GX_MODEL ((m_model==HP48_GX) || (m_model==HP48_GP))
+#define HP49_G_MODEL ((m_model==HP49_G))
+
+static const char *const hp48_module_names[6] =
+{ "HDW (I/O)", "NCE2 (RAM)", "CE1", "CE2", "NCE3", "NCE1 (ROM)" };
+
+/* values returned by C=ID */
+static const UINT8 hp48_module_mask_id[6] = { 0x00, 0x03, 0x05, 0x07, 0x01, 0x00 };
+static const UINT8 hp48_module_addr_id[6] = { 0x19, 0xf4, 0xf6, 0xf8, 0xf2, 0x00 };
+
+
+
+
+
+/***************************************************************************
+ FUNCTIONS
+***************************************************************************/
+
+void hp48_state::hp48_pulse_irq( int irq_line)
+{
+ m_maincpu->set_input_line(irq_line, ASSERT_LINE );
+ m_maincpu->set_input_line(irq_line, CLEAR_LINE );
+}
+
+
+/* ---------------- serial --------------------- */
+
+#define RS232_DELAY attotime::from_usec( 300 )
+
+/* end of receive event */
+TIMER_CALLBACK_MEMBER(hp48_state::hp48_rs232_byte_recv_cb)
+{
+ LOG_SERIAL(( "%f hp48_rs232_byte_recv_cb: end of receive, data=%02x\n",
+ machine().time().as_double(), param ));
+
+ m_io[0x14] = param & 0xf; /* receive zone */
+ m_io[0x15] = param >> 4;
+ m_io[0x11] &= ~2; /* clear byte receiving */
+ m_io[0x11] |= 1; /* set byte received */
+
+ /* interrupt */
+ if ( m_io[0x10] & 2 )
+ {
+ hp48_pulse_irq( SATURN_IRQ_LINE );
+ }
+}
+
+/* outside world initiates a receive event */
+void hp48_state::hp48_rs232_start_recv_byte( UINT8 data )
+{
+ LOG_SERIAL(( "%f hp48_rs232_start_recv_byte: start receiving, data=%02x\n",
+ machine().time().as_double(), data ));
+
+ m_io[0x11] |= 2; /* set byte receiving */
+
+ /* interrupt */
+ if ( m_io[0x10] & 1 )
+ {
+ hp48_pulse_irq( SATURN_IRQ_LINE );
+ }
+
+ /* schedule end of reception */
+ machine().scheduler().timer_set( RS232_DELAY, timer_expired_delegate(FUNC(hp48_state::hp48_rs232_byte_recv_cb),this), data);
+}
+
+
+/* end of send event */
+TIMER_CALLBACK_MEMBER(hp48_state::hp48_rs232_byte_sent_cb)
+{
+ //device_image_interface *xmodem = dynamic_cast<device_image_interface *>(machine().device("rs232_x"));
+ //device_image_interface *kermit = dynamic_cast<device_image_interface *>(machine().device("rs232_k"));
+
+ LOG_SERIAL(( "%f hp48_rs232_byte_sent_cb: end of send, data=%02x\n",
+ machine().time().as_double(), param ));
+
+ m_io[0x12] &= ~3; /* clear byte sending and buffer full */
+
+ /* interrupt */
+ if ( m_io[0x10] & 4 )
+ {
+ hp48_pulse_irq( SATURN_IRQ_LINE );
+ }
+
+ /* protocol action */
+ //if ( xmodem && xmodem->exists() ) xmodem_receive_byte( &xmodem->device(), param );
+ //else if ( kermit && kermit->exists() ) kermit_receive_byte( &kermit->device(), param );
+}
+
+/* CPU initiates a send event */
+void hp48_state::hp48_rs232_send_byte( )
+{
+ UINT8 data = HP48_IO_8(0x16); /* byte to send */
+
+ LOG_SERIAL(( "%s %f hp48_rs232_send_byte: start sending, data=%02x\n",
+ machine().describe_context(), machine().time().as_double(), data ));
+
+ /* set byte sending and send buffer full */
+ m_io[0x12] |= 3;
+
+ /* schedule transmission */
+ machine().scheduler().timer_set( RS232_DELAY, timer_expired_delegate(FUNC(hp48_state::hp48_rs232_byte_sent_cb),this), data);
+}
+
+
+
+
+/* ------ Saturn's IN / OUT registers ---------- */
+
+
+/* CPU sets OUT register (keyboard + beeper) */
+WRITE32_MEMBER( hp48_state::hp48_reg_out )
+{
+ LOG(( "%s %f hp48_reg_out: %03x\n",
+ machine().describe_context(), machine().time().as_double(), data ));
+
+ /* bits 0-8: keyboard lines */
+ m_out = data & 0x1ff;
+
+ /* bits 9-10: unused */
+
+ /* bit 11: beeper */
+ m_dac->write_unsigned8((data & 0x800) ? 0x80 : 00 );
+}
+
+int hp48_state::hp48_get_in( )
+{
+ int in = 0;
+
+ /* regular keys */
+ if ( (m_out >> 0) & 1 ) in |= ioport( "LINE0" )->read();
+ if ( (m_out >> 1) & 1 ) in |= ioport( "LINE1" )->read();
+ if ( (m_out >> 2) & 1 ) in |= ioport( "LINE2" )->read();
+ if ( (m_out >> 3) & 1 ) in |= ioport( "LINE3" )->read();
+ if ( (m_out >> 4) & 1 ) in |= ioport( "LINE4" )->read();
+ if ( (m_out >> 5) & 1 ) in |= ioport( "LINE5" )->read();
+ if ( (m_out >> 6) & 1 ) in |= ioport( "LINE6" )->read();
+ if ( (m_out >> 7) & 1 ) in |= ioport( "LINE7" )->read();
+ if ( (m_out >> 8) & 1 ) in |= ioport( "LINE8" )->read();
+
+ /* on key */
+ in |= ioport( "ON" )->read();
+
+ return in;
+}
+
+/* CPU reads IN register (keyboard) */
+READ32_MEMBER( hp48_state::hp48_reg_in )
+{
+ int in = hp48_get_in();
+ LOG(( "%s %f hp48_reg_in: %04x\n",
+ machine().describe_context(), machine().time().as_double(), in ));
+ return in;
+}
+
+/* key detect */
+void hp48_state::hp48_update_kdn( )
+{
+ int in = hp48_get_in();
+
+ /* interrupt on raising edge */
+ if ( in && !m_kdn )
+ {
+ LOG(( "%f hp48_update_kdn: interrupt\n", machine().time().as_double() ));
+ m_io[0x19] |= 8; /* service request */
+ hp48_pulse_irq( SATURN_WAKEUP_LINE );
+ hp48_pulse_irq( SATURN_IRQ_LINE );
+ }
+
+ m_kdn = (in!=0);
+}
+
+/* periodic keyboard polling, generates an interrupt */
+TIMER_CALLBACK_MEMBER(hp48_state::hp48_kbd_cb)
+{
+ /* NMI for ON key */
+ if ( ioport( "ON" )->read() )
+ {
+ LOG(( "%f hp48_kbd_cb: keyboard interrupt, on key\n",
+ machine().time().as_double() ));
+ m_io[0x19] |= 8; /* set service request */
+ hp48_pulse_irq( SATURN_WAKEUP_LINE );
+ hp48_pulse_irq( SATURN_NMI_LINE );
+ return;
+ }
+
+ /* regular keys */
+ hp48_update_kdn();
+}
+
+/* RSI opcode */
+WRITE_LINE_MEMBER( hp48_state::hp48_rsi )
+{
+ LOG(( "%s %f hp48_rsi\n", machine().describe_context(), machine().time().as_double() ));
+
+ /* enables interrupts on key repeat
+ (normally, there is only one interrupt, when the key is pressed)
+ */
+ m_kdn = 0;
+}
+
+
+/* ------------- annonciators ------------ */
+
+void hp48_state::hp48_update_annunciators()
+{
+ /* bit 0: left shift
+ bit 1: right shift
+ bit 2: alpha
+ bit 3: alert
+ bit 4: busy
+ bit 5: transmit
+ bit 7: master enable
+ */
+ int markers = HP48_IO_8(0xb);
+ output_set_value( "lshift0", (markers & 0x81) == 0x81 );
+ output_set_value( "rshift0", (markers & 0x82) == 0x82 );
+ output_set_value( "alpha0", (markers & 0x84) == 0x84 );
+ output_set_value( "alert0", (markers & 0x88) == 0x88 );
+ output_set_value( "busy0", (markers & 0x90) == 0x90 );
+ output_set_value( "transmit0", (markers & 0xb0) == 0xb0 );
+}
+
+
+/* ------------- I/O registers ----------- */
+
+/* Some part of the I/O registers are simple r/w registers. We store them in hp48_io.
+ Special cases are registers that:
+ - have a different meaning on read and write
+ - perform some action on read / write
+ */
+
+WRITE8_MEMBER(hp48_state::hp48_io_w)
+{
+ LOG(( "%s %f hp48_io_w: off=%02x data=%x\n",
+ space.machine().describe_context(), space.machine().time().as_double(), offset, data ));
+
+ switch( offset )
+ {
+ /* CRC register */
+ case 0x04: m_crc = (m_crc & 0xfff0) | data; break;
+ case 0x05: m_crc = (m_crc & 0xff0f) | (data << 4); break;
+ case 0x06: m_crc = (m_crc & 0xf0ff) | (data << 8); break;
+ case 0x07: m_crc = (m_crc & 0x0fff) | (data << 12); break;
+
+ /* annunciators */
+ case 0x0b:
+ case 0x0c:
+ m_io[offset] = data;
+ hp48_update_annunciators();
+ break;
+
+ /* cntrl ROM */
+ case 0x29:
+ {
+ int old_cntrl = m_io[offset] & 8;
+ m_io[offset] = data;
+ if ( old_cntrl != (data & 8) )
+ {
+ hp48_apply_modules();
+ }
+ break;
+ }
+
+ /* timers */
+ case 0x37: m_timer1 = data; break;
+ case 0x38: m_timer2 = (m_timer2 & 0xfffffff0) | data; break;
+ case 0x39: m_timer2 = (m_timer2 & 0xffffff0f) | (data << 4); break;
+ case 0x3a: m_timer2 = (m_timer2 & 0xfffff0ff) | (data << 8); break;
+ case 0x3b: m_timer2 = (m_timer2 & 0xffff0fff) | (data << 12); break;
+ case 0x3c: m_timer2 = (m_timer2 & 0xfff0ffff) | (data << 16); break;
+ case 0x3d: m_timer2 = (m_timer2 & 0xff0fffff) | (data << 20); break;
+ case 0x3e: m_timer2 = (m_timer2 & 0xf0ffffff) | (data << 24); break;
+ case 0x3f: m_timer2 = (m_timer2 & 0x0fffffff) | (data << 28); break;
+
+ /* cards */
+ case 0x0e:
+ LOG(( "%s: card control write %02x\n", space.machine().describe_context(), data ));
+
+ /* bit 0: software interrupt */
+ if ( data & 1 )
+ {
+ LOG(( "%f hp48_io_w: software interrupt requested\n",
+ space.machine().time().as_double() ));
+ hp48_pulse_irq( SATURN_IRQ_LINE );
+ data &= ~1;
+ }
+
+ /* XXX not implemented
+ bit 1: card test?
+ */
+
+ m_io[0x0e] = data;
+ break;
+
+ case 0x0f:
+ LOG(( "%s: card info write %02x\n", space.machine().describe_context(), data ));
+ m_io[0x0f] = data;
+ break;
+
+ /* serial */
+ case 0x13:
+ m_io[0x11] &= ~4; /* clear error status */
+ break;
+ case 0x16:
+ /* first nibble of sent data */
+ m_io[offset] = data;
+ break;
+ case 0x17:
+ /* second nibble of sent data */
+ m_io[offset] = data;
+ hp48_rs232_send_byte();
+ break;
+
+ /* XXX not implemented:
+
+ - 0x0d: RS232c speed:
+ bits 0-2: speed
+ 000 = 1200 bauds
+ 010 = 2400 bauds
+ 100 = 4800 bauds
+ 110 = 9600 bauds
+ bit 3: ?
+
+ - 0x1a: I/R input
+ bit 0: irq
+ bit 1: irq enable
+ bit 2: 1=RS232c mode 0=direct mode
+ bit 3: receiving
+
+ - 0x1c: I/R output control
+ bit 0: buffer full
+ bit 1: transmitting
+ bit 2: irq enable on buffer empty
+ bit 3: led on (direct mode)
+ on HP49 G, flash ROM write enable
+
+ - 0x1d: I/R output buffer
+ */
+
+ default: m_io[offset] = data;
+ }
+
+}
+
+
+READ8_MEMBER(hp48_state::hp48_io_r)
+{
+ UINT8 data = 0;
+
+ switch( offset )
+ {
+ /* CRC register */
+ case 0x04: data = m_crc & 0xf; break;
+ case 0x05: data = (m_crc >> 4) & 0xf; break;
+ case 0x06: data = (m_crc >> 8) & 0xf; break;
+ case 0x07: data = (m_crc >> 12) & 0xf; break;
+
+ /* battery test */
+ case 0x08:
+ data = 0;
+ if ( m_io[0x9] & 8 ) /* test enable */
+ {
+ /* XXX not implemented:
+ bit 3: battery in port 2
+ bit 2: battery in port 1
+ */
+ switch ( ioport( "BATTERY" )->read() )
+ {
+ case 1: data = 2; break; /* low */
+ case 2: data = 3; break; /* low | critical */
+ }
+ }
+ break;
+
+ /* remaining lines in main bitmap */
+ case 0x28:
+ case 0x29:
+ {
+ int last_line = HP48_IO_8(0x28) & 0x3f; /* last line of main bitmap before menu */
+ int cur_line = space.machine().first_screen()->vpos();
+ if ( last_line <= 1 ) last_line = 0x3f;
+ data = ( cur_line >= 0 && cur_line <= last_line ) ? last_line - cur_line : 0;
+ if ( offset == 0x29 )
+ {
+ data >>= 4;
+ data |= HP48_IO_4(0x29) & 0xc;
+ }
+ else
+ {
+ data &= 0xf;
+ }
+ break;
+ }
+
+ /* timers */
+ case 0x37: data = m_timer1; break;
+ case 0x38: data = m_timer2 & 0xf; break;
+ case 0x39: data = (m_timer2 >> 4) & 0xf; break;
+ case 0x3a: data = (m_timer2 >> 8) & 0xf; break;
+ case 0x3b: data = (m_timer2 >> 12) & 0xf; break;
+ case 0x3c: data = (m_timer2 >> 16) & 0xf; break;
+ case 0x3d: data = (m_timer2 >> 20) & 0xf; break;
+ case 0x3e: data = (m_timer2 >> 24) & 0xf; break;
+ case 0x3f: data = (m_timer2 >> 28) & 0xf; break;
+
+ /* serial */
+ case 0x15:
+ {
+ /* second nibble of received data */
+
+ //device_image_interface *xmodem = dynamic_cast<device_image_interface *>(space.machine().device("rs232_x"));
+ //device_image_interface *kermit = dynamic_cast<device_image_interface *>(space.machine().device("rs232_k"));
+
+ m_io[0x11] &= ~1; /* clear byte received */
+ data = m_io[offset];
+
+ /* protocol action */
+ //if ( xmodem && xmodem->exists() ) xmodem_byte_transmitted( &xmodem->device() );
+ //else if ( kermit && kermit->exists() ) kermit_byte_transmitted( &kermit->device() );
+ break;
+ }
+
+ /* cards */
+ case 0x0e: /* detection */
+ data = m_io[0x0e];
+ LOG(( "%s: card control read %02x\n", space.machine().describe_context(), data ));
+ break;
+ case 0x0f: /* card info */
+ data = 0;
+ if ( HP48_G_SERIES )
+ {
+ if ( m_port_size[1] ) data |= 1;
+ if ( m_port_size[0] ) data |= 2;
+ if ( m_port_size[1] && m_port_write[1] ) data |= 4;
+ if ( m_port_size[0] && m_port_write[0] ) data |= 8;
+ }
+ else
+ {
+ if ( m_port_size[0] ) data |= 1;
+ if ( m_port_size[1] ) data |= 2;
+ if ( m_port_size[0] && m_port_write[0] ) data |= 4;
+ if ( m_port_size[1] && m_port_write[1] ) data |= 8;
+ }
+ LOG(( "%s: card info read %02x\n", space.machine().describe_context(), data ));
+ break;
+
+
+ default: data = m_io[offset];
+ }
+
+ LOG(( "%s %f hp48_io_r: off=%02x data=%x\n",
+ space.machine().describe_context(), space.machine().time().as_double(), offset, data ));
+ return data;
+}
+
+
+/* ---------- bank switcher --------- */
+
+READ8_MEMBER(hp48_state::hp48_bank_r)
+{
+ /* HP48 GX
+ bit 0: ignored
+ bits 2-5: bank number
+ bit 6: enable
+ */
+
+ /* HP49 G
+ bit 0: ignored
+ bits 1-2: select bank 0x00000-0x3ffff
+ bits 3-6: select bank 0x40000-0x7ffff
+ */
+ offset &= 0x7e;
+ if ( m_bank_switch != offset )
+ {
+ LOG(( "%s %f hp48_bank_r: off=%03x\n", space.machine().describe_context(), space.machine().time().as_double(), offset ));
+ m_bank_switch = offset;
+ hp48_apply_modules();
+ }
+ return 0;
+}
+
+
+WRITE8_MEMBER(hp48_state::hp49_bank_w)
+{
+ offset &= 0x7e;
+ if ( m_bank_switch != offset )
+ {
+ LOG(( "%05x %f hp49_bank_w: off=%03x\n", space.device().safe_pcbase(), space.machine().time().as_double(), offset ));
+ m_bank_switch = offset;
+ hp48_apply_modules();
+ }
+}
+
+
+
+/* ---------------- timers --------------- */
+
+TIMER_CALLBACK_MEMBER(hp48_state::hp48_timer1_cb)
+{
+ if ( !(m_io[0x2f] & 1) ) return; /* timer enable */
+
+ m_timer1 = (m_timer1 - 1) & 0xf;
+
+ /* wake-up on carry */
+ if ( (m_io[0x2e] & 4) && (m_timer1 == 0xf) )
+ {
+ LOG(( "wake-up on timer1\n" ));
+ m_io[0x2e] |= 8; /* set service request */
+ m_io[0x18] |= 4; /* set service request */
+ hp48_pulse_irq( SATURN_WAKEUP_LINE );
+ }
+ /* interrupt on carry */
+ if ( (m_io[0x2e] & 2) && (m_timer1 == 0xf) )
+ {
+ LOG(( "generate timer1 interrupt\n" ));
+ m_io[0x2e] |= 8; /* set service request */
+ m_io[0x18] |= 4; /* set service request */
+ hp48_pulse_irq( SATURN_NMI_LINE );
+ }
+}
+
+TIMER_CALLBACK_MEMBER(hp48_state::hp48_timer2_cb)
+{
+ if ( !(m_io[0x2f] & 1) ) return; /* timer enable */
+
+ m_timer2 = (m_timer2 - 1) & 0xffffffff;
+
+ /* wake-up on carry */
+ if ( (m_io[0x2f] & 4) && (m_timer2 == 0xffffffff) )
+ {
+ LOG(( "wake-up on timer2\n" ));
+ m_io[0x2f] |= 8; /* set service request */
+ m_io[0x18] |= 4; /* set service request */
+ hp48_pulse_irq( SATURN_WAKEUP_LINE );
+ }
+ /* interrupt on carry */
+ if ( (m_io[0x2f] & 2) && (m_timer2 == 0xffffffff) )
+ {
+ LOG(( "generate timer2 interrupt\n" ));
+ m_io[0x2f] |= 8; /* set service request */
+ m_io[0x18] |= 4; /* set service request */
+ hp48_pulse_irq( SATURN_NMI_LINE );
+ }
+}
+
+
+
+
+/* --------- memory controller ----------- */
+
+/*
+ Clark (S series) and York (G series) CPUs have 6 daisy-chained modules
+
+
+ <-- highest --------- priority ------------- lowest -->
+
+ CPU ---------------------------------------------------
+ | | | | | |
+ HDW NCE2 CE1 CE2 NCE3 NCE1
+
+
+ However, controller usage is different in both series:
+
+
+ controller 48 S series 48 G series 49 G series
+ (Clark CPU) (York CPU) (York CPU)
+
+ HDW 32B I/O RAM 32B I/O RAM 32B I/O RAM
+ NCE2 32KB RAM 32/128KB RAM 256KB RAM
+ CE1 port1 bank switcher bank switcher
+ CE2 port2 port1 128KB RAM
+ NCE3 unused port2 128KB RAM
+ NCE1 256KB ROM 512KB ROM 2MB flash ROM
+
+
+ - NCE1 (ROM) cannot be configured, it is always visible at addresses
+ 00000-7ffff not covered by higher priority modules.
+
+ - only the address of HDW (I/O) can be configured, its size is constant
+ (64 nibbles)
+
+ - other modules can have their address & size set
+
+ */
+
+
+/* remap all modules according to hp48_modules */
+void hp48_state::hp48_apply_modules()
+{
+ int i;
+ int nce3_enable = 1;
+ address_space& space = m_maincpu->space(AS_PROGRAM);
+
+ m_io_addr = 0x100000;
+
+ /* NCE1 (ROM) is a bit special, so treat it separately */
+ space.unmap_readwrite( 0, 0xfffff, 0, 0 );
+ if ( HP49_G_MODEL )
+ {
+ int bank_lo = (m_bank_switch >> 5) & 3;
+ int bank_hi = (m_bank_switch >> 1) & 15;
+ LOG(( "hp48_apply_modules: low ROM bank is %i\n", bank_lo ));
+ LOG(( "hp48_apply_modules: high ROM bank is %i\n", bank_hi ));
+ space.install_read_bank( 0x00000, 0x3ffff, 0, 0x80000, "bank5" );
+ space.install_read_bank( 0x40000, 0x7ffff, 0, 0x80000, "bank6" );
+ if ( m_rom )
+ {
+ membank("bank5")->set_base( m_rom + bank_lo * 0x40000 );
+ membank("bank6")->set_base( m_rom + bank_hi * 0x40000 );
+ }
+ }
+ else if ( HP48_G_SERIES )
+ {
+ /* port 2 bank switch */
+ if ( m_port_size[1] > 0 )
+ {
+ int off = (m_bank_switch << 16) % m_port_size[1];
+ LOG(( "hp48_apply_modules: port 2 offset is %i\n", off ));
+ m_modules[HP48_NCE3].data = m_port_data[1] + off;
+ }
+
+ /* ROM A19 (hi 256 KB) / NCE3 (port 2) control switch */
+ if ( m_io[0x29] & 8 )
+ {
+ /* A19 */
+ LOG(( "hp48_apply_modules: A19 enabled, NCE3 disabled\n" ));
+ nce3_enable = 0;
+ space.install_read_bank( 0, 0xfffff, 0, 0, "bank5" );
+ }
+ else
+ {
+ /* NCE3 */
+ nce3_enable = m_bank_switch >> 6;
+ LOG(( "hp48_apply_modules: A19 disabled, NCE3 %s\n", nce3_enable ? "enabled" : "disabled" ));
+ space.install_read_bank( 0, 0x7ffff, 0, 0x80000, "bank5" );
+ }
+ if ( m_rom )
+ membank("bank5")->set_base( m_rom );
+ }
+ else
+ {
+ space.install_read_bank( 0, 0x7ffff, 0, 0x80000, "bank5" );
+ if ( m_rom )
+ membank("bank5")->set_base( m_rom );
+ }
+
+
+ /* from lowest to highest priority */
+ for ( i = 4; i >= 0; i-- )
+ {
+ UINT32 select_mask = m_modules[i].mask;
+ UINT32 nselect_mask = ~select_mask & 0xfffff;
+ UINT32 base = m_modules[i].base;
+ UINT32 off_mask = m_modules[i].off_mask;
+ UINT32 mirror = nselect_mask & ~off_mask;
+ UINT32 end = base + (off_mask & nselect_mask);
+ char bank[10];
+ sprintf(bank,"bank%d",i);
+
+ if ( m_modules[i].state != HP48_MODULE_CONFIGURED ) continue;
+
+ if ( (i == 4) && !nce3_enable ) continue;
+
+ /* our code assumes that the 20-bit select_mask is all 1s followed by all 0s */
+ if ( nselect_mask & (nselect_mask+1) )
+ {
+ logerror( "hp48_apply_modules: invalid mask %05x for module %s\n",
+ select_mask, hp48_module_names[i] );
+ continue;
+ }
+
+ if (m_modules[i].data)
+ space.install_read_bank( base, end, 0, mirror, bank );
+ else
+ {
+ if (!m_modules[i].read.isnull())
+ space.install_read_handler( base, end, 0, mirror, m_modules[i].read );
+ }
+
+ if (m_modules[i].isnop)
+ space.nop_write(base, end, 0, mirror);
+ else
+ {
+ if (m_modules[i].data)
+ {
+ space.install_write_bank( base, end, 0, mirror, bank );
+ }
+ else
+ {
+ if (!m_modules[i].write.isnull())
+ space.install_write_handler( base, end, 0, mirror, m_modules[i].write );
+ }
+ }
+
+ LOG(( "hp48_apply_modules: module %s configured at %05x-%05x, mirror %05x\n",
+ hp48_module_names[i], base, end, mirror ));
+
+ if ( m_modules[i].data )
+ {
+ membank( bank )->set_base( m_modules[i].data );
+ }
+
+ if ( i == 0 )
+ {
+ m_io_addr = base;
+ }
+ }
+}
+
+
+/* reset the configuration */
+void hp48_state::hp48_reset_modules( )
+{
+ int i;
+ /* fixed size for HDW */
+ m_modules[HP48_HDW].state = HP48_MODULE_MASK_KNOWN;
+ m_modules[HP48_HDW].mask = 0xfffc0;
+ /* unconfigure NCE2, CE1, CE2, NCE3 */
+ for ( i = 1; i < 5; i++ )
+ {
+ m_modules[i].state = HP48_MODULE_UNCONFIGURED;
+ }
+
+ /* fixed configuration for NCE1 */
+ m_modules[HP48_NCE1].state = HP48_MODULE_CONFIGURED;
+ m_modules[HP48_NCE1].base = 0;
+ m_modules[HP48_NCE1].mask = 0;
+
+ hp48_apply_modules();
+}
+
+
+/* RESET opcode */
+WRITE_LINE_MEMBER( hp48_state::hp48_mem_reset )
+{
+ LOG(( "%s %f hp48_mem_reset\n", machine().describe_context(), machine().time().as_double() ));
+ hp48_reset_modules();
+}
+
+
+/* CONFIG opcode */
+WRITE32_MEMBER( hp48_state::hp48_mem_config )
+{
+ int i;
+
+ LOG(( "%s %f hp48_mem_config: %05x\n", machine().describe_context(), machine().time().as_double(), data ));
+
+ /* find the highest priority unconfigured module (except non-configurable NCE1)... */
+ for ( i = 0; i < 5; i++ )
+ {
+ /* ... first call sets the address mask */
+ if ( m_modules[i].state == HP48_MODULE_UNCONFIGURED )
+ {
+ m_modules[i].mask = data & 0xff000;
+ m_modules[i].state = HP48_MODULE_MASK_KNOWN;
+ break;
+ }
+
+ /* ... second call sets the base address */
+ if ( m_modules[i].state == HP48_MODULE_MASK_KNOWN )
+ {
+ m_modules[i].base = data & m_modules[i].mask;
+ m_modules[i].state = HP48_MODULE_CONFIGURED;
+ LOG(( "hp48_mem_config: module %s configured base=%05x, mask=%05x\n",
+ hp48_module_names[i], m_modules[i].base, m_modules[i].mask ));
+ hp48_apply_modules();
+ break;
+ }
+ }
+}
+
+
+/* UNCFG opcode */
+WRITE32_MEMBER( hp48_state::hp48_mem_unconfig )
+{
+ int i;
+ LOG(( "%s %f hp48_mem_unconfig: %05x\n", machine().describe_context(), machine().time().as_double(), data ));
+
+ /* find the highest priority fully configured module at address v (except NCE1)... */
+ for ( i = 0; i < 5; i++ )
+ {
+ /* ... and unconfigure it */
+ if ( m_modules[i].state == HP48_MODULE_CONFIGURED &&
+ (m_modules[i].base == (data & m_modules[i].mask)) )
+ {
+ m_modules[i].state = i> 0 ? HP48_MODULE_UNCONFIGURED : HP48_MODULE_MASK_KNOWN;
+ LOG(( "hp48_mem_unconfig: module %s\n", hp48_module_names[i] ));
+ hp48_apply_modules();
+ break;
+ }
+ }
+}
+
+
+/* C=ID opcode */
+READ32_MEMBER( hp48_state::hp48_mem_id )
+{
+ int i;
+ int data = 0; /* 0 = everything is configured */
+
+ /* find the highest priority unconfigured module (except NCE1)... */
+ for ( i = 0; i < 5; i++ )
+ {
+ /* ... mask need to be configured */
+ if ( m_modules[i].state == HP48_MODULE_UNCONFIGURED )
+ {
+ data = hp48_module_mask_id[i] | (m_modules[i].mask & ~0xff);
+ break;
+ }
+
+ /* ... address need to be configured */
+ if ( m_modules[i].state == HP48_MODULE_MASK_KNOWN )
+ {
+ data = hp48_module_addr_id[i] | (m_modules[i].base & ~0x3f);
+ break;
+ }
+ }
+
+ LOG(( "%s %f hp48_mem_id = %02x\n",
+ machine().describe_context(), machine().time().as_double(), data ));
+
+ return data; /* everything is configured */
+}
+
+
+
+/* --------- CRC ---------- */
+
+/* each memory read by the CPU updates the internal CRC state */
+WRITE32_MEMBER( hp48_state::hp48_mem_crc )
+{
+ /* no CRC for I/O RAM */
+ if ( offset >= m_io_addr && offset < m_io_addr + 0x40 ) return;
+
+ m_crc = (m_crc >> 4) ^ (((m_crc ^ data) & 0xf) * 0x1081);
+}
+
+
+
+/* ------ utilities ------- */
+
+
+/* decodes size bytes into 2*size nibbles (least significant first) */
+void hp48_state::hp48_decode_nibble( UINT8* dst, UINT8* src, int size )
+{
+ int i;
+ for ( i=size-1; i >= 0; i-- )
+ {
+ dst[2*i+1] = src[i] >> 4;
+ dst[2*i] = src[i] & 0xf;
+ }
+}
+
+/* inverse of hp48_decode_nibble */
+void hp48_state::hp48_encode_nibble( UINT8* dst, UINT8* src, int size )
+{
+ int i;
+ for ( i=0; i < size; i++ )
+ {
+ dst[i] = (src[2*i] & 0xf) | (src[2*i+1] << 4);
+ }
+}
+
+
+
+/* ----- card images ------ */
+const device_type HP48_PORT = &device_creator<hp48_port_image_device>;
+
+/* helper for load and create */
+void hp48_port_image_device::hp48_fill_port()
+{
+ hp48_state *state = machine().driver_data<hp48_state>();
+ int size = state->m_port_size[m_port];
+ LOG(( "hp48_fill_port: %s module=%i size=%i rw=%i\n", tag(), m_module, size, state->m_port_write[m_port] ));
+ state->m_port_data[m_port] = global_alloc_array(UINT8, 2 * size);
+ memset( state->m_port_data[m_port], 0, 2 * size );
+ state->m_modules[m_module].off_mask = 2 * (( size > 128 * 1024 ) ? 128 * 1024 : size) - 1;
+ state->m_modules[m_module].read = read8_delegate();
+ state->m_modules[m_module].write = write8_delegate();
+ state->m_modules[m_module].isnop = state->m_port_write[m_port] ? 0 : 1;
+ state->m_modules[m_module].data = state->m_port_data[m_port];
+ state->hp48_apply_modules();
+}
+
+/* helper for start and unload */
+void hp48_port_image_device::hp48_unfill_port()
+{
+ hp48_state *state = machine().driver_data<hp48_state>();
+ LOG(( "hp48_unfill_port\n" ));
+ state->m_modules[m_module].off_mask = 0x00fff; /* 2 KB */
+ state->m_modules[m_module].read = read8_delegate();
+ state->m_modules[m_module].write = write8_delegate();
+ state->m_modules[m_module].data = NULL;
+ state->m_modules[m_module].isnop = 1;
+ state->m_port_size[m_port] = 0;
+}
+
+
+bool hp48_port_image_device::call_load()
+{
+ hp48_state *state = machine().driver_data<hp48_state>();
+ int size = length();
+ if ( size == 0 ) size = m_max_size; /* default size */
+ LOG(( "hp48_port_image load: size=%i\n", size ));
+
+ /* check size */
+ if ( (size < 32*1024) || (size > m_max_size) || (size & (size-1)) )
+ {
+ logerror( "hp48: image size for %s should be a power of two between %i and %i\n", tag(), 32*1024, m_max_size );
+ return IMAGE_INIT_FAIL;
+ }
+
+ state->m_port_size[m_port] = size;
+ state->m_port_write[m_port] = !is_readonly();
+ hp48_fill_port( );
+ fread(state->m_port_data[m_port], state->m_port_size[m_port] );
+ state->hp48_decode_nibble( state->m_port_data[m_port], state->m_port_data[m_port], state->m_port_size[m_port] );
+ return IMAGE_INIT_PASS;
+}
+
+bool hp48_port_image_device::call_create(int format_type, option_resolution *format_options)
+{
+ hp48_state *state = machine().driver_data<hp48_state>();
+ int size = m_max_size;
+ LOG(( "hp48_port_image create: size=%i\n", size ));
+ /* XXX defaults to max_size; get user-specified size instead */
+
+ /* check size */
+ /* size must be a power of 2 between 32K and max_size */
+ if ( (size < 32*1024) || (size > m_max_size) || (size & (size-1)) )
+ {
+ logerror( "hp48: image size for %s should be a power of two between %i and %i\n", tag(), 32*1024, m_max_size );
+ return IMAGE_INIT_FAIL;
+ }
+
+ state->m_port_size[m_port] = size;
+ state->m_port_write[m_port] = 1;
+ hp48_fill_port();
+ return IMAGE_INIT_PASS;
+}
+
+void hp48_port_image_device::call_unload()
+{
+ hp48_state *state = machine().driver_data<hp48_state>();
+ LOG(( "hp48_port image unload: %s size=%i rw=%i\n",
+ tag(), state->m_port_size[m_port], state->m_port_write[m_port] ));
+ if ( state->m_port_write[m_port] )
+ {
+ state->hp48_encode_nibble( state->m_port_data[m_port], state->m_port_data[m_port], state->m_port_size[m_port] );
+ fseek( 0, SEEK_SET );
+ fwrite( state->m_port_data[m_port], state->m_port_size[m_port] );
+ }
+ global_free_array( state->m_port_data[m_port] );
+ hp48_unfill_port();
+ state->hp48_apply_modules();
+}
+
+void hp48_port_image_device::device_start()
+{
+ LOG(("hp48_port_image start\n"));
+ hp48_unfill_port();
+}
+
+hp48_port_image_device::hp48_port_image_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
+ : device_t(mconfig, HP48_PORT, "HP48 memory card", tag, owner, clock, "hp48_port_image", __FILE__),
+ device_image_interface(mconfig, *this)
+{
+}
+
+void hp48_port_image_device::device_config_complete()
+{
+ update_names(HP48_PORT, "port", "p");
+}
+
+/***************************************************************************
+ MACHINES
+***************************************************************************/
+
+DRIVER_INIT_MEMBER(hp48_state,hp48)
+{
+ int i;
+ LOG(( "hp48: driver init called\n" ));
+ for ( i = 0; i < 6; i++ )
+ {
+ m_modules[i].off_mask = 0x00fff; /* 2 KB */
+ m_modules[i].read = read8_delegate();
+ m_modules[i].write = write8_delegate();
+ m_modules[i].data = NULL;
+ m_modules[i].isnop = 0;
+ }
+ m_port_size[0] = 0;
+ m_port_size[1] = 0;
+ m_rom = NULL;
+}
+
+void hp48_state::machine_reset()
+{
+ LOG(( "hp48: machine reset called\n" ));
+ m_bank_switch = 0;
+ hp48_reset_modules();
+ hp48_update_annunciators();
+}
+
+void hp48_state::hp48_machine_start( hp48_models model )
+{
+ UINT8 *ram;
+ int ram_size, rom_size, i;
+
+ LOG(( "hp48_machine_start: model %i\n", model ));
+
+ m_model = model;
+
+ /* internal RAM */
+ ram_size =
+ HP49_G_MODEL ? (512 * 1024) :
+ HP48_GX_MODEL ? (128 * 1024) : (32 * 1024);
+
+ ram = auto_alloc_array(machine(), UINT8, 2 * ram_size);
+ machine().device<nvram_device>("nvram")->set_base(ram, 2 * ram_size);
+
+
+ /* ROM load */
+ rom_size =
+ HP49_G_MODEL ? (2048 * 1024) :
+ HP48_S_SERIES ? (256 * 1024) : (512 * 1024);
+ m_rom = auto_alloc_array(machine(), UINT8, 2 * rom_size);
+ hp48_decode_nibble( m_rom, memregion( "maincpu" )->base(), rom_size );
+
+ /* init state */
+ memset( ram, 0, 2 * ram_size );
+ memset( m_io, 0, sizeof( m_io ) );
+ m_out = 0;
+ m_kdn = 0;
+ m_crc = 0;
+ m_timer1 = 0;
+ m_timer2 = 0;
+ m_bank_switch = 0;
+
+ /* I/O RAM */
+ m_modules[HP48_HDW].off_mask = 0x0003f; /* 32 B */
+ m_modules[HP48_HDW].read = read8_delegate(FUNC(hp48_state::hp48_io_r),this);
+ m_modules[HP48_HDW].write = write8_delegate(FUNC(hp48_state::hp48_io_w),this);
+
+ /* internal RAM */
+ if ( HP49_G_MODEL )
+ {
+ m_modules[HP48_NCE2].off_mask = 2 * 256 * 1024 - 1;
+ m_modules[HP48_NCE2].data = ram;
+ m_modules[HP48_CE2].off_mask = 2 * 128 * 1024 - 1;
+ m_modules[HP48_CE2].data = ram + 2 * 256 * 1024;
+ m_modules[HP48_NCE3].off_mask = 2 * 128 * 1024 - 1;
+ m_modules[HP48_NCE3].data = ram + 2 * (128+256) * 1024;
+ }
+ else
+ {
+ m_modules[HP48_NCE2].off_mask = 2 * ram_size - 1;
+ m_modules[HP48_NCE2].data = ram;
+ }
+
+ /* bank switcher */
+ if ( HP48_G_SERIES )
+ {
+ m_modules[HP48_CE1].off_mask = 0x00fff; /* 2 KB */
+ m_modules[HP48_CE1].read = read8_delegate(FUNC(hp48_state::hp48_bank_r),this);
+ m_modules[HP48_CE1].write = HP49_G_MODEL ? write8_delegate(FUNC(hp48_state::hp49_bank_w),this) : write8_delegate();
+ }
+
+ /* timers */
+ machine().scheduler().timer_pulse(attotime::from_hz( 16 ), timer_expired_delegate(FUNC(hp48_state::hp48_timer1_cb),this));
+ machine().scheduler().timer_pulse(attotime::from_hz( 8192 ), timer_expired_delegate(FUNC(hp48_state::hp48_timer2_cb),this));
+
+ /* 1ms keyboard polling */
+ machine().scheduler().timer_pulse(attotime::from_msec( 1 ), timer_expired_delegate(FUNC(hp48_state::hp48_kbd_cb),this));
+
+ /* save state */
+ save_item(NAME(m_out) );
+ save_item(NAME(m_kdn) );
+ save_item(NAME(m_io_addr) );
+ save_item(NAME(m_crc) );
+ save_item(NAME(m_timer1) );
+ save_item(NAME(m_timer2) );
+ save_item(NAME(m_bank_switch) );
+ for ( i = 0; i < 6; i++ )
+ {
+ save_item(m_modules[i].state, "globals/m_modules[i].state", i);
+ save_item(m_modules[i].base, "globals/m_modules[i].base", i);
+ save_item(m_modules[i].mask, "globals/m_modules[i].mask", i);
+ }
+ save_item(NAME(m_io) );
+ machine().save().register_postload( save_prepost_delegate(FUNC(hp48_state::hp48_update_annunciators), this ));
+ machine().save().register_postload( save_prepost_delegate(FUNC(hp48_state::hp48_apply_modules), this ));
+
+}
+
+
+MACHINE_START_MEMBER(hp48_state,hp48s)
+{
+ hp48_machine_start( HP48_S );
+}
+
+
+MACHINE_START_MEMBER(hp48_state,hp48sx)
+{
+ hp48_machine_start( HP48_SX );
+}
+
+
+MACHINE_START_MEMBER(hp48_state,hp48g)
+{
+ hp48_machine_start( HP48_G );
+}
+
+MACHINE_START_MEMBER(hp48_state,hp48gx)
+{
+ hp48_machine_start( HP48_GX );
+}
+
+MACHINE_START_MEMBER(hp48_state,hp48gp)
+{
+ hp48_machine_start( HP48_GP );
+}
+
+MACHINE_START_MEMBER(hp48_state,hp49g)
+{
+ hp48_machine_start( HP49_G );
+}