summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--scripts/src/formats.lua12
-rw-r--r--src/lib/formats/lw30_dsk.cpp211
-rw-r--r--src/lib/formats/lw30_dsk.h29
-rw-r--r--src/mame/brother/lw30.cpp935
-rwxr-xr-xsrc/mame/mame.lst3
5 files changed, 1190 insertions, 0 deletions
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
@@ -1189,6 +1189,18 @@ 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 <array>
+#include <cassert>
+#include <cstring>
+
+
+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<uint8_t, 3> 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<uint8_t, 416> gcr_encode_and_checksum(const uint8_t *input /* 256 bytes */) {
+ std::array<uint8_t, 416> 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<uint8_t, 5> 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<uint32_t> &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<uint32_t> &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<uint32_t> &variants) const override;
+ virtual bool load(util::random_read &io, uint32_t form_factor, const std::vector<uint32_t>& 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<read_stream_view> const &inputs, std::vector<write_stream_view> &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<read_stream_view> const &inputs, std::vector<write_stream_view> &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<hd64180rp_device> maincpu;
+ required_device<screen_device> screen;
+
+ required_device<floppy_connector> floppy;
+ required_device<lw30_beep_device> beeper;
+ required_ioport_array<9> m_io_kbrow;
+ required_region_ptr<uint8_t> 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.