/*  s100_dj2d.c: Morrow DISK JOCKEY 2D/B Floppy Disk Interface

    Created by Patrick Linstruth (patrick@deltecent.com)

    Permission is hereby granted, free of charge, to any person obtaining a
    copy of this software and associated documentation files (the "Software"),
    to deal in the Software without restriction, including without limitation
    the rights to use, copy, modify, merge, publish, distribute, sublicense,
    and/or sell copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
    PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
    IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
    CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

    Except as contained in this notice, the name of Patrick Linstruth shall not
    be used in advertising or otherwise to promote the sale, use or other dealings
    in this Software without prior written authorization from Patrick Linstruth.

    These functions support the original Morrow DISK JOCKEY 2D (Model A) and
    Model B disk controllers.

    The model is selected using the "SET DJ2D MODEL={A|B}" command. The default
    is the Model B.

    DJ2D units:

    DJ2D0 - Drive A
    DJ2D1 - Drive B
    DJ2D3 - Drive C
    DJ2D4 - Drive D
    DJ2D5 - Serial Port

*/

/* #define DBG_MSG */

#include "altairz80_defs.h"
#include "sim_imd.h"
#include "sim_tmxr.h"

#ifdef DBG_MSG
#define DBG_PRINT(args) sim_printf args
#else
#define DBG_PRINT(args)
#endif

extern uint32 PCX;
extern t_stat set_membase(UNIT *uptr, int32 val, CONST char *cptr, void *desc);
extern t_stat show_membase(FILE *st, UNIT *uptr, int32 val, CONST void *desc);
extern uint32 sim_map_resource(uint32 baseaddr, uint32 size, uint32 resource_type,
                               int32 (*routine)(const int32, const int32, const int32), const char* name, uint8 unmap);
extern DEVICE *find_dev (const char *cptr);
extern uint32 getClockFrequency(void);
extern void setClockFrequency(const uint32 Value);

#define DJ2D_MAX_ADAPTERS      1
#define DJ2D_MAX_DRIVES        4
#define DJ2D_UNITS             DJ2D_MAX_DRIVES+1
#define DJ2D_SIO_UNIT          DJ2D_UNITS-1
#define DJ2D_TRACKS            77
#define DJ2D_TIMER             1             /* 1ms timer */
#define DJ2D_ROTATION_MS       166           /* 166 milliseconds per revolution */
#define DJ2D_HEAD_TIMEOUT      (DJ2D_ROTATION_MS / DJ2D_TIMER * 6)   /* 6 revolutions */
#define DJ2D_INDEX_TIMEOUT     (DJ2D_ROTATION_MS / DJ2D_TIMER)       /* 1 revolution */
#define DJ2D_BUSY_TIMEOUT      2                                     /* 2 timer ticks */

#define DJ2D_BAUD              19200         /* Default baud rate            */

enum { FMT_SD, FMT_256, FMT_512, FMT_1024, FMT_UNKNOWN };

static uint32 dj2d_image_size[] = {256256, 509184, 587008, 625920, 0};
static uint32 dj2d_2s_image_size[] = {512512, 1021696, 1178368, 1256704, 0};
static uint16 dj2d_sector_len[] = {128, 256, 512, 1024, 0};
static uint16 dj2d_spt[] = {26, 26, 15, 8, 0};
static uint16 dj2d_track_len[] = {5000, 9800, 10300, 9700, 0};

#define DJ2D_MEM_READ         FALSE
#define DJ2D_MEM_WRITE        TRUE

#define DJ2D_PROM_BASE   0xe000
#define DJ2D_PROM_SIZE   1024
#define DJ2D_PROM_MASK   (DJ2D_PROM_SIZE-1)
#define DJ2D_MEM_BASE    DJ2D_PROM_BASE + DJ2D_PROM_SIZE
#define DJ2D_MEM_SIZE    1024                /* Must be on a page boundary */
#define DJ2D_MEM_MASK    (DJ2D_MEM_SIZE-1)

static uint8 dj2d_mem[DJ2D_MEM_SIZE];

/* DJ2D PROM is 1016 bytes following by 8 memory-mapped I/O bytes */

static uint8 dj2d_proma_e000[DJ2D_PROM_SIZE] = {
    0xc3, 0x61, 0xe0, 0xc3, 0xff, 0xe0, 0xc3, 0xf0,
    0xe0, 0xc3, 0x6f, 0xe1, 0xc3, 0xa0, 0xe1, 0xc3,
    0x93, 0xe1, 0xc3, 0x56, 0xe1, 0xc3, 0xa9, 0xe1,
    0xc3, 0xfc, 0xe1, 0xc3, 0x4b, 0xe1, 0xc3, 0x0e,
    0xe1, 0xc3, 0x19, 0xe1, 0xc3, 0x43, 0xe1, 0xc3,
    0x1f, 0xe1, 0xc3, 0xdb, 0xe0, 0xc3, 0xbf, 0xe3,
    0xc3, 0xde, 0xe3, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x31, 0xfa, 0xe6, 0x21, 0xe1, 0xe6, 0x11,
    0xeb, 0xe2, 0x06, 0x04, 0x1a, 0xbe, 0xc2, 0x7a,
    0xe0, 0x23, 0x13, 0x05, 0xc2, 0x6c, 0xe0, 0xc3,
    0x7d, 0xe0, 0xcd, 0xe9, 0xe3, 0x21, 0x01, 0x00,
    0xe5, 0x2e, 0x03, 0xe5, 0x26, 0xff, 0xe5, 0xe5,
    0xe5, 0xe5, 0x21, 0x00, 0x00, 0xe5, 0x33, 0x26,
    0x08, 0xe5, 0x26, 0xfe, 0xe5, 0x26, 0xe7, 0xe5,
    0x26, 0x18, 0xe5, 0x3e, 0x03, 0x32, 0xfa, 0xe3,
    0x3e, 0xd0, 0x32, 0xfc, 0xe3, 0xaf, 0xcd, 0x25,
    0xe3, 0xd2, 0xb7, 0xe0, 0x3e, 0x1e, 0x32, 0xea,
    0xe6, 0xcd, 0xe9, 0xe3, 0xc3, 0xa5, 0xe0, 0x3e,
    0x3e, 0x32, 0xea, 0xe6, 0x36, 0x03, 0xe1, 0xcd,
    0xa2, 0xe3, 0xc1, 0xc5, 0xd5, 0x2a, 0xed, 0xe2,
    0xe5, 0x2a, 0xeb, 0xe2, 0xe5, 0x00, 0xc5, 0x06,
    0x0a, 0xc5, 0xcd, 0xa9, 0xe1, 0xc1, 0xd0, 0x05,
    0xc2, 0xd1, 0xe0, 0x0e, 0x3f, 0x11, 0xc3, 0xa2,
    0x1b, 0x7a, 0xb3, 0xc2, 0xe0, 0xe0, 0x3e, 0x20,
    0xa9, 0x32, 0xf9, 0xe3, 0x4f, 0xc3, 0xdd, 0xe0,
    0x3a, 0xf9, 0xe3, 0xe6, 0x08, 0xc2, 0xf0, 0xe0,
    0x79, 0x2f, 0x32, 0xf8, 0xe3, 0x2f, 0xc9, 0x3a,
    0xf9, 0xe3, 0xe6, 0x04, 0xc2, 0xff, 0xe0, 0x3a,
    0xf8, 0xe3, 0x2f, 0xe6, 0x7f, 0xc9, 0x3a, 0xf9,
    0xe3, 0xe6, 0x04, 0xc0, 0xcd, 0xff, 0xe0, 0xb9,
    0xc9, 0x3a, 0xf9, 0xe3, 0xe6, 0x04, 0xc9, 0x3a,
    0xfe, 0xe3, 0x47, 0x3a, 0xfd, 0xe3, 0x4f, 0x3a,
    0xf6, 0xe6, 0x2f, 0xe6, 0x01, 0x0f, 0x57, 0x3a,
    0xf7, 0xe6, 0x07, 0x07, 0x07, 0x82, 0x57, 0x3a,
    0xfd, 0xe6, 0x17, 0x17, 0x82, 0x57, 0x3a, 0xec,
    0xe6, 0x82, 0xc9, 0xe5, 0x2a, 0xe7, 0xe6, 0x44,
    0x4d, 0xe1, 0xc9, 0x3e, 0xfc, 0x81, 0x3e, 0x10,
    0xd8, 0x79, 0x32, 0xeb, 0xe6, 0xc9, 0x21, 0x08,
    0x20, 0x09, 0xd2, 0x68, 0xe1, 0x21, 0x00, 0x1c,
    0x09, 0xda, 0x68, 0xe1, 0x37, 0x3e, 0x10, 0xc9,
    0x60, 0x69, 0x22, 0xe7, 0xe6, 0xaf, 0xc9, 0xcd,
    0x7b, 0xe1, 0xf5, 0x9f, 0x32, 0xf9, 0xe6, 0xf1,
    0xc3, 0x2a, 0xe2, 0xcd, 0xef, 0xe2, 0xd8, 0xaf,
    0x32, 0xed, 0xe6, 0x32, 0xe9, 0xe6, 0x21, 0x00,
    0x00, 0x3e, 0x09, 0xcd, 0x6c, 0xe3, 0xe6, 0x04,
    0xc0, 0x37, 0xc9, 0xaf, 0xb1, 0x37, 0xc8, 0x79,
    0xfe, 0x1b, 0x3f, 0xd8, 0x32, 0xf8, 0xe6, 0xc9,
    0x79, 0xfe, 0x4d, 0x3f, 0xd8, 0x32, 0xf9, 0xe6,
    0xc9, 0xcd, 0x35, 0xe2, 0xe1, 0x3e, 0x40, 0xbb,
    0xca, 0xe8, 0xe1, 0xe5, 0x19, 0x19, 0xe5, 0xf9,
    0x1b, 0x21, 0xff, 0xe3, 0x3e, 0x80, 0x32, 0xfc,
    0xe3, 0x46, 0x4e, 0xc5, 0x46, 0x1d, 0xc2, 0xc2,
    0xe1, 0x15, 0xf2, 0xc2, 0xe1, 0x4e, 0xc5, 0x31,
    0xc6, 0xe6, 0xe1, 0xd1, 0x2b, 0x46, 0x1a, 0x77,
    0x78, 0x12, 0x13, 0x7d, 0xbb, 0xc2, 0xd4, 0xe1,
    0x7c, 0xba, 0xc2, 0xd4, 0xe1, 0xc3, 0x18, 0xe2,
    0x07, 0x4f, 0x11, 0xff, 0xe3, 0x3e, 0x80, 0x32,
    0xfc, 0xe3, 0x1a, 0x77, 0x23, 0x0d, 0xc2, 0xf2,
    0xe1, 0xc3, 0x18, 0xe2, 0xcd, 0x35, 0xe2, 0xe1,
    0xf9, 0x1b, 0x21, 0xff, 0xe3, 0x3e, 0xa0, 0x32,
    0xfc, 0xe3, 0xc1, 0x71, 0x70, 0xc1, 0x71, 0x1d,
    0xc2, 0x0c, 0xe2, 0x15, 0xf2, 0x0c, 0xe2, 0x70,
    0x3a, 0xfc, 0xe3, 0x1f, 0xda, 0x18, 0xe2, 0x17,
    0xe6, 0xdf, 0xca, 0x26, 0xe2, 0x37, 0x2a, 0xca,
    0xe6, 0xf9, 0xf5, 0x3a, 0xf6, 0xe6, 0xee, 0x10,
    0x32, 0xfa, 0xe3, 0xf1, 0xc9, 0xd1, 0x21, 0x00,
    0x00, 0x39, 0x31, 0xcc, 0xe6, 0xe5, 0x2a, 0xe7,
    0xe6, 0xe5, 0xd5, 0x21, 0x26, 0xe2, 0xe5, 0xcd,
    0xef, 0xe2, 0xd8, 0x3a, 0xfd, 0xe3, 0x3c, 0xcc,
    0x7b, 0xe1, 0xda, 0x9f, 0xe2, 0x21, 0xfd, 0xe3,
    0x3a, 0xf9, 0xe6, 0xbe, 0x23, 0x23, 0x77, 0x79,
    0x32, 0xfa, 0xe3, 0xca, 0x7d, 0xe2, 0xaf, 0x32,
    0xe9, 0xe6, 0x3a, 0xfa, 0xe3, 0xe6, 0x08, 0x7f,
    0x1f, 0x1f, 0xc6, 0x18, 0x21, 0x00, 0x00, 0xcd,
    0x6c, 0xe3, 0xda, 0x9f, 0xe2, 0x3a, 0xe9, 0xe6,
    0xb7, 0xc2, 0xc9, 0xe2, 0x06, 0x02, 0x3e, 0x1d,
    0xcd, 0x67, 0xe3, 0xe6, 0x99, 0xca, 0xa5, 0xe2,
    0x3a, 0xf6, 0xe6, 0xee, 0x01, 0x32, 0xf6, 0xe6,
    0x32, 0xfa, 0xe3, 0x05, 0xc2, 0x86, 0xe2, 0xcd,
    0x6f, 0xe1, 0xc3, 0x9e, 0xe3, 0x06, 0x0a, 0x11,
    0xff, 0xe3, 0x21, 0xfa, 0xe6, 0x3e, 0xc4, 0x32,
    0xfc, 0xe3, 0x1a, 0x77, 0x2c, 0xc2, 0xb2, 0xe2,
    0x21, 0xfc, 0xe3, 0xcd, 0x7e, 0xe3, 0xb7, 0xca,
    0xc9, 0xe2, 0x05, 0xc2, 0xa7, 0xe2, 0xc3, 0x9f,
    0xe2, 0x3a, 0xfd, 0xe6, 0x4f, 0x06, 0x00, 0x21,
    0xeb, 0xe2, 0x09, 0x3a, 0xf8, 0xe6, 0x47, 0x86,
    0x3e, 0x10, 0xd8, 0xe1, 0x78, 0x32, 0xfe, 0xe3,
    0x21, 0x40, 0x00, 0x0d, 0x54, 0x5d, 0xf8, 0x29,
    0xc3, 0xe3, 0xe2, 0xe5, 0xe5, 0xf0, 0xf7, 0x21,
    0xeb, 0xe6, 0x4e, 0x23, 0x5e, 0x71, 0x23, 0x7b,
    0xb9, 0x7e, 0x36, 0x04, 0x23, 0xca, 0x25, 0xe3,
    0xe5, 0x16, 0x00, 0x42, 0x19, 0x19, 0x3a, 0xf6,
    0xe6, 0x77, 0x23, 0x11, 0xfd, 0xe3, 0x1a, 0x77,
    0xe1, 0x09, 0x09, 0x7e, 0x32, 0xf6, 0xe6, 0x23,
    0x7e, 0x12, 0x3e, 0x7f, 0x07, 0x0d, 0xf2, 0x1c,
    0xe3, 0x32, 0xea, 0xe6, 0xaf, 0x21, 0xfa, 0xe3,
    0xa6, 0x32, 0xe9, 0xe6, 0xf5, 0x3a, 0xea, 0xe6,
    0x4f, 0x3a, 0xf7, 0xe6, 0x2f, 0xa1, 0x32, 0xf9,
    0xe3, 0x3a, 0xf6, 0xe6, 0x4f, 0x3a, 0xf9, 0xe6,
    0xd6, 0x01, 0x9f, 0x3d, 0x2f, 0xb1, 0x77, 0xee,
    0x02, 0x4f, 0xf1, 0xc2, 0x59, 0xe3, 0xe5, 0x2a,
    0xe5, 0xe6, 0x2b, 0x7c, 0xb5, 0xc2, 0x52, 0xe3,
    0xe1, 0x7e, 0xe6, 0x20, 0xc8, 0x3a, 0xf6, 0xe6,
    0xf6, 0x18, 0x77, 0x3e, 0x80, 0x37, 0xc9, 0x2a,
    0xe5, 0xe6, 0x29, 0x29, 0xeb, 0x21, 0xfc, 0xe3,
    0xc3, 0x76, 0xe3, 0xc3, 0xef, 0xe2, 0x00, 0x00,
    0x77, 0x7e, 0x1f, 0xd2, 0x79, 0xe3, 0x7e, 0x1f,
    0x7e, 0xd0, 0x1b, 0x7a, 0xb3, 0xc2, 0x7e, 0xe3,
    0xe5, 0x23, 0x56, 0x3a, 0xf6, 0xe6, 0xee, 0x04,
    0x32, 0xfa, 0xe3, 0xee, 0x04, 0xe3, 0x32, 0xfa,
    0xe3, 0x36, 0xd0, 0xe3, 0x72, 0xe1, 0x3e, 0x11,
    0x37, 0xc9, 0x11, 0x00, 0x00, 0x21, 0xfa, 0xe3,
    0x0e, 0x10, 0x7e, 0xa1, 0xc2, 0xaa, 0xe3, 0x7e,
    0xa1, 0xca, 0xaf, 0xe3, 0x13, 0xe3, 0xe3, 0xe3,
    0xe3, 0x7e, 0xa1, 0xc2, 0xb4, 0xe3, 0xc9, 0x79,
    0xe6, 0x01, 0x2f, 0x47, 0x21, 0xeb, 0xe6, 0x5e,
    0x16, 0x00, 0x23, 0x7e, 0xab, 0xf5, 0x23, 0x23,
    0x19, 0x19, 0x7e, 0xf6, 0x01, 0xa0, 0x77, 0xf1,
    0xc0, 0x7e, 0x32, 0xf6, 0xe6, 0xc9, 0x79, 0xe6,
    0x01, 0x17, 0x17, 0x17, 0x17, 0x32, 0xf7, 0xe6,
    0xc9, 0x21, 0x00, 0x00, 0x2b, 0x7c, 0xb5, 0xe3,
    0xe3, 0xc2, 0xec, 0xe3, 0xc9, 0xe0, 0x00, 0xc3,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
    };

static uint8 dj2d_proma_f800[DJ2D_PROM_SIZE] = {
    0xc3, 0x61, 0xf8, 0xc3, 0xff, 0xf8, 0xc3, 0xf0,
    0xf8, 0xc3, 0x6f, 0xf9, 0xc3, 0xa0, 0xf9, 0xc3,
    0x93, 0xf9, 0xc3, 0x56, 0xf9, 0xc3, 0xa9, 0xf9,
    0xc3, 0xfc, 0xf9, 0xc3, 0x4b, 0xf9, 0xc3, 0x0e,
    0xf9, 0xc3, 0x19, 0xf9, 0xc3, 0x43, 0xf9, 0xc3,
    0x1f, 0xf9, 0xc3, 0xdb, 0xf8, 0xc3, 0xbf, 0xfb,
    0xc3, 0xde, 0xfb, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x31, 0xfa, 0xfe, 0x21, 0xe1, 0xfe, 0x11,
    0xeb, 0xfa, 0x06, 0x04, 0x1a, 0xbe, 0xc2, 0x7a,
    0xf8, 0x23, 0x13, 0x05, 0xc2, 0x6c, 0xf8, 0xc3,
    0x7d, 0xf8, 0xcd, 0xe9, 0xfb, 0x21, 0x01, 0x00,
    0xe5, 0x2e, 0x03, 0xe5, 0x26, 0xff, 0xe5, 0xe5,
    0xe5, 0xe5, 0x21, 0x00, 0x00, 0xe5, 0x33, 0x26,
    0x08, 0xe5, 0x26, 0xfe, 0xe5, 0x26, 0xff, 0xe5,
    0x26, 0x18, 0xe5, 0x3e, 0x03, 0x32, 0xfa, 0xfb,
    0x3e, 0xd0, 0x32, 0xfc, 0xfb, 0xaf, 0xcd, 0x25,
    0xfb, 0xd2, 0xb7, 0xf8, 0x3e, 0x1e, 0x32, 0xea,
    0xfe, 0xcd, 0xe9, 0xfb, 0xc3, 0xa5, 0xf8, 0x3e,
    0x3e, 0x32, 0xea, 0xfe, 0x36, 0x03, 0xe1, 0xcd,
    0xa2, 0xfb, 0xc1, 0xc5, 0xd5, 0x2a, 0xed, 0xfa,
    0xe5, 0x2a, 0xeb, 0xfa, 0xe5, 0x00, 0xc5, 0x06,
    0x0a, 0xc5, 0xcd, 0xa9, 0xf9, 0xc1, 0xd0, 0x05,
    0xc2, 0xd1, 0xf8, 0x0e, 0x3f, 0x11, 0xc3, 0xa2,
    0x1b, 0x7a, 0xb3, 0xc2, 0xe0, 0xf8, 0x3e, 0x20,
    0xa9, 0x32, 0xf9, 0xfb, 0x4f, 0xc3, 0xdd, 0xf8,
    0x3a, 0xf9, 0xfb, 0xe6, 0x08, 0xc2, 0xf0, 0xf8,
    0x79, 0x2f, 0x32, 0xf8, 0xfb, 0x2f, 0xc9, 0x3a,
    0xf9, 0xfb, 0xe6, 0x04, 0xc2, 0xff, 0xf8, 0x3a,
    0xf8, 0xfb, 0x2f, 0xe6, 0x7f, 0xc9, 0x3a, 0xf9,
    0xfb, 0xe6, 0x04, 0xc0, 0xcd, 0xff, 0xf8, 0xb9,
    0xc9, 0x3a, 0xf9, 0xfb, 0xe6, 0x04, 0xc9, 0x3a,
    0xfe, 0xfb, 0x47, 0x3a, 0xfd, 0xfb, 0x4f, 0x3a,
    0xf6, 0xfe, 0x2f, 0xe6, 0x01, 0x0f, 0x57, 0x3a,
    0xf7, 0xfe, 0x07, 0x07, 0x07, 0x82, 0x57, 0x3a,
    0xfd, 0xfe, 0x17, 0x17, 0x82, 0x57, 0x3a, 0xec,
    0xfe, 0x82, 0xc9, 0xe5, 0x2a, 0xe7, 0xfe, 0x44,
    0x4d, 0xe1, 0xc9, 0x3e, 0xfc, 0x81, 0x3e, 0x10,
    0xd8, 0x79, 0x32, 0xeb, 0xfe, 0xc9, 0x21, 0x08,
    0x08, 0x09, 0xd2, 0x68, 0xf9, 0x21, 0x00, 0x04,
    0x09, 0xda, 0x68, 0xf9, 0x37, 0x3e, 0x10, 0xc9,
    0x60, 0x69, 0x22, 0xe7, 0xfe, 0xaf, 0xc9, 0xcd,
    0x7b, 0xf9, 0xf5, 0x9f, 0x32, 0xf9, 0xfe, 0xf1,
    0xc3, 0x2a, 0xfa, 0xcd, 0xef, 0xfa, 0xd8, 0xaf,
    0x32, 0xed, 0xfe, 0x32, 0xe9, 0xfe, 0x21, 0x00,
    0x00, 0x3e, 0x09, 0xcd, 0x6c, 0xfb, 0xe6, 0x04,
    0xc0, 0x37, 0xc9, 0xaf, 0xb1, 0x37, 0xc8, 0x79,
    0xfe, 0x1b, 0x3f, 0xd8, 0x32, 0xf8, 0xfe, 0xc9,
    0x79, 0xfe, 0x4d, 0x3f, 0xd8, 0x32, 0xf9, 0xfe,
    0xc9, 0xcd, 0x35, 0xfa, 0xe1, 0x3e, 0x40, 0xbb,
    0xca, 0xe8, 0xf9, 0xe5, 0x19, 0x19, 0xe5, 0xf9,
    0x1b, 0x21, 0xff, 0xfb, 0x3e, 0x80, 0x32, 0xfc,
    0xfb, 0x46, 0x4e, 0xc5, 0x46, 0x1d, 0xc2, 0xc2,
    0xf9, 0x15, 0xf2, 0xc2, 0xf9, 0x4e, 0xc5, 0x31,
    0xc6, 0xfe, 0xe1, 0xd1, 0x2b, 0x46, 0x1a, 0x77,
    0x78, 0x12, 0x13, 0x7d, 0xbb, 0xc2, 0xd4, 0xf9,
    0x7c, 0xba, 0xc2, 0xd4, 0xf9, 0xc3, 0x18, 0xfa,
    0x07, 0x4f, 0x11, 0xff, 0xfb, 0x3e, 0x80, 0x32,
    0xfc, 0xfb, 0x1a, 0x77, 0x23, 0x0d, 0xc2, 0xf2,
    0xf9, 0xc3, 0x18, 0xfa, 0xcd, 0x35, 0xfa, 0xe1,
    0xf9, 0x1b, 0x21, 0xff, 0xfb, 0x3e, 0xa0, 0x32,
    0xfc, 0xfb, 0xc1, 0x71, 0x70, 0xc1, 0x71, 0x1d,
    0xc2, 0x0c, 0xfa, 0x15, 0xf2, 0x0c, 0xfa, 0x70,
    0x3a, 0xfc, 0xfb, 0x1f, 0xda, 0x18, 0xfa, 0x17,
    0xe6, 0xdf, 0xca, 0x26, 0xfa, 0x37, 0x2a, 0xca,
    0xfe, 0xf9, 0xf5, 0x3a, 0xf6, 0xfe, 0xee, 0x10,
    0x32, 0xfa, 0xfb, 0xf1, 0xc9, 0xd1, 0x21, 0x00,
    0x00, 0x39, 0x31, 0xcc, 0xfe, 0xe5, 0x2a, 0xe7,
    0xfe, 0xe5, 0xd5, 0x21, 0x26, 0xfa, 0xe5, 0xcd,
    0xef, 0xfa, 0xd8, 0x3a, 0xfd, 0xfb, 0x3c, 0xcc,
    0x7b, 0xf9, 0xda, 0x9f, 0xfa, 0x21, 0xfd, 0xfb,
    0x3a, 0xf9, 0xfe, 0xbe, 0x23, 0x23, 0x77, 0x79,
    0x32, 0xfa, 0xfb, 0xca, 0x7d, 0xfa, 0xaf, 0x32,
    0xe9, 0xfe, 0x3a, 0xfa, 0xfb, 0xe6, 0x08, 0x7f,
    0x1f, 0x1f, 0xc6, 0x18, 0x21, 0x00, 0x00, 0xcd,
    0x6c, 0xfb, 0xda, 0x9f, 0xfa, 0x3a, 0xe9, 0xfe,
    0xb7, 0xc2, 0xc9, 0xfa, 0x06, 0x02, 0x3e, 0x1d,
    0xcd, 0x67, 0xfb, 0xe6, 0x99, 0xca, 0xa5, 0xfa,
    0x3a, 0xf6, 0xfe, 0xee, 0x01, 0x32, 0xf6, 0xfe,
    0x32, 0xfa, 0xfb, 0x05, 0xc2, 0x86, 0xfa, 0xcd,
    0x6f, 0xf9, 0xc3, 0x9e, 0xfb, 0x06, 0x0a, 0x11,
    0xff, 0xfb, 0x21, 0xfa, 0xfe, 0x3e, 0xc4, 0x32,
    0xfc, 0xfb, 0x1a, 0x77, 0x2c, 0xc2, 0xb2, 0xfa,
    0x21, 0xfc, 0xfb, 0xcd, 0x7e, 0xfb, 0xb7, 0xca,
    0xc9, 0xfa, 0x05, 0xc2, 0xa7, 0xfa, 0xc3, 0x9f,
    0xfa, 0x3a, 0xfd, 0xfe, 0x4f, 0x06, 0x00, 0x21,
    0xeb, 0xfa, 0x09, 0x3a, 0xf8, 0xfe, 0x47, 0x86,
    0x3e, 0x10, 0xd8, 0xe1, 0x78, 0x32, 0xfe, 0xfb,
    0x21, 0x40, 0x00, 0x0d, 0x54, 0x5d, 0xf8, 0x29,
    0xc3, 0xe3, 0xfa, 0xe5, 0xe5, 0xf0, 0xf7, 0x21,
    0xeb, 0xfe, 0x4e, 0x23, 0x5e, 0x71, 0x23, 0x7b,
    0xb9, 0x7e, 0x36, 0x04, 0x23, 0xca, 0x25, 0xfb,
    0xe5, 0x16, 0x00, 0x42, 0x19, 0x19, 0x3a, 0xf6,
    0xfe, 0x77, 0x23, 0x11, 0xfd, 0xfb, 0x1a, 0x77,
    0xe1, 0x09, 0x09, 0x7e, 0x32, 0xf6, 0xfe, 0x23,
    0x7e, 0x12, 0x3e, 0x7f, 0x07, 0x0d, 0xf2, 0x1c,
    0xfb, 0x32, 0xea, 0xfe, 0xaf, 0x21, 0xfa, 0xfb,
    0xa6, 0x32, 0xe9, 0xfe, 0xf5, 0x3a, 0xea, 0xfe,
    0x4f, 0x3a, 0xf7, 0xfe, 0x2f, 0xa1, 0x32, 0xf9,
    0xfb, 0x3a, 0xf6, 0xfe, 0x4f, 0x3a, 0xf9, 0xfe,
    0xd6, 0x01, 0x9f, 0x3d, 0x2f, 0xb1, 0x77, 0xee,
    0x02, 0x4f, 0xf1, 0xc2, 0x59, 0xfb, 0xe5, 0x2a,
    0xe5, 0xfe, 0x2b, 0x7c, 0xb5, 0xc2, 0x52, 0xfb,
    0xe1, 0x7e, 0xe6, 0x20, 0xc8, 0x3a, 0xf6, 0xfe,
    0xf6, 0x18, 0x77, 0x3e, 0x80, 0x37, 0xc9, 0x2a,
    0xe5, 0xfe, 0x29, 0x29, 0xeb, 0x21, 0xfc, 0xfb,
    0xc3, 0x76, 0xfb, 0xc3, 0xef, 0xfa, 0x00, 0x00,
    0x77, 0x7e, 0x1f, 0xd2, 0x79, 0xfb, 0x7e, 0x1f,
    0x7e, 0xd0, 0x1b, 0x7a, 0xb3, 0xc2, 0x7e, 0xfb,
    0xe5, 0x23, 0x56, 0x3a, 0xf6, 0xfe, 0xee, 0x04,
    0x32, 0xfa, 0xfb, 0xee, 0x04, 0xe3, 0x32, 0xfa,
    0xfb, 0x36, 0xd0, 0xe3, 0x72, 0xe1, 0x3e, 0x11,
    0x37, 0xc9, 0x11, 0x00, 0x00, 0x21, 0xfa, 0xfb,
    0x0e, 0x10, 0x7e, 0xa1, 0xc2, 0xaa, 0xfb, 0x7e,
    0xa1, 0xca, 0xaf, 0xfb, 0x13, 0xe3, 0xe3, 0xe3,
    0xe3, 0x7e, 0xa1, 0xc2, 0xb4, 0xfb, 0xc9, 0x79,
    0xe6, 0x01, 0x2f, 0x47, 0x21, 0xeb, 0xfe, 0x5e,
    0x16, 0x00, 0x23, 0x7e, 0xab, 0xf5, 0x23, 0x23,
    0x19, 0x19, 0x7e, 0xf6, 0x01, 0xa0, 0x77, 0xf1,
    0xc0, 0x7e, 0x32, 0xf6, 0xfe, 0xc9, 0x79, 0xe6,
    0x01, 0x17, 0x17, 0x17, 0x17, 0x32, 0xf7, 0xfe,
    0xc9, 0x21, 0x00, 0x00, 0x2b, 0x7c, 0xb5, 0xe3,
    0xe3, 0xc2, 0xec, 0xfb, 0xc9, 0xf8, 0x00, 0xc3,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
    };

static uint8 dj2d_promb_e000[DJ2D_PROM_SIZE] = {
    0xc3, 0x69, 0xe0, 0xc3, 0xe9, 0xe0, 0xc3, 0xda,
    0xe0, 0xc3, 0x5a, 0xe1, 0xc3, 0x8b, 0xe1, 0xc3,
    0x81, 0xe1, 0xc3, 0x43, 0xe1, 0xc3, 0xdd, 0xe1,
    0xc3, 0xbc, 0xe1, 0xc3, 0x3c, 0xe1, 0xc3, 0xf8,
    0xe0, 0xc3, 0x03, 0xe1, 0xc3, 0x34, 0xe1, 0xc3,
    0x09, 0xe1, 0xc3, 0xc5, 0xe0, 0xc3, 0xb3, 0xe3,
    0xc3, 0xe5, 0xe3, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0x31, 0xfa, 0xe7, 0xcd, 0xd2, 0xe3, 0x21,
    0x01, 0x00, 0xe5, 0x2e, 0x09, 0xe5, 0x26, 0xff,
    0xe5, 0xe5, 0xe5, 0xe5, 0x21, 0x08, 0x00, 0xe5,
    0x2e, 0x7e, 0xe5, 0x2e, 0x08, 0xe5, 0x26, 0x18,
    0xe5, 0x3e, 0x7f, 0x32, 0xf9, 0xe3, 0x3e, 0xd0,
    0x32, 0xfc, 0xe3, 0xaf, 0xcd, 0x1b, 0xe3, 0xd2,
    0xa5, 0xe0, 0x3e, 0x01, 0x32, 0xf6, 0xe7, 0xcd,
    0xd2, 0xe3, 0xc3, 0x93, 0xe0, 0x3e, 0x09, 0x32,
    0xf6, 0xe7, 0xcd, 0x96, 0xe3, 0xc1, 0x01, 0x00,
    0xe7, 0xc5, 0xd5, 0x21, 0x00, 0x00, 0xe5, 0x00,
    0xc5, 0x06, 0x0c, 0xc5, 0xcd, 0xdd, 0xe1, 0xc1,
    0xd0, 0x05, 0xc2, 0xbb, 0xe0, 0x0e, 0x09, 0x11,
    0xc3, 0xa2, 0x1b, 0x7a, 0xb3, 0xc2, 0xca, 0xe0,
    0x3e, 0x08, 0xa9, 0x4f, 0x32, 0xfa, 0xe3, 0xc3,
    0xc7, 0xe0, 0x3a, 0xf9, 0xe3, 0xe6, 0x08, 0xc2,
    0xda, 0xe0, 0x79, 0x2f, 0x32, 0xf8, 0xe3, 0x2f,
    0xc9, 0x3a, 0xf9, 0xe3, 0xe6, 0x04, 0xc2, 0xe9,
    0xe0, 0x3a, 0xf8, 0xe3, 0x2f, 0xe6, 0x7f, 0xc9,
    0x3a, 0xf9, 0xe3, 0xe6, 0x04, 0xc0, 0xcd, 0xe9,
    0xe0, 0xb9, 0xc9, 0x3a, 0xf9, 0xe3, 0xe6, 0x04,
    0xc9, 0x21, 0xfd, 0xe3, 0x4e, 0x23, 0x46, 0x3a,
    0xf6, 0xe7, 0x2f, 0xe6, 0x01, 0x0f, 0x57, 0x3a,
    0xf7, 0xe7, 0x07, 0x07, 0x07, 0xb2, 0x57, 0x3a,
    0xe8, 0xe7, 0xee, 0x08, 0x17, 0x17, 0x82, 0x57,
    0x3a, 0xfd, 0xe7, 0x17, 0x17, 0xb2, 0x57, 0x3a,
    0xec, 0xe7, 0x82, 0xc9, 0xe5, 0x2a, 0xe6, 0xe7,
    0x44, 0x4d, 0xe1, 0xc9, 0x79, 0xe6, 0x03, 0x32,
    0xeb, 0xe7, 0xc9, 0x21, 0x00, 0x1c, 0x09, 0xda,
    0x54, 0xe1, 0x21, 0x08, 0x20, 0x09, 0xd2, 0x54,
    0xe1, 0x3e, 0x10, 0xc9, 0x60, 0x69, 0x22, 0xe6,
    0xe7, 0xc9, 0xcd, 0xe3, 0xe2, 0xd8, 0xcd, 0x70,
    0xe1, 0xf5, 0x9f, 0x32, 0xf9, 0xe7, 0x32, 0xfd,
    0xe3, 0xaf, 0x32, 0xed, 0xe7, 0xc3, 0x23, 0xe2,
    0xaf, 0x32, 0xe9, 0xe7, 0x21, 0x00, 0x00, 0x3e,
    0x09, 0xcd, 0x62, 0xe3, 0xe6, 0x04, 0xc0, 0x37,
    0xc9, 0xaf, 0xb1, 0x37, 0xc8, 0xe6, 0x3f, 0x32,
    0xf8, 0xe7, 0xc9, 0x79, 0xfe, 0x4d, 0x3f, 0xd8,
    0x32, 0xf9, 0xe7, 0xc9, 0x32, 0xe3, 0xe7, 0xcd,
    0x96, 0xe3, 0x0e, 0x01, 0x79, 0x32, 0xfe, 0xe3,
    0x3a, 0xf8, 0xe7, 0xb9, 0xc8, 0x3e, 0x80, 0xcd,
    0x5d, 0xe3, 0xda, 0x20, 0xe2, 0x0c, 0xc3, 0x9c,
    0xe1, 0x32, 0xfc, 0xe3, 0x48, 0x11, 0xff, 0xe3,
    0x2a, 0xe6, 0xe7, 0xc9, 0xcd, 0x33, 0xe2, 0xda,
    0x22, 0xe2, 0x3e, 0xa0, 0xcd, 0xb1, 0xe1, 0x7e,
    0x23, 0x12, 0x7e, 0x23, 0x12, 0x7e, 0x23, 0x12,
    0x0d, 0x7e, 0x23, 0x12, 0xc2, 0xc7, 0xe1, 0x21,
    0xc2, 0xe1, 0xc3, 0xfb, 0xe1, 0xcd, 0x33, 0xe2,
    0xda, 0x22, 0xe2, 0x3e, 0x80, 0xcd, 0xb1, 0xe1,
    0x1a, 0x77, 0x23, 0x1a, 0x77, 0x23, 0x1a, 0x77,
    0x23, 0x0d, 0x1a, 0x77, 0x23, 0xc2, 0xe8, 0xe1,
    0x21, 0xe3, 0xe1, 0xe5, 0x21, 0xfc, 0xe3, 0xcd,
    0x6c, 0xe3, 0xe6, 0x5f, 0xca, 0x21, 0xe2, 0xfe,
    0x10, 0xc2, 0x20, 0xe2, 0x3a, 0xe2, 0xe7, 0x3d,
    0xfa, 0x17, 0xe2, 0x32, 0xe2, 0xe7, 0xc9, 0x3a,
    0xe3, 0xe7, 0x3d, 0xf2, 0x94, 0xe1, 0x3e, 0x10,
    0x37, 0xe1, 0xf5, 0x3a, 0xf6, 0xe7, 0xee, 0x04,
    0x32, 0xfa, 0xe3, 0x3a, 0xea, 0xe7, 0x32, 0xf9,
    0xe3, 0xf1, 0xc9, 0xcd, 0xe3, 0xe2, 0xd8, 0x3a,
    0xfd, 0xe3, 0x3c, 0xcc, 0x70, 0xe1, 0xd8, 0x21,
    0xfd, 0xe3, 0x3a, 0xf9, 0xe7, 0xbe, 0x23, 0x23,
    0x77, 0x79, 0x32, 0xf9, 0xe3, 0xca, 0x6a, 0xe2,
    0xaf, 0x32, 0xe9, 0xe7, 0x3a, 0xfa, 0xe3, 0xe6,
    0x08, 0x32, 0xe8, 0xe7, 0x1f, 0x1f, 0x1f, 0xc6,
    0x18, 0x21, 0x00, 0x00, 0xcd, 0x62, 0xe3, 0xda,
    0x8e, 0xe2, 0x3a, 0xe9, 0xe7, 0xb7, 0xc2, 0xb9,
    0xe2, 0x06, 0x02, 0x3e, 0x1d, 0xcd, 0x5d, 0xe3,
    0xe6, 0x99, 0x57, 0xca, 0x95, 0xe2, 0x3a, 0xf6,
    0xe7, 0xee, 0x01, 0x32, 0xf6, 0xe7, 0x32, 0xfa,
    0xe3, 0x05, 0xc2, 0x73, 0xe2, 0x7a, 0x37, 0xf5,
    0xcd, 0x70, 0xe1, 0xf1, 0xc9, 0x06, 0x0a, 0x11,
    0xff, 0xe3, 0x21, 0xfa, 0xe7, 0x3e, 0xc4, 0x32,
    0xfc, 0xe3, 0x1a, 0x77, 0x2c, 0xc2, 0xa2, 0xe2,
    0x21, 0xfc, 0xe3, 0xcd, 0x6c, 0xe3, 0xb7, 0xca,
    0xb9, 0xe2, 0x05, 0xc2, 0x97, 0xe2, 0xc3, 0x8e,
    0xe2, 0x3a, 0xfd, 0xe7, 0x4f, 0x06, 0x00, 0x21,
    0xdf, 0xe2, 0x09, 0x3a, 0xf8, 0xe7, 0x47, 0x86,
    0x3e, 0x10, 0xd8, 0x78, 0x32, 0xfe, 0xe3, 0x3e,
    0x20, 0x21, 0x05, 0x05, 0x22, 0xe2, 0xe7, 0x0d,
    0x47, 0xf8, 0x17, 0xb7, 0xc3, 0xd7, 0xe2, 0xd5,
    0xd5, 0xf0, 0xf7, 0x21, 0xeb, 0xe7, 0x4e, 0x23,
    0x5e, 0x71, 0x23, 0x7b, 0xb9, 0x7e, 0x36, 0x01,
    0xca, 0x1b, 0xe3, 0x23, 0xe5, 0x16, 0x00, 0x42,
    0x19, 0x19, 0x3a, 0xf6, 0xe7, 0x77, 0x23, 0x11,
    0xfd, 0xe3, 0x1a, 0x77, 0xe1, 0x09, 0x09, 0x7e,
    0x32, 0xf6, 0xe7, 0x23, 0x7e, 0x12, 0x3e, 0x7f,
    0x07, 0x0d, 0xf2, 0x10, 0xe3, 0xe6, 0x7f, 0x32,
    0xea, 0xe7, 0xaf, 0x21, 0xfa, 0xe3, 0xa6, 0x32,
    0xe9, 0xe7, 0xf5, 0x3a, 0xea, 0xe7, 0x4f, 0x3a,
    0xf7, 0xe7, 0x2f, 0xa1, 0x32, 0xf9, 0xe3, 0xee,
    0x40, 0x4f, 0x3a, 0xf6, 0xe7, 0x47, 0x3a, 0xf9,
    0xe7, 0xd6, 0x01, 0x9f, 0x3d, 0x2f, 0xb0, 0x77,
    0xf1, 0xc2, 0x4f, 0xe3, 0xe5, 0x2a, 0xe4, 0xe7,
    0x2b, 0x7c, 0xb5, 0xc2, 0x48, 0xe3, 0xe1, 0x7e,
    0xe6, 0x80, 0xc0, 0x3a, 0xf6, 0xe7, 0xf6, 0x06,
    0x77, 0x3e, 0x80, 0x37, 0xc9, 0x2a, 0xe4, 0xe7,
    0x29, 0x29, 0xeb, 0x21, 0xfc, 0xe3, 0x77, 0x7e,
    0x1f, 0xd2, 0x67, 0xe3, 0x7e, 0x1f, 0x7e, 0xd0,
    0xc3, 0x76, 0xe3, 0xc3, 0xe3, 0xe2, 0x1b, 0x7a,
    0xb3, 0xc2, 0x6c, 0xe3, 0x5e, 0xe5, 0x23, 0x56,
    0x3a, 0xea, 0xe7, 0xee, 0x80, 0x32, 0xf9, 0xe3,
    0xee, 0xc0, 0xe3, 0x32, 0xf9, 0xe3, 0x36, 0xd0,
    0xe3, 0x72, 0xe1, 0x7b, 0x37, 0xc9, 0x11, 0x00,
    0x00, 0x21, 0xfa, 0xe3, 0x0e, 0x10, 0x7e, 0xa1,
    0xca, 0x9e, 0xe3, 0x7e, 0xa1, 0xc2, 0xa3, 0xe3,
    0x13, 0xe3, 0xe3, 0xe3, 0xe3, 0x7e, 0xa1, 0xca,
    0xa8, 0xe3, 0xc9, 0x79, 0xe6, 0x01, 0x2f, 0x47,
    0x21, 0xeb, 0xe7, 0x5e, 0x16, 0x00, 0x23, 0x7e,
    0xab, 0xf5, 0x23, 0x23, 0x19, 0x19, 0x7e, 0xf6,
    0x01, 0xa0, 0x77, 0xf1, 0xc0, 0x7e, 0x32, 0xf6,
    0xe7, 0xc9, 0x21, 0x00, 0x00, 0x2b, 0x7c, 0xb5,
    0xe3, 0xe3, 0xc2, 0xd5, 0xe3, 0xc9, 0xe5, 0x21,
    0xe2, 0xe3, 0xe9, 0xe1, 0xc9, 0x79, 0xe6, 0x01,
    0x17, 0x17, 0x17, 0x17, 0x32, 0xf7, 0xe7, 0xc9,
    0x00, 0x00, 0x00, 0x00, 0x00, 0xc3, 0x00, 0xe0,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
    };

static uint8 dj2d_promb_f800[DJ2D_PROM_SIZE] = {
    0xc3, 0x69, 0xf8, 0xc3, 0xe9, 0xf8, 0xc3, 0xda,
    0xf8, 0xc3, 0x5a, 0xf9, 0xc3, 0x8b, 0xf9, 0xc3,
    0x81, 0xf9, 0xc3, 0x43, 0xf9, 0xc3, 0xdd, 0xf9,
    0xc3, 0xbc, 0xf9, 0xc3, 0x3c, 0xf9, 0xc3, 0xf8,
    0xf8, 0xc3, 0x03, 0xf9, 0xc3, 0x34, 0xf9, 0xc3,
    0x09, 0xf9, 0xc3, 0xc5, 0xf8, 0xc3, 0xb3, 0xfb,
    0xc3, 0xe5, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0x31, 0xfa, 0xff, 0xcd, 0xd2, 0xfb, 0x21,
    0x01, 0x00, 0xe5, 0x2e, 0x09, 0xe5, 0x26, 0xff,
    0xe5, 0xe5, 0xe5, 0xe5, 0x21, 0x08, 0x00, 0xe5,
    0x2e, 0x7e, 0xe5, 0x2e, 0x08, 0xe5, 0x26, 0x18,
    0xe5, 0x3e, 0x7f, 0x32, 0xf9, 0xfb, 0x3e, 0xd0,
    0x32, 0xfc, 0xfb, 0xaf, 0xcd, 0x1b, 0xfb, 0xd2,
    0xa5, 0xf8, 0x3e, 0x01, 0x32, 0xf6, 0xff, 0xcd,
    0xd2, 0xfb, 0xc3, 0x93, 0xf8, 0x3e, 0x09, 0x32,
    0xf6, 0xff, 0xcd, 0x96, 0xfb, 0xc1, 0x01, 0x00,
    0xff, 0xc5, 0xd5, 0x21, 0x00, 0x00, 0xe5, 0x00,
    0xc5, 0x06, 0x0c, 0xc5, 0xcd, 0xdd, 0xf9, 0xc1,
    0xd0, 0x05, 0xc2, 0xbb, 0xf8, 0x0e, 0x09, 0x11,
    0xc3, 0xa2, 0x1b, 0x7a, 0xb3, 0xc2, 0xca, 0xf8,
    0x3e, 0x08, 0xa9, 0x4f, 0x32, 0xfa, 0xfb, 0xc3,
    0xc7, 0xf8, 0x3a, 0xf9, 0xfb, 0xe6, 0x08, 0xc2,
    0xda, 0xf8, 0x79, 0x2f, 0x32, 0xf8, 0xfb, 0x2f,
    0xc9, 0x3a, 0xf9, 0xfb, 0xe6, 0x04, 0xc2, 0xe9,
    0xf8, 0x3a, 0xf8, 0xfb, 0x2f, 0xe6, 0x7f, 0xc9,
    0x3a, 0xf9, 0xfb, 0xe6, 0x04, 0xc0, 0xcd, 0xe9,
    0xf8, 0xb9, 0xc9, 0x3a, 0xf9, 0xfb, 0xe6, 0x04,
    0xc9, 0x21, 0xfd, 0xfb, 0x4e, 0x23, 0x46, 0x3a,
    0xf6, 0xff, 0x2f, 0xe6, 0x01, 0x0f, 0x57, 0x3a,
    0xf7, 0xff, 0x07, 0x07, 0x07, 0xb2, 0x57, 0x3a,
    0xe8, 0xff, 0xee, 0x08, 0x17, 0x17, 0x82, 0x57,
    0x3a, 0xfd, 0xff, 0x17, 0x17, 0xb2, 0x57, 0x3a,
    0xec, 0xff, 0x82, 0xc9, 0xe5, 0x2a, 0xe6, 0xff,
    0x44, 0x4d, 0xe1, 0xc9, 0x79, 0xe6, 0x03, 0x32,
    0xeb, 0xff, 0xc9, 0x21, 0x00, 0x04, 0x09, 0xda,
    0x54, 0xf9, 0x21, 0x08, 0x08, 0x09, 0xd2, 0x54,
    0xf9, 0x3e, 0x10, 0xc9, 0x60, 0x69, 0x22, 0xe6,
    0xff, 0xc9, 0xcd, 0xe3, 0xfa, 0xd8, 0xcd, 0x70,
    0xf9, 0xf5, 0x9f, 0x32, 0xf9, 0xff, 0x32, 0xfd,
    0xfb, 0xaf, 0x32, 0xed, 0xff, 0xc3, 0x23, 0xfa,
    0xaf, 0x32, 0xe9, 0xff, 0x21, 0x00, 0x00, 0x3e,
    0x09, 0xcd, 0x62, 0xfb, 0xe6, 0x04, 0xc0, 0x37,
    0xc9, 0xaf, 0xb1, 0x37, 0xc8, 0xe6, 0x3f, 0x32,
    0xf8, 0xff, 0xc9, 0x79, 0xfe, 0x4d, 0x3f, 0xd8,
    0x32, 0xf9, 0xff, 0xc9, 0x32, 0xe3, 0xff, 0xcd,
    0x96, 0xfb, 0x0e, 0x01, 0x79, 0x32, 0xfe, 0xfb,
    0x3a, 0xf8, 0xff, 0xb9, 0xc8, 0x3e, 0x80, 0xcd,
    0x5d, 0xfb, 0xda, 0x20, 0xfa, 0x0c, 0xc3, 0x9c,
    0xf9, 0x32, 0xfc, 0xfb, 0x48, 0x11, 0xff, 0xfb,
    0x2a, 0xe6, 0xff, 0xc9, 0xcd, 0x33, 0xfa, 0xda,
    0x22, 0xfa, 0x3e, 0xa0, 0xcd, 0xb1, 0xf9, 0x7e,
    0x23, 0x12, 0x7e, 0x23, 0x12, 0x7e, 0x23, 0x12,
    0x0d, 0x7e, 0x23, 0x12, 0xc2, 0xc7, 0xf9, 0x21,
    0xc2, 0xf9, 0xc3, 0xfb, 0xf9, 0xcd, 0x33, 0xfa,
    0xda, 0x22, 0xfa, 0x3e, 0x80, 0xcd, 0xb1, 0xf9,
    0x1a, 0x77, 0x23, 0x1a, 0x77, 0x23, 0x1a, 0x77,
    0x23, 0x0d, 0x1a, 0x77, 0x23, 0xc2, 0xe8, 0xf9,
    0x21, 0xe3, 0xf9, 0xe5, 0x21, 0xfc, 0xfb, 0xcd,
    0x6c, 0xfb, 0xe6, 0x5f, 0xca, 0x21, 0xfa, 0xfe,
    0x10, 0xc2, 0x20, 0xfa, 0x3a, 0xe2, 0xff, 0x3d,
    0xfa, 0x17, 0xfa, 0x32, 0xe2, 0xff, 0xc9, 0x3a,
    0xe3, 0xff, 0x3d, 0xf2, 0x94, 0xf9, 0x3e, 0x10,
    0x37, 0xe1, 0xf5, 0x3a, 0xf6, 0xff, 0xee, 0x04,
    0x32, 0xfa, 0xfb, 0x3a, 0xea, 0xff, 0x32, 0xf9,
    0xfb, 0xf1, 0xc9, 0xcd, 0xe3, 0xfa, 0xd8, 0x3a,
    0xfd, 0xfb, 0x3c, 0xcc, 0x70, 0xf9, 0xd8, 0x21,
    0xfd, 0xfb, 0x3a, 0xf9, 0xff, 0xbe, 0x23, 0x23,
    0x77, 0x79, 0x32, 0xf9, 0xfb, 0xca, 0x6a, 0xfa,
    0xaf, 0x32, 0xe9, 0xff, 0x3a, 0xfa, 0xfb, 0xe6,
    0x08, 0x32, 0xe8, 0xff, 0x1f, 0x1f, 0x1f, 0xc6,
    0x18, 0x21, 0x00, 0x00, 0xcd, 0x62, 0xfb, 0xda,
    0x8e, 0xfa, 0x3a, 0xe9, 0xff, 0xb7, 0xc2, 0xb9,
    0xfa, 0x06, 0x02, 0x3e, 0x1d, 0xcd, 0x5d, 0xfb,
    0xe6, 0x99, 0x57, 0xca, 0x95, 0xfa, 0x3a, 0xf6,
    0xff, 0xee, 0x01, 0x32, 0xf6, 0xff, 0x32, 0xfa,
    0xfb, 0x05, 0xc2, 0x73, 0xfa, 0x7a, 0x37, 0xf5,
    0xcd, 0x70, 0xf9, 0xf1, 0xc9, 0x06, 0x0a, 0x11,
    0xff, 0xfb, 0x21, 0xfa, 0xff, 0x3e, 0xc4, 0x32,
    0xfc, 0xfb, 0x1a, 0x77, 0x2c, 0xc2, 0xa2, 0xfa,
    0x21, 0xfc, 0xfb, 0xcd, 0x6c, 0xfb, 0xb7, 0xca,
    0xb9, 0xfa, 0x05, 0xc2, 0x97, 0xfa, 0xc3, 0x8e,
    0xfa, 0x3a, 0xfd, 0xff, 0x4f, 0x06, 0x00, 0x21,
    0xdf, 0xfa, 0x09, 0x3a, 0xf8, 0xff, 0x47, 0x86,
    0x3e, 0x10, 0xd8, 0x78, 0x32, 0xfe, 0xfb, 0x3e,
    0x20, 0x21, 0x05, 0x05, 0x22, 0xe2, 0xff, 0x0d,
    0x47, 0xf8, 0x17, 0xb7, 0xc3, 0xd7, 0xfa, 0xd5,
    0xd5, 0xf0, 0xf7, 0x21, 0xeb, 0xff, 0x4e, 0x23,
    0x5e, 0x71, 0x23, 0x7b, 0xb9, 0x7e, 0x36, 0x01,
    0xca, 0x1b, 0xfb, 0x23, 0xe5, 0x16, 0x00, 0x42,
    0x19, 0x19, 0x3a, 0xf6, 0xff, 0x77, 0x23, 0x11,
    0xfd, 0xfb, 0x1a, 0x77, 0xe1, 0x09, 0x09, 0x7e,
    0x32, 0xf6, 0xff, 0x23, 0x7e, 0x12, 0x3e, 0x7f,
    0x07, 0x0d, 0xf2, 0x10, 0xfb, 0xe6, 0x7f, 0x32,
    0xea, 0xff, 0xaf, 0x21, 0xfa, 0xfb, 0xa6, 0x32,
    0xe9, 0xff, 0xf5, 0x3a, 0xea, 0xff, 0x4f, 0x3a,
    0xf7, 0xff, 0x2f, 0xa1, 0x32, 0xf9, 0xfb, 0xee,
    0x40, 0x4f, 0x3a, 0xf6, 0xff, 0x47, 0x3a, 0xf9,
    0xff, 0xd6, 0x01, 0x9f, 0x3d, 0x2f, 0xb0, 0x77,
    0xf1, 0xc2, 0x4f, 0xfb, 0xe5, 0x2a, 0xe4, 0xff,
    0x2b, 0x7c, 0xb5, 0xc2, 0x48, 0xfb, 0xe1, 0x7e,
    0xe6, 0x80, 0xc0, 0x3a, 0xf6, 0xff, 0xf6, 0x06,
    0x77, 0x3e, 0x80, 0x37, 0xc9, 0x2a, 0xe4, 0xff,
    0x29, 0x29, 0xeb, 0x21, 0xfc, 0xfb, 0x77, 0x7e,
    0x1f, 0xd2, 0x67, 0xfb, 0x7e, 0x1f, 0x7e, 0xd0,
    0xc3, 0x76, 0xfb, 0xc3, 0xe3, 0xfa, 0x1b, 0x7a,
    0xb3, 0xc2, 0x6c, 0xfb, 0x5e, 0xe5, 0x23, 0x56,
    0x3a, 0xea, 0xff, 0xee, 0x80, 0x32, 0xf9, 0xfb,
    0xee, 0xc0, 0xe3, 0x32, 0xf9, 0xfb, 0x36, 0xd0,
    0xe3, 0x72, 0xe1, 0x7b, 0x37, 0xc9, 0x11, 0x00,
    0x00, 0x21, 0xfa, 0xfb, 0x0e, 0x10, 0x7e, 0xa1,
    0xca, 0x9e, 0xfb, 0x7e, 0xa1, 0xc2, 0xa3, 0xfb,
    0x13, 0xe3, 0xe3, 0xe3, 0xe3, 0x7e, 0xa1, 0xca,
    0xa8, 0xfb, 0xc9, 0x79, 0xe6, 0x01, 0x2f, 0x47,
    0x21, 0xeb, 0xff, 0x5e, 0x16, 0x00, 0x23, 0x7e,
    0xab, 0xf5, 0x23, 0x23, 0x19, 0x19, 0x7e, 0xf6,
    0x01, 0xa0, 0x77, 0xf1, 0xc0, 0x7e, 0x32, 0xf6,
    0xff, 0xc9, 0x21, 0x00, 0x00, 0x2b, 0x7c, 0xb5,
    0xe3, 0xe3, 0xc2, 0xd5, 0xfb, 0xc9, 0xe5, 0x21,
    0xe2, 0xfb, 0xe9, 0xe1, 0xc9, 0x79, 0xe6, 0x01,
    0x17, 0x17, 0x17, 0x17, 0x32, 0xf7, 0xff, 0xc9,
    0x00, 0x00, 0x00, 0x00, 0x00, 0xc3, 0x00, 0xf8,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
    };

/* PROM selection (default E000) */
uint8 *dj2d_prom = dj2d_promb_e000;

/*
** Western Digital WD1791 Registers and Interface Controls
*/
typedef struct {
    uint8   track;          /* Track Register */
    uint8   sector;         /* Sector Register */
    uint8   command;        /* Command Register */
    uint8   status;         /* Status Register */
    uint8   data;           /* Data Register */
    uint8   intrq;          /* Interrupt Request */
    uint8   drq;            /* Data Request */
    uint8   index;          /* Index */
    int8    stepDir;        /* Last Step Direction */
    uint32  dataCount;      /* Number of data bytes transferred from controller for current sector/address */
    uint32  trkCount;       /* Number of data bytes transferred from controller for current track */
    uint8   readActive;     /* Read Active */
    uint8   readTrkActive;  /* Read Track Active */
    uint8   writeActive;    /* Write Active */
    uint8   writeTrkActive; /* Write Track Active */
    uint8   idAddrMrk;      /* ID Addr Mark Flag */
    uint8   dataAddrMrk;    /* Data Addr Mark Flag */
    uint8   addrActive;     /* Address Active */
} WD1791_REG;

/*
** Disk Jockey 2D Registers
*/
typedef struct {
    uint8   uart_rxd;       /* UART rx data register */
    uint8   uart_txd;       /* UART tx data register */
    uint8   uart_txp;       /* UART tx data pending */
    uint8   uart_status;    /* UART status register */
    uint16  uart_baud;      /* UART baud rate */
    uint8   status;         /* Disk Jockey status register */
    uint8   control;        /* Disk Jockey control register */
    uint8   function;       /* Disk Jockey function register */
    uint8   led;            /* Disk Jockey LED status */
} DJ2D_REG;

#define WD1791_STAT_NOTREADY   0x80
#define WD1791_STAT_WRITEPROT  0x40
#define WD1791_STAT_RTYPEMSB   0x40
#define WD1791_STAT_HEADLOAD   0x20
#define WD1791_STAT_RTYPELSB   0x20
#define WD1791_STAT_WRITEFAULT 0x20
#define WD1791_STAT_SEEKERROR  0x10
#define WD1791_STAT_NOTFOUND   0x10
#define WD1791_STAT_CRCERROR   0x08
#define WD1791_STAT_TRACK0     0x04
#define WD1791_STAT_LOSTDATA   0x04
#define WD1791_STAT_INDEX      0x02
#define WD1791_STAT_DRQ        0x02
#define WD1791_STAT_BUSY       0x01

static TMLN dj2d_tmln[] = {         /* line descriptors */
    { 0 }
};

static TMXR dj2d_tmxr = {           /* multiplexer descriptor */
    1,                              /* number of terminal lines */
    0,                              /* listening port (reserved) */
    0,                              /* master socket  (reserved) */
    dj2d_tmln,                      /* line descriptor array */
    NULL,                           /* line connection order */
    NULL                            /* multiplexer device (derived internally) */
};

typedef struct {
    uint32     io_base;        /* NOT USED */
    uint32     io_size;        /* NOT USED */
    uint32     mem_base;       /* Memory Base Address */
    uint32     mem_size;       /* Memory Address space requirement */
    uint32     prom_base;      /* PROM Base Address */
    uint32     prom_size;      /* PROM Address space requirement */
    int32      conn;           /* Connected Status */
    TMLN      *tmln;           /* TMLN pointer     */
    TMXR      *tmxr;           /* TMXR pointer     */
    uint8      modelB;         /* Model B? */
    uint8      promEnabled;    /* PROM is enabled */
    uint32     ticks;          /* Timer ticks */
    uint32     sioticks;       /* SIO Timer ticks */
    uint16     headTimeout;    /* Head unload timer tick value */
    uint16     indexTimeout;   /* Index timer tick value */
    uint16     busyTimeout;    /* Busy timer tick value */
    uint8      writeProtect;   /* Write Protect is enabled */
    uint8      currentDrive;   /* currently selected drive */
    uint8      secsPerTrack;   /* sectors per track */
    uint16     bytesPerTrack;  /* bytes per track */
    uint8      sides2[DJ2D_MAX_DRIVES];         /* double sided flag */
    uint8      headLoaded[DJ2D_MAX_DRIVES];     /* Head Loaded */
    uint8      format[DJ2D_MAX_DRIVES];         /* Attached disk format */
    uint16     sectorLen[DJ2D_MAX_DRIVES];      /* Attached disk sector length */
    uint8      side[DJ2D_MAX_DRIVES];           /* side 0 or 1 */
    WD1791_REG WD1791;         /* WD1791 Registers and Data */
    DJ2D_REG   DJ2D;           /* DJ2D Registers and Data */
    UNIT      *uptr[DJ2D_UNITS];
} DJ2D_INFO;

static DJ2D_INFO dj2d_info_data = {
    0, 0,
    DJ2D_MEM_BASE, DJ2D_MEM_SIZE,
    DJ2D_PROM_BASE, DJ2D_PROM_SIZE,
    0, dj2d_tmln, &dj2d_tmxr,
    TRUE, TRUE,
    0, 0, 0, 0, 0, 0, 0, 0, 0,
    {0, 0, 0, 0}
};

static DJ2D_INFO *dj2d_info = &dj2d_info_data;

static uint8 sdata[1024];       /* Sector data buffer */

/* DJ2D Registers */
#define DJ2D_REG_BASE           (DJ2D_PROM_BASE + 0x03f8)
#define DJ2D_REG_UART_DATA      0x00  /* Register 0 */
#define DJ2D_REG_UART_STATUS    0x01  /* Register 1 */
#define DJ2D_REG_2D_CONTROL     0x01  /* Register 1 */
#define DJ2D_REG_2D_FUNCTION    0x02  /* Register 2 */
#define DJ2D_REG_2D_STATUS      0x02  /* Register 2 */
#define DJ2D_REG_1791_STATUS    0x04  /* Register 4 */
#define DJ2D_REG_1791_COMMAND   0x04  /* Register 4 */
#define DJ2D_REG_1791_TRACK     0x05  /* Register 5 */
#define DJ2D_REG_1791_SECTOR    0x06  /* Register 6 */
#define DJ2D_REG_1791_DATA      0x07  /* Register 7 */

/* DJ2D MODEL A/B */
#define DJ2D_STAT_PE            0x01  /* Parity Error  */
#define DJ2D_STAT_OE            0x02  /* Overrun Error */
#define DJ2D_STAT_DR            0x04  /* Data Ready    */
#define DJ2D_STAT_TBRE          0x08  /* TBRE          */
#define DJ2D_STAT_FE            0x10  /* Framing Error */

/* DJ2D MODEL B */
#define DJ2D_STAT_HEAD          0x01  /* HEAD        */
#define DJ2D_STAT_DATARQ        0x02  /* DATARQ      */
#define DJ2D_STAT_INTRQ         0x04  /* INTRQ       */
#define DJ2D_STAT_N2SIDED       0x08  /* NOT 2 SIDED */
#define DJ2D_STAT_INDEX         0x10  /* INDEX       */
#define DJ2D_STAT_READY         0x80  /* READY       */

#define DJ2D_CTRL_DSEL          0x0f  /* Drive Select Mask */
#define DJ2D_CTRL_SIDE0         0x10  /* Side 0 Select     */
#define DJ2D_CTRL_INTDSBL       0x20  /* Interrupt Disable */
#define DJ2D_CTRL_AENBL         0x40  /* AENBL             */
#define DJ2D_CTRL_RESET         0x80  /* Reset 1791        */

#define DJ2D_FUNC_SINGLE        0x01  /* Single Density */
#define DJ2D_FUNC_HDMASK        0x06  /* Head Mask      */
#define DJ2D_FUNC_HDLOAD        0x00  /* Head Loaded    */
#define DJ2D_FUNC_HDUNLD        0x06  /* Head Unloaded  */
#define DJ2D_FUNC_LEDOFF        0x08  /* LED Off        */

/* DJ2D MODEL A */
#define DJ2DA_STAT_INTRQ        0x01  /* INTRQ       */
#define DJ2DA_STAT_DATARQ       0x02  /* DATARQ      */
#define DJ2DA_STAT_HEAD         0x04  /* HEAD        */
#define DJ2DA_STAT_N2SIDED      0x08  /* NOT 2 SIDED */
#define DJ2DA_STAT_NINDEX       0x10  /* NOT INDEX   */
#define DJ2DA_STAT_NREADY       0x20  /* NOT READY   */

#define DJ2DA_CTRL_DSEL         0x0f  /* Drive Select Mask */
#define DJ2DA_CTRL_SIDE0        0x10  /* Side 0 Select     */
#define DJ2DA_CTRL_LEDOFF       0x20  /* LED Off           */

#define DJ2DA_FUNC_SINGLE       0x01  /* Single Density */
#define DJ2DA_FUNC_AENBL        0x02  /* AENBL          */
#define DJ2DA_FUNC_CLRFDC       0x04  /* Reset 1791     */
#define DJ2DA_FUNC_HDMASK       0x18  /* Head Mask      */
#define DJ2DA_FUNC_HDLOAD       0x00  /* Head Loaded    */
#define DJ2DA_FUNC_HDUNLD       0x18  /* Head Unloaded  */
#define DJ2DA_FUNC_VCOFF        0x20  /* VCOFF          */

/* WD1791 Commands */
#define WD1791_CMD_RESTORE        0x00
#define WD1791_CMD_SEEK           0x10
#define WD1791_CMD_STEP           0x20
#define WD1791_CMD_STEPU          (WD1791_CMD_STEP | WD1791_FLAG_U)
#define WD1791_CMD_STEPIN         0x40
#define WD1791_CMD_STEPINU        (WD1791_CMD_STEPIN | WD1791_FLAG_U)
#define WD1791_CMD_STEPOUT        0x60
#define WD1791_CMD_STEPOUTU       (WD1791_CMD_STEPOUT | WD1791_FLAG_U)
#define WD1791_CMD_READ           0x80
#define WD1791_CMD_READM          (WD1791_CMD_READ | WD1791_FLAG_M)
#define WD1791_CMD_WRITE          0xA0
#define WD1791_CMD_WRITEM         (WD1791_CMD_WRITE | WD1791_FLAG_M)
#define WD1791_CMD_READ_ADDRESS   0xC0
#define WD1791_CMD_READ_TRACK     0xE0
#define WD1791_CMD_WRITE_TRACK    0xF0
#define WD1791_CMD_FORCE_INTR     0xD0

#define WD1791_FLAG_V             0x04
#define WD1791_FLAG_H             0x08
#define WD1791_FLAG_U             0x10
#define WD1791_FLAG_M             0x10
#define WD1791_FLAG_B             0x08
#define WD1791_FLAG_S             0x01
#define WD1791_FLAG_E             0x04

#define WD1791_FLAG_A1A0_FB       0x00
#define WD1791_FLAG_A1A0_FA       0x01
#define WD1791_FLAG_A1A0_F9       0x02
#define WD1791_FLAG_A1A0_F8       0x03

#define WD1791_FLAG_I0            0x01
#define WD1791_FLAG_I1            0x02
#define WD1791_FLAG_I2            0x04
#define WD1791_FLAG_I3            0x08

#define WD1791_FLAG_R1R0_6MS      0x00
#define WD1791_FLAG_R1R0_10ms     0x02
#define WD1791_FLAG_R1R0_20ms     0x03

#define WD1791_ADDR_TRACK         0x00
#define WD1791_ADDR_ZEROS         0x01
#define WD1791_ADDR_SECTOR        0x02
#define WD1791_ADDR_LENGTH        0x03
#define WD1791_ADDR_CRC1          0x04
#define WD1791_ADDR_CRC2          0x05

/* Local function prototypes */
static t_stat dj2d_reset(DEVICE *dj2d_dev);
static t_stat dj2d_svc(UNIT *uptr);
static t_stat dj2d_sio_svc(UNIT *uptr);
static t_stat dj2d_attach(UNIT *uptr, CONST char *cptr);
static t_stat dj2d_detach(UNIT *uptr);
static t_stat dj2d_boot(int32 unitno, DEVICE *dptr);
static t_stat dj2d_set_prombase(UNIT *uptr, int32 val, CONST char *cptr, void *desc);
static t_stat dj2d_show_prombase(FILE *st, UNIT *uptr, int32 val, CONST void *desc);
static t_stat dj2d_set_model(UNIT *uptr, int32 val, CONST char *cptr, void *desc);
static t_stat dj2d_show_model(FILE *st, UNIT *uptr, int32 val, CONST void *desc);
static t_stat dj2d_set_prom(UNIT *uptr, int32 val, CONST char *cptr, void *desc);
static t_stat dj2d_show_prom(FILE *st, UNIT *uptr, int32 val, CONST void *desc);
static t_stat dj2d_set_sides(UNIT *uptr, int32 val, CONST char *cptr, void *desc);
static t_stat dj2d_show_sides(FILE *st, UNIT *uptr, int32 val, CONST void *desc);
static t_stat dj2d_set_baud(UNIT *uptr, int32 value, const char *cptr, void *desc);
static t_stat dj2d_show_baud(FILE *st, UNIT *uptr, int32 value, const void *desc);
static t_stat dj2d_config_line(void);
static uint16 sector_len(uint8 drive, uint8 track);
static uint32 secs_per_track(uint8 track);
static uint32 bytes_per_track(uint8 track);
static t_offset calculate_dj2d_sec_offset(uint8 track, uint8 sector);
static void DJ2D_HeadLoad(UNIT *uptr, WD1791_REG *pWD1791, uint8 load);
static void DJ2D_LED(DJ2D_REG *pDJ2D, int status);
static uint8 DJ2D_Read(uint32 Addr);
static uint8 DJ2D_Write(uint32 Addr, int32 data);
static const char * DJ2D_CommandString(uint8 command);
static uint8 DJ2D_Command(UNIT *uptr, WD1791_REG *pWD1791, int32 data);
static uint32 DJ2D_ReadSector(UNIT *uptr, uint8 track, uint8 sector, uint8 *buffer);
static uint32 DJ2D_WriteSector(UNIT *uptr, uint8 track, uint8 sector, uint8 *buffer);
static const char* dj2d_description(DEVICE *dptr);
static void showdata(int32 isRead);

static int32 dj2dprom(int32 Addr, int32 rw, int32 data);
static int32 dj2dmem(int32 Addr, int32 rw, int32 data);

static UNIT dj2d_unit[DJ2D_UNITS] = {
    { UDATA (dj2d_svc, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, 0), 10000 },
    { UDATA (dj2d_svc, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, 0), 10000 },
    { UDATA (dj2d_svc, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, 0), 10000 },
    { UDATA (dj2d_svc, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, 0), 10000 },
    { UDATA (dj2d_sio_svc, UNIT_ATTABLE + UNIT_DISABLE, 0), 10000 }
};

static REG dj2d_reg[] = {
    { DRDATAD (DRIVE, dj2d_info_data.currentDrive, 8, "Current drive register"), },
    { HRDATAD (STATUS, dj2d_info_data.WD1791.status, 8, "Status register"), },
    { HRDATAD (COMMAND, dj2d_info_data.WD1791.command, 8, "Command register"), },
    { HRDATAD (DATA, dj2d_info_data.WD1791.data, 8, "Data register"), },
    { DRDATAD (TRACK, dj2d_info_data.WD1791.track, 8, "Track register"), },
    { DRDATAD (SECTOR, dj2d_info_data.WD1791.sector, 8, "Sector register"), },
    { DRDATAD (SPT, dj2d_info_data.secsPerTrack, 8, "Sectors per track register"), },
    { DRDATAD (BPT, dj2d_info_data.bytesPerTrack, 16, "Bytes per track register"), },
    { DRDATAD (STEPDIR, dj2d_info_data.WD1791.stepDir, 8, "Last step direction register"), },
    { DRDATAD (SECCNT, dj2d_info_data.WD1791.dataCount, 16, "Sector byte count register"), },
    { DRDATAD (TRKCNT, dj2d_info_data.WD1791.trkCount, 16, "Track byte count register"), },
    { FLDATAD (RDACT, dj2d_info_data.WD1791.readActive, 0, "Read sector active status bit"), },
    { FLDATAD (WRACT, dj2d_info_data.WD1791.writeActive, 0, "Write sector active status bit"), },
    { FLDATAD (RDTACT, dj2d_info_data.WD1791.readTrkActive, 0, "Read track active status bit"), },
    { FLDATAD (WRTACT, dj2d_info_data.WD1791.writeTrkActive, 0, "Write track active status bit"), },
    { FLDATAD (INTRQ, dj2d_info_data.WD1791.intrq, 0, "INTRQ status bit"), },
    { FLDATAD (DRQ, dj2d_info_data.WD1791.drq, 0, "DRQ status bit"), },
    { FLDATAD (PROM, dj2d_info_data.promEnabled, 0, "PROM enabled bit"), },
    { FLDATAD (WRTPROT, dj2d_info_data.writeProtect, 0, "Write protect enabled bit"), },
    { DRDATAD (TICKS, dj2d_info_data.ticks, 32, "Timer ticks"), },
    { DRDATAD (SIOTICKS, dj2d_info_data.sioticks, 32, "SIO timer ticks"), },
    { DRDATAD (HEAD, dj2d_info_data.headTimeout, 16, "Head unload timeout"), },
    { DRDATAD (INDEX, dj2d_info_data.indexTimeout, 16, "Index timeout"), },
    { DRDATAD (BUSY, dj2d_info_data.busyTimeout, 16, "Busy timeout"), },
    { HRDATAD (DJSTAT, dj2d_info_data.DJ2D.status, 8, "DJ2D status register"), },
    { HRDATAD (DJCTRL, dj2d_info_data.DJ2D.control, 8, "DJ2D control register"), },
    { HRDATAD (DJFUNC, dj2d_info_data.DJ2D.function, 8, "DJ2D function register"), },
    { HRDATAD (URXD, dj2d_info_data.DJ2D.uart_rxd, 8, "UART RX data register"), },
    { HRDATAD (UTXD, dj2d_info_data.DJ2D.uart_txd, 8, "UART TX data register"), },
    { HRDATAD (UTXP, dj2d_info_data.DJ2D.uart_txp, 8, "UART TX data pending"), },
    { HRDATAD (USTAT, dj2d_info_data.DJ2D.uart_status, 8, "UART status register"), },
    { DRDATAD (BAUD, dj2d_info_data.DJ2D.uart_baud, 16, "UART baud rate"), },
    { FLDATAD (LED, dj2d_info_data.DJ2D.led, 0, "LED status"), },
    { NULL }
};

#define DJ2D_NAME  "DISK JOCKEY 2D/B Floppy Disk Controller"
#define DJ2D_SNAME "DJ2D"

static const char* dj2d_description(DEVICE *dptr) {
    return DJ2D_NAME;
}

#define UNIT_V_DJ2D_WPROTECT     (UNIT_V_UF + 0)                      /* WRTENB / WRTPROT */
#define UNIT_DJ2D_WPROTECT       (1 << UNIT_V_DJ2D_WPROTECT)

/* Terminal multiplexer library descriptors */

static MTAB dj2d_mod[] = {
    { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "PROM", "PROM={ENABLE|DISABLE}",
        &dj2d_set_prom, &dj2d_show_prom, NULL, "Set/Show PROM enabled/disabled status"},
    { MTAB_XTD|MTAB_VDV,    0,                  "PROMBASE",  "PROMBASE",
        &dj2d_set_prombase, &dj2d_show_prombase, NULL, "Sets PROM base address"   },
    { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "MODEL", "MODEL={A|B}",
        &dj2d_set_model, &dj2d_show_model, NULL, "Set/Show the current controller model" },
    { MTAB_XTD|MTAB_VUN|MTAB_VALR, 0, "SIDES", "SIDES={1|2}",
        &dj2d_set_sides, &dj2d_show_sides, NULL, "Set/Show the current drive sides" },
    { UNIT_DJ2D_WPROTECT,  0,                      "WRTENB",    "WRTENB",  NULL, NULL, NULL,
        "Enables " DJ2D_SNAME "n for writing"                 },
    { UNIT_DJ2D_WPROTECT,  UNIT_DJ2D_WPROTECT,  "WRTPROT",    "WRTPROT",  NULL, NULL, NULL,
        "Protects " DJ2D_SNAME "n from writing"                },
    { MTAB_XTD|MTAB_VDV|MTAB_VALR,  0,   "BAUD",  "BAUD",  &dj2d_set_baud, &dj2d_show_baud,
        NULL, "Set baud rate (default=19200)" },
    { 0 }
};

/* Debug flags */
#define ERROR_MSG           (1 << 0)
#define SEEK_MSG            (1 << 1)
#define CMD_MSG             (1 << 2)
#define RD_DATA_MSG         (1 << 3)
#define WR_DATA_MSG         (1 << 4)
#define STATUS_MSG          (1 << 5)
#define RD_DATA_DETAIL_MSG  (1 << 6)
#define WR_DATA_DETAIL_MSG  (1 << 7)
#define VERBOSE_MSG         (1 << 8)
#define DEBUG_MSG           (1 << 9)

/* Debug Flags */
static DEBTAB dj2d_dt[] = {
    { "ERROR",      ERROR_MSG,          "Error messages"        },
    { "SEEK",       SEEK_MSG,           "Seek messages"         },
    { "CMD",        CMD_MSG,            "Command messages"      },
    { "READ",       RD_DATA_MSG,        "Read messages"         },
    { "WRITE",      WR_DATA_MSG,        "Write messages"        },
    { "STATUS",     STATUS_MSG,         "Status messages"       },
    { "RDDETAIL",   RD_DATA_DETAIL_MSG, "Read detail messages"  },
    { "WRDETAIL",   WR_DATA_DETAIL_MSG, "Write detail messages" },
    { "VERBOSE",    VERBOSE_MSG,        "Verbose messages"      },
    { "DEBUG",      DEBUG_MSG,          "Debug messages"        },
    { NULL,         0                                           }
};

DEVICE dj2d_dev = {
    DJ2D_SNAME,                             /* name */
    dj2d_unit,                              /* unit */
    dj2d_reg,                               /* registers */
    dj2d_mod,                               /* modifiers */
    DJ2D_UNITS,                             /* # units */
    10,                                     /* address radix */
    31,                                     /* address width */
    1,                                      /* addr increment */
    DJ2D_UNITS,                             /* data radix */
    DJ2D_UNITS,                             /* data width */
    NULL,                                   /* examine routine */
    NULL,                                   /* deposit routine */
    &dj2d_reset,                            /* reset routine */
    &dj2d_boot,                             /* boot routine */
    &dj2d_attach,                           /* attach routine */
    &dj2d_detach,                           /* detach routine */
    &dj2d_info_data,                        /* context */
    (DEV_DISABLE | DEV_DIS | DEV_DEBUG),    /* flags */
    ERROR_MSG,                              /* debug control */
    dj2d_dt,                                /* debug flags */
    NULL,                                   /* mem size routine */
    NULL,                                   /* logical name */
    NULL,                                   /* help */
    NULL,                                   /* attach help */
    NULL,                                   /* context for help */
    &dj2d_description                       /* description */
};

/* Reset routine */
static t_stat dj2d_reset(DEVICE *dptr)
{
    uint8 i;
    DJ2D_INFO *pInfo = (DJ2D_INFO *)dptr->ctxt;

    for (i = 0; i < DJ2D_UNITS; i++) {
        if (dj2d_info->uptr[i] == NULL) {
            dj2d_info->uptr[i] = &dj2d_dev.units[i];
        }
    }

    if (dptr->flags & DEV_DIS) { /* Disconnect I/O Ports */
        sim_map_resource(pInfo->prom_base, pInfo->prom_size, RESOURCE_TYPE_MEMORY, &dj2dprom, "dj2dprom", TRUE);
        sim_map_resource(pInfo->mem_base, pInfo->mem_size, RESOURCE_TYPE_MEMORY, &dj2dmem, "dj2dmem", TRUE);

        /* Cancel timers */
        sim_cancel(dj2d_info->uptr[0]);
        sim_cancel(dj2d_info->uptr[DJ2D_SIO_UNIT]);
    } else {
        if (sim_map_resource(pInfo->prom_base, pInfo->prom_size, RESOURCE_TYPE_MEMORY, &dj2dprom, "dj2dprom", FALSE) != 0) {
            sim_debug(ERROR_MSG, &dj2d_dev, DJ2D_SNAME ": Error mapping PROM resource at 0x%04x\n", pInfo->prom_base);
            return SCPE_ARG;
        }
        if (sim_map_resource(pInfo->mem_base, pInfo->mem_size, RESOURCE_TYPE_MEMORY, &dj2dmem, "dj2dmem", FALSE) != 0) {
            sim_debug(ERROR_MSG, &dj2d_dev, DJ2D_SNAME ": Error mapping MEM resource at 0x%04x\n", pInfo->mem_base);
            return SCPE_ARG;
        }

        /* Start timer for unit 0 (we only need 1 timer for all drive units) */
        dj2d_info->indexTimeout = DJ2D_ROTATION_MS;
        sim_activate_after(dj2d_info->uptr[0], DJ2D_TIMER * 1000);

        /* Start timer for SIO unit */
        sim_activate_after(dj2d_info->uptr[DJ2D_SIO_UNIT], 500);  /* activate 500 us timer */

        /* Disable clockFrequency if it's set */
        if (getClockFrequency()) {
            setClockFrequency(0);
            sim_printf(DJ2D_SNAME ": CPU CLOCK register not supported. Use THROTTLE.\n");
        }

        /* Configure the serial interface */
        dj2d_config_line();
    }

    /* Reset Registers */
    pInfo->currentDrive = 0;
    pInfo->promEnabled = TRUE;
    pInfo->writeProtect = FALSE;

    pInfo->DJ2D.uart_status = DJ2D_STAT_TBRE;
    pInfo->DJ2D.uart_txp = FALSE;
    pInfo->DJ2D.uart_baud = DJ2D_BAUD;
    pInfo->DJ2D.led = FALSE;

    pInfo->WD1791.track = 0;
    pInfo->WD1791.sector = 1;
    pInfo->WD1791.command = 0;
    pInfo->WD1791.status = 0;
    pInfo->WD1791.data = 0;
    pInfo->WD1791.drq = FALSE;
    pInfo->WD1791.index = FALSE;
    pInfo->WD1791.intrq = FALSE;
    pInfo->WD1791.stepDir = 1;
    pInfo->WD1791.dataCount = 0;
    pInfo->WD1791.trkCount = 0;
    pInfo->WD1791.addrActive = FALSE;
    pInfo->WD1791.readActive = FALSE;
    pInfo->WD1791.readTrkActive = FALSE;
    pInfo->WD1791.writeActive = FALSE;
    pInfo->WD1791.writeTrkActive = FALSE;
    pInfo->WD1791.addrActive = FALSE;

    for (i = 0; i < DJ2D_MAX_DRIVES; i++) {
        dj2d_info->headLoaded[i] = FALSE;
    }

    sim_debug(STATUS_MSG, &dj2d_dev, DJ2D_SNAME ": reset controller.\n");

    return SCPE_OK;
}

static t_stat dj2d_sio_svc(UNIT *uptr)
{
    int32 c;
    t_stat r;

    dj2d_info->sioticks++;

    /* Check for new incoming connection */
    if (uptr->flags & UNIT_ATT) {
        if (tmxr_poll_conn(dj2d_info->tmxr) >= 0) {      /* poll connection */

            dj2d_info->conn = 1;          /* set connected   */

            sim_debug(STATUS_MSG, uptr->dptr, "new connection.\n");
        }
    }

    /* TX byte pending? */
    if (dj2d_info->DJ2D.uart_txp) {
        if (uptr->flags & UNIT_ATT) {
            r = tmxr_putc_ln(dj2d_info->tmln, dj2d_info->DJ2D.uart_txd);
        } else {
            r = sim_putchar(dj2d_info->DJ2D.uart_txd);
        }

        dj2d_info->DJ2D.uart_txp = FALSE;

        if (r == SCPE_LOST) {
            dj2d_info->conn = 0;          /* Connection was lost */
            sim_debug(STATUS_MSG, uptr->dptr, "lost connection.\n");
        }
    }

    /* Update TBRE */
    if (!(dj2d_info->DJ2D.uart_status & DJ2D_STAT_TBRE)) {
        if (uptr->flags & UNIT_ATT) {
            tmxr_poll_tx(dj2d_info->tmxr);
            dj2d_info->DJ2D.uart_status |= (tmxr_txdone_ln(dj2d_info->tmln) && dj2d_info->conn) ? DJ2D_STAT_TBRE : 0;
        } else {
            dj2d_info->DJ2D.uart_status |= DJ2D_STAT_TBRE;
        }
    }

    /* Check for Data if RX buffer empty */
    if (!(dj2d_info->DJ2D.uart_status & DJ2D_STAT_DR)) {
        if (uptr->flags & UNIT_ATT) {
            tmxr_poll_rx(dj2d_info->tmxr);

            c = tmxr_getc_ln(dj2d_info->tmln);
        } else {
            c = sim_poll_kbd();
        }

        if (c & (TMXR_VALID | SCPE_KFLAG)) {
            dj2d_info->DJ2D.uart_rxd = c & 0xff;
            dj2d_info->DJ2D.uart_status |= DJ2D_STAT_DR;
            dj2d_info->DJ2D.uart_status &= ~(DJ2D_STAT_FE | DJ2D_STAT_OE | DJ2D_STAT_PE);
        }
    }

    /* Restart timer */
    sim_activate_after(uptr, 500);  /* reactivate 500 us timer */

    return SCPE_OK;
}

static t_stat dj2d_svc(UNIT *uptr)
{
    WD1791_REG *pWD1791;

    pWD1791 = &dj2d_info->WD1791;

    dj2d_info->ticks++;

    if (dj2d_info->headTimeout) {
        if (!(--dj2d_info->headTimeout)) {
            DJ2D_HeadLoad(uptr, pWD1791, FALSE);
        }
    }

    if (dj2d_info->indexTimeout) {
        if (!(--dj2d_info->indexTimeout)) {
            pWD1791->index = FALSE;
            dj2d_info->indexTimeout = DJ2D_INDEX_TIMEOUT;
        } else {
            pWD1791->index = TRUE;
        }
    }

    if (dj2d_info->busyTimeout) {
        if (!(--dj2d_info->busyTimeout)) {
            pWD1791->status &= ~WD1791_STAT_BUSY;
            pWD1791->drq = FALSE;
            pWD1791->intrq = TRUE;
        }
    }

    /* Restart timer */
    sim_activate_after(uptr, DJ2D_TIMER * 1000);  /* activate timer */

    return SCPE_OK;
}

/*
** Verify that prombase is within valid range
** before calling set_membase
*/
static t_stat dj2d_set_prombase(UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
    uint32 newba;
    t_stat r;

    if (cptr == NULL)
        return SCPE_ARG;

    newba = get_uint (cptr, 16, 0xFFFF, &r);
    if (r != SCPE_OK)
        return r;

    if ((newba != 0xe000) && (newba != 0xf800)) {
        sim_printf(DJ2D_SNAME ": Valid options are E000,F800\n");
        return SCPE_ARG;
    }

    /*
    ** Release previous memory maps
    */
    sim_map_resource(dj2d_info->prom_base, dj2d_info->prom_size, RESOURCE_TYPE_MEMORY, &dj2dprom, "dj2dprom", TRUE);
    sim_map_resource(dj2d_info->mem_base, dj2d_info->mem_size, RESOURCE_TYPE_MEMORY, &dj2dmem, "dj2dmem", TRUE);

    dj2d_info->prom_base = newba;
    dj2d_info->mem_base = newba+DJ2D_PROM_SIZE;

    if (sim_map_resource(dj2d_info->prom_base, dj2d_info->prom_size, RESOURCE_TYPE_MEMORY, &dj2dprom, "dj2dprom", FALSE) != 0) {
        sim_debug(ERROR_MSG, &dj2d_dev, DJ2D_SNAME ": Error mapping PROM resource at 0x%04x\n", dj2d_info->prom_base);
        return SCPE_ARG;
    }
    if (sim_map_resource(dj2d_info->mem_base, dj2d_info->mem_size, RESOURCE_TYPE_MEMORY, &dj2dmem, "dj2dmem", FALSE) != 0) {
        sim_debug(ERROR_MSG, &dj2d_dev, DJ2D_SNAME ": Error mapping MEM resource at 0x%04x\n", dj2d_info->mem_base);
        return SCPE_ARG;
    }

    if (newba == 0xe000) {
        if (dj2d_info->modelB) {
            dj2d_prom = dj2d_promb_e000;
        } else {
            dj2d_prom = dj2d_proma_e000;
        }
    } else {
        if (dj2d_info->modelB) {
            dj2d_prom = dj2d_promb_f800;
        } else {
            dj2d_prom = dj2d_proma_f800;
        }
    }

    return dj2d_reset(&dj2d_dev);
}

/* Show Base Address routine */
t_stat dj2d_show_prombase(FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
    DEVICE *dptr;
    DJ2D_INFO *pInfo;

    if (uptr == NULL)
        return SCPE_IERR;
    dptr = find_dev_from_unit (uptr);
    if (dptr == NULL)
        return SCPE_IERR;

    pInfo = (DJ2D_INFO *) dptr->ctxt;

    if(pInfo->promEnabled) {
        fprintf(st, "PROM=0x%04X-0x%04X", pInfo->prom_base, pInfo->prom_base+pInfo->prom_size-9);
        fprintf(st, ", REG=0x%04X-0x%04X", pInfo->prom_base+pInfo->prom_size-8, pInfo->prom_base+pInfo->prom_size-1);
        fprintf(st, ", RAM=0x%04X-0x%04X", pInfo->mem_base, pInfo->mem_base+pInfo->mem_size-1);
    }

    return SCPE_OK;
}

static t_stat dj2d_set_model(UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
    char base[5];

    if (!cptr) return SCPE_IERR;

    /* this assumes that the parameter has already been upcased */
    if (!strcmp(cptr, "B")) {
        dj2d_info->modelB = TRUE;
    } else if (!strcmp(cptr, "A")) {
        dj2d_info->modelB = FALSE;
    } else {
        return SCPE_ARG;
    }

    sprintf(base, "%04x", dj2d_info->prom_base);

    dj2d_set_prombase(uptr, dj2d_info->prom_base, base, "PROMBASE");

    return SCPE_OK;
}

static t_stat dj2d_show_model(FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
    fprintf(st, "MODEL=%s", (dj2d_info->modelB) ? "B" : "A");

    return SCPE_OK;
}

static t_stat dj2d_set_sides(UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
    int i;

    if (!cptr) return SCPE_IERR;

    for (i = 0; i < DJ2D_MAX_DRIVES; i++) {
        if (dj2d_info->uptr[i] == uptr) {
            break;
        }
    }

    /* this assumes that the parameter has already been upcased */
    if (i < DJ2D_MAX_DRIVES) {
        if (!strcmp(cptr, "1")) {
            dj2d_info->sides2[i] = FALSE;
        } else if (!strcmp(cptr, "2")) {
            dj2d_info->sides2[i] = TRUE;
        } else {
            return SCPE_ARG;
        }

        return SCPE_OK;
    }

    return SCPE_ARG;
}

static t_stat dj2d_show_sides(FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
    int i;

    for (i = 0; i < DJ2D_MAX_DRIVES; i++) {
        if (dj2d_info->uptr[i] == uptr) {
            fprintf(st, "SIDES=%d", (dj2d_info->sides2[i]) ? 2 : 1);
            break;
        }
    }

    return SCPE_OK;
}

/* Attach routine */
static t_stat dj2d_attach(UNIT *uptr, CONST char *cptr)
{
    char header[4];
    t_stat r;
    int i,f;

    /* Attaching to serial interface? */
    if (uptr == &dj2d_dev.units[DJ2D_SIO_UNIT]) {
        if ((r = tmxr_attach(dj2d_info->tmxr, uptr, cptr)) == SCPE_OK) {
            dj2d_info->tmln->rcve = 1;

            sim_debug(VERBOSE_MSG, uptr->dptr, "attached '%s' to serial interface.\n", cptr);
        }

        return r;
    }

    r = attach_unit(uptr, cptr);    /* attach unit  */
    if (r != SCPE_OK) {              /* error?       */
        sim_debug(ERROR_MSG, &dj2d_dev, DJ2D_SNAME ": ATTACH error=%d\n", r);
        return r;
    }

    for (i = 0; i < DJ2D_MAX_DRIVES; i++) {
        if (dj2d_dev.units[i].fileref == uptr->fileref) {
            break;
        }
    }

    if (i >= DJ2D_MAX_DRIVES) {
        return SCPE_ARG;
    }

    uptr->capac = sim_fsize(uptr->fileref);

    /* Default is 1024 byte sectors */
    dj2d_info->format[i] = FMT_1024;
    dj2d_info->sectorLen[i] = dj2d_sector_len[FMT_1024];
    dj2d_info->sides2[i] = FALSE;

    for (f = 0; f < FMT_UNKNOWN; f++) {
        if (uptr->capac == dj2d_image_size[f]) {
            dj2d_info->format[i] = f;
            dj2d_info->sectorLen[i] = dj2d_sector_len[f];
            dj2d_info->sides2[i] = FALSE;
        } else if (uptr->capac == dj2d_2s_image_size[f]) {
            dj2d_info->format[i] = f;
            dj2d_info->sectorLen[i] = dj2d_sector_len[f];
            dj2d_info->sides2[i] = TRUE;
        }
    }

    sim_debug(DEBUG_MSG, &dj2d_dev, DJ2D_SNAME ": ATTACH drive=%d uptr->capac=%d format=%d sectorLen=%d 2sided=%d\n", i, uptr->capac, dj2d_info->format[i], dj2d_info->sectorLen[i], dj2d_info->sides2[i]);

    /* Default for new file is DSK */
    uptr->u3 = IMAGE_TYPE_DSK;

    if (uptr->capac > 0) {
        char *rtn = fgets(header, 4, uptr->fileref);
        if ((rtn != NULL) && (strncmp(header, "CPT", 3) == 0)) {
            sim_printf("CPT images not yet supported\n");
            uptr->u3 = IMAGE_TYPE_CPT;
            dj2d_detach(uptr);
            return SCPE_OPENERR;
        }
    }


    sim_debug(VERBOSE_MSG, uptr->dptr, DJ2D_SNAME "%d: attached to '%s', type=%s, len=%d\n", i, cptr,
        uptr->u3 == IMAGE_TYPE_CPT ? "CPT" : "DSK",
        uptr->capac);

    return SCPE_OK;
}


/* Detach routine */
static t_stat dj2d_detach(UNIT *uptr)
{
    t_stat r;
    int8 i;

    for (i = 0; i < DJ2D_UNITS; i++) {
        if (dj2d_dev.units[i].fileref == uptr->fileref) {
            break;
        }
    }

    if (i >= DJ2D_UNITS) {
        return SCPE_ARG;
    }

    r = detach_unit(uptr);  /* detach unit */

    if (r != SCPE_OK) {
        return r;
    }

    dj2d_dev.units[i].fileref = NULL;

    dj2d_info->WD1791.index = TRUE;
    dj2d_info->indexTimeout = 0;

    sim_debug(VERBOSE_MSG, uptr->dptr, DJ2D_SNAME "%d: detached\n", i);

    return SCPE_OK;
}

static t_stat dj2d_set_baud(UNIT *uptr, int32 value, const char *cptr, void *desc)
{
    int32 baud;
    t_stat r = SCPE_ARG;

    /* Force serial interface unit */
    uptr = &dj2d_dev.units[DJ2D_SIO_UNIT];

    if (!(uptr->flags & UNIT_ATT)) {
        return SCPE_UNATT;
    }

    if (cptr != NULL) {
        if (sscanf(cptr, "%d", &baud)) {
            switch (baud) {
                case 110:
                case 1200:
                case 9600:
                case 19200:
                    dj2d_info->DJ2D.uart_baud = baud;
                    r = dj2d_config_line();
                    break;

                default:
                    break;
            }
        }
    }

    return r;
}

static t_stat dj2d_show_baud(FILE *st, UNIT *uptr, int32 value, const void *desc)
{
    if (uptr->flags & UNIT_ATT) {
        fprintf(st, "Baud rate: %d", dj2d_info->DJ2D.uart_baud);
    }

    return SCPE_OK;
}

static t_stat dj2d_config_line(void)
{
    char config[20];
    const char *fmt;
    t_stat r = SCPE_IERR;

    fmt = "8N1";

    sprintf(config, "%d-%s", dj2d_info->DJ2D.uart_baud, fmt);

    r = tmxr_set_config_line(dj2d_info->tmln, config);

    sim_debug(STATUS_MSG, &dj2d_dev, "port configuration set to '%s'.\n", config);

    return r;
}

static t_stat dj2d_set_prom(UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
    if (!cptr) return SCPE_IERR;
    if (!strlen(cptr)) return SCPE_ARG;

    /* this assumes that the parameter has already been upcased */
    if (!strncmp(cptr, "ENABLE", strlen(cptr))) {
        sim_map_resource(dj2d_info->prom_base, dj2d_info->prom_size,
            RESOURCE_TYPE_MEMORY, &dj2dprom, "dj2dprom", FALSE);
        dj2d_info->promEnabled = TRUE;
    } else if (!strncmp(cptr, "DISABLE", strlen(cptr))) {
        dj2d_info->promEnabled = FALSE;
        sim_map_resource(dj2d_info->prom_base, dj2d_info->prom_size,
            RESOURCE_TYPE_MEMORY, &dj2dprom, "dj2dprom", TRUE);
    } else {
        return SCPE_ARG;
    }

    return SCPE_OK;
}

static t_stat dj2d_show_prom(FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
    if (dj2d_info->promEnabled) {
        fprintf(st, "PROM");
    } else {
        fprintf(st, "NOPROM");
    }

    return SCPE_OK;
}

static t_stat dj2d_boot(int32 unitno, DEVICE *dptr)
{

    DJ2D_INFO *pInfo = (DJ2D_INFO *)dptr->ctxt;

    sim_debug(STATUS_MSG, &dj2d_dev, DJ2D_SNAME ": Booting Controller at 0x%04x\n", pInfo->prom_base);

    *((int32 *) sim_PC->loc) = pInfo->prom_base;

    return SCPE_OK;
}

static void showdata(int32 isRead) {
    int32 i;
    sim_debug(isRead ? RD_DATA_DETAIL_MSG : WR_DATA_DETAIL_MSG, &dj2d_dev, DJ2D_SNAME ": %s track/sector %02d/%03d:\n\t", isRead ? "Read" : "Write", dj2d_info->WD1791.track, dj2d_info->WD1791.sector);
    for (i = 0; i < sector_len(dj2d_info->currentDrive, dj2d_info->WD1791.track); i++) {
        sim_debug(isRead ? RD_DATA_DETAIL_MSG : WR_DATA_DETAIL_MSG, &dj2d_dev, "%02X ", sdata[i]);
        if (((i+1) & 0xf) == 0) {
            sim_debug(isRead ? RD_DATA_DETAIL_MSG : WR_DATA_DETAIL_MSG, &dj2d_dev, "\n\t");
        }
    }
    if (isRead) {
        sim_debug(RD_DATA_DETAIL_MSG, &dj2d_dev, "\n");
    } else {
        sim_debug(WR_DATA_DETAIL_MSG, &dj2d_dev, "\n");
    }
}

static uint16 sector_len(uint8 drive, uint8 track)
{
    if (track == 0) {  /* Track 0 is always SD */
        return(dj2d_sector_len[FMT_SD]);
    }

    return(dj2d_info->sectorLen[drive]);
}

static uint32 secs_per_track(uint8 track)
{
    if (track == 0) {
        dj2d_info->secsPerTrack = (uint8)dj2d_spt[FMT_SD];
    } else {
        dj2d_info->secsPerTrack = (uint8)dj2d_spt[dj2d_info->format[dj2d_info->currentDrive]];
    }

    return dj2d_info->secsPerTrack;
}

static uint32 bytes_per_track(uint8 track)
{
    int8 format;

    format = dj2d_info->format[dj2d_info->currentDrive];

    if (track == 0) {
        dj2d_info->bytesPerTrack = dj2d_track_len[FMT_SD];
    } else {
        dj2d_info->bytesPerTrack = dj2d_track_len[format];
    }

    return dj2d_info->bytesPerTrack;
}

static t_offset calculate_dj2d_sec_offset(uint8 track, uint8 sector)
{
    t_offset offset;
    uint8 ds;
    uint8 format;

    ds = dj2d_info->side[dj2d_info->currentDrive];
    format = dj2d_info->format[dj2d_info->currentDrive];

    /*
    ** Side 0: tracks 0-76
    ** Side 1: tracks 77-153
    */
    if (ds) {
        track += 77;
    }

    /*
    ** Calculate track offset
    */
    if (track == 0) {    /* Track 0 is always SD */
        offset = 0;
        format = FMT_SD;
    } else {
        offset = (t_offset)(dj2d_spt[FMT_SD]) * (t_offset)(dj2d_sector_len[FMT_SD]); /* Track 0 / Side 0 always SD */
        offset += (t_offset)(track-1) * (t_offset)(dj2d_spt[format]) * (t_offset)(dj2d_sector_len[format]); /* Track 1-153 */
    }

    /*
    ** Add sector offset to track offset
    */
    offset += (t_offset)(sector-1) * (t_offset)(dj2d_sector_len[format]);

    sim_debug(DEBUG_MSG, &dj2d_dev, DJ2D_SNAME ": OFFSET=%08llx drive=%d side=%d format=%d track=%03d sector=%03d\r\n", offset, dj2d_info->currentDrive, ds, dj2d_info->format[dj2d_info->currentDrive], track, sector);

    return (offset);
}

static void DJ2D_HeadLoad(UNIT *uptr, WD1791_REG *pWD1791, uint8 load)
{
    /*
    ** If no disk has been attached, uptr will be NULL - return
    */
    if (uptr == NULL) {
        return;
    }

    if (load) {
        dj2d_info->headTimeout = DJ2D_HEAD_TIMEOUT;

        if (dj2d_info->headLoaded[dj2d_info->currentDrive] == FALSE) {
            sim_debug(STATUS_MSG, &dj2d_dev, DJ2D_SNAME ": Drive %d head Loaded.\n", dj2d_info->currentDrive);
        }
    } else {
        dj2d_info->headTimeout = 0;

        if (dj2d_info->headLoaded[dj2d_info->currentDrive] == TRUE) {
            sim_debug(STATUS_MSG, &dj2d_dev, DJ2D_SNAME ": Drive %d head Unloaded.\n", dj2d_info->currentDrive);
        }
    }

    dj2d_info->headLoaded[dj2d_info->currentDrive] = load;
}

static void DJ2D_LED(DJ2D_REG *pDJ2D, int status)
{
        if (pDJ2D->led != status) {
            sim_debug(STATUS_MSG, &dj2d_dev, DJ2D_SNAME ": LED %s.\n", (status) ? "ON" : "OFF");
        }

        pDJ2D->led = status;
}

static uint8 DJ2D_Read(uint32 Addr)
{
    uint8 cData;
    uint8 driveNum;
    WD1791_REG *pWD1791;
    DJ2D_REG *pDJ2D;
    UNIT *uptr;

    driveNum = dj2d_info->currentDrive;
    uptr = dj2d_info->uptr[driveNum];
    pWD1791 = &dj2d_info->WD1791;
    pDJ2D = &dj2d_info->DJ2D;

    switch(Addr & 0x07) {
        case DJ2D_REG_UART_DATA:  /* Read character from UART */
            if (pDJ2D->uart_status & DJ2D_STAT_DR) {
                cData = ~pDJ2D->uart_rxd;  /* Inverted */
                pDJ2D->uart_status &= ~DJ2D_STAT_DR;
            } else {
                cData = 0xff;
            }
            break;

        case DJ2D_REG_UART_STATUS:
            cData = ~pDJ2D->uart_status;  /* Inverted */
            break;

        case DJ2D_REG_2D_STATUS:
            if (dj2d_info->modelB) {
                cData = (pWD1791->intrq) ? DJ2D_STAT_INTRQ : 0;
                cData |= (pWD1791->drq) ? DJ2D_STAT_DATARQ : 0;
                cData |= (pWD1791->index) ? DJ2D_STAT_INDEX : 0;
                cData |= (dj2d_info->headLoaded[dj2d_info->currentDrive]) ? DJ2D_STAT_HEAD : 0;
                cData |= (pWD1791->status & WD1791_STAT_NOTREADY) ? 0 : DJ2D_STAT_READY;
                cData |= (dj2d_info->sides2[dj2d_info->currentDrive]) ? 0 : DJ2D_STAT_N2SIDED;
            } else {
                cData = (pWD1791->intrq) ? DJ2DA_STAT_INTRQ : 0;
                cData |= (pWD1791->drq) ? DJ2DA_STAT_DATARQ : 0;
                cData |= (pWD1791->index) ? 0 : DJ2DA_STAT_NINDEX;
                cData |= (dj2d_info->headLoaded[dj2d_info->currentDrive]) ? DJ2DA_STAT_HEAD : 0;
                cData |= (pWD1791->status & WD1791_STAT_NOTREADY) ? DJ2DA_STAT_NREADY : 0;
                cData |= (dj2d_info->sides2[dj2d_info->currentDrive]) ? 0 : DJ2D_STAT_N2SIDED;
            }
            if (pDJ2D->status != cData) {
                sim_debug(STATUS_MSG, &dj2d_dev, DJ2D_SNAME ": 2D_STATUS=%02X\n", cData);
            }
            pDJ2D->status = cData;
            break;

        case DJ2D_REG_1791_STATUS:
            cData = pWD1791->status;
            break;

        case DJ2D_REG_1791_TRACK:
            cData = pWD1791->track;
            break;

        case DJ2D_REG_1791_DATA:
            /*
            ** If a READ operation is currently active, get the next byte
            */
            if (pWD1791->readActive) {
                /* Store byte in DATA register */
                pWD1791->data = sdata[pWD1791->dataCount++];

                /* If we reached the end of the sector, terminate command and set INTRQ */
                if (pWD1791->dataCount == sector_len(driveNum, pWD1791->track)) {
                    pWD1791->readActive = FALSE;
                    pWD1791->dataCount = 0;
                    pWD1791->status = 0x00;
                    pWD1791->drq = FALSE;
                    pWD1791->intrq = TRUE;
                }

                DJ2D_HeadLoad(uptr, pWD1791, TRUE);
            } else if (pWD1791->readTrkActive) {
                /* If we reached the end of the track data, terminate command and set INTRQ */
                if (pWD1791->trkCount == bytes_per_track(pWD1791->track)) {
                    pWD1791->readTrkActive = FALSE;
                    pWD1791->status = 0x00;
                    pWD1791->drq = FALSE;
                    pWD1791->intrq = TRUE;
                } else {
                    pWD1791->trkCount++;
                }

                DJ2D_HeadLoad(uptr, pWD1791, TRUE);
            } else if (pWD1791->addrActive) {
                /* Store byte in DATA register */
                pWD1791->data = sdata[pWD1791->dataCount++];

                /* If we reached the end of the address data, terminate command and set INTRQ */
                if (pWD1791->dataCount > WD1791_ADDR_CRC2) {
                    pWD1791->addrActive = FALSE;
                    pWD1791->status = 0x00;
                    pWD1791->drq = FALSE;
                    pWD1791->intrq = TRUE;
                }

                DJ2D_HeadLoad(uptr, pWD1791, TRUE);
            }

            cData = pWD1791->data;
            break;

        case DJ2D_REG_1791_SECTOR:
            cData = pWD1791->sector;
            break;

        default:
            sim_debug(ERROR_MSG, &dj2d_dev, DJ2D_SNAME ": READ REG Invalid I/O Address %02x (%02x)\n", Addr & 0xFF, Addr & 0x07);
            cData = 0xff;
            break;
    }

    sim_debug(DEBUG_MSG, &dj2d_dev, DJ2D_SNAME ": READ REG currentDrive=%d format=%d track=%02d sector=%02d data=%02x status=%02x\n", dj2d_info->currentDrive, dj2d_info->format[dj2d_info->currentDrive], pWD1791->track, pWD1791->sector, pWD1791->data, pWD1791->status);

    return (cData);
}

static uint8 DJ2D_Write(uint32 Addr, int32 Data)
{
    uint8 cData;
    uint8 driveNum;
    int32 rtn;
    UNIT *uptr;
    WD1791_REG *pWD1791;
    DJ2D_REG *pDJ2D;

    Data &= 0xff;

    sim_debug(CMD_MSG, &dj2d_dev, DJ2D_SNAME ": OUT %04X Data %02X\n", Addr, Data);

    cData = 0;
    driveNum = dj2d_info->currentDrive;
    uptr = dj2d_info->uptr[driveNum];
    pWD1791 = &dj2d_info->WD1791;
    pDJ2D = &dj2d_info->DJ2D;

    switch(Addr & 0x07) {
        case DJ2D_REG_UART_DATA:
            pDJ2D->uart_txd = ~Data;  /* Character is inverted */
            pDJ2D->uart_txp = TRUE;
            pDJ2D->uart_status &= ~DJ2D_STAT_TBRE;
            break;

        case DJ2D_REG_1791_COMMAND:
            cData = DJ2D_Command(uptr, pWD1791, Data);
            break;

        case DJ2D_REG_2D_FUNCTION:
            pDJ2D->function = Data;

            if (dj2d_info->modelB) {
                switch (Data & DJ2D_FUNC_HDMASK) {
                    case DJ2D_FUNC_HDLOAD:
                        DJ2D_HeadLoad(uptr, pWD1791, TRUE);
                        break;

                    case DJ2D_FUNC_HDUNLD:
                        DJ2D_HeadLoad(uptr, pWD1791, FALSE);
                        break;

                    default:
                        break;
                }

                DJ2D_LED(pDJ2D, !(Data & DJ2D_FUNC_LEDOFF));

            } else {
                switch (Data & DJ2DA_FUNC_HDMASK) {
                    case DJ2DA_FUNC_HDLOAD:
                        DJ2D_HeadLoad(uptr, pWD1791, TRUE);
                        break;

                    case DJ2DA_FUNC_HDUNLD:
                        DJ2D_HeadLoad(uptr, pWD1791, FALSE);
                        break;

                    default:
                        break;
                }
            }

            break;

        case DJ2D_REG_1791_DATA:
            pWD1791->data = Data;   /* Store byte in DATA register */

            if (pWD1791->writeActive) {

                /* Store DATA register in Sector Buffer */
                sdata[pWD1791->dataCount++] = pWD1791->data;

                /* If we reached the end of the sector, write sector, terminate command and set INTRQ */
                if (pWD1791->dataCount == sector_len(driveNum, pWD1791->track)) {
                    pWD1791->status = 0x00;  /* Clear Status Bits */

                    rtn = DJ2D_WriteSector(uptr, pWD1791->track, pWD1791->sector, sdata);

                    showdata(FALSE);

                    if (rtn != sector_len(driveNum, pWD1791->track)) {
                        sim_debug(ERROR_MSG, &dj2d_dev, DJ2D_SNAME ": sim_fwrite errno=%d\n", errno);

                        pWD1791->status |= WD1791_STAT_WRITEFAULT;
                    }
                    pWD1791->writeActive = FALSE;
                    pWD1791->dataCount = 0;
                    pWD1791->drq = FALSE;
                    pWD1791->intrq = TRUE;
                }

                DJ2D_HeadLoad(uptr, pWD1791, TRUE);
            } else if (pWD1791->writeTrkActive) {

                if (pWD1791->idAddrMrk) {
                    if (++pWD1791->dataCount == 4) {   /* Sector Len */
                        dj2d_info->sectorLen[dj2d_info->currentDrive] = dj2d_sector_len[pWD1791->data];
                        dj2d_info->format[dj2d_info->currentDrive] = pWD1791->data;
                        pWD1791->idAddrMrk = 0;
                        pWD1791->dataCount = 0;
                    }
                } else if (pWD1791->dataAddrMrk) {
                    /* Store DATA register in Sector Buffer */
                    sdata[pWD1791->dataCount++] = pWD1791->data;

                    /* If we reached the end of the sector, write sector */
                    if (pWD1791->dataCount == sector_len(driveNum, pWD1791->track)) {
                        pWD1791->status &= ~WD1791_STAT_WRITEFAULT;  /* Clear Status Bit */

                        showdata(FALSE);

                        rtn = DJ2D_WriteSector(uptr, pWD1791->track, pWD1791->sector, sdata);

                        if (rtn != sector_len(driveNum, pWD1791->track)) {
                            pWD1791->status |= WD1791_STAT_WRITEFAULT;
                            sim_debug(ERROR_MSG, &dj2d_dev, DJ2D_SNAME ": WRITE ERROR could not write track %03d sector %03d\n", pWD1791->track, pWD1791->sector);
                        }

                        sim_debug(DEBUG_MSG, &dj2d_dev, DJ2D_SNAME ": WRITE TRACK drive=%d track=%03d sector=%03d trkcount=%d datacount=%d data=%02X status=%02X\n", driveNum, pWD1791->track, pWD1791->sector, pWD1791->trkCount, pWD1791->dataCount, pWD1791->data, pWD1791->status);

                        pWD1791->dataCount = 0;
                        pWD1791->idAddrMrk = FALSE;
                        pWD1791->dataAddrMrk = FALSE;

                        if (pWD1791->sector < secs_per_track(pWD1791->track)) {
                            pWD1791->sector++;
                        }
                    }
                } else if (pWD1791->data == 0xFE) {
                        pWD1791->idAddrMrk = TRUE;
                } else if (pWD1791->data == 0xFB) {
                        pWD1791->dataAddrMrk = TRUE;
                }

                /*
                ** Increment number for bytes written to track
                */
                pWD1791->trkCount++;

                if (pWD1791->trkCount == bytes_per_track(pWD1791->track)) {
                    pWD1791->status = 0x00;  /* Clear Status Bits */
                    pWD1791->drq = FALSE;
                    pWD1791->intrq = TRUE;
                    pWD1791->writeTrkActive = FALSE;

                    sim_debug(WR_DATA_MSG, &dj2d_dev, DJ2D_SNAME ": WRITE TRACK COMPLETE track=%03d sector=%03d trkcount=%d datacount=%d data=%02X status=%02X\n", pWD1791->track, pWD1791->sector, pWD1791->trkCount, pWD1791->dataCount, pWD1791->data, pWD1791->status);
                }

                DJ2D_HeadLoad(uptr, pWD1791, TRUE);
            }

            break;

        case DJ2D_REG_1791_TRACK:
            pWD1791->track = Data;
            break;

        case DJ2D_REG_1791_SECTOR:
            pWD1791->sector = Data;
            break;

        case DJ2D_REG_2D_CONTROL:
            pDJ2D->control = Data & 0xff;

            /* Drive Select */
            switch (~Data & DJ2D_CTRL_DSEL) {
                case 0x01:
                    cData = 0;
                    break;
                case 0x02:
                    cData = 1;
                    break;
                case 0x04:
                    cData = 2;
                    break;
                case 0x08:
                    cData = 3;
                    break;
            }

            /* Side */
            if (dj2d_info->sides2[cData]) {
                dj2d_info->side[cData] = (Data & DJ2D_CTRL_SIDE0) == 0x00;
            } else {
                dj2d_info->side[cData] = FALSE;
            }

            if (dj2d_info->currentDrive != cData) {
                sim_debug(STATUS_MSG, &dj2d_dev, DJ2D_SNAME ": Current drive now %d side %d\n", cData, dj2d_info->side[cData]);
            }

            dj2d_info->currentDrive = cData;

            if (dj2d_info->modelB) {
                if (Data & DJ2D_CTRL_RESET) {
                    /* Reset 1791 */
                    pWD1791->status = 0x00;
                    pWD1791->track = 0;
                    pWD1791->dataCount = 0;
                    pWD1791->trkCount = 0;
                    pWD1791->readActive = FALSE;
                    pWD1791->readTrkActive = FALSE;
                    pWD1791->writeActive = FALSE;
                    pWD1791->writeTrkActive = FALSE;
                    pWD1791->addrActive = FALSE;
                    pWD1791->dataAddrMrk = FALSE;
                    pWD1791->idAddrMrk = FALSE;
                    pWD1791->drq = FALSE;
                    pWD1791->intrq = FALSE;
                }
            } else {
                DJ2D_LED(pDJ2D, !(Data & DJ2DA_CTRL_LEDOFF));
            }
            break;

        default:
            sim_debug(ERROR_MSG, &dj2d_dev, DJ2D_SNAME ": WRITE Invalid I/O Address %02x (%02x)\n", Addr & 0xFF, Addr & 0x07);
            cData = 0xff;
            break;
    }

    sim_debug(DEBUG_MSG, &dj2d_dev, DJ2D_SNAME ": WRITE REG currentDrive=%d format=%d track=%02d sector=%02d data=%02x status=%02x\n", dj2d_info->currentDrive, dj2d_info->format[dj2d_info->currentDrive], pWD1791->track, pWD1791->sector, pWD1791->data, pWD1791->status);

    return(cData);
}

static uint32 DJ2D_ReadSector(UNIT *uptr, uint8 track, uint8 sector, uint8 *buffer)
{
    uint32 sec_offset;
    uint32 rtn = 0;
    uint32 len;

    if (uptr->fileref == NULL) {
        sim_debug(ERROR_MSG, &dj2d_dev, DJ2D_SNAME ": READSEC uptr.fileref is NULL!\n");
        return 0;
    }

    sec_offset = (uint32)calculate_dj2d_sec_offset(track, sector);

    len = sector_len(dj2d_info->currentDrive, track);

    sim_debug(RD_DATA_MSG, &dj2d_dev, DJ2D_SNAME ": READSEC track %03d sector %03d at offset %04X len %d\n", track, sector, sec_offset, len);

    if (sim_fseek(uptr->fileref, sec_offset, SEEK_SET) != 0) {
        sim_debug(ERROR_MSG, &dj2d_dev, DJ2D_SNAME ": READSEC sim_fseek error.\n");
        return 0;
    }

    rtn = sim_fread(buffer, 1, len, uptr->fileref);

    return rtn;
}

static uint32 DJ2D_WriteSector(UNIT *uptr, uint8 track, uint8 sector, uint8 *buffer)
{
    t_offset sec_offset;
    uint32 len;
    uint32 rtn = 0;

    if (uptr->fileref == NULL) {
        sim_debug(ERROR_MSG, &dj2d_dev, DJ2D_SNAME ": READSEC uptr.fileref is NULL!\n");
        return 0;
    }

    sec_offset = calculate_dj2d_sec_offset(track, sector);

    len = sector_len(dj2d_info->currentDrive, track);

    if (sim_fseek(uptr->fileref, (t_addr)sec_offset, SEEK_SET) != 0) {
        sim_debug(ERROR_MSG, &dj2d_dev, DJ2D_SNAME ": WRITESEC sim_fseek error.\n");
        return 0;
    }

    rtn = sim_fwrite(buffer, 1, len, uptr->fileref);

    sim_debug(WR_DATA_MSG, &dj2d_dev, DJ2D_SNAME ": WRITESEC track %03d sector %03d at offset %08llX len %d rtn=%d\n", track, sector, sec_offset, len, rtn);

    return rtn;
}

static const char * DJ2D_CommandString(uint8 command)
{
    switch (command & 0xf0) {
        case WD1791_CMD_RESTORE:
            return "RESTORE";

        case WD1791_CMD_SEEK:
            return "SEEK";

        case WD1791_CMD_STEP:
            return "STEP";

        case WD1791_CMD_STEPU:
            return "STEP U";

        case WD1791_CMD_STEPIN:
            return "STEP IN";

        case WD1791_CMD_STEPINU:
            return "STEP IN U";

        case WD1791_CMD_STEPOUT:
            return "STEP OUT";

        case WD1791_CMD_STEPOUTU:
            return "STEP OUT U";

        case WD1791_CMD_READ:
            return "READ";

        case WD1791_CMD_WRITE:
            return "WRITE";

        case WD1791_CMD_WRITEM:
            return "WRITE M";

        case WD1791_CMD_READ_ADDRESS:
            return "READ ADDRESS";

        case WD1791_CMD_READ_TRACK:
            return "READ TRACK";

        case WD1791_CMD_WRITE_TRACK:
            return "WRITE TRACK";

        case WD1791_CMD_FORCE_INTR:
            return "FORCE INTR";

        default:
            break;
    }

    return "UNRECOGNIZED COMMAND";
}

static uint8 DJ2D_Command(UNIT *uptr, WD1791_REG *pWD1791, int32 Data)
{
    uint8 cData;
    uint8 newTrack;
    uint8 statusUpdate;
    int32 rtn;

    cData = 0;
    statusUpdate = TRUE;

    if (uptr == NULL) {
        return cData;
    }

    pWD1791->command = Data;

    /*
    ** Type II-IV Command
    */
    if (pWD1791->command & 0x80) {
        pWD1791->readActive = FALSE;
        pWD1791->writeActive = FALSE;
        pWD1791->readTrkActive = FALSE;
        pWD1791->writeTrkActive = FALSE;
        pWD1791->addrActive = FALSE;
        pWD1791->dataCount = 0;

        pWD1791->status &= ~WD1791_STAT_DRQ;    /* Reset DRQ */
        pWD1791->drq = FALSE;
    }

    /*
    ** Set BUSY for all but Force Interrupt
    */
    if ((pWD1791->command & WD1791_CMD_FORCE_INTR) != WD1791_CMD_FORCE_INTR) {
        pWD1791->status |= WD1791_STAT_BUSY;
        dj2d_info->busyTimeout = DJ2D_BUSY_TIMEOUT;
    }

    pWD1791->intrq = FALSE;

    switch(pWD1791->command & 0xf0) {
        case WD1791_CMD_RESTORE:
            pWD1791->track = 0;

            sim_debug(SEEK_MSG, &dj2d_dev, DJ2D_SNAME ": RESTORE track=%03d\n", pWD1791->track);

            DJ2D_HeadLoad(uptr, pWD1791, (Data & WD1791_FLAG_H) ? TRUE : FALSE);

            pWD1791->status &= ~WD1791_STAT_SEEKERROR;
            pWD1791->status &= ~WD1791_STAT_DRQ;
            pWD1791->drq = FALSE;
            break;

        case WD1791_CMD_SEEK:
            newTrack = pWD1791->data;

            pWD1791->status &= ~WD1791_STAT_SEEKERROR;

            if (newTrack < DJ2D_TRACKS) {
                pWD1791->track = newTrack;

                DJ2D_HeadLoad(uptr, pWD1791, (Data & WD1791_FLAG_H) ? TRUE : FALSE);

                sim_debug(SEEK_MSG, &dj2d_dev, DJ2D_SNAME ": SEEK       track=%03d\n", pWD1791->track);
            } else {
                pWD1791->status |= WD1791_STAT_SEEKERROR;
                sim_debug(SEEK_MSG, &dj2d_dev, DJ2D_SNAME ": SEEK ERR   track=%03d\n", newTrack);
            }

            pWD1791->status &= ~WD1791_STAT_DRQ;
            pWD1791->drq = FALSE;
            break;

        case WD1791_CMD_STEP:
        case WD1791_CMD_STEPU:
            pWD1791->status &= ~WD1791_STAT_SEEKERROR;

            newTrack = pWD1791->track + pWD1791->stepDir;

            if (newTrack < DJ2D_TRACKS) {
                if (Data & WD1791_FLAG_U) {
                    pWD1791->track = newTrack;
                }
                sim_debug(SEEK_MSG, &dj2d_dev, DJ2D_SNAME ": STEP        track=%03d\n", pWD1791->track);
            } else {
                pWD1791->status |= WD1791_STAT_SEEKERROR;
                sim_debug(SEEK_MSG, &dj2d_dev, DJ2D_SNAME ": STEP ERR    track=%03d\n", newTrack);
            }

            DJ2D_HeadLoad(uptr, pWD1791, (Data & WD1791_FLAG_H) ? TRUE : FALSE);

            pWD1791->status &= ~WD1791_STAT_DRQ;
            pWD1791->drq = FALSE;
            break;

        case WD1791_CMD_STEPIN:
        case WD1791_CMD_STEPINU:
            pWD1791->status &= ~WD1791_STAT_SEEKERROR;

            if (pWD1791->track < DJ2D_TRACKS-1) {
                if (Data & WD1791_FLAG_U) {
                    pWD1791->track++;
                }

                DJ2D_HeadLoad(uptr, pWD1791, (Data & WD1791_FLAG_H) ? TRUE : FALSE);

                sim_debug(SEEK_MSG, &dj2d_dev, DJ2D_SNAME ": STEPIN      track=%03d\n", pWD1791->track);
            } else {
                pWD1791->status |= WD1791_STAT_SEEKERROR;
                sim_debug(SEEK_MSG, &dj2d_dev, DJ2D_SNAME ": STEPIN ERR  track=%03d\n", pWD1791->track+1);
            }

            pWD1791->stepDir = 1;
            pWD1791->status &= ~WD1791_STAT_DRQ;
            pWD1791->drq = FALSE;
            break;

        case WD1791_CMD_STEPOUT:
        case WD1791_CMD_STEPOUTU:
            pWD1791->status &= ~WD1791_STAT_SEEKERROR;

            if (pWD1791->track > 0) {
                if (Data & WD1791_FLAG_U) {
                    pWD1791->track--;
                }

                DJ2D_HeadLoad(uptr, pWD1791, (Data & WD1791_FLAG_H) ? TRUE : FALSE);

                sim_debug(SEEK_MSG, &dj2d_dev, DJ2D_SNAME ": STEPOUT     track=%03d\n", pWD1791->track);
            } else {
                pWD1791->status |= WD1791_STAT_SEEKERROR;
                sim_debug(SEEK_MSG, &dj2d_dev, DJ2D_SNAME ": STEPOUT ERR track=%03d\n", pWD1791->track-1);
            }

            pWD1791->stepDir = -1;
            pWD1791->status &= ~WD1791_STAT_DRQ;
            pWD1791->drq = FALSE;
            break;

        case WD1791_CMD_READ:

            if ((uptr == NULL) || (uptr->fileref == NULL)) {
                sim_debug(STATUS_MSG, &dj2d_dev, DJ2D_SNAME ": " ADDRESS_FORMAT
                          " Drive: %d not attached - read ignored.\n",
                          PCX, dj2d_info->currentDrive);

                return cData;
            }

            rtn = DJ2D_ReadSector(uptr, pWD1791->track, pWD1791->sector, sdata);

            if (rtn == sector_len(dj2d_info->currentDrive, pWD1791->track)) {
                pWD1791->readActive = TRUE;
                pWD1791->drq = TRUE;

                dj2d_info->busyTimeout = 0;  /* BUSY not cleared until all bytes read */

                showdata(TRUE);
            } else {
                sim_debug(ERROR_MSG, &dj2d_dev, DJ2D_SNAME ": sim_fread errno=%d rtn=%d len=%d\n", errno, rtn, sector_len(dj2d_info->currentDrive, pWD1791->track));

                pWD1791->status |= WD1791_STAT_NOTFOUND;
                pWD1791->intrq = TRUE;
            }

            break;

        case WD1791_CMD_WRITE:
            /*
            ** If no disk in drive, return
            */
            if ((uptr == NULL) || (uptr->fileref == NULL)) {
                sim_debug(STATUS_MSG, &dj2d_dev, DJ2D_SNAME ": " ADDRESS_FORMAT
                          " Drive: %d not attached - write ignored.\n",
                          PCX, dj2d_info->currentDrive);
            }

            if ((uptr->flags & UNIT_DJ2D_WPROTECT) || dj2d_info->writeProtect) {
                sim_debug(VERBOSE_MSG, &dj2d_dev, DJ2D_SNAME  ": Disk write protected. uptr->flags=%04x writeProtect=%04x\n", uptr->flags & UNIT_DJ2D_WPROTECT, dj2d_info->writeProtect);
                pWD1791->intrq = TRUE;
            } else {
                dj2d_info->busyTimeout = 0;  /* BUSY not cleared until all bytes written */

                pWD1791->writeActive = TRUE;
                pWD1791->dataCount = 0;
                pWD1791->drq = TRUE;
            }

            break;

        case WD1791_CMD_READ_ADDRESS:
            sdata[WD1791_ADDR_TRACK] = pWD1791->track;
            sdata[WD1791_ADDR_ZEROS] = 0;
            sdata[WD1791_ADDR_SECTOR] = pWD1791->sector;
            sdata[WD1791_ADDR_LENGTH] = (pWD1791->track) ? dj2d_info->format[dj2d_info->currentDrive] : 0;
            sdata[WD1791_ADDR_CRC1] = 0;
            sdata[WD1791_ADDR_CRC2] = 0;

            pWD1791->addrActive = TRUE;
            pWD1791->drq = TRUE;

            break;

        case WD1791_CMD_READ_TRACK:
            dj2d_info->busyTimeout = 0;  /* BUSY not cleared until all bytes read */
            pWD1791->readTrkActive = TRUE;
            pWD1791->trkCount = 0;
            pWD1791->dataCount = 0;
            pWD1791->sector = 1;
            pWD1791->drq = TRUE;
            break;

        case WD1791_CMD_WRITE_TRACK:
            if ((uptr->flags & UNIT_DJ2D_WPROTECT) || dj2d_info->writeProtect) {
                sim_debug(DEBUG_MSG, &dj2d_dev, DJ2D_SNAME ": Disk write protected. uptr->flags=%04x writeProtect=%04x\n", uptr->flags & UNIT_DJ2D_WPROTECT, dj2d_info->writeProtect);
                pWD1791->intrq = TRUE;
            } else {
                dj2d_info->busyTimeout = 0;  /* BUSY not cleared until all bytes written */
                pWD1791->writeTrkActive = TRUE;
                pWD1791->trkCount = 0;
                pWD1791->dataCount = 0;
                pWD1791->sector = 1;
                pWD1791->idAddrMrk = 0;
                pWD1791->dataAddrMrk = 0;
                pWD1791->drq = TRUE;
            }
            break;

        case WD1791_CMD_FORCE_INTR:
            if (pWD1791->status & WD1791_STAT_BUSY) {
                pWD1791->status &= ~WD1791_STAT_BUSY;
                dj2d_info->busyTimeout = 0;
                statusUpdate = FALSE;
            }

            /* Reset Status */
            pWD1791->dataCount = 0;
            pWD1791->trkCount = 0;
            pWD1791->readActive = FALSE;
            pWD1791->readTrkActive = FALSE;
            pWD1791->writeActive = FALSE;
            pWD1791->writeTrkActive = FALSE;
            pWD1791->addrActive = FALSE;
            break;

        default:
            cData = 0xFF;
            sim_debug(ERROR_MSG, &dj2d_dev, DJ2D_SNAME ": UNRECOGNIZED CMD %02X\n", pWD1791->command);
            pWD1791->intrq = TRUE;
            break;
    }

    /**************************/
    /* Update Status Register */
    /**************************/

    /* drive not ready bit */
    pWD1791->status &= ~WD1791_STAT_NOTREADY;
    pWD1791->status |= (uptr->fileref == NULL) ? WD1791_STAT_NOTREADY : 0x00;

    /* DRQ bit */
    pWD1791->status &= ~WD1791_STAT_DRQ;
    pWD1791->status |= (pWD1791->drq) ? WD1791_STAT_DRQ : 0x00;

    switch(pWD1791->command & 0xf0) {
        case WD1791_CMD_RESTORE:
        case WD1791_CMD_SEEK:
        case WD1791_CMD_STEP:
        case WD1791_CMD_STEPU:
        case WD1791_CMD_STEPIN:
        case WD1791_CMD_STEPINU:
        case WD1791_CMD_STEPOUT:
        case WD1791_CMD_STEPOUTU:
        case WD1791_CMD_FORCE_INTR:
            if (statusUpdate) {
                pWD1791->status &= ~WD1791_STAT_HEADLOAD;
                pWD1791->status &= ~WD1791_STAT_WRITEPROT;
                pWD1791->status &= ~WD1791_STAT_CRCERROR;
                pWD1791->status &= ~WD1791_STAT_TRACK0;
                pWD1791->status &= ~WD1791_STAT_INDEX;
                pWD1791->status |= ((uptr->flags & UNIT_DJ2D_WPROTECT) || dj2d_info->writeProtect) ? WD1791_STAT_WRITEPROT : 0x00;
                pWD1791->status |= (pWD1791->track) ? 0x00 : WD1791_STAT_TRACK0;
                pWD1791->status |= (dj2d_info->headLoaded[dj2d_info->currentDrive]) ? WD1791_STAT_HEADLOAD : 0x00;
                pWD1791->status |= (pWD1791->index) ? WD1791_STAT_INDEX : 0x00;
            }
            break;

        case WD1791_CMD_READ:
            pWD1791->status &= ~WD1791_STAT_LOSTDATA;
            pWD1791->status &= ~WD1791_STAT_NOTFOUND;
            pWD1791->status &= ~WD1791_STAT_CRCERROR;
            pWD1791->status &= ~WD1791_STAT_RTYPELSB;
            break;

        case WD1791_CMD_WRITE:
            pWD1791->status &= ~WD1791_STAT_WRITEPROT;
            pWD1791->status &= ~WD1791_STAT_LOSTDATA;
            pWD1791->status &= ~WD1791_STAT_NOTFOUND;
            pWD1791->status &= ~WD1791_STAT_CRCERROR;
            pWD1791->status &= ~WD1791_STAT_RTYPELSB;
            pWD1791->status |= ((uptr->flags & UNIT_DJ2D_WPROTECT) || dj2d_info->writeProtect) ? WD1791_STAT_WRITEPROT : 0x00;
            break;

        case WD1791_CMD_READ_ADDRESS:
            pWD1791->status &= ~0x20;
            pWD1791->status &= ~0x40;
            pWD1791->status &= ~WD1791_STAT_LOSTDATA;
            pWD1791->status &= ~WD1791_STAT_NOTFOUND;
            pWD1791->status &= ~WD1791_STAT_CRCERROR;
            break;

        case WD1791_CMD_READ_TRACK:
            pWD1791->status &= ~0x08;
            pWD1791->status &= ~0x10;
            pWD1791->status &= ~0x20;
            pWD1791->status &= ~0x40;
            pWD1791->status &= ~WD1791_STAT_LOSTDATA;
            break;

        case WD1791_CMD_WRITE_TRACK:
            pWD1791->status &= ~0x08;
            pWD1791->status &= ~0x10;
            pWD1791->status &= ~WD1791_STAT_WRITEPROT;
            pWD1791->status &= ~WD1791_STAT_LOSTDATA;
            pWD1791->status |= ((uptr->flags & UNIT_DJ2D_WPROTECT) || dj2d_info->writeProtect) ? WD1791_STAT_WRITEPROT : 0x00;
            break;
    }

    sim_debug(CMD_MSG, &dj2d_dev,
            DJ2D_SNAME ": CMD cmd=%02X (%s) drive=%d side=%d track=%03d sector=%03d status=%02X\n",
            pWD1791->command, DJ2D_CommandString(pWD1791->command), dj2d_info->currentDrive,
            dj2d_info->side[dj2d_info->currentDrive],
            pWD1791->track, pWD1791->sector, pWD1791->status);

    return(cData);
}

/*
** The DJ2D has 1016 bytes of PROM followed by 8 memory-mapped
** I/O registers.
*/
static int32 dj2dprom(int32 Addr, int32 rw, int32 Data)
{
    /*
    ** Check for memory-mapped I/O
    */
    if ((Addr & DJ2D_REG_BASE) == DJ2D_REG_BASE) {
        if (rw == DJ2D_MEM_READ) { /* Read */
            return(DJ2D_Read(Addr));
        } else {                   /* Write */
            return(DJ2D_Write(Addr, Data));
        }
    }

    /*
    ** Read from PROM
    */
    if (rw == DJ2D_MEM_READ) {
        return(dj2d_prom[Addr & DJ2D_PROM_MASK]);
    }

    /*
    ** Writes are ignored and return 0xff
    */
    return 0xff;
}

/*
** The DJ2D has 1K of RAM following the PROM
*/
static int32 dj2dmem(int32 Addr, int32 rw, int32 Data)
{
    if (rw == DJ2D_MEM_WRITE) {
        dj2d_mem[Addr & DJ2D_MEM_MASK] = Data;
    }
    else {
        Data = dj2d_mem[Addr & DJ2D_MEM_MASK];
    }

    return Data;
}

