/*
 * NeXT Cube System Driver
 *
 * Copyright (c) 2011 Bryce Lanham
 *
 * This code is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published
 * by the Free Software Foundation; either version 2 of the License,
 * or (at your option) any later version.
 */

#include "qemu/osdep.h"
#include "exec/hwaddr.h"
#include "sysemu/sysemu.h"
#include "sysemu/qtest.h"
#include "hw/irq.h"
#include "hw/m68k/next-cube.h"
#include "hw/boards.h"
#include "hw/loader.h"
#include "hw/scsi/esp.h"
#include "hw/sysbus.h"
#include "qom/object.h"
#include "hw/char/escc.h" /* ZILOG 8530 Serial Emulation */
#include "hw/block/fdc.h"
#include "hw/qdev-properties.h"
#include "qapi/error.h"
#include "qemu/error-report.h"
#include "ui/console.h"
#include "target/m68k/cpu.h"
#include "migration/vmstate.h"

/* #define DEBUG_NEXT */
#ifdef DEBUG_NEXT
#define DPRINTF(fmt, ...) \
    do { printf("NeXT: " fmt , ## __VA_ARGS__); } while (0)
#else
#define DPRINTF(fmt, ...) do { } while (0)
#endif

#define TYPE_NEXT_MACHINE MACHINE_TYPE_NAME("next-cube")
OBJECT_DECLARE_SIMPLE_TYPE(NeXTState, NEXT_MACHINE)

#define ENTRY       0x0100001e
#define RAM_SIZE    0x4000000
#define ROM_FILE    "Rev_2.5_v66.bin"

typedef struct next_dma {
    uint32_t csr;

    uint32_t saved_next;
    uint32_t saved_limit;
    uint32_t saved_start;
    uint32_t saved_stop;

    uint32_t next;
    uint32_t limit;
    uint32_t start;
    uint32_t stop;

    uint32_t next_initbuf;
    uint32_t size;
} next_dma;

typedef struct NextRtc {
    uint8_t ram[32];
    uint8_t command;
    uint8_t value;
    uint8_t status;
    uint8_t control;
    uint8_t retval;
} NextRtc;

struct NeXTState {
    MachineState parent;

    next_dma dma[10];
};

#define TYPE_NEXT_PC "next-pc"
OBJECT_DECLARE_SIMPLE_TYPE(NeXTPC, NEXT_PC)

/* NeXT Peripheral Controller */
struct NeXTPC {
    SysBusDevice parent_obj;

    M68kCPU *cpu;

    MemoryRegion mmiomem;
    MemoryRegion scrmem;

    uint32_t scr1;
    uint32_t scr2;
    uint8_t scsi_csr_1;
    uint8_t scsi_csr_2;
    uint32_t int_mask;
    uint32_t int_status;

    NextRtc rtc;
};

/* Thanks to NeXT forums for this */
/*
static const uint8_t rtc_ram3[32] = {
    0x94, 0x0f, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0xfb, 0x6d, 0x00, 0x00, 0x7B, 0x00,
    0x00, 0x00, 0x65, 0x6e, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x13
};
*/
static const uint8_t rtc_ram2[32] = {
    0x94, 0x0f, 0x40, 0x03, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0xfb, 0x6d, 0x00, 0x00, 0x4b, 0x00,
    0x41, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x7e,
};

#define SCR2_RTCLK 0x2
#define SCR2_RTDATA 0x4
#define SCR2_TOBCD(x) (((x / 10) << 4) + (x % 10))

static void nextscr2_write(NeXTPC *s, uint32_t val, int size)
{
    static int led;
    static int phase;
    static uint8_t old_scr2;
    uint8_t scr2_2;
    NextRtc *rtc = &s->rtc;

    if (size == 4) {
        scr2_2 = (val >> 8) & 0xFF;
    } else {
        scr2_2 = val & 0xFF;
    }

    if (val & 0x1) {
        DPRINTF("fault!\n");
        led++;
        if (led == 10) {
            DPRINTF("LED flashing, possible fault!\n");
            led = 0;
        }
    }

    if (scr2_2 & 0x1) {
        /* DPRINTF("RTC %x phase %i\n", scr2_2, phase); */
        if (phase == -1) {
            phase = 0;
        }
        /* If we are in going down clock... do something */
        if (((old_scr2 & SCR2_RTCLK) != (scr2_2 & SCR2_RTCLK)) &&
                ((scr2_2 & SCR2_RTCLK) == 0)) {
            if (phase < 8) {
                rtc->command = (rtc->command << 1) |
                               ((scr2_2 & SCR2_RTDATA) ? 1 : 0);
            }
            if (phase >= 8 && phase < 16) {
                rtc->value = (rtc->value << 1) |
                             ((scr2_2 & SCR2_RTDATA) ? 1 : 0);

                /* if we read RAM register, output RT_DATA bit */
                if (rtc->command <= 0x1F) {
                    scr2_2 = scr2_2 & (~SCR2_RTDATA);
                    if (rtc->ram[rtc->command] & (0x80 >> (phase - 8))) {
                        scr2_2 |= SCR2_RTDATA;
                    }

                    rtc->retval = (rtc->retval << 1) |
                                  ((scr2_2 & SCR2_RTDATA) ? 1 : 0);
                }
                /* read the status 0x30 */
                if (rtc->command == 0x30) {
                    scr2_2 = scr2_2 & (~SCR2_RTDATA);
                    /* for now status = 0x98 (new rtc + FTU) */
                    if (rtc->status & (0x80 >> (phase - 8))) {
                        scr2_2 |= SCR2_RTDATA;
                    }

                    rtc->retval = (rtc->retval << 1) |
                                  ((scr2_2 & SCR2_RTDATA) ? 1 : 0);
                }
                /* read the status 0x31 */
                if (rtc->command == 0x31) {
                    scr2_2 = scr2_2 & (~SCR2_RTDATA);
                    if (rtc->control & (0x80 >> (phase - 8))) {
                        scr2_2 |= SCR2_RTDATA;
                    }
                    rtc->retval = (rtc->retval << 1) |
                                  ((scr2_2 & SCR2_RTDATA) ? 1 : 0);
                }

                if ((rtc->command >= 0x20) && (rtc->command <= 0x2F)) {
                    scr2_2 = scr2_2 & (~SCR2_RTDATA);
                    /* for now 0x00 */
                    time_t time_h = time(NULL);
                    struct tm *info = localtime(&time_h);
                    int ret = 0;

                    switch (rtc->command) {
                    case 0x20:
                        ret = SCR2_TOBCD(info->tm_sec);
                        break;
                    case 0x21:
                        ret = SCR2_TOBCD(info->tm_min);
                        break;
                    case 0x22:
                        ret = SCR2_TOBCD(info->tm_hour);
                        break;
                    case 0x24:
                        ret = SCR2_TOBCD(info->tm_mday);
                        break;
                    case 0x25:
                        ret = SCR2_TOBCD((info->tm_mon + 1));
                        break;
                    case 0x26:
                        ret = SCR2_TOBCD((info->tm_year - 100));
                        break;

                    }

                    if (ret & (0x80 >> (phase - 8))) {
                        scr2_2 |= SCR2_RTDATA;
                    }
                    rtc->retval = (rtc->retval << 1) |
                                  ((scr2_2 & SCR2_RTDATA) ? 1 : 0);
                }

            }

            phase++;
            if (phase == 16) {
                if (rtc->command >= 0x80 && rtc->command <= 0x9F) {
                    rtc->ram[rtc->command - 0x80] = rtc->value;
                }
                /* write to x30 register */
                if (rtc->command == 0xB1) {
                    /* clear FTU */
                    if (rtc->value & 0x04) {
                        rtc->status = rtc->status & (~0x18);
                        s->int_status = s->int_status & (~0x04);
                    }
                }
            }
        }
    } else {
        /* else end or abort */
        phase = -1;
        rtc->command = 0;
        rtc->value = 0;
    }
    s->scr2 = val & 0xFFFF00FF;
    s->scr2 |= scr2_2 << 8;
    old_scr2 = scr2_2;
}

static uint32_t mmio_readb(NeXTPC *s, hwaddr addr)
{
    switch (addr) {
    case 0xc000:
        return (s->scr1 >> 24) & 0xFF;
    case 0xc001:
        return (s->scr1 >> 16) & 0xFF;
    case 0xc002:
        return (s->scr1 >> 8)  & 0xFF;
    case 0xc003:
        return (s->scr1 >> 0)  & 0xFF;

    case 0xd000:
        return (s->scr2 >> 24) & 0xFF;
    case 0xd001:
        return (s->scr2 >> 16) & 0xFF;
    case 0xd002:
        return (s->scr2 >> 8)  & 0xFF;
    case 0xd003:
        return (s->scr2 >> 0)  & 0xFF;
    case 0x14020:
        DPRINTF("MMIO Read 0x4020\n");
        return 0x7f;

    default:
        DPRINTF("MMIO Read B @ %"HWADDR_PRIx"\n", addr);
        return 0x0;
    }
}

static uint32_t mmio_readw(NeXTPC *s, hwaddr addr)
{
    switch (addr) {
    default:
        DPRINTF("MMIO Read W @ %"HWADDR_PRIx"\n", addr);
        return 0x0;
    }
}

static uint32_t mmio_readl(NeXTPC *s, hwaddr addr)
{
    switch (addr) {
    case 0x7000:
        /* DPRINTF("Read INT status: %x\n", s->int_status); */
        return s->int_status;

    case 0x7800:
        DPRINTF("MMIO Read INT mask: %x\n", s->int_mask);
        return s->int_mask;

    case 0xc000:
        return s->scr1;

    case 0xd000:
        return s->scr2;

    default:
        DPRINTF("MMIO Read L @ %"HWADDR_PRIx"\n", addr);
        return 0x0;
    }
}

static void mmio_writeb(NeXTPC *s, hwaddr addr, uint32_t val)
{
    switch (addr) {
    case 0xd003:
        nextscr2_write(s, val, 1);
        break;
    default:
        DPRINTF("MMIO Write B @ %x with %x\n", (unsigned int)addr, val);
    }

}

static void mmio_writew(NeXTPC *s, hwaddr addr, uint32_t val)
{
    DPRINTF("MMIO Write W\n");
}

static void mmio_writel(NeXTPC *s, hwaddr addr, uint32_t val)
{
    switch (addr) {
    case 0x7000:
        DPRINTF("INT Status old: %x new: %x\n", s->int_status, val);
        s->int_status = val;
        break;
    case 0x7800:
        DPRINTF("INT Mask old: %x new: %x\n", s->int_mask, val);
        s->int_mask  = val;
        break;
    case 0xc000:
        DPRINTF("SCR1 Write: %x\n", val);
        break;
    case 0xd000:
        nextscr2_write(s, val, 4);
        break;

    default:
        DPRINTF("MMIO Write l @ %x with %x\n", (unsigned int)addr, val);
    }
}

static uint64_t mmio_readfn(void *opaque, hwaddr addr, unsigned size)
{
    NeXTPC *s = NEXT_PC(opaque);

    switch (size) {
    case 1:
        return mmio_readb(s, addr);
    case 2:
        return mmio_readw(s, addr);
    case 4:
        return mmio_readl(s, addr);
    default:
        g_assert_not_reached();
    }
}

static void mmio_writefn(void *opaque, hwaddr addr, uint64_t value,
                         unsigned size)
{
    NeXTPC *s = NEXT_PC(opaque);

    switch (size) {
    case 1:
        mmio_writeb(s, addr, value);
        break;
    case 2:
        mmio_writew(s, addr, value);
        break;
    case 4:
        mmio_writel(s, addr, value);
        break;
    default:
        g_assert_not_reached();
    }
}

static const MemoryRegionOps mmio_ops = {
    .read = mmio_readfn,
    .write = mmio_writefn,
    .valid.min_access_size = 1,
    .valid.max_access_size = 4,
    .endianness = DEVICE_NATIVE_ENDIAN,
};

static uint32_t scr_readb(NeXTPC *s, hwaddr addr)
{
    switch (addr) {
    case 0x14108:
        DPRINTF("FD read @ %x\n", (unsigned int)addr);
        return 0x40 | 0x04 | 0x2 | 0x1;
    case 0x14020:
        DPRINTF("SCSI 4020  STATUS READ %X\n", s->scsi_csr_1);
        return s->scsi_csr_1;

    case 0x14021:
        DPRINTF("SCSI 4021 STATUS READ %X\n", s->scsi_csr_2);
        return 0x40;

    /*
     * These 4 registers are the hardware timer, not sure which register
     * is the latch instead of data, but no problems so far
     */
    case 0x1a000:
        return 0xff & (clock() >> 24);
    case 0x1a001:
        return 0xff & (clock() >> 16);
    case 0x1a002:
        return 0xff & (clock() >> 8);
    case 0x1a003:
        /* Hack: We need to have this change consistently to make it work */
        return 0xFF & clock();

    default:
        DPRINTF("BMAP Read B @ %x\n", (unsigned int)addr);
        return 0;
    }
}

static uint32_t scr_readw(NeXTPC *s, hwaddr addr)
{
    DPRINTF("BMAP Read W @ %x\n", (unsigned int)addr);
    return 0;
}

static uint32_t scr_readl(NeXTPC *s, hwaddr addr)
{
    DPRINTF("BMAP Read L @ %x\n", (unsigned int)addr);
    return 0;
}

#define SCSICSR_ENABLE  0x01
#define SCSICSR_RESET   0x02  /* reset scsi dma */
#define SCSICSR_FIFOFL  0x04
#define SCSICSR_DMADIR  0x08  /* if set, scsi to mem */
#define SCSICSR_CPUDMA  0x10  /* if set, dma enabled */
#define SCSICSR_INTMASK 0x20  /* if set, interrupt enabled */

static void scr_writeb(NeXTPC *s, hwaddr addr, uint32_t value)
{
    switch (addr) {
    case 0x14108:
        DPRINTF("FDCSR Write: %x\n", value);

        if (value == 0x0) {
            /* qemu_irq_raise(s->fd_irq[0]); */
        }
        break;
    case 0x14020: /* SCSI Control Register */
        if (value & SCSICSR_FIFOFL) {
            DPRINTF("SCSICSR FIFO Flush\n");
            /* will have to add another irq to the esp if this is needed */
            /* esp_puflush_fifo(esp_g); */
            /* qemu_irq_pulse(s->scsi_dma); */
        }

        if (value & SCSICSR_ENABLE) {
            DPRINTF("SCSICSR Enable\n");
            /*
             * qemu_irq_raise(s->scsi_dma);
             * s->scsi_csr_1 = 0xc0;
             * s->scsi_csr_1 |= 0x1;
             * qemu_irq_pulse(s->scsi_dma);
             */
        }
        /*
         * else
         *     s->scsi_csr_1 &= ~SCSICSR_ENABLE;
         */

        if (value & SCSICSR_RESET) {
            DPRINTF("SCSICSR Reset\n");
            /* I think this should set DMADIR. CPUDMA and INTMASK to 0 */
            /* qemu_irq_raise(s->scsi_reset); */
            /* s->scsi_csr_1 &= ~(SCSICSR_INTMASK |0x80|0x1); */

        }
        if (value & SCSICSR_DMADIR) {
            DPRINTF("SCSICSR DMAdir\n");
        }
        if (value & SCSICSR_CPUDMA) {
            DPRINTF("SCSICSR CPUDMA\n");
            /* qemu_irq_raise(s->scsi_dma); */

            s->int_status |= 0x4000000;
        } else {
            s->int_status &= ~(0x4000000);
        }
        if (value & SCSICSR_INTMASK) {
            DPRINTF("SCSICSR INTMASK\n");
            /*
             * int_mask &= ~0x1000;
             * s->scsi_csr_1 |= value;
             * s->scsi_csr_1 &= ~SCSICSR_INTMASK;
             * if (s->scsi_queued) {
             *     s->scsi_queued = 0;
             *     next_irq(s, NEXT_SCSI_I, level);
             * }
             */
        } else {
            /* int_mask |= 0x1000; */
        }
        if (value & 0x80) {
            /* int_mask |= 0x1000; */
            /* s->scsi_csr_1 |= 0x80; */
        }
        DPRINTF("SCSICSR Write: %x\n", value);
        /* s->scsi_csr_1 = value; */
        return;
    /* Hardware timer latch - not implemented yet */
    case 0x1a000:
    default:
        DPRINTF("BMAP Write B @ %x with %x\n", (unsigned int)addr, value);
    }
}

static void scr_writew(NeXTPC *s, hwaddr addr, uint32_t value)
{
    DPRINTF("BMAP Write W @ %x with %x\n", (unsigned int)addr, value);
}

static void scr_writel(NeXTPC *s, hwaddr addr, uint32_t value)
{
    DPRINTF("BMAP Write L @ %x with %x\n", (unsigned int)addr, value);
}

static uint64_t scr_readfn(void *opaque, hwaddr addr, unsigned size)
{
    NeXTPC *s = NEXT_PC(opaque);

    switch (size) {
    case 1:
        return scr_readb(s, addr);
    case 2:
        return scr_readw(s, addr);
    case 4:
        return scr_readl(s, addr);
    default:
        g_assert_not_reached();
    }
}

static void scr_writefn(void *opaque, hwaddr addr, uint64_t value,
                        unsigned size)
{
    NeXTPC *s = NEXT_PC(opaque);

    switch (size) {
    case 1:
        scr_writeb(s, addr, value);
        break;
    case 2:
        scr_writew(s, addr, value);
        break;
    case 4:
        scr_writel(s, addr, value);
        break;
    default:
        g_assert_not_reached();
    }
}

static const MemoryRegionOps scr_ops = {
    .read = scr_readfn,
    .write = scr_writefn,
    .valid.min_access_size = 1,
    .valid.max_access_size = 4,
    .endianness = DEVICE_NATIVE_ENDIAN,
};

#define NEXTDMA_SCSI(x)      (0x10 + x)
#define NEXTDMA_FD(x)        (0x10 + x)
#define NEXTDMA_ENTX(x)      (0x110 + x)
#define NEXTDMA_ENRX(x)      (0x150 + x)
#define NEXTDMA_CSR          0x0
#define NEXTDMA_NEXT         0x4000
#define NEXTDMA_LIMIT        0x4004
#define NEXTDMA_START        0x4008
#define NEXTDMA_STOP         0x400c
#define NEXTDMA_NEXT_INIT    0x4200
#define NEXTDMA_SIZE         0x4204

static void dma_writel(void *opaque, hwaddr addr, uint64_t value,
                       unsigned int size)
{
    NeXTState *next_state = NEXT_MACHINE(opaque);

    switch (addr) {
    case NEXTDMA_ENRX(NEXTDMA_CSR):
        if (value & DMA_DEV2M) {
            next_state->dma[NEXTDMA_ENRX].csr |= DMA_DEV2M;
        }

        if (value & DMA_SETENABLE) {
            /* DPRINTF("SCSI DMA ENABLE\n"); */
            next_state->dma[NEXTDMA_ENRX].csr |= DMA_ENABLE;
        }
        if (value & DMA_SETSUPDATE) {
            next_state->dma[NEXTDMA_ENRX].csr |= DMA_SUPDATE;
        }
        if (value & DMA_CLRCOMPLETE) {
            next_state->dma[NEXTDMA_ENRX].csr &= ~DMA_COMPLETE;
        }

        if (value & DMA_RESET) {
            next_state->dma[NEXTDMA_ENRX].csr &= ~(DMA_COMPLETE | DMA_SUPDATE |
                                                  DMA_ENABLE | DMA_DEV2M);
        }
        /* DPRINTF("RXCSR \tWrite: %x\n",value); */
        break;
    case NEXTDMA_ENRX(NEXTDMA_NEXT_INIT):
        next_state->dma[NEXTDMA_ENRX].next_initbuf = value;
        break;
    case NEXTDMA_ENRX(NEXTDMA_NEXT):
        next_state->dma[NEXTDMA_ENRX].next = value;
        break;
    case NEXTDMA_ENRX(NEXTDMA_LIMIT):
        next_state->dma[NEXTDMA_ENRX].limit = value;
        break;
    case NEXTDMA_SCSI(NEXTDMA_CSR):
        if (value & DMA_DEV2M) {
            next_state->dma[NEXTDMA_SCSI].csr |= DMA_DEV2M;
        }
        if (value & DMA_SETENABLE) {
            /* DPRINTF("SCSI DMA ENABLE\n"); */
            next_state->dma[NEXTDMA_SCSI].csr |= DMA_ENABLE;
        }
        if (value & DMA_SETSUPDATE) {
            next_state->dma[NEXTDMA_SCSI].csr |= DMA_SUPDATE;
        }
        if (value & DMA_CLRCOMPLETE) {
            next_state->dma[NEXTDMA_SCSI].csr &= ~DMA_COMPLETE;
        }

        if (value & DMA_RESET) {
            next_state->dma[NEXTDMA_SCSI].csr &= ~(DMA_COMPLETE | DMA_SUPDATE |
                                                  DMA_ENABLE | DMA_DEV2M);
            /* DPRINTF("SCSI DMA RESET\n"); */
        }
        /* DPRINTF("RXCSR \tWrite: %x\n",value); */
        break;

    case NEXTDMA_SCSI(NEXTDMA_NEXT):
        next_state->dma[NEXTDMA_SCSI].next = value;
        break;

    case NEXTDMA_SCSI(NEXTDMA_LIMIT):
        next_state->dma[NEXTDMA_SCSI].limit = value;
        break;

    case NEXTDMA_SCSI(NEXTDMA_START):
        next_state->dma[NEXTDMA_SCSI].start = value;
        break;

    case NEXTDMA_SCSI(NEXTDMA_STOP):
        next_state->dma[NEXTDMA_SCSI].stop = value;
        break;

    case NEXTDMA_SCSI(NEXTDMA_NEXT_INIT):
        next_state->dma[NEXTDMA_SCSI].next_initbuf = value;
        break;

    default:
        DPRINTF("DMA write @ %x w/ %x\n", (unsigned)addr, (unsigned)value);
    }
}

static uint64_t dma_readl(void *opaque, hwaddr addr, unsigned int size)
{
    NeXTState *next_state = NEXT_MACHINE(opaque);

    switch (addr) {
    case NEXTDMA_SCSI(NEXTDMA_CSR):
        DPRINTF("SCSI DMA CSR READ\n");
        return next_state->dma[NEXTDMA_SCSI].csr;
    case NEXTDMA_ENRX(NEXTDMA_CSR):
        return next_state->dma[NEXTDMA_ENRX].csr;
    case NEXTDMA_ENRX(NEXTDMA_NEXT_INIT):
        return next_state->dma[NEXTDMA_ENRX].next_initbuf;
    case NEXTDMA_ENRX(NEXTDMA_NEXT):
        return next_state->dma[NEXTDMA_ENRX].next;
    case NEXTDMA_ENRX(NEXTDMA_LIMIT):
        return next_state->dma[NEXTDMA_ENRX].limit;

    case NEXTDMA_SCSI(NEXTDMA_NEXT):
        return next_state->dma[NEXTDMA_SCSI].next;
    case NEXTDMA_SCSI(NEXTDMA_NEXT_INIT):
        return next_state->dma[NEXTDMA_SCSI].next_initbuf;
    case NEXTDMA_SCSI(NEXTDMA_LIMIT):
        return next_state->dma[NEXTDMA_SCSI].limit;
    case NEXTDMA_SCSI(NEXTDMA_START):
        return next_state->dma[NEXTDMA_SCSI].start;
    case NEXTDMA_SCSI(NEXTDMA_STOP):
        return next_state->dma[NEXTDMA_SCSI].stop;

    default:
        DPRINTF("DMA read @ %x\n", (unsigned int)addr);
        return 0;
    }

    /*
     * once the csr's are done, subtract 0x3FEC from the addr, and that will
     * normalize the upper registers
     */
}

static const MemoryRegionOps dma_ops = {
    .read = dma_readl,
    .write = dma_writel,
    .impl.min_access_size = 4,
    .valid.min_access_size = 4,
    .valid.max_access_size = 4,
    .endianness = DEVICE_NATIVE_ENDIAN,
};

static void next_irq(void *opaque, int number, int level)
{
    NeXTPC *s = NEXT_PC(opaque);
    M68kCPU *cpu = s->cpu;
    int shift = 0;

    /* first switch sets interrupt status */
    /* DPRINTF("IRQ %i\n",number); */
    switch (number) {
    /* level 3 - floppy, kbd/mouse, power, ether rx/tx, scsi, clock */
    case NEXT_FD_I:
        shift = 7;
        break;
    case NEXT_KBD_I:
        shift = 3;
        break;
    case NEXT_PWR_I:
        shift = 2;
        break;
    case NEXT_ENRX_I:
        shift = 9;
        break;
    case NEXT_ENTX_I:
        shift = 10;
        break;
    case NEXT_SCSI_I:
        shift = 12;
        break;
    case NEXT_CLK_I:
        shift = 5;
        break;

    /* level 5 - scc (serial) */
    case NEXT_SCC_I:
        shift = 17;
        break;

    /* level 6 - audio etherrx/tx dma */
    case NEXT_ENTX_DMA_I:
        shift = 28;
        break;
    case NEXT_ENRX_DMA_I:
        shift = 27;
        break;
    case NEXT_SCSI_DMA_I:
        shift = 26;
        break;
    case NEXT_SND_I:
        shift = 23;
        break;
    case NEXT_SCC_DMA_I:
        shift = 21;
        break;

    }
    /*
     * this HAS to be wrong, the interrupt handlers in mach and together
     * int_status and int_mask and return if there is a hit
     */
    if (s->int_mask & (1 << shift)) {
        DPRINTF("%x interrupt masked @ %x\n", 1 << shift, cpu->env.pc);
        /* return; */
    }

    /* second switch triggers the correct interrupt */
    if (level) {
        s->int_status |= 1 << shift;

        switch (number) {
        /* level 3 - floppy, kbd/mouse, power, ether rx/tx, scsi, clock */
        case NEXT_FD_I:
        case NEXT_KBD_I:
        case NEXT_PWR_I:
        case NEXT_ENRX_I:
        case NEXT_ENTX_I:
        case NEXT_SCSI_I:
        case NEXT_CLK_I:
            m68k_set_irq_level(cpu, 3, 27);
            break;

        /* level 5 - scc (serial) */
        case NEXT_SCC_I:
            m68k_set_irq_level(cpu, 5, 29);
            break;

        /* level 6 - audio etherrx/tx dma */
        case NEXT_ENTX_DMA_I:
        case NEXT_ENRX_DMA_I:
        case NEXT_SCSI_DMA_I:
        case NEXT_SND_I:
        case NEXT_SCC_DMA_I:
            m68k_set_irq_level(cpu, 6, 30);
            break;
        }
    } else {
        s->int_status &= ~(1 << shift);
        cpu_reset_interrupt(CPU(cpu), CPU_INTERRUPT_HARD);
    }
}

static void next_escc_init(DeviceState *pcdev)
{
    DeviceState *dev;
    SysBusDevice *s;

    dev = qdev_new(TYPE_ESCC);
    qdev_prop_set_uint32(dev, "disabled", 0);
    qdev_prop_set_uint32(dev, "frequency", 9600 * 384);
    qdev_prop_set_uint32(dev, "it_shift", 0);
    qdev_prop_set_bit(dev, "bit_swap", true);
    qdev_prop_set_chr(dev, "chrB", serial_hd(1));
    qdev_prop_set_chr(dev, "chrA", serial_hd(0));
    qdev_prop_set_uint32(dev, "chnBtype", escc_serial);
    qdev_prop_set_uint32(dev, "chnAtype", escc_serial);

    s = SYS_BUS_DEVICE(dev);
    sysbus_realize_and_unref(s, &error_fatal);
    sysbus_connect_irq(s, 0, qdev_get_gpio_in(pcdev, NEXT_SCC_I));
    sysbus_connect_irq(s, 1, qdev_get_gpio_in(pcdev, NEXT_SCC_DMA_I));
    sysbus_mmio_map(s, 0, 0x2118000);
}

static void next_pc_reset(DeviceState *dev)
{
    NeXTPC *s = NEXT_PC(dev);

    /* Set internal registers to initial values */
    /*     0x0000XX00 << vital bits */
    s->scr1 = 0x00011102;
    s->scr2 = 0x00ff0c80;

    s->rtc.status = 0x90;

    /* Load RTC RAM - TODO: provide possibility to load contents from file */
    memcpy(s->rtc.ram, rtc_ram2, 32);
}

static void next_pc_realize(DeviceState *dev, Error **errp)
{
    NeXTPC *s = NEXT_PC(dev);
    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);

    qdev_init_gpio_in(dev, next_irq, NEXT_NUM_IRQS);

    memory_region_init_io(&s->mmiomem, OBJECT(s), &mmio_ops, s,
                          "next.mmio", 0xD0000);
    memory_region_init_io(&s->scrmem, OBJECT(s), &scr_ops, s,
                          "next.scr", 0x20000);
    sysbus_init_mmio(sbd, &s->mmiomem);
    sysbus_init_mmio(sbd, &s->scrmem);
}

/*
 * If the m68k CPU implemented its inbound irq lines as GPIO lines
 * rather than via the m68k_set_irq_level() function we would not need
 * this cpu link property and could instead provide outbound IRQ lines
 * that the board could wire up to the CPU.
 */
static Property next_pc_properties[] = {
    DEFINE_PROP_LINK("cpu", NeXTPC, cpu, TYPE_M68K_CPU, M68kCPU *),
    DEFINE_PROP_END_OF_LIST(),
};

static const VMStateDescription next_rtc_vmstate = {
    .name = "next-rtc",
    .version_id = 1,
    .minimum_version_id = 1,
    .fields = (VMStateField[]) {
        VMSTATE_UINT8_ARRAY(ram, NextRtc, 32),
        VMSTATE_UINT8(command, NextRtc),
        VMSTATE_UINT8(value, NextRtc),
        VMSTATE_UINT8(status, NextRtc),
        VMSTATE_UINT8(control, NextRtc),
        VMSTATE_UINT8(retval, NextRtc),
        VMSTATE_END_OF_LIST()
    },
};

static const VMStateDescription next_pc_vmstate = {
    .name = "next-pc",
    .version_id = 1,
    .minimum_version_id = 1,
    .fields = (VMStateField[]) {
        VMSTATE_UINT32(scr1, NeXTPC),
        VMSTATE_UINT32(scr2, NeXTPC),
        VMSTATE_UINT32(int_mask, NeXTPC),
        VMSTATE_UINT32(int_status, NeXTPC),
        VMSTATE_UINT8(scsi_csr_1, NeXTPC),
        VMSTATE_UINT8(scsi_csr_2, NeXTPC),
        VMSTATE_STRUCT(rtc, NeXTPC, 0, next_rtc_vmstate, NextRtc),
        VMSTATE_END_OF_LIST()
    },
};

static void next_pc_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);

    dc->desc = "NeXT Peripheral Controller";
    dc->realize = next_pc_realize;
    dc->reset = next_pc_reset;
    device_class_set_props(dc, next_pc_properties);
    dc->vmsd = &next_pc_vmstate;
}

static const TypeInfo next_pc_info = {
    .name = TYPE_NEXT_PC,
    .parent = TYPE_SYS_BUS_DEVICE,
    .instance_size = sizeof(NeXTPC),
    .class_init = next_pc_class_init,
};

static void next_cube_init(MachineState *machine)
{
    M68kCPU *cpu;
    CPUM68KState *env;
    MemoryRegion *rom = g_new(MemoryRegion, 1);
    MemoryRegion *dmamem = g_new(MemoryRegion, 1);
    MemoryRegion *bmapm1 = g_new(MemoryRegion, 1);
    MemoryRegion *bmapm2 = g_new(MemoryRegion, 1);
    MemoryRegion *sysmem = get_system_memory();
    const char *bios_name = machine->firmware ?: ROM_FILE;
    DeviceState *dev;
    DeviceState *pcdev;

    /* Initialize the cpu core */
    cpu = M68K_CPU(cpu_create(machine->cpu_type));
    if (!cpu) {
        error_report("Unable to find m68k CPU definition");
        exit(1);
    }
    env = &cpu->env;

    /* Initialize CPU registers.  */
    env->vbr = 0;
    env->sr  = 0x2700;

    /* Peripheral Controller */
    pcdev = qdev_new(TYPE_NEXT_PC);
    object_property_set_link(OBJECT(pcdev), "cpu", OBJECT(cpu), &error_abort);
    sysbus_realize_and_unref(SYS_BUS_DEVICE(pcdev), &error_fatal);

    /* 64MB RAM starting at 0x04000000  */
    memory_region_add_subregion(sysmem, 0x04000000, machine->ram);

    /* Framebuffer */
    dev = qdev_new(TYPE_NEXTFB);
    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0x0B000000);

    /* MMIO */
    sysbus_mmio_map(SYS_BUS_DEVICE(pcdev), 0, 0x02000000);

    /* BMAP IO - acts as a catch-all for now */
    sysbus_mmio_map(SYS_BUS_DEVICE(pcdev), 1, 0x02100000);

    /* BMAP memory */
    memory_region_init_ram_flags_nomigrate(bmapm1, NULL, "next.bmapmem", 64,
                                           RAM_SHARED, &error_fatal);
    memory_region_add_subregion(sysmem, 0x020c0000, bmapm1);
    /* The Rev_2.5_v66.bin firmware accesses it at 0x820c0020, too */
    memory_region_init_alias(bmapm2, NULL, "next.bmapmem2", bmapm1, 0x0, 64);
    memory_region_add_subregion(sysmem, 0x820c0000, bmapm2);

    /* KBD */
    dev = qdev_new(TYPE_NEXTKBD);
    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0x0200e000);

    /* Load ROM here */
    /* still not sure if the rom should also be mapped at 0x0*/
    memory_region_init_rom(rom, NULL, "next.rom", 0x20000, &error_fatal);
    memory_region_add_subregion(sysmem, 0x01000000, rom);
    if (load_image_targphys(bios_name, 0x01000000, 0x20000) < 8) {
        if (!qtest_enabled()) {
            error_report("Failed to load firmware '%s'.", bios_name);
        }
    } else {
        uint8_t *ptr;
        /* Initial PC is always at offset 4 in firmware binaries */
        ptr = rom_ptr(0x01000004, 4);
        g_assert(ptr != NULL);
        env->pc = ldl_p(ptr);
        if (env->pc >= 0x01020000) {
            error_report("'%s' does not seem to be a valid firmware image.",
                         bios_name);
            exit(1);
        }
    }

    /* Serial */
    next_escc_init(pcdev);

    /* TODO: */
    /* Network */
    /* SCSI */

    /* DMA */
    memory_region_init_io(dmamem, NULL, &dma_ops, machine, "next.dma", 0x5000);
    memory_region_add_subregion(sysmem, 0x02000000, dmamem);
}

static void next_machine_class_init(ObjectClass *oc, void *data)
{
    MachineClass *mc = MACHINE_CLASS(oc);

    mc->desc = "NeXT Cube";
    mc->init = next_cube_init;
    mc->default_ram_size = RAM_SIZE;
    mc->default_ram_id = "next.ram";
    mc->default_cpu_type = M68K_CPU_TYPE_NAME("m68040");
}

static const TypeInfo next_typeinfo = {
    .name = TYPE_NEXT_MACHINE,
    .parent = TYPE_MACHINE,
    .class_init = next_machine_class_init,
    .instance_size = sizeof(NeXTState),
};

static void next_register_type(void)
{
    type_register_static(&next_typeinfo);
    type_register_static(&next_pc_info);
}

type_init(next_register_type)
