// license:BSD-3-Clause
// copyright-holders:Joakim Larsson Edström
/***************************************************************************************************
*
* Ericsson Information Systems PC "compatibles"
*
* The Ericsson PC was the first original Ericsson design for the office PC market replacing the
* Step/One which was an OEM:ed clone of the Matsushita Mybrain 3000 (see myb3k.cpp driver).
*
**************************************************************
* Ericsson PC
*------------
* Links: https://youtu.be/6uilOdMJc24
* Form Factor: Desktop
* CPU: 8088 @ 4.77MHz
* RAM: 256K
* Bus: 6x ISA
* Video: Monchrome or Color 80x25 character mode. 320x200 and 640x400 grahics modes
* Display: Orange Gas Plasma (GP) display
* Mass storage: 2 x 5.25" 360K or 1 20Mb HDD
* On board ports: Beeper,
* Ports: serial, parallel
* Internal Options: Up to 640K RAM through add-on RAM card
* Misc: The hardware was not 100% PC compatible so non BIOS based software would not always run. 50.000+ units sold
*
* TODO
* - Complete the Ericsson 1070 MDA ISA board and test all the graphics modes including 640x400 (aka HR)
* - Add the Ericsson 1065 HDC and boot from a hard drive
* - Add softlist
* - Pass the diagnostics software system test at EPC2.IMD, it currently hangs the keyboard.
* A later version of the test on EPC5.IMD works though so need to verify EPC2.IMD on real hardware first.
*
* CREDITS The driver code is inspired from m24.cpp, myb3k.cpp and genpc.cpp. Information about the EPC has
* been contributed by many, mainly the people at Dalby Computer museum http://www.datormuseum.se/
* A dead pcb was donated by rfka01 and rom dumps by ZnaxQue@sweclockers.com
*
************************************************************************************************************/
/*
Links
-----
*/
#include "emu.h"
#include "machine/eispc_kb.h"
#include "epc.lh"
// Devices
#include "cpu/i86/i86.h"
#include "machine/am9517a.h"
#include "machine/i8087.h"
#include "machine/i8251.h"
#include "machine/i8255.h"
#include "machine/pit8253.h"
#include "machine/pic8259.h"
#include "machine/upd765.h"
#include "machine/ins8250.h"
// Expansion cards
//#include "bus/isa/isa.h"
//#include "bus/isa/isa_cards.h"
#include "bus/isa/ega.h"
#include "bus/isa/eis_hgb107x.h"
#include "bus/isa/eis_twib.h"
#include "machine/pc_lpt.h"
#include "machine/ram.h"
#include "machine/timer.h"
#include "sound/spkrdev.h"
#include "speaker.h"
#include "imagedev/floppy.h"
#include "formats/imd_dsk.h"
#include "formats/pc_dsk.h"
#include "bus/rs232/rs232.h"
#define LOG_PPI (1U << 1)
#define LOG_PIT (1U << 2)
#define LOG_PIC (1U << 3)
#define LOG_KBD (1U << 4)
#define LOG_DMA (1U << 5)
#define LOG_IRQ (1U << 6)
#define LOG_FDC (1U << 7)
#define LOG_LPT (1U << 8)
#define LOG_NMI (1U << 9)
#define LOG_BITS (1U << 10)
#define LOG_FPU (1U << 11)
#define LOG_COM (1U << 12)
//#define VERBOSE (LOG_LPT)
//#define LOG_OUTPUT_STREAM std::cout
#include "logmacro.h"
#define LOGPPI(...) LOGMASKED(LOG_PPI, __VA_ARGS__)
#define LOGPIT(...) LOGMASKED(LOG_PIT, __VA_ARGS__)
#define LOGPIC(...) LOGMASKED(LOG_PIC, __VA_ARGS__)
#define LOGKBD(...) LOGMASKED(LOG_KBD, __VA_ARGS__)
#define LOGDMA(...) LOGMASKED(LOG_DMA, __VA_ARGS__)
#define LOGIRQ(...) LOGMASKED(LOG_IRQ, __VA_ARGS__)
#define LOGFDC(...) LOGMASKED(LOG_FDC, __VA_ARGS__)
#define LOGLPT(...) LOGMASKED(LOG_LPT, __VA_ARGS__)
#define LOGNMI(...) LOGMASKED(LOG_NMI, __VA_ARGS__)
#define LOGBITS(...) LOGMASKED(LOG_BITS, __VA_ARGS__)
#define LOGFPU(...) LOGMASKED(LOG_FPU, __VA_ARGS__)
#define LOGCOM(...) LOGMASKED(LOG_COM, __VA_ARGS__)
class epc_state : public driver_device
{
public:
epc_state(const machine_config &mconfig, device_type type, const char *tag) :
driver_device(mconfig, type, tag)
, m_maincpu(*this, "maincpu")
, m_ram(*this, RAM_TAG)
, m_isabus(*this, "isabus")
, m_dma8237a(*this, "dma8237")
, m_ppi8255(*this, "ppi8255")
, m_io_dsw(*this, "DSW")
, m_io_j10(*this, "J10")
, m_io_s21(*this, "S21")
, m_lpt(*this, "lpt")
, m_kbd8251(*this, "kbd8251")
, m_keyboard(*this, "keyboard")
, m_leds(*this, "kbled%u")
, m_pic8259(*this, "pic8259")
, m_pit8253(*this, "pit8253")
, m_speaker(*this, "speaker")
, m_fdc(*this, "fdc")
, m_floppy_connectors(*this, "fdc:%u", 0)
, m_uart(*this, "uart")
{ }
void epc(machine_config &config);
void init_epc();
protected:
virtual void machine_start() override;
virtual void machine_reset() override;
private:
required_device<i8086_cpu_device> m_maincpu;
required_device<ram_device> m_ram;
required_device<isa8_device> m_isabus;
// DMA
DECLARE_WRITE_LINE_MEMBER(dma_tc_w);
DECLARE_WRITE_LINE_MEMBER(dreq0_ck_w);
DECLARE_WRITE_LINE_MEMBER( epc_dma_hrq_changed );
DECLARE_WRITE_LINE_MEMBER( epc_dma8237_out_eop );
DECLARE_READ8_MEMBER( epc_dma_read_byte );
DECLARE_WRITE8_MEMBER( epc_dma_write_byte );
template <int Channel> uint8_t epc_dma8237_io_r(offs_t offset);
template <int Channel> void epc_dma8237_io_w(offs_t offset, uint8_t data);
template <int Channel> DECLARE_WRITE_LINE_MEMBER(epc_dack_w);
required_device<am9517a_device> m_dma8237a;
uint8_t m_dma_segment[4];
uint8_t m_dma_active;
bool m_tc;
bool m_txd;
bool m_rxrdy;
bool m_int;
bool m_dreq0_ck;
// PPI
required_device<i8255_device> m_ppi8255;
DECLARE_WRITE8_MEMBER(ppi_portb_w);
DECLARE_READ8_MEMBER(ppi_portc_r);
uint8_t m_ppi_portb;
required_ioport m_io_dsw;
required_ioport m_io_j10;
required_ioport m_io_s21;
// Printer port
optional_device<pc_lpt_device> m_lpt;
// Keyboard Controller/USART
required_device<i8251_device> m_kbd8251;
required_device<eispc_keyboard_device> m_keyboard;
emu_timer *m_kbdclk_timer;
TIMER_CALLBACK_MEMBER(rxtxclk_w);
bool m_8251rxtx_clk_state;
bool m_kbdclk_state;
bool m_8251dtr_state;
int m_kbdclk;
output_finder<3> m_leds;
// Interrupt Controller
required_device<pic8259_device> m_pic8259;
DECLARE_WRITE_LINE_MEMBER(int_w);
uint8_t m_nmi_enabled;
uint8_t m_8087_int = 0;
uint8_t m_parer_int = 0;
uint8_t m_iochck_int = 0;
void update_nmi();
// Timer
required_device<pit8253_device> m_pit8253;
// Speaker
DECLARE_WRITE_LINE_MEMBER(speaker_ck_w);
required_device<speaker_sound_device> m_speaker;
bool m_pc4;
bool m_pc5;
void epc_map(address_map &map);
void epc_io(address_map &map);
// FDC
void check_fdc_irq();
void check_fdc_drq();
required_device<i8272a_device> m_fdc;
uint8_t m_ocr;
bool m_irq; // System signal after glue logic
bool m_drq; // System signal after glue logic
bool m_fdc_irq; // FDC output pin
bool m_fdc_drq; // FDC output pin
optional_device_array<floppy_connector, 4> m_floppy_connectors;
DECLARE_FLOPPY_FORMATS( epc_floppy_formats );
// UART
required_device<ins8250_device> m_uart;
};
void epc_state::check_fdc_irq()
{
bool pirq = m_irq;
m_irq = m_fdc_irq && (m_ocr & 4) && (m_ocr & 8); // IRQ enabled and not in reset?
if(m_irq != pirq) // has the state changed?
{
LOGIRQ("FDC: IRQ6 request: %d\n", m_irq);
m_pic8259->ir6_w(m_irq);
}
}
void epc_state::check_fdc_drq()
{
bool pdrq = m_drq;
m_drq = m_fdc_drq && (m_ocr & 4) && (m_ocr & 8); // DREQ enabled and not in reset?
if(m_drq != pdrq) // has the state changed?
{
LOGDMA("FDC: DMA channel 2 request: %d\n", m_drq);
m_dma8237a->dreq2_w(m_drq);
}
}
void epc_state::epc_map(address_map &map)
{
map.unmap_value_high();
map(0x20000, 0x9ffff).noprw(); // Base RAM - mapped to avoid unmaped errors when BIOS is probing RAM size
// 0xa0000-0xaffff is reserved
map(0xb0000, 0xb7fff).noprw(); // Monochrome RAM - mapped to avoid unaped errors when BIOS is probing RAM size
map(0xb0000, 0xb7fff).noprw(); // Monochrome RAM - mapped to avoid unaped errors when BIOS is probing RAM size
map(0xb8000, 0xbffff).noprw(); // Color/Graphics RAM - mapped to avoid unaped errors when BIOS is probing RAM size
map(0xc0000, 0xeffff).noprw(); // Expansion ROM area - Hard Disk BIOS etc
map(0xf0000, 0xfffff).rom().region("bios", 0);
}
void epc_state::epc_io(address_map &map)
{
map(0x0000, 0x000f).mirror(0x10).lrw8(
[this](offs_t offset) -> uint8_t
{
uint8_t data = m_dma8237a->read(offset);
LOGDMA("dma8237_r %04x\n", offset);
return data;
},
"dma8237_r",
[this](offs_t offset, uint8_t data)
{
LOGDMA("dma8237_w %04x: %02x\n", offset, data);
m_dma8237a->write(offset, data);
},
"dma8237_w"
);
map(0x0020, 0x0021).mirror(0x1e).lrw8(
[this](offs_t offset) -> uint8_t
{
uint8_t data = m_pic8259->read(offset);
LOGPIC("pic8259_r %04x: %02x\n", offset, data);
return data;
},
"pic8259_r",
[this](offs_t offset, uint8_t data)
{
LOGPIC("pic8259_w %04x: %02x\n", offset, data);
m_pic8259->write(offset, data);
},
"pic8259_w"
);
map(0x0040, 0x0043).mirror(0x1c).lrw8(
[this](offs_t offset) -> uint8_t
{
uint8_t data = m_pit8253->read(offset);
LOGPIT("pit8253_r %04x\n", offset);
return data;
},
"pit8253_r",
[this](offs_t offset, uint8_t data)
{
LOGPIT("pit8253_w %04x: %02x\n", offset, data);
m_pit8253->write(offset, data);
},
"pit8253_w"
);
map(0x0060, 0x0060).mirror(0x1c).lrw8(
[this]() -> uint8_t
{
uint8_t data = m_kbd8251->data_r();
LOGKBD("kbd8251_r %02x\n", data);
return data;
},
"kbd_8251_data_r",
[this](offs_t offset, uint8_t data)
{
LOGKBD("kbd8251_w 0x60 %02x\n", data);
m_kbd8251->data_w(data);
},
"kbd_8251_data_w"
);
// NOTE: PPI Port A is not mapped
map(0x0061, 0x0061).mirror(0x1c).lrw8( // PPI Port B
[this](offs_t offset) -> uint8_t
{
uint8_t data = m_ppi8255->read(1);
LOGPPI("ppi8255_r Port B: %02x\n", data);
return data;
},
"ppi8255_r",
[this](offs_t offset, uint8_t data)
{
LOGPPI("ppi8255_w Port B: %02x\n", data);
m_ppi8255->write(1, data);
},
"ppi8255_w"
);
map(0x0062, 0x0062).mirror(0x1c).lrw8( // PPI Port C
[this](offs_t offset) -> uint8_t
{
uint8_t data = m_ppi8255->read(2);
LOGPPI("ppi8255_r Port C: %02x\n", data);
return data;
},
"ppi8255_r",
[this](offs_t offset, uint8_t data)
{
LOGPPI("ppi8255_w Port C: %02x\n", data);
m_ppi8255->write(2, data);
},
"ppi8255_w"
);
map(0x0063, 0x0063).lrw8( // PPI Control register
[this](offs_t offset) -> uint8_t
{
uint8_t data = m_ppi8255->read(3);
LOGPPI("ppi8255_r Control: %02x\n", data);
return data;
},
"ppi8255_r",
[this](offs_t offset, uint8_t data)
{
LOGPPI("ppi8255_w Control: %02x\n", data);
m_ppi8255->write(3, data);
},
"ppi8255_w"
);
map(0x0070, 0x0070).mirror(0x0e).lw8(
[this](offs_t offset, uint8_t data)
{
LOGKBD("kbd8251_w 0x70: %02x\n", data);
m_kbd8251->data_w(data);
},
"i8251_data_w"
);
map(0x0071, 0x0071).mirror(0x0e).lrw8(
[this](offs_t offset) -> uint8_t
{
uint8_t stat = m_kbd8251->status_r();
//LOGKBD("kbd8251_status_r %02x\n", stat);
return stat;
},
"kbd_8251_stat_ctrl_r",
[this](offs_t offset, uint8_t data)
{
LOGKBD("kbd8251_control_w 0x71: %02x\n", data);
m_kbd8251->control_w(data);
},
"kbd_8251_stat_ctrl_w"
);
map(0x0080, 0x0083).mirror(0xc).lw8(
[this](offs_t offset, uint8_t data)
{
LOGDMA("dma_segment_w %04x: %02x\n", offset, data);
m_dma_segment[offset] = data & 0x0f;
},
"dma_segement_w"
);
map(0x00a0, 0x00a1).mirror(0xe).lw8(
[this](offs_t offset, uint8_t data)
{
LOGNMI("nmi_enable_w %04x: %02x\n", offset, data);
m_nmi_enabled = BIT(data,7);
update_nmi();
},
"nmi_enable_w"
);
// FDC Output Control Register (same as PC XT DOR)
map(0x03f2, 0x03f3).lw8( // B0-B1 Drive select 0-3
[this](offs_t offset, uint8_t data) // B2 FDC Reset line
{ // B3 Enable FDC DMA/IRQ
LOGFDC("FDC OCR: %02x\n", data);// B4-B7 Motor on for selected drive
uint8_t pocr = m_ocr;
uint8_t fid = m_ocr & 3;
m_ocr = data;
if ((m_ocr & 4) && m_floppy_connectors[fid]) // Not in reset and there is a floppy drive attached
{
floppy_image_device *floppy = m_floppy_connectors[fid]->get_device(); // try to retrieve the floppy
if (floppy)
{
LOGFDC(" - Motor %s for drive %d\n", (m_ocr & (0x10 << fid)) ? "ON" : "OFF", fid);
floppy->mon_w(!(m_ocr & (0x10 << fid)));
LOGFDC(" - Setting a floppy for drive %d\n", fid);
m_fdc->set_floppy((m_ocr & (0x10 << fid)) ? floppy : nullptr);
}
}
if (((pocr ^ m_ocr) & 4) && (m_ocr & 4) == 0) // If FDC reset state bit has changed to low then reset the FDC
m_fdc->reset();
check_fdc_irq();
check_fdc_drq();
},
"ocr_w"
);
map(0x03f4, 0x03f5).m(m_fdc, FUNC(i8272a_device::map));
map(0x03bc, 0x03be).lrw8(
[this](address_space &space, offs_t offset, uint8_t mem_mask) -> uint8_t
{
uint8_t data = m_lpt->read(space, offset);
LOGLPT("LPT read offset %02x: %02x\n", offset, data);
return data;
},
"lpt_r",
[this](address_space &space, offs_t offset, uint8_t data)
{
LOGLPT("LPT write offset %02x: %02x\n", offset, data);
m_lpt->write(space, offset, data);
},
"lpt_w"
);
map(0x03f8, 0x03ff).rw(m_uart, FUNC(ins8250_device::ins8250_r), FUNC(ins8250_device::ins8250_w));
}
void epc_state::machine_start()
{
m_maincpu->space(AS_PROGRAM).install_ram(0, m_ram->size() - 1, m_ram->pointer());
std::fill_n(&m_dma_segment[0], 4, 0);
save_item(NAME(m_dma_segment));
save_item(NAME(m_dma_active));
save_item(NAME(m_tc));
save_item(NAME(m_txd));
save_item(NAME(m_rxrdy));
save_item(NAME(m_int));
save_item(NAME(m_dreq0_ck));
save_item(NAME(m_ppi_portb));
save_item(NAME(m_8251rxtx_clk_state));
save_item(NAME(m_kbdclk_state));
save_item(NAME(m_kbdclk));
save_item(NAME(m_8251dtr_state));
save_item(NAME(m_nmi_enabled));
save_item(NAME(m_8087_int));
save_item(NAME(m_parer_int));
save_item(NAME(m_iochck_int));
save_item(NAME(m_pc4));
save_item(NAME(m_pc5));
save_item(NAME(m_ocr));
save_item(NAME(m_irq));
save_item(NAME(m_drq));
save_item(NAME(m_fdc_irq));
save_item(NAME(m_fdc_drq));
m_leds.resolve();
}
void epc_state::machine_reset()
{
m_dma_active = 0;
m_tc = false;
m_txd = false;
m_rxrdy = false;
m_int = 1;
m_dreq0_ck = true;
m_ppi_portb = 0;
m_8251rxtx_clk_state = 0;
m_kbdclk_state = 0;
m_kbdclk = 0;
m_8251dtr_state = 1;
m_nmi_enabled = 0;
m_8087_int = 0;
m_parer_int = 0;
m_iochck_int = 0;
m_pc4 = 0;
m_pc5 = 0;
m_ocr = 0;
m_irq = 0;
m_drq = 0;
m_fdc_irq = 0;
m_fdc_drq = 0;
m_keyboard->rst_line_w(ASSERT_LINE);
m_kbd8251->write_cts(0); // Tied to GND
}
void epc_state::init_epc()
{
/* Keyboard UART Rxc/Txc is 19.2 kHz from x960 divider */
m_kbdclk_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(epc_state::rxtxclk_w), this));
m_kbdclk_timer->adjust(attotime::from_hz(XTAL(18'432'000) / 960) / 2);
}
TIMER_CALLBACK_MEMBER(epc_state::rxtxclk_w)
{
m_kbd8251->write_rxc(m_8251rxtx_clk_state);
m_kbd8251->write_txc(m_8251rxtx_clk_state);
// The EPC PCB has an option to support a custom receive clock for the INS8250 apart from the TX clock through a mux controlled
// by the DTR pin of the I8251. The ins8250 device doesn't support RCLK as it is considerd implicitly as the same as BAUDOUT
// First attempt to support this in INS8250 by lifting out the BRG from deserial was reverted due to lots of regressions.
// We probably need to remove diserial dependencies completely from ins8250 or implement BRG hooks in diserial.cpp.
// if (!m_8251dtr_state) m_uart->rclk_w(m_8251rxtx_clk_state); // TODO: fix RCLK support in INS8250
m_8251rxtx_clk_state = !m_8251rxtx_clk_state;
// If CLK signal is jumpered in instead of reset signal for the keyboard
if ((m_io_s21->read() & 0x01) == 0x01)
{
if (m_kbdclk++ >= 4) // Frequncy is taken out of the same divider as the rxtx clock but 2 steps later
{
m_keyboard->rst_line_w(m_kbdclk_state);
m_kbdclk = 0;
m_kbdclk_state = !m_kbdclk_state;
}
}
/* Keyboard UART Rxc/Txc is 19.2 kHz from x960 divider ( 15 (74ls161) * 4 (74ls393.1) * 16 (74ls393) ) */
m_kbdclk_timer->adjust(attotime::from_hz(XTAL(18'432'000) / 960) / 2);
}
template <int Channel>
uint8_t epc_state::epc_dma8237_io_r(offs_t offset)
{
LOGDMA("epc_dma8237_io_r: %d\n", Channel);
if (Channel == 2)
return m_fdc->dma_r();
else
return m_isabus->dack_r(Channel);
}
template <int Channel>
void epc_state::epc_dma8237_io_w(offs_t offset, uint8_t data)
{
LOGDMA("epc_dma8237_io_w: %d - %02x\n", Channel, data);
if (Channel == 2)
m_fdc->dma_w(data);
else
m_isabus->dack_w(Channel, data);
}
template <int Channel>
WRITE_LINE_MEMBER(epc_state::epc_dack_w)
{
LOGDMA("epc_dack_w: %d - %d\n", Channel, state);
m_isabus->dack_line_w(Channel, state);
if (!state)
{
m_dma_active |= 1 << Channel;
if (Channel == 0)
m_dma8237a->dreq0_w(0);
if (m_tc)
m_isabus->eop_w(Channel, ASSERT_LINE);
}
else
{
m_dma_active &= ~(1 << Channel);
if (m_tc)
m_isabus->eop_w(Channel, CLEAR_LINE);
}
}
WRITE_LINE_MEMBER(epc_state::dma_tc_w)
{
m_tc = (state == ASSERT_LINE);
for (int channel = 0; channel < 4; channel++)
{
if (BIT(m_dma_active, channel))
{
LOGDMA("dma_tc_w ch %d: %d\n", channel, state);
m_isabus->eop_w(channel, state);
}
}
// Special treatment for on board FDC
if (BIT(m_dma_active, 2))
{
m_fdc->tc_w(0);
}
else
{
m_fdc->tc_w(1);
}
}
WRITE_LINE_MEMBER(epc_state::dreq0_ck_w)
{
if (state && !m_dreq0_ck && !BIT(m_dma_active, 0))
m_dma8237a->dreq0_w(1);
m_dreq0_ck = state;
}
WRITE_LINE_MEMBER(epc_state::speaker_ck_w)
{
m_pc5 = state;
m_pc4 = (m_ppi_portb & 0x02) && state ? 1 : 0;
m_speaker->level_w(m_pc4);
}
/**********************************************************
*
* PPI8255 interface
*
*
* PORT A (not used)
*
* Reads of port A is shadowed by UART8251A's read register
* gaining some compatibility with PC software. The UART8251
* communicates with the serial keyboard and extends it with
* write capability enabling keyboard led control as with a
* PC AT keyboard.
*
* PORT B (output)
* 0 - PB0 - - Control signal for the sound generator (short beeps)
* 1 - PB1 - - Control signal for the sound generator
* 2 - PB2 - - Unused
* 3 - PB3 - - Data select for the configuration switches 0=SW1-4 1=SW5-8
* 4 - PB4 - * - Enable ram parity check
* 5 - PB5 - * - Enable expansion I/O check
* 6 - PB6 - * - Keyboard reset
* 7 - PB7 - - Reset keyboard interrupt
*
* PORT C
* 0 - PC0 - - Dipswitch SW 1/5 PB3=0/PB3=1
* 1 - PC1 - - Dipswitch SW 2/6 PB3=0/PB3=1
* 2 - PC2 - - Dipswitch SW 3/7 PB3=0/PB3=1
* 3 - PC3 - - Dipswitch SW 4/8 PB3=0/PB3=1
* 4 - PC4 - SPK - Speaker/cassette data (spare in PC XT spec)
* 5 - PC5 - OUT2 - OUT2 from 8253 (ibmpcjr compatible)
* 6 - PC6 -
* 7 - PC7 -
*
* Ericsson PC SW:
* 1 - Not used. Must be set to OFF
* 2 - OFF - 8087 present
* ON - No 8087 present *)
* 3 - Not Used. Don't care but OFF *)
* 4 - Not Used. Must be set to ON
* 5+6 - Used to select display
* OFF OFF - Monochrome HR graphics monitor 3111 installed + 1020 color secondary monitor
* ON OFF - Monochrome HR graphics monitor 3111 installed + optional 1020 color main monitor *)
* OFF ON - Not used
* ON ON - Not used
* 7+8 - Used to select number of disk drives
* OFF OFF - Not used
* ON OFF - Not used
* OFF ON - two disk drives, system units 1030-1 and 1030-2
* ON ON - one disk drive, system units 1030-3, 1030-4, 1031-1 and 1031-2
*
* *) - Factory settings
*
**********************************************************/
READ8_MEMBER( epc_state::ppi_portc_r )
{
uint8_t data;
// Read 4 configurations dip switches depending on PB3
data = (m_io_dsw->read() >> ((m_ppi_portb & 0x08) ? 4 : 0) & 0x0f);
data |= (m_pc4 ? 1U << 4 : 0); // Feedback from gated speaker beep
data |= (m_pc5 ? 1U << 5 : 0); // Feedback from timer source for speaker beep
LOGPPI("PPI Port C read: %02x\n", data);
return data;
}
WRITE8_MEMBER( epc_state::ppi_portb_w )
{
LOGPPI("PPI Port B write: %02x\n", data);
LOGPPI(" PB0 - Enable beeper : %d\n", (data & 0x01) ? 1 : 0);
LOGPPI(" PB1 - Beeper data : %d\n", (data & 0x02) ? 1 : 0);
LOGPPI(" PB2 - Unused : %d\n", (data & 0x04) ? 1 : 0);
LOGPPI(" PB3 - Port C dip switch select : %d\n", (data & 0x08) ? 1 : 0);
LOGPPI(" PB4 - RAM parity enable : %d\n", (data & 0x10) ? 1 : 0);
LOGPPI(" PB5 - ISA error checking enable : %d\n", (data & 0x20) ? 1 : 0);
LOGPPI(" PB6 - Reset keyboard : %d\n", (data & 0x40) ? 1 : 0);
LOGPPI(" PB7 - Reset keyboard interrupt : %d\n", (data & 0x80) ? 1 : 0);
uint8_t changed = m_ppi_portb ^ data;
m_ppi_portb = data;
if (changed & 0x40)
{
if ((m_io_s21->read() & 0x01) == 0x00)
{
if (m_ppi_portb & 0x40)
{
LOGKBD("PB6 set, clearing Keyboard RESET\n");
m_keyboard->rst_line_w(CLEAR_LINE);
}
else
{
LOGKBD("PB6 cleared, asserting Keyboard RESET\n");
m_keyboard->rst_line_w(ASSERT_LINE);
}
}
}
if (changed & m_ppi_portb & 0x80)
{
LOGIRQ("PB7 set, clearing IRQ1 and releasing HOLD\n");
m_pic8259->ir1_w(CLEAR_LINE);
m_keyboard->hold_w(ASSERT_LINE);
}
}
WRITE_LINE_MEMBER(epc_state::int_w)
{
if (m_int != state)
{
LOGIRQ("int_w: %d\n", state);
m_int = state;
m_maincpu->set_input_line(0, m_int);
}
}
static void epc_isa8_cards(device_slot_interface &device)
{
device.option_add("epc_mda", ISA8_EPC_MDA);
device.option_add("ega", ISA8_EGA);
device.option_add("epc_twib", ISA8_EIS_TWIB);
// device.option_add("epc_hdc1065", ISA8_EPC_HDC1065);
// device.option_add("epc_mb1080", ISA8_EPC_MB1080);
}
FLOPPY_FORMATS_MEMBER( epc_state::epc_floppy_formats )
FLOPPY_PC_FORMAT,
FLOPPY_IMD_FORMAT
FLOPPY_FORMATS_END
static void epc_sd_floppies(device_slot_interface &device)
{
device.option_add("525sd", FLOPPY_525_SD);
}
void epc_state::epc(machine_config &config)
{
config.set_default_layout(layout_epc);
// CPU
I8088(config, m_maincpu, XTAL(14'318'181) / 3.0); // TWE crystal marked X1 verified divided through a 82874
m_maincpu->set_addrmap(AS_PROGRAM, &epc_state::epc_map);
m_maincpu->set_addrmap(AS_IO, &epc_state::epc_io);
m_maincpu->set_irq_acknowledge_callback("pic8259", FUNC(pic8259_device::inta_cb));
m_maincpu->esc_opcode_handler().set("fpu8087", FUNC(i8087_device::insn_w));
m_maincpu->esc_data_handler().set("fpu8087", FUNC(i8087_device::addr_w));
i8087_device &i8087(I8087(config, "fpu8087", XTAL(14'318'181) / 3.0));
i8087.set_space_88(m_maincpu, AS_PROGRAM);
i8087.irq().set([this](bool state)
{
LOGFPU("8087 INT: %d\n", state);
m_8087_int = state;
update_nmi();
});
i8087.busy().set_inputline(m_maincpu, INPUT_LINE_TEST);
// DMA
AM9517A(config, m_dma8237a, XTAL(14'318'181) / 3.0); // TWE crystal marked X1 verified
m_dma8237a->out_hreq_callback().set(FUNC(epc_state::epc_dma_hrq_changed));
m_dma8237a->out_eop_callback().set(FUNC(epc_state::dma_tc_w));
m_dma8237a->in_memr_callback().set(FUNC(epc_state::epc_dma_read_byte));
m_dma8237a->out_memw_callback().set(FUNC(epc_state::epc_dma_write_byte));
m_dma8237a->in_ior_callback<1>().set(FUNC(epc_state::epc_dma8237_io_r<1>));
m_dma8237a->in_ior_callback<2>().set(FUNC(epc_state::epc_dma8237_io_r<2>));
m_dma8237a->in_ior_callback<3>().set(FUNC(epc_state::epc_dma8237_io_r<3>));
m_dma8237a->out_iow_callback<0>().set(FUNC(epc_state::epc_dma8237_io_w<0>));
m_dma8237a->out_iow_callback<1>().set(FUNC(epc_state::epc_dma8237_io_w<1>));
m_dma8237a->out_iow_callback<2>().set(FUNC(epc_state::epc_dma8237_io_w<2>));
m_dma8237a->out_iow_callback<3>().set(FUNC(epc_state::epc_dma8237_io_w<3>));
m_dma8237a->out_dack_callback<0>().set(FUNC(epc_state::epc_dack_w<0>));
m_dma8237a->out_dack_callback<1>().set(FUNC(epc_state::epc_dack_w<1>));
m_dma8237a->out_dack_callback<2>().set(FUNC(epc_state::epc_dack_w<2>));
m_dma8237a->out_dack_callback<3>().set(FUNC(epc_state::epc_dack_w<3>));
// TTL-level serial keyboard callback
EISPC_KB(config, m_keyboard);
m_keyboard->txd_cb().set([this](bool state)
{
LOGBITS("KBD->EPC: %d\n", state);
m_kbd8251->write_rxd(state);
});
m_keyboard->caps_cb().set( [this](bool state){ m_leds[0] = state; });
m_keyboard->num_cb().set( [this](bool state){ m_leds[1] = state; });
m_keyboard->scroll_cb().set([this](bool state){ m_leds[2] = state; });
// Keyboard USART
I8251( config, m_kbd8251, XTAL(14'318'181) / 6.0 ); // TWE crystal marked X1 verified divided through a 82874
m_kbd8251->txd_handler().set([this](bool state)
{
if (m_txd != state)
{
LOGBITS("EPC->KBD: %d\n", state);
m_txd = state;
m_keyboard->rxd_w(m_txd);
}
});
m_kbd8251->rxrdy_handler().set([this](bool state)
{
m_rxrdy = state;
LOGKBD("KBD RxRdy: %d HOLD: %d\n", m_rxrdy ? 1 : 0, m_rxrdy ? 0 : 1);
m_keyboard->hold_w(!m_rxrdy);
if (m_rxrdy)
{
LOGIRQ("RxRdy set, asserting IRQ1\n");
m_pic8259->ir1_w(ASSERT_LINE); // Cleared by setting PB7
}
});
m_kbd8251->dtr_handler().set([this](bool state) // Controls RCLK for INS8250, either 19.2KHz or INS8250 BAUDOUT
{
LOGCOM("KBD DTR: %d\n", state ? 1 : 0);
m_8251dtr_state = state;
});
// Interrupt Controller
PIC8259(config, m_pic8259);
m_pic8259->out_int_callback().set(FUNC(epc_state::int_w));
// Parallel port
I8255A(config, m_ppi8255);
m_ppi8255->out_pa_callback().set([this] (uint8_t data) { LOGPPI("PPI: write %02x to unused Port A\n", data); } ); // Port A is not used
m_ppi8255->out_pb_callback().set(FUNC(epc_state::ppi_portb_w));
m_ppi8255->in_pc_callback().set(FUNC(epc_state::ppi_portc_r));
// system board Parallel port
PC_LPT(config, m_lpt);
m_lpt->irq_handler().set([this](int state)
{ // Jumper field J10 decides what IRQ to pull
if ((m_io_j10->read() & 0x03) == 0x01) { LOGIRQ("LPT IRQ2: %d\n", state); m_pic8259->ir2_w(state); }
if ((m_io_j10->read() & 0x0c) == 0x04) { LOGIRQ("LPT IRQ3: %d\n", state); m_pic8259->ir3_w(state); }
if ((m_io_j10->read() & 0x30) == 0x10) { LOGIRQ("LPT IRQ4: %d\n", state); m_pic8259->ir4_w(state); }
if ((m_io_j10->read() & 0xc0) == 0x40) { LOGIRQ("LPT IRQ7: %d\n", state); m_pic8259->ir7_w(state); } // Factory setting
});
// Timer
PIT8253(config, m_pit8253);
m_pit8253->set_clk<0>((XTAL(14'318'181) / 3.0) / 2.0 );
m_pit8253->set_clk<1>((XTAL(14'318'181) / 3.0) / 2.0 );
m_pit8253->set_clk<2>((XTAL(14'318'181) / 3.0) / 2.0 );
m_pit8253->out_handler<0>().set(m_pic8259, FUNC(pic8259_device::ir0_w));
m_pit8253->out_handler<1>().set(FUNC(epc_state::dreq0_ck_w));
m_pit8253->out_handler<2>().set(FUNC(epc_state::speaker_ck_w));
// Speaker
SPEAKER(config, "mono").front_center();
SPEAKER_SOUND(config, m_speaker).add_route(ALL_OUTPUTS, "mono", 1.00);
// ISA bus
ISA8(config, m_isabus, XTAL(14'318'181) / 3.0); // TEW crystal marked X1 verified
m_isabus->set_memspace(m_maincpu, AS_PROGRAM);
m_isabus->set_iospace(m_maincpu, AS_IO);
m_isabus->irq2_callback().set(m_pic8259, FUNC(pic8259_device::ir2_w)); // Reserved in service manual
m_isabus->irq3_callback().set(m_pic8259, FUNC(pic8259_device::ir3_w));
m_isabus->irq4_callback().set(m_pic8259, FUNC(pic8259_device::ir4_w));
m_isabus->irq5_callback().set(m_pic8259, FUNC(pic8259_device::ir5_w));
m_isabus->irq6_callback().set(m_pic8259, FUNC(pic8259_device::ir6_w));
m_isabus->irq7_callback().set(m_pic8259, FUNC(pic8259_device::ir7_w));
m_isabus->drq1_callback().set(m_dma8237a, FUNC(am9517a_device::dreq1_w));
m_isabus->drq2_callback().set(m_dma8237a, FUNC(am9517a_device::dreq2_w));
m_isabus->drq3_callback().set(m_dma8237a, FUNC(am9517a_device::dreq3_w));
m_isabus->iochck_callback().set([this] (int state)
{
if (m_nmi_enabled && !state && 0)
{
LOGNMI("IOCHCK: NMI Requested\n");
update_nmi();
}
});
ISA8_SLOT(config, "isa1", 0, m_isabus, epc_isa8_cards, "epc_mda", false);
ISA8_SLOT(config, "isa2", 0, m_isabus, epc_isa8_cards, nullptr, false);
ISA8_SLOT(config, "isa3", 0, m_isabus, epc_isa8_cards, nullptr, false);
ISA8_SLOT(config, "isa4", 0, m_isabus, epc_isa8_cards, nullptr, false);
ISA8_SLOT(config, "isa5", 0, m_isabus, epc_isa8_cards, nullptr, false);
ISA8_SLOT(config, "isa6", 0, m_isabus, epc_isa8_cards, nullptr, false);
// System board has 128kB memory with parity, expansion can be achieved through the
// 128kB Memory Expansion Board 1090 and/or the 128kB Multifunction Board MB1080-001
// and/or the 384kB MB1080-002. The MB1080 DRAM might need to be dynamically added as
// base address and also a video memory hole is configurable.
RAM(config, m_ram).set_default_size("128K").set_extra_options("256K, 384K, 512K, 640K");
// FDC
I8272A(config, m_fdc, XTAL(16'000'000) / 2, false); // TEW crystal marked X3 verified
m_fdc->intrq_wr_callback().set([this] (int state){ m_fdc_irq = state; check_fdc_irq(); });
m_fdc->drq_wr_callback().set([this] (int state){ m_fdc_drq = state; check_fdc_drq(); });
FLOPPY_CONNECTOR(config, m_floppy_connectors[0], epc_sd_floppies, "525sd", epc_floppy_formats);
FLOPPY_CONNECTOR(config, m_floppy_connectors[1], epc_sd_floppies, "525sd", epc_floppy_formats);
//SOFTWARE_LIST(config, "epc_flop_list").set_original("epc_flop");
// system board UART
INS8250(config, m_uart, XTAL(18'432'000) / 10); // TEW crystal marked X2 verified. TODO: Let 8051 DTR control RCLK (see above)
m_uart->out_tx_callback().set("com1", FUNC(rs232_port_device::write_txd));
m_uart->out_dtr_callback().set("com1", FUNC(rs232_port_device::write_dtr));
m_uart->out_rts_callback().set("com1", FUNC(rs232_port_device::write_rts));
m_uart->out_int_callback().set([this](int state)
{ // Jumper field J10 decides what IRQ to pull
if ((m_io_j10->read() & 0x03) == 0x02) { LOGCOM("UART IRQ2: %d\n", state); m_pic8259->ir2_w(state); }
if ((m_io_j10->read() & 0x0c) == 0x08) { LOGCOM("UART IRQ3: %d\n", state); m_pic8259->ir3_w(state); }
if ((m_io_j10->read() & 0x30) == 0x20) { LOGCOM("UART IRQ4: %d\n", state); m_pic8259->ir4_w(state); } // Factory setting
if ((m_io_j10->read() & 0xc0) == 0x80) { LOGCOM("UART IRQ7: %d\n", state); m_pic8259->ir7_w(state); }
});
// m_uart->out_baudout_callback().set([this](int state){ if (m_8251dtr_state) m_uart->rclk_w(state); }); // TODO: Fix INS8250 BAUDOUT pin support
rs232_port_device &rs232(RS232_PORT(config, "com1", default_rs232_devices, nullptr));
rs232.rxd_handler().set(m_uart, FUNC(ins8250_uart_device::rx_w));
rs232.dcd_handler().set(m_uart, FUNC(ins8250_uart_device::dcd_w));
rs232.dsr_handler().set(m_uart, FUNC(ins8250_uart_device::dsr_w));
rs232.ri_handler().set(m_uart, FUNC(ins8250_uart_device::ri_w));
rs232.cts_handler().set(m_uart, FUNC(ins8250_uart_device::cts_w));
}
void epc_state::update_nmi()
{
if (m_nmi_enabled &&
((m_8087_int && (m_io_dsw->read() & 0x02)) || // FPU int only if FPU is enabled by DSW2
(m_parer_int != 0) || // Parity error is always false as it is an emulator, at least for now
(m_iochck_int != 0))) // Same goes for ISA board errors
{
LOGNMI(" NMI Asserted\n");
m_maincpu->set_input_line(INPUT_LINE_NMI, ASSERT_LINE);
}
else
{
LOGNMI(" NMI Cleared\n");
m_maincpu->set_input_line(INPUT_LINE_NMI, CLEAR_LINE);
}
}
WRITE_LINE_MEMBER( epc_state::epc_dma_hrq_changed )
{
LOGDMA("epc_dma_hrq_changed %d\n", state);
m_maincpu->set_input_line(INPUT_LINE_HALT, state ? ASSERT_LINE : CLEAR_LINE);
/* Assert HLDA */
m_dma8237a->hack_w(state);
}
READ8_MEMBER( epc_state::epc_dma_read_byte )
{
if ((m_dma_active & 0x0f) == 0)
{
LOGDMA("epc_dma_read_byte failed\n");
return 0xff;
}
const int seg = (BIT(m_dma_active, 2) ? 0 : 2) | (BIT(m_dma_active, 3) ? 0 : 1);
return m_maincpu->space(AS_PROGRAM).read_byte(offset | u32(m_dma_segment[seg]) << 16);
}
WRITE8_MEMBER( epc_state::epc_dma_write_byte )
{
if ((m_dma_active & 0x0f) == 0)
{
LOGDMA("epc_dma_write_byte failed\n");
return;
}
const int seg = (BIT(m_dma_active, 2) ? 0 : 2) | (BIT(m_dma_active, 3) ? 0 : 1);
m_maincpu->space(AS_PROGRAM).write_byte(offset | u32(m_dma_segment[seg]) << 16, data);
}
static INPUT_PORTS_START( epc_ports )
PORT_START("DSW")
PORT_DIPNAME( 0x01, 0x01, "Not used")
PORT_DIPSETTING( 0x00, "ON - Don't use")
PORT_DIPSETTING( 0x01, "OFF - Factory Setting")
PORT_DIPNAME( 0x02, 0x00, "8087 installed")
PORT_DIPSETTING( 0x00, DEF_STR(No) )
PORT_DIPSETTING( 0x02, DEF_STR(Yes) )
PORT_DIPNAME( 0x04, 0x04, "Not used")
PORT_DIPSETTING( 0x00, "ON - Don't care")
PORT_DIPSETTING( 0x04, "OFF - Factory Setting")
PORT_DIPNAME( 0x08, 0x00, "Not used")
PORT_DIPSETTING( 0x00, "ON - Factory Setting")
PORT_DIPSETTING( 0x08, "OFF - Don't use")
PORT_DIPNAME( 0x30, 0x30, "Main monitor")
PORT_DIPSETTING( 0x00, "Not used" )
PORT_DIPSETTING( 0x10, "Optional 1020 color" )
PORT_DIPSETTING( 0x20, "Not used" )
PORT_DIPSETTING( 0x30, "3111 HR Monochrome" )
PORT_DIPNAME( 0xc0, 0x40, "Number of floppy drives")
PORT_DIPSETTING( 0x00, "1" )
PORT_DIPSETTING( 0x40, "2" )
PORT_DIPSETTING( 0x80, "Not used" )
PORT_DIPSETTING( 0xc0, "Not used" )
PORT_START("J10") // Jumper area, field 0=no jumper 1=LPT 2=COM 3=n/a
PORT_DIPNAME(0x03, 0x00, "IRQ2")
PORT_DIPSETTING(0x00, "no jumper")
PORT_DIPSETTING(0x01, "LPT")
PORT_DIPSETTING(0x02, "COM")
PORT_DIPNAME(0x0c, 0x00, "IRQ3")
PORT_DIPSETTING(0x00, "no jumper")
PORT_DIPSETTING(0x04, "LPT")
PORT_DIPSETTING(0x08, "COM")
PORT_DIPNAME(0x30, 0x20, "IRQ4")
PORT_DIPSETTING(0x00, "no jumper")
PORT_DIPSETTING(0x10, "LPT")
PORT_DIPSETTING(0x20, "COM")
PORT_DIPNAME(0xc0, 0x40, "IRQ7")
PORT_DIPSETTING(0x00, "no jumper")
PORT_DIPSETTING(0x40, "LPT")
PORT_DIPSETTING(0x80, "COM")
PORT_START("S21") // Jumper 0=PB6 reset, 1=KBCLK 4.8kHz - what to send to keyboard pin 3
PORT_DIPNAME(0x01, 0x00, "Keyboard Clock/Reset pin")
PORT_DIPSETTING(0x00, "PB6")
PORT_DIPSETTING(0x01, "4.8kHz") // This setting is apparantly for another keyboard, currently unknown
INPUT_PORTS_END
ROM_START( epc )
ROM_REGION(0x10000,"bios", 0)
ROM_DEFAULT_BIOS("p860110")
ROM_SYSTEM_BIOS(0, "p840705", "P840705")
ROMX_LOAD("ericsson_8088.bin", 0xe000, 0x2000, CRC(3953c38d) SHA1(2bfc1f1d11d0da5664c3114994fc7aa3d6dd010d), ROM_BIOS(0))
ROM_SYSTEM_BIOS(1, "p860110", "P860110")
ROMX_LOAD("epcbios1.bin", 0xe000, 0x02000, CRC(79a83706) SHA1(33528c46a24d7f65ef5a860fbed05afcf797fc55), ROM_BIOS(1))
ROMX_LOAD("epcbios2.bin", 0xa000, 0x02000, CRC(3ca764ca) SHA1(02232fedef22d31a641f4b65933b9e269afce19e), ROM_BIOS(1))
ROMX_LOAD("epcbios3.bin", 0xc000, 0x02000, CRC(70483280) SHA1(b44b09da94d77b0269fc48f07d130b2d74c4bb8f), ROM_BIOS(1))
ROM_END
COMP( 1985, epc, 0, 0, epc, epc_ports, epc_state, init_epc, "Ericsson Information System", "Ericsson PC" , 0)
//COMP( 1985, eppc, ibm5150, 0, pccga, pccga, pc_state, empty_init, "Ericsson Information System", "Ericsson Portable PC", MACHINE_NOT_WORKING )