From 4ff301b1349156c570211c0ced362e890284d8dc Mon Sep 17 00:00:00 2001 From: BartmanAbyss Date: Sun, 19 Mar 2023 17:54:11 +0100 Subject: brother/lw30.cpp: Added driver for Brother LW-30 word processor. (#10996) New working systems --------------------- Brother LW-30 --- scripts/src/formats.lua | 12 + src/lib/formats/lw30_dsk.cpp | 211 ++++++++++ src/lib/formats/lw30_dsk.h | 29 ++ src/mame/brother/lw30.cpp | 935 +++++++++++++++++++++++++++++++++++++++++++ src/mame/mame.lst | 3 + 5 files changed, 1190 insertions(+) create mode 100644 src/lib/formats/lw30_dsk.cpp create mode 100644 src/lib/formats/lw30_dsk.h create mode 100644 src/mame/brother/lw30.cpp diff --git a/scripts/src/formats.lua b/scripts/src/formats.lua index 92055468830..d16b4b2b522 100644 --- a/scripts/src/formats.lua +++ b/scripts/src/formats.lua @@ -1187,6 +1187,18 @@ if opt_tool(FORMATS, "JVC_DSK") then } end +-------------------------------------------------- +-- +--@src/lib/formats/lw30_dsk.h,FORMATS["LW30_DSK"] = true +-------------------------------------------------- + +if opt_tool(FORMATS, "LW30_DSK") then + files { + MAME_DIR.. "src/lib/formats/lw30_dsk.cpp", + MAME_DIR.. "src/lib/formats/lw30_dsk.h", + } +end + -------------------------------------------------- -- --@src/lib/formats/os9_dsk.h,FORMATS["OS9_DSK"] = true diff --git a/src/lib/formats/lw30_dsk.cpp b/src/lib/formats/lw30_dsk.cpp new file mode 100644 index 00000000000..1818453ce1d --- /dev/null +++ b/src/lib/formats/lw30_dsk.cpp @@ -0,0 +1,211 @@ +// license:BSD-3-Clause +// copyright-holders:Bartman/Abyss +/*************************************************************************** + + Brother LW-30 Disk image format + see https://github.com/BartmanAbyss/brother-diskconv for disk conversion tool + +***************************************************************************/ +#include "lw30_dsk.h" + +#include "ioprocs.h" + +#include +#include +#include + + +namespace { + constexpr uint16_t sync_table[]{ + 0xdaef, 0xb7ad, 0xfbbe, 0xeadf, 0xbffa, 0xaeb6, 0xf5d7, 0xdbee, 0xbaab, 0xfdbd, + 0xebde, 0xd5f7, 0xafb5, 0xf6d6, 0xdded, 0xbbaa, 0xedbb, 0xd6dd, 0xb5f6, 0xf7af, + 0xded5, 0xbdeb, 0xabfd, 0xeeba, 0xd7db, 0xb6f5, 0xfaae, 0xdfbf, 0xbeea, 0xadfb, + 0xefb7, 0xdada, 0xb7ef, 0xfbad, 0xeabe, 0xbfdf, 0xaefa, 0xf5b6, 0xdbd7, 0xbaee, + 0xfdab, 0xebbd, 0xd5de, 0xaff7, 0xf6b5, 0xddd6, 0xbbed, 0xaadd, 0xedf6, 0xd6af, + 0xb5d5, 0xf7eb, 0xdefd, 0xbdba, 0xabdb, 0xeef5, 0xd7ae, 0xb6bf, 0xfaea, 0xdffb, + 0xbeb7, 0xadda, 0xefef, 0xdaad, 0xb7be, 0xfbdf, 0xeafa, 0xbfb6, 0xaed7, 0xf5ee, + 0xdbab, 0xbabd, 0xfdde, 0xebf7, 0xd5b5, 0xafd6, 0xf6ed, 0xddaa, 0xd6bb, 0xb5dd + }; + + constexpr uint8_t gcr_table[]{ + 0xaa, 0xab, 0xad, 0xae, 0xaf, 0xb5, 0xb6, 0xb7, + 0xba, 0xbb, 0xbd, 0xbe, 0xbf, 0xd5, 0xd6, 0xd7, + 0xda, 0xdb, 0xdd, 0xde, 0xdf, 0xea, 0xeb, 0xed, + 0xee, 0xef, 0xf5, 0xf6, 0xf7, 0xfa, 0xfb, 0xfd, + 0xfe, 0xff // FE, FF are reserved + }; + + // format + constexpr uint8_t sector_prefix[]{ + 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xab + }; + + // write + constexpr uint8_t sector_header[]{ + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xed + }; + + // write + constexpr uint8_t sector_footer[]{ + 0xf5, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd + }; + + constexpr uint8_t sector_interleave1[]{ // 1-based + 1, 6, 11, 4, 9, 2, 7, 12, 5, 10, 3, 8 + }; + + constexpr uint8_t sector_interleave2[]{ // 1-based + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 + }; + + constexpr uint8_t sector_interleave3[]{ // 1-based + 1, 4, 7, 10, 6, 9, 12, 3, 11, 2, 5, 8 + }; + + void gcr_encode_5_to_8(const uint8_t *input, uint8_t *output) + { + // input: + // 76543210 + // -------- + // 00000111 0 + // 11222223 1 + // 33334444 2 + // 45555566 3 + // 66677777 4 + + output[0] = gcr_table[(input[0] >> 3) & 0x1f]; + output[1] = gcr_table[((input[0] << 2) & 0x1f) | ((input[1] >> 6) & 0b00000011)]; + output[2] = gcr_table[(input[1] >> 1) & 0x1f]; + output[3] = gcr_table[((input[1] << 4) & 0x1f) | ((input[2] >> 4) & 0b00001111)]; + output[4] = gcr_table[((input[2] << 1) & 0x1f) | ((input[3] >> 7) & 0b00000001)]; + output[5] = gcr_table[(input[3] >> 2) & 0x1f]; + output[6] = gcr_table[((input[3] << 3) & 0x1f) | ((input[4] >> 5) & 0b00000111)]; + output[7] = gcr_table[input[4] & 0x1f]; + } + + std::array checksum_256_bytes(const uint8_t *input) + { + size_t i = 0; + uint8_t a = 0; + uint8_t c = input[i++]; + uint8_t d = input[i++]; + uint8_t e = input[i++]; + for(size_t b = 0; b < 253; b++) { + a = d; + if(c & 0b10000000) + a ^= 1; + d = c; + c = a; + a = (d << 1) ^ e; + e = d; + d = a; + e ^= input[i++]; + } + + return { c, d, e }; + } + + std::array gcr_encode_and_checksum(const uint8_t *input /* 256 bytes */) { + std::array output; + for(int i = 0; i < 51; i++) + gcr_encode_5_to_8(&input[i * 5], &output[i * 8]); + + auto checksum = checksum_256_bytes(input); + std::array end_and_checksum{ input[255], checksum[0], checksum[1], checksum[2], 0x58 }; + gcr_encode_5_to_8(&end_and_checksum[0], &output[408]); + + return output; + } +} + +static constexpr int TRACKS_PER_DISK = 78; +static constexpr int SECTORS_PER_TRACK = 12; +static constexpr int SECTOR_SIZE = 256; + +static constexpr int RPM = 300; +static constexpr int CELLS_PER_REV = 250'000 / (RPM / 60); + +// format track: 0xaa (2), 0xaa (48), 12*sector +// format sector: sector_prefix (8), track_sync (2), sector_sync (2), predata (19), payload=0xaa (414), postdata (13), 0xaa (42), should come out to ~4,000 bits +// write sector: (after sector_sync, 0xdd) sector_header (2+14), payload (416), sector_footer (11) + +// from write_format, write_sector_data_header_data_footer +static constexpr int raw_sector_size = 8/*sector_prefix*/ + 2/*track_sync*/ + 2/*sector_sync*/ + 1/*0xdd*/ + 16/*sector_header*/ + 416/*payload*/ + 11/*sector_footer*/ + 42/*0xaa*/; +static constexpr int raw_track_size = 2/*0xaa*/ + 48/*0xaa*/ + SECTORS_PER_TRACK * raw_sector_size; + +int lw30_format::identify(util::random_read &io, uint32_t form_factor, const std::vector &variants) const +{ + uint64_t size = 0; + io.length(size); + if(size == TRACKS_PER_DISK * SECTORS_PER_TRACK * SECTOR_SIZE) + return 50; // identified by size + + return 0; +} + +bool lw30_format::load(util::random_read &io, uint32_t form_factor, const std::vector &variants, floppy_image *image) const +{ + uint8_t trackdata[SECTORS_PER_TRACK * SECTOR_SIZE], rawdata[CELLS_PER_REV / 8]; + memset(rawdata, 0xaa, sizeof(rawdata)); + for(int track = 0; track < TRACKS_PER_DISK; track++) { + size_t actual{}; + io.read_at(track * SECTORS_PER_TRACK * SECTOR_SIZE, trackdata, SECTORS_PER_TRACK * SECTOR_SIZE, actual); + if(actual != SECTORS_PER_TRACK * SECTOR_SIZE) + return false; + size_t i = 0; + for(int x = 0; x < 2 + 48; x++) + rawdata[i++] = 0xaa; + auto interleave_offset = (track % 4) * 4; + for(size_t s = interleave_offset; s < interleave_offset + SECTORS_PER_TRACK; s++) { + auto sector = sector_interleave1[s % SECTORS_PER_TRACK] - 1; + // according to check_track_and_sector + for(const auto& d : sector_prefix) // 8 bytes + rawdata[i++] = d; + rawdata[i++] = sync_table[track] & 0xff; + rawdata[i++] = sync_table[track] >> 8; + rawdata[i++] = sync_table[sector] & 0xff; + rawdata[i++] = sync_table[sector] >> 8; + rawdata[i++] = 0xdd; + for(const auto& d : sector_header) // 16 bytes + rawdata[i++] = d; + auto payload = gcr_encode_and_checksum(trackdata + sector * SECTOR_SIZE); // 256 -> 416 bytes + for(const auto &d : payload) + rawdata[i++] = d; + for(const auto &d : sector_footer) // 11 bytes + rawdata[i++] = d; + for(int x = 0; x < 42; x++) + rawdata[i++] = 0xaa; + } + assert(i == raw_track_size); + assert(i <= CELLS_PER_REV / 8); + generate_track_from_bitstream(track, 0, rawdata, CELLS_PER_REV, image); + } + + image->set_variant(floppy_image::SSDD); + + return true; +} + +const char *lw30_format::name() const +{ + return "lw30"; +} + +const char *lw30_format::description() const +{ + return "Brother LW-30 floppy disk image"; +} + +const char *lw30_format::extensions() const +{ + return "img"; +} + +bool lw30_format::supports_save() const +{ + // TODO + return false; +} + +const lw30_format FLOPPY_LW30_FORMAT; diff --git a/src/lib/formats/lw30_dsk.h b/src/lib/formats/lw30_dsk.h new file mode 100644 index 00000000000..ec5dff380b1 --- /dev/null +++ b/src/lib/formats/lw30_dsk.h @@ -0,0 +1,29 @@ +// license:BSD-3-Clause +// copyright-holders:Bartman/Abyss +/*************************************************************************** + + Brother LW-30 Disk image format + see https://github.com/BartmanAbyss/brother-diskconv for disk conversion tool + +***************************************************************************/ +#ifndef MAME_FORMATS_LW30_DSK_H +#define MAME_FORMATS_LW30_DSK_H + +#pragma once + +#include "flopimg.h" + +class lw30_format : public floppy_image_format_t +{ +public: + virtual int identify(util::random_read& io, uint32_t form_factor, const std::vector &variants) const override; + virtual bool load(util::random_read &io, uint32_t form_factor, const std::vector& variants, floppy_image *image) const override; + virtual const char *name() const override; + virtual const char *description() const override; + virtual const char *extensions() const override; + virtual bool supports_save() const override; +}; + +extern const lw30_format FLOPPY_LW30_FORMAT; + +#endif // MAME_FORMATS_LW30_DSK_H diff --git a/src/mame/brother/lw30.cpp b/src/mame/brother/lw30.cpp new file mode 100644 index 00000000000..fad866b5b63 --- /dev/null +++ b/src/mame/brother/lw30.cpp @@ -0,0 +1,935 @@ +// license:BSD-3-Clause +// copyright-holders:Bartman/Abyss + +#include "emu.h" + +#include "cpu/z180/z180.h" +#include "imagedev/floppy.h" +#include "machine/timer.h" +#include "sound/beep.h" +#include "video/mc6845.h" + +#include "emupal.h" +#include "screen.h" +#include "speaker.h" + +#include "formats/lw30_dsk.h" + +#include "util/utf8.h" + + +#define LOG_FLOPPY (1U << 1) + +//#define VERBOSE (LOG_GENERAL | LOG_FLOPPY) +//#define VERBOSE (LOG_GENERAL) +#include "logmacro.h" + +#define LOGFLOPPY(...) LOGMASKED(LOG_FLOPPY, __VA_ARGS__) + + +// command line parameters: +// -log -debug -window -intscalex 2 -intscaley 2 lw30 -resolution 960x256 -flop roms\lw30\tetris.img + +// floppy: see src\devices\bus\vtech\memexp\floppy.cpp + +////////////////////////////////////////////////////////////////////////// +// LW-30 +////////////////////////////////////////////////////////////////////////// + +// *** Hit Ctrl+Q during bootup to be able to start programs (like Tetris) from disk + +/*************************************************************************** + +Brother LW-30 +1991 + +Hardware: + +#7 +Hitachi +HD64180RP6 +8-bit CMOS Micro Processing Unit +fully compatible with Zilog Z80180 (Z180) +6 MHz, DP-64S, Address Space 512 K Byte +MuRata CST12MTW 12.00 MHz Ceramic Resonator + +#8 +Mitsubishi +M65122ASP +UA5445-B LC-1 + +#6 +NEC +D23C4001EC-172 +UA2849-A +4MBit Mask ROM for Dictionary + +#5 +LH532H07 +UA5362-A +2MBit Mask ROM + +#11 +Hitachi +HM6264ALP-15 +High Speed CMOS Static RAM (8kbit x 8) 150ns + +#10 +Mitsubishi +M65017FP +UA5498-A +Murata CST4MGW 4.00 MHz Ceramic Resonator + +#3, #4 +Mitsubishi +HD74LS157P + +#1, #2 +NEC +D41464C-10 +Dynamic NMOS RAM (64kbit x 4) 100ns + +QA1, QA2 +Mitsubishi +M54587P + +#12 +Texas Instruments +SN74HC04N + +Floppy - custom single sided 3.5" DD +240kb capacity +256 bytes/sector +12 sectors/track +78 tracks +custom 5-to-8 GCR encoding (very similar to Apple II's 5-and-3 encoding) +300 rpm +FF FF FF used as sync-start, AB sync-mark for sector header, DE sync-mark for sector data +FAT12 File System + +ROHM +BA6580DK +Read/Write Amplifier for FDD + +see https://github.com/BartmanAbyss/brother-hardware/tree/master/1G%20-%20Brother%20LW-30 for datasheets, photos + +Emulation Status: +Printer not working +Floppy Disk writing not working +Floppy Disk Sync not implemented (reading works) +Dictionary ROM not working +Cursor shapes not implemented except block cursor +Keyboard not 100% (mostly copied from LW-350) + +TODO: find self-test; verify RAM address map +// 8kb SRAM, 64kb DRAM <- where? + +***************************************************************************/ + +class lw30_beep_device : public device_t, public device_sound_interface +{ +public: + lw30_beep_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + +protected: + // device-level overrides + void device_start() override ATTR_COLD; + + // sound stream update overrides + void sound_stream_update(sound_stream &stream, std::vector const &inputs, std::vector &outputs) override; + +public: + DECLARE_WRITE_LINE_MEMBER(set_state); // enable/disable sound output + void set_clock(uint32_t frequency); // output frequency + +private: + sound_stream *m_stream; /* stream number */ + uint8_t m_state; /* enable beep */ + int m_frequency; /* set frequency - this can be changed using the appropriate function */ + int m_incr; /* initial wave state */ + int8_t m_signal; /* current signal */ +}; + +DEFINE_DEVICE_TYPE(BROTHER_BEEP, lw30_beep_device, "lw30_beep", "Brother LW-30 Beeper") + +static constexpr auto BROTHER_BEEP_RATE = 48000; + +lw30_beep_device::lw30_beep_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : device_t(mconfig, BROTHER_BEEP, tag, owner, clock) + , device_sound_interface(mconfig, *this) + , m_stream(nullptr) + , m_state(0xff) + , m_frequency(clock) +{ +} + +void lw30_beep_device::device_start() +{ + m_stream = stream_alloc(0, 1, BROTHER_BEEP_RATE); + m_state = 0xff; + m_signal = 0x01; + + // register for savestates + save_item(NAME(m_state)); + save_item(NAME(m_frequency)); + save_item(NAME(m_incr)); + save_item(NAME(m_signal)); +} + +void lw30_beep_device::sound_stream_update(sound_stream &stream, std::vector const &inputs, std::vector &outputs) +{ + auto &buffer = outputs[0]; + auto signal = m_signal; + int clock = 0, rate = BROTHER_BEEP_RATE / 2; + + /* get progress through wave */ + int incr = m_incr; + + if(m_frequency > 0) + clock = m_frequency; + + /* if we're not enabled, just fill with 0 */ + if(m_state == 0xff || clock == 0) + { + buffer.fill(0); + return; + } + + /* fill in the sample */ + for(int sampindex = 0; sampindex < buffer.samples(); sampindex++) + { + if(BIT(m_state, 7) != 0) + buffer.put(sampindex, signal > 0 ? 1.f : -1.f); + else + buffer.put(sampindex, 0.f); + + incr -= clock; + while(incr < 0) + { + incr += rate; + signal = -signal; + } + } + + /* store progress through wave */ + m_incr = incr; + m_signal = signal; +} + +WRITE_LINE_MEMBER(lw30_beep_device::set_state) +{ + // only update if new state is not the same as old state + if(m_state == state) + return; + + m_stream->update(); + + if(m_state == 0) { + // restart wave from beginning + m_incr = 0; + m_signal = 0x01; + } + m_state = state; +} + +void lw30_beep_device::set_clock(uint32_t frequency) +{ + if(m_frequency == frequency) + return; + + m_stream->update(); + m_frequency = frequency; + m_signal = 0x01; + m_incr = 0; +} + + +namespace { + +class lw30_state : public driver_device +{ +public: + lw30_state(const machine_config &mconfig, device_type type, const char *tag) : + driver_device(mconfig, type, tag), + maincpu(*this, "maincpu"), + screen(*this, "screen"), + floppy(*this, "floppy"), + beeper(*this, "beeper"), + m_io_kbrow(*this, "kbrow.%u", 0), + rom(*this, "maincpu"), + font_normal(*this, "font_normal"), + font_bold(*this, "font_bold") + { + //video_control = 0b00000010; // TEST LW-10 screen height + } + + void lw30(machine_config& config); + +protected: + // driver_device overrides + virtual void machine_start() override ATTR_COLD; + virtual void machine_reset() override ATTR_COLD; + +private: + // devices + required_device maincpu; + required_device screen; + + required_device floppy; + required_device beeper; + required_ioport_array<9> m_io_kbrow; + required_region_ptr rom, font_normal, font_bold; + + // floppy + uint8_t floppy_data = 0; + uint8_t io_88 = 0; + uint8_t io_98 = 0; + uint8_t floppy_control = 0; // stepper motor control + uint8_t floppy_steps = 0; // quarter track + uint8_t floppy_shifter = 0, floppy_latch = 0; + bool floppy_read_until_zerobit = false; + + // video + uint8_t videoram[0x2000]; // 80 chars * 14 lines; 2 bytes per char (attribute, char) + uint8_t video_cursor_x, video_cursor_y, video_pos_x, video_pos_y, video_control, io_b8; + uint8_t cursor_state; + + // screen updates + uint32_t screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect); + + uint8_t illegal_r(offs_t offset) + { + if(!machine().side_effects_disabled()) + LOG("%s: unmapped memory read from %0*X\n", machine().describe_context(), 6, offset); + return 0; + } + void illegal_w(offs_t offset, uint8_t data) + { + LOG("%s: unmapped memory write to %0*X = %0*X\n", machine().describe_context(), 6, offset, 2, data); + } + + // IO + void video_cursor_x_w(uint8_t data) // 70 + { + video_cursor_x = data; + } + void video_cursor_y_w(uint8_t data) // 71 + { + video_cursor_y = data; + } + void video_pos_x_w(uint8_t data) // 72 + { + video_pos_x = data; + } + void video_pos_y_w(uint8_t data) // 73 + { + video_pos_y = data; + } + uint8_t video_data_r() // 74 + { + uint8_t data = 0x00; + if(video_pos_y < 0x20) + data = videoram[video_pos_y * 256 + video_pos_x]; + else { + if(!machine().side_effects_disabled()) + LOG("%s: video_data_r out of range: x=%u, y=%u\n", machine().describe_context(), video_pos_x, video_pos_y); + } + + return data; + } + + void video_data_w(uint8_t data) // 74 + { + if(video_pos_y < 0x20) + videoram[video_pos_y * 256 + video_pos_x] = data; + else + LOG("%s: video_data_w out of range: x=%u, y=%u\n", machine().describe_context(), video_pos_x, video_pos_y); + + video_pos_x++; + if(video_pos_x == 0) + video_pos_y++; + } + + uint8_t video_control_r() // 75 + { + return video_control; + } + void video_control_w(uint8_t data) + { + video_control = data; // | 0b00000010; // TEST LW-10 screen height + } + // 76 + uint8_t io_77_r() // config + { + // TODO: use PORT_CONFNAME, etc + uint8_t out = 0x20; // 14 lines + out |= 0x00; // german + //out |= 0x01; // french + //out |= 0x02; // german + return ~out; + } + + // Floppy + TIMER_DEVICE_CALLBACK_MEMBER(floppy_timer_callback) + { + auto floppy_device = floppy->get_device(); + if(floppy_device->ready_r() != false) + return; + + floppy_latch <<= 1; + + attotime now = machine().time(); + attotime when = now - attotime::from_usec(4); + attotime reversal = floppy_device->get_next_transition(when); + if(reversal > when && reversal <= now) + floppy_latch |= 1; + + floppy_shifter++; + if((floppy_read_until_zerobit && (floppy_latch & 1) == 0) || (!floppy_read_until_zerobit && floppy_shifter == 8)) { + //LOGFLOPPY("%s: floppy_timer_callback: floppy_read_until_zerobit=%d latch=%02X\n", machine().describe_context(), floppy_read_until_zerobit, floppy_latch); + floppy_control |= 0x80; // floppy_data_available = true; + floppy_data = floppy_latch; + floppy_latch = floppy_shifter = 0; + floppy_read_until_zerobit = false; + } + //LOGFLOPPY("%s: floppy_timer_callback: IO_80=%02X, shifter=%u offset=%u\n", machine().describe_context(), io_80, floppy_shifter, floppy_track_offset % cache.size()); + //LOGFLOPPY("%s: read_io_80 track=%d,offset=%4x => %02x\n", callstack(), floppy_steps / 4, floppy_track_offset, ret); + } + + uint8_t floppy_data_r() // 80 + { + if(!machine().side_effects_disabled()) { + floppy_control &= ~0x80; // floppy_data_available = false; + LOGFLOPPY("%s: read %02X from IO 80\n", machine().describe_context(), floppy_data); + } + return floppy_data; + } + void floppy_data_w(uint8_t data) + { + LOGFLOPPY("%s: write %02X to IO 80\n", machine().describe_context(), data); + floppy_data = data; + } + + uint8_t io_88_r() + { + // bit 0: set in start_write; cleared in end_write + // bit 1: pulsed after 3*0xFF sync (read next floppydata until zero-bit) + // bit 2: cleared in stepper routines, rst28_06 + // bit 3: set in start_write; cleared in end_write + // bit 5: cleared in rst28_06; motor-on? + if(!machine().side_effects_disabled()) + LOGFLOPPY("%s: read %02X from IO 88\n", machine().describe_context(), io_88); + return io_88; + } + void io_88_w(uint8_t data) + { + LOGFLOPPY("%s: write %02X to IO 88\n", machine().describe_context(), data); + io_88 = data; + floppy->get_device()->mon_w((io_88 & (1 << 5)) == 0); + } + + uint8_t floppy_status_r() // 90 + { + // bit 7 set; data ready from floppy + // bit 6 clear; unknown meaning + // bit 5 clear; unknown meaning + // bit 4 clear; unknown meaning + // bit 3-0: stepper motor + if(!machine().side_effects_disabled()) + LOGFLOPPY("%s: read %02X from IO 90\n", machine().describe_context(), floppy_control); + return floppy_control; + } + void floppy_stepper_w(uint8_t data) + { + LOGFLOPPY("%s: write %02X to IO 90\n", machine().describe_context(), data); + // write directly to 4-wire bipolar stepper motor (see stepper_table) + // a rotation to the left means decrease quarter-track + // a rotation to the right means increase quarter-track + const auto rol4 = [](uint8_t d) { return ((d << 1) & 0b1111) | ((d >> 3) & 0b0001); }; + const auto ror4 = [](uint8_t d) { return ((d >> 1) & 0b0111) | ((d << 3) & 0b1000); }; + const auto old_track = floppy_steps / 4; + switch(data & 0xf) { + case 0b0011: + case 0b0110: + case 0b1100: + case 0b1001: + if((data & 0x0f) == rol4(floppy_control)) + floppy_steps--; + else if((data & 0x0f) == ror4(floppy_control)) + floppy_steps++; + else + LOGFLOPPY("%s: illegal step %02x=>%02x\n", machine().describe_context(), floppy_control, data); + break; + default: + LOGFLOPPY("%s: initial step %02x=>%02x\n", machine().describe_context(), floppy_control, data); + break; + } + const auto new_track = floppy_steps / 4; + auto floppy_device = floppy->get_device(); + if(new_track != old_track) { + floppy_device->dir_w(new_track < old_track); + floppy_device->stp_w(true); + floppy_device->stp_w(false); + } + LOGFLOPPY("%s: floppy_steps=%3d => old_track=%2d new_track=%2d cyl=%2d\n", machine().describe_context(), floppy_steps, old_track, new_track, floppy_device->get_cyl()); + assert(floppy_device->get_cyl() == new_track); + floppy_control = (floppy_control & 0xf0) | (data & 0x0f); + } + + uint8_t io_98_r() + { + // mirrored in RAM + // bit 0: cleared in rst28_06 in mirror + // bit 2: cleared before formatting in mirror; set after formatting + // bit 3: cleared before formatting in mirror + // bit 4: cleared before writing in mirror; set after writing + if(!machine().side_effects_disabled()) { + if(io_88 & 0b10) + floppy_read_until_zerobit = true; + else + floppy_read_until_zerobit = false; + + LOGFLOPPY("%s: read %02X from IO 98\n", machine().describe_context(), io_98); + } + return io_98; + } + void io_98_w(uint8_t data) + { + LOGFLOPPY("%s: write %02X to IO 98\n", machine().describe_context(), data); + io_98 = data; + } + + uint8_t illegal_io_r(offs_t offset, uint8_t mem_mask = ~0) + { + if(!machine().side_effects_disabled()) + LOGFLOPPY("%s: unmapped IO read from %0*X & %0*X\n", machine().describe_context(), 4, offset + 0x40, 2, mem_mask); + return 0; + } + void illegal_io_w(offs_t offset, uint8_t data, uint8_t mem_mask = ~0) + { + LOGFLOPPY("%s: unmapped IO write to %0*X = %0*X & %0*X\n", machine().describe_context(), 4, offset + 0x40, 2, data, 2, mem_mask); + } + + uint8_t io_b0_r() + { + // Tetris reads bit 3, needed for correct keyboard layout + return 0b1000; + } + uint8_t io_b8_r() // B8 (keyboard) + { + // keyboard matrix + if(io_b8 <= 8) + return m_io_kbrow[io_b8].read(); + return 0x00; + } + void io_b8_w(uint8_t data) + { + io_b8 = data; + } + + void beeper_w(uint8_t data) // F0 + { + beeper->set_state(data); + } + + void irqack_w(uint8_t) // F8 + { + maincpu->set_input_line(INPUT_LINE_IRQ1, CLEAR_LINE); + } + + TIMER_DEVICE_CALLBACK_MEMBER(int1_timer_callback) + { + maincpu->set_input_line(INPUT_LINE_IRQ1, ASSERT_LINE); + } + + TIMER_DEVICE_CALLBACK_MEMBER(cursor_timer_callback) + { + cursor_state = !cursor_state; + } + + static void floppy_formats(format_registration &fr) + { + fr.add(FLOPPY_LW30_FORMAT); + } + + static void lw30_floppies(device_slot_interface &device) ATTR_COLD + { + device.option_add("35ssdd", FLOPPY_35_SSDD); + } + + void map_program(address_map &map) ATTR_COLD + { + map(0x00000, 0x01fff).rom(); + map(0x02000, 0x05fff).ram(); + map(0x06000, 0x3ffff).rom(); + map(0x50000, 0x51fff).ram(); // ??? + map(0x61000, 0x61fff).ram(); + map(0x42000, 0x45fff).rom().region("maincpu", 0x02000).w(FUNC(lw30_state::illegal_w)); // => ROM 0x02000-0x05fff + map(0x65000, 0x70fff).ram(); + } + + void map_io(address_map &map) ATTR_COLD + { + map.global_mask(0xff); + map(0x00, 0x3f).noprw(); // Z180 internal registers + + // video + map(0x70, 0x70).w(FUNC(lw30_state::video_cursor_x_w)); + map(0x71, 0x71).w(FUNC(lw30_state::video_cursor_y_w)); + map(0x72, 0x72).w(FUNC(lw30_state::video_pos_x_w)); + map(0x73, 0x73).w(FUNC(lw30_state::video_pos_y_w)); + map(0x74, 0x74).rw(FUNC(lw30_state::video_data_r), FUNC(lw30_state::video_data_w)); + map(0x75, 0x75).rw(FUNC(lw30_state::video_control_r), FUNC(lw30_state::video_control_w)); + map(0x76, 0x76).noprw(); // NOP just to shut up the log + map(0x77, 0x77).r(FUNC(lw30_state::io_77_r)).nopw(); // NOP just to shut up the log + + // floppy + map(0x80, 0x80).rw(FUNC(lw30_state::floppy_data_r), FUNC(lw30_state::floppy_data_w)); + map(0x88, 0x88).rw(FUNC(lw30_state::io_88_r), FUNC(lw30_state::io_88_w)); + map(0x90, 0x90).rw(FUNC(lw30_state::floppy_status_r), FUNC(lw30_state::floppy_stepper_w)); + map(0x98, 0x98).rw(FUNC(lw30_state::io_98_r), FUNC(lw30_state::io_98_w)); + + map(0xa8, 0xa8).noprw(); // NOP just to shut up the log + map(0xb0, 0xb0).r(FUNC(lw30_state::io_b0_r)); + map(0xb8, 0xb8).rw(FUNC(lw30_state::io_b8_r), FUNC(lw30_state::io_b8_w)); + map(0xd8, 0xd8).noprw(); // NOP just to shut up the log + map(0xf0, 0xf0).w(FUNC(lw30_state::beeper_w)); + map(0xf8, 0xf8).w(FUNC(lw30_state::irqack_w)); + + //map(0x40, 0xff).rw(FUNC(lw30_state::illegal_io_r), FUNC(lw30_state::illegal_io_w)); + } +}; + +uint32_t lw30_state::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect) +{ + // based on LW-350 ROM draw_char routine @ 6B14 + enum attrs : uint8_t { + UNDERLINE = 0b00000001, + OVERLINE = 0b00000010, + BOLD = 0b00000100, + VERTICAL_LINE = 0b00001000, + INVERT_FULL = 0b00010000, + INVERT_UPPER_HALF = 0b00100000, + INVERT_LOWER_HALF = 0b01000000 + }; + + const rgb_t palette[]{ + 0xffffffff, + 0xff000000, + }; + + enum control : uint8_t { + DISPLAY_ON = 0b00000001, + HALF_HEIGHT = 0b00000010, // 64px height (LW-10/20) instead of 128px height (LW-30) + BITMAP_MODE = 0b00001000, + TILE_MODE = 0b00100000, // 8x8 tiles at videoram[0x1000] + }; + + if(video_control & DISPLAY_ON) { + if(video_control & TILE_MODE) { + uint8_t pixmap[60 * 128]; // pixel data + for(auto y = 0; y < 16; y++) { + for(auto x = 0; x < 60; x++) { + const auto atr = videoram[y * 256 + x * 2 + 0]; + const auto chr = videoram[y * 256 + x * 2 + 1]; + const auto fnt = &videoram[0x1000 + chr * 8 + ((atr & BOLD) ? 0x800 : 0)]; + uint8_t charbuf[8]; + for(int i = 0; i < 8; i++) { + charbuf[i] = fnt[i]; + } + if(atr & UNDERLINE) { + charbuf[7] = 0xff; + } + if(atr & VERTICAL_LINE) { + for(int i = 0; i < 8; i++) { + charbuf[i] |= 0b1; + } + } + + for(int i = 0; i < 8; i++) { + pixmap[(y * 8 + i) * 60 + x] = charbuf[i]; + } + } + } + for(auto y = 0; y < 128; y++) { + uint32_t *p = &bitmap.pix(y); + for(auto x = 0; x < 480; x += 8) { + const auto gfx = pixmap[y * 60 + x / 8]; + *p++ = palette[BIT(gfx, 7)]; + *p++ = palette[BIT(gfx, 6)]; + *p++ = palette[BIT(gfx, 5)]; + *p++ = palette[BIT(gfx, 4)]; + *p++ = palette[BIT(gfx, 3)]; + *p++ = palette[BIT(gfx, 2)]; + *p++ = palette[BIT(gfx, 1)]; + *p++ = palette[BIT(gfx, 0)]; + } + } + } else if(video_control & BITMAP_MODE) { + for(auto y = 0; y < 128; y++) { + uint32_t *p = &bitmap.pix(y); + for(auto x = 0; x < 480; x += 8) { + const auto gfx = videoram[y * 64 + x / 8]; + *p++ = palette[BIT(gfx, 7)]; + *p++ = palette[BIT(gfx, 6)]; + *p++ = palette[BIT(gfx, 5)]; + *p++ = palette[BIT(gfx, 4)]; + *p++ = palette[BIT(gfx, 3)]; + *p++ = palette[BIT(gfx, 2)]; + *p++ = palette[BIT(gfx, 1)]; + *p++ = palette[BIT(gfx, 0)]; + } + } + } else { + // default font + uint8_t pixmap[80 * 128]{}; // pixel data + for(auto y = 0; y < 14; y++) { + for(auto x = 0; x < 80; x++) { + const auto atr = videoram[y * 256 + x * 2 + 0]; + const auto chr = videoram[y * 256 + x * 2 + 1]; + const auto fnt = (atr & BOLD) ? &font_bold[chr * 8] : &font_normal[chr * 8]; + uint8_t charbuf[9]; + charbuf[0] = 0x00; + for(int i = 0; i < 8; i++) { + charbuf[i + 1] = fnt[i]; + } + + if(atr & UNDERLINE) { + charbuf[8] = 0xff; + } + if(atr & OVERLINE) { + charbuf[0] = 0xff; + } + if(atr & VERTICAL_LINE) { + for(int i = 0; i < 9; i++) { + charbuf[i] |= 0b1; + } + } + if(atr & INVERT_FULL) { + for(int i = 0; i < 9; i++) { + charbuf[i] ^= 0xff; + } + } + if(atr & INVERT_LOWER_HALF) { + for(int i = 4; i < 9; i++) { + charbuf[i] ^= 0xff; + } + } + if(atr & INVERT_UPPER_HALF) { + for(int i = 0; i < 5; i++) { + charbuf[i] ^= 0xff; + } + } + + for(int i = 0; i < 9; i++) { + pixmap[(y * 9 + i) * 80 + x] = charbuf[i]; + } + } + } + + // draw cursor; TODO: shape + if(cursor_state) { + const auto cursor_x = video_cursor_x & 0x7f; + const auto cursor_y = (video_cursor_x >> 7) | ((video_cursor_y & 7) << 1); + if(cursor_x < 80 && cursor_y < 14) { + for(int i = 0; i < 9; i++) { + pixmap[(cursor_y * 9 + i) * 80 + cursor_x] ^= 0xff; + } + } + } + for(auto y = 0; y < 128; y++) { + uint32_t *p = &bitmap.pix(y); + for(auto x = 0; x < 640; x += 8) { + const auto gfx = pixmap[y * 80 + x / 8]; + *p++ = palette[BIT(gfx, 5)]; + *p++ = palette[BIT(gfx, 4)]; + *p++ = palette[BIT(gfx, 3)]; + *p++ = palette[BIT(gfx, 2)]; + *p++ = palette[BIT(gfx, 1)]; + *p++ = palette[BIT(gfx, 0)]; + } + } + } + } else { + // display off + for(auto y = 0; y < 128; y++) { + uint32_t *p = &bitmap.pix(y); + for(auto x = 0; x < 480; x++) { + *p++ = palette[0]; + } + } + } + + return 0; +} + +void lw30_state::machine_start() +{ + screen->set_visible_area(0, 480 - 1, 0, 128 - 1); + + // patch out printer init + rom[0x280f4] = 0x00; + + // patch out autosave load + rom[0x28c3a] = rom[0x28c3a + 1] = rom[0x28c3a + 2] = 0x00; + + // always jump to "zusatzprogramme" (otherwise hit Ctrl+Q during bootup) + //rom[0x28103] = 0xc3; + + // floppy debugging + //if(machine().debug_enabled()) { + // machine().debugger().console().execute_command(R"(bp 6a2c,1,{logerror "expect AB; A=%02X\n",a; g})", false); + // machine().debugger().console().execute_command(R"(bp 6617,1,{logerror "expect DE; A=%02X\n",a; g})", false); + //} +} + +void lw30_state::machine_reset() +{ + cursor_state = 0; + video_cursor_x = video_cursor_y = 0; + video_pos_x = video_pos_y = 0; + video_control = 0; + // TODO more reset variables + + memcpy(&videoram[0x1000], font_normal, 0x800); +} + +static INPUT_PORTS_START(lw30) + PORT_START("kbrow.0") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_4) PORT_CHAR('4') PORT_CHAR('$') + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_3) PORT_CHAR('3') PORT_CHAR(U'§') + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_W) PORT_CHAR('w') PORT_CHAR('W') + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_E) PORT_CHAR('e') PORT_CHAR('E') + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_D) PORT_CHAR('d') PORT_CHAR('D') + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_X) PORT_CHAR('x') PORT_CHAR('X') + PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_UNUSED) + PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_TAB) PORT_CHAR(UCHAR_MAMEKEY(TAB)) + + PORT_START("kbrow.1") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_5) PORT_CHAR('5') PORT_CHAR('%') + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_6) PORT_CHAR('6') PORT_CHAR('&') + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_R) PORT_CHAR('r') PORT_CHAR('R') + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_T) PORT_CHAR('t') PORT_CHAR('T') + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_C) PORT_CHAR('c') PORT_CHAR('C') + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_F) PORT_CHAR('f') PORT_CHAR('F') + PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_UNUSED) + PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME(UTF8_UP) PORT_CODE(KEYCODE_UP) PORT_CHAR(UCHAR_MAMEKEY(UP)) + + PORT_START("kbrow.2") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_8) PORT_CHAR('8') PORT_CHAR('(') + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_7) PORT_CHAR('7') PORT_CHAR('/') + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_Z) PORT_CHAR('z') PORT_CHAR('Z') + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_H) PORT_CHAR('h') PORT_CHAR('H') + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_G) PORT_CHAR('g') PORT_CHAR('G') + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_V) PORT_CHAR('v') PORT_CHAR('V') + PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_UNUSED) + PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_END) PORT_CHAR(UCHAR_MAMEKEY(END)) + + PORT_START("kbrow.3") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_1) PORT_CHAR('1') PORT_CHAR('!') + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_2) PORT_CHAR('2') PORT_CHAR('"') + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_Q) PORT_CHAR('q') PORT_CHAR('Q') + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_Y) PORT_CHAR('y') PORT_CHAR('Y') + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_A) PORT_CHAR('a') PORT_CHAR('A') + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_S) PORT_CHAR('s') PORT_CHAR('S') + PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_UNUSED) + PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_CAPSLOCK) PORT_CHAR(UCHAR_MAMEKEY(CAPSLOCK)) + + PORT_START("kbrow.4") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_9) PORT_CHAR('9') PORT_CHAR(')') + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_J) PORT_CHAR('j') PORT_CHAR('J') + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_I) PORT_CHAR('i') PORT_CHAR('I') + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_U) PORT_CHAR('u') PORT_CHAR('U') + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_B) PORT_CHAR('b') PORT_CHAR('B') + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_N) PORT_CHAR('n') PORT_CHAR('N') + PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_UNUSED) + PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_RIGHT) PORT_CHAR(UCHAR_MAMEKEY(RIGHT)) + + PORT_START("kbrow.5") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_MINUS) PORT_CHAR(U'ß') PORT_CHAR('?') + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_0) PORT_CHAR('0') PORT_CHAR('=') + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_P) PORT_CHAR('p') PORT_CHAR('P') + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_O) PORT_CHAR('o') PORT_CHAR('O') + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_M) PORT_CHAR('m') PORT_CHAR('M') + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_COMMA) PORT_CHAR(',') PORT_CHAR(';') + PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_UNUSED) + PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_MENU) PORT_CHAR(UCHAR_MAMEKEY(MENU)) + + PORT_START("kbrow.6") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("FILE/SPELL") PORT_CODE(KEYCODE_HOME) PORT_CHAR(UCHAR_MAMEKEY(HOME)) + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_COLON) PORT_CHAR(U'ö') PORT_CHAR(U'Ö') + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_CLOSEBRACE) PORT_CHAR('+') PORT_CHAR('*') + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_OPENBRACE) PORT_CHAR(U'ü') PORT_CHAR(U'Ü') + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_LEFT) PORT_CHAR(UCHAR_MAMEKEY(LEFT)) + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_DOWN) PORT_CHAR(UCHAR_MAMEKEY(DOWN)) + PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_LCONTROL) PORT_CHAR(UCHAR_MAMEKEY(LCONTROL)) + PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_UNUSED) + + PORT_START("kbrow.7") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("TW/WP/LAYOUT") PORT_CODE(KEYCODE_PRTSCR) + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("CANCEL") PORT_CODE(KEYCODE_PAUSE) PORT_CHAR(UCHAR_MAMEKEY(CANCEL)) + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_ENTER) PORT_CHAR(UCHAR_MAMEKEY(ENTER)) + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_BACKSPACE) PORT_CHAR(UCHAR_MAMEKEY(BACKSPACE)) + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) //PORT_CODE(KEYCODE_) + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("WORD OUT/LINE OUT") //PORT_CODE(KEYCODE_) + PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_SPACE) PORT_CHAR(UCHAR_MAMEKEY(SPACE)) + PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_LSHIFT) PORT_CHAR(UCHAR_SHIFT_1) + + PORT_START("kbrow.8") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_QUOTE) PORT_CHAR(U'´') PORT_CHAR(U'`') + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_L) PORT_CHAR('l') PORT_CHAR('K') + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) //PORT_CODE(KEYCODE_TILDE) + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_K) PORT_CHAR('k') PORT_CHAR('K') + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_STOP) PORT_CHAR('.') PORT_CHAR(':') + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_SLASH) PORT_CHAR('-') PORT_CHAR('_') + PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_CODE(KEYCODE_QUOTE) PORT_CHAR(U'ä') PORT_CHAR(U'Ä') + PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_UNUSED) +INPUT_PORTS_END + +void lw30_state::lw30(machine_config &config) +{ + // basic machine hardware + HD64180RP(config, maincpu, 12'000'000 / 2); + maincpu->set_addrmap(AS_PROGRAM, &lw30_state::map_program); + maincpu->set_addrmap(AS_IO, &lw30_state::map_io); + + // video hardware + SCREEN(config, screen, SCREEN_TYPE_RASTER); + screen->set_color(rgb_t(6, 245, 206)); + screen->set_physical_aspect(480, 128); + screen->set_refresh_hz(78.1); + screen->set_screen_update(FUNC(lw30_state::screen_update)); + screen->set_size(480, 128); + + // floppy disk + FLOPPY_CONNECTOR(config, floppy, lw30_state::lw30_floppies, "35ssdd", lw30_state::floppy_formats).enable_sound(true); + + // sound hardware + SPEAKER(config, "mono").front_center(); + BROTHER_BEEP(config, beeper, 4'000).add_route(ALL_OUTPUTS, "mono", 1.0); // 4.0 kHz + + // timers + TIMER(config, "timer_1khz").configure_periodic(FUNC(lw30_state::int1_timer_callback), attotime::from_hz(1000)); + TIMER(config, "timer_floppy").configure_periodic(FUNC(lw30_state::floppy_timer_callback), attotime::from_usec(4)); + TIMER(config, "timer_cursor").configure_periodic(FUNC(lw30_state::cursor_timer_callback), attotime::from_msec(512)); +} + +/*************************************************************************** + Machine driver(s) +***************************************************************************/ + +ROM_START( lw30 ) + ROM_REGION( 0x40000, "maincpu", 0 ) + ROM_LOAD("ua5362-a", 0x00000, 0x40000, CRC(DAC77867) SHA1(5c7ab30dec55a24eb1b7f241e5015e3836ebf077)) + ROM_REGION(0x80000, "dictionary", 0) + ROM_LOAD("ua2849-a", 0x00000, 0x80000, CRC(FA8712EB) SHA1(2d3454138c79e75604b30229c05ed8fb8e7d15fe)) + ROM_REGION(0x800, "font_normal", 0) + ROM_LOAD("font-normal", 0x00000, 0x800, CRC(56A8B45D) SHA1(3f2860667ee56944cf5a79bfd4e80bebf532b51a)) + ROM_REGION(0x800, "font_bold", 0) + ROM_LOAD("font-bold", 0x00000, 0x800, CRC(D81B79C4) SHA1(fa6be6f9dd0d7ae6d001802778272ecce8f425bc)) +ROM_END + +} // anonymous namespace + +// YEAR NAME PARENT COMPAT MACHINE INPUT CLASS INIT COMPANY FULLNAME FLAGS +COMP( 1991, lw30, 0, 0, lw30, lw30, lw30_state, empty_init, "Brother", "Brother LW-30", MACHINE_NODEVICE_PRINTER ) diff --git a/src/mame/mame.lst b/src/mame/mame.lst index 8cf5da02d75..4d113fe3897 100755 --- a/src/mame/mame.lst +++ b/src/mame/mame.lst @@ -14792,6 +14792,9 @@ apexc // 1955(?) APEXC: All-Purpose Electronic X-ray C @source:brother/ax145.cpp ax145 // Brother AX-145 (c) 198? Brother Industries, Ltd. +@source:brother/lw30.cpp +lw30 // Brother LW-30 (c) 1991 Brother Industries, Ltd. + @source:brother/lw840.cpp lw840 // Brother LW-840ic (c) 1997 Brother Industries, Ltd. -- cgit v1.2.3