/*
 * MCPX DSP emulator
 *
 * Copyright (c) 2015 espes
 *
 * Adapted from Hatari DSP M56001 emulation
 * (C) 2001-2008 ARAnyM developer team
 * Adaption to Hatari (C) 2008 by Thomas Huth
 *
 * This program 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.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "qemu/osdep.h"

#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <assert.h>

#include "dsp_cpu.h"
#include "dsp_dma.h"
#include "dsp_state.h"

#include "dsp.h"

/* Defines */
#define BITMASK(x)  ((1<<(x))-1)

#define INTERRUPT_ABORT_FRAME (1 << 0)
#define INTERRUPT_START_FRAME (1 << 1)
#define INTERRUPT_DMA_EOL (1 << 7)

// #define DEBUG_DSP

#ifdef DEBUG_DSP
#define DPRINTF(fmt, ...) \
    do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
#else
#define DPRINTF(fmt, ...) \
    do { } while (0)
#endif

static uint32_t read_peripheral(dsp_core_t* core, uint32_t address);
static void write_peripheral(dsp_core_t* core, uint32_t address, uint32_t value);

DSPState *dsp_init(void *rw_opaque,
                   dsp_scratch_rw_func scratch_rw,
                   dsp_fifo_rw_func fifo_rw)
{
    DPRINTF("dsp_init\n");

    DSPState* dsp = (DSPState*)malloc(sizeof(DSPState));
    memset(dsp, 0, sizeof(*dsp));

    dsp->core.read_peripheral = read_peripheral;
    dsp->core.write_peripheral = write_peripheral;

    dsp->dma.core = &dsp->core;
    dsp->dma.rw_opaque = rw_opaque;
    dsp->dma.scratch_rw = scratch_rw;
    dsp->dma.fifo_rw = fifo_rw;

    dsp_reset(dsp);

    return dsp;
}

void dsp_reset(DSPState* dsp)
{
    dsp56k_reset_cpu(&dsp->core);
    dsp->save_cycles = 0;
}

void dsp_destroy(DSPState* dsp)
{
    free(dsp);
}

static uint32_t read_peripheral(dsp_core_t* core, uint32_t address) {
    DSPState* dsp = container_of(core, DSPState, core);

    DPRINTF("read_peripheral 0x%06x", address);

    uint32_t v = 0xababa;
    switch(address) {
    case 0xFFFFB3:
        v = 0; // core->num_inst; // ??
        break;
    case 0xFFFFC5:
        v = dsp->interrupts;
        if (dsp->dma.eol) {
            v |= INTERRUPT_DMA_EOL;
        }
        break;
    case 0xFFFFD4:
        v = dsp_dma_read(&dsp->dma, DMA_NEXT_BLOCK);
        break;
    case 0xFFFFD5:
        v = dsp_dma_read(&dsp->dma, DMA_START_BLOCK);
        break;
    case 0xFFFFD6:
        v = dsp_dma_read(&dsp->dma, DMA_CONTROL);
        break;
    case 0xFFFFD7:
        v = dsp_dma_read(&dsp->dma, DMA_CONFIGURATION);
        break;
    }

    DPRINTF(" -> 0x%06x\n", v);
    return v;
}

static void write_peripheral(dsp_core_t* core, uint32_t address, uint32_t value) {
    DSPState* dsp = container_of(core, DSPState, core);

    DPRINTF("write_peripheral [0x%06x] = 0x%06x\n", address, value);

    switch(address) {
    case 0xFFFFC4:
        if (value & 1) {
            core->is_idle = true;
        }
        break;
    case 0xFFFFC5:
        dsp->interrupts &= ~value;
        if (value & INTERRUPT_DMA_EOL) {
            dsp->dma.eol = false;
        }
        break;
    case 0xFFFFD4:
        dsp_dma_write(&dsp->dma, DMA_NEXT_BLOCK, value);
        break;
    case 0xFFFFD5:
        dsp_dma_write(&dsp->dma, DMA_START_BLOCK, value);
        break;
    case 0xFFFFD6:
        dsp_dma_write(&dsp->dma, DMA_CONTROL, value);
        break;
    case 0xFFFFD7:
        dsp_dma_write(&dsp->dma, DMA_CONFIGURATION, value);
        break;
    }
}


void dsp_step(DSPState* dsp)
{
    dsp56k_execute_instruction(&dsp->core);
}

void dsp_run(DSPState* dsp, int cycles)
{
    dsp->save_cycles += cycles;

    if (dsp->save_cycles <= 0) return;

    int dma_timer = 0;

    while (dsp->save_cycles > 0)
    {
        dsp56k_execute_instruction(&dsp->core);
        dsp->save_cycles -= dsp->core.instr_cycle;
        dsp->core.cycle_count++;

        if (dsp->dma.control & DMA_CONTROL_RUNNING) {
            dma_timer++;
        }

        if (dma_timer > 2) {
            dma_timer = 0;
            dsp->dma.control &= ~DMA_CONTROL_RUNNING;
            dsp->dma.control |= DMA_CONTROL_STOPPED;
        }

        if (dsp->core.is_idle) break;
    }

    /* FIXME: DMA timing be done cleaner. Xbox enables running
     * then polls to make sure its running. But we complete DMA instantaneously,
     * so when is it supposed to be signaled that it stopped? Maybe just wait at
     * least one cycle? How long does hardware wait?
     */
}

void dsp_bootstrap(DSPState* dsp)
{
    // scratch memory is dma'd in to pram by the bootrom
    dsp->dma.scratch_rw(dsp->dma.rw_opaque,
        (uint8_t*)dsp->core.pram, 0, 0x800*4, false);
    for (int i = 0; i < 0x800; i++) {
        if (dsp->core.pram[i] & 0xff000000) {
            DPRINTF(stderr, "Bootstrap %04x: %08x\n", i, dsp->core.pram[i]);
            dsp->core.pram[i] &= 0x00ffffff;
        }
    }
    memset(dsp->core.pram_opcache, 0, sizeof(dsp->core.pram_opcache));
}

void dsp_start_frame(DSPState* dsp)
{
    dsp->interrupts |= INTERRUPT_START_FRAME;
}

/**
 * Disassemble DSP code between given addresses, return next PC address
 */
uint32_t dsp_disasm_address(DSPState* dsp, FILE *out, uint32_t lowerAdr, uint32_t UpperAdr)
{
    uint32_t dsp_pc;

    for (dsp_pc=lowerAdr; dsp_pc<=UpperAdr; dsp_pc++) {
        dsp_pc += dsp56k_execute_one_disasm_instruction(&dsp->core, out, dsp_pc);
    }
    return dsp_pc;
}

uint32_t dsp_read_memory(DSPState* dsp, char space, uint32_t address)
{
    int space_id;

    switch (space) {
    case 'X':
        space_id = DSP_SPACE_X;
        break;
    case 'Y':
        space_id = DSP_SPACE_Y;
        break;
    case 'P':
        space_id = DSP_SPACE_P;
        break;
    default:
        assert(false);
        return 0;
    }

    return dsp56k_read_memory(&dsp->core, space_id, address);
}

void dsp_write_memory(DSPState* dsp, char space, uint32_t address, uint32_t value)
{
    int space_id;

    switch (space) {
    case 'X':
        space_id = DSP_SPACE_X;
        break;
    case 'Y':
        space_id = DSP_SPACE_Y;
        break;
    case 'P':
        space_id = DSP_SPACE_P;
        break;
    default:
        assert(false);
        return;
    }

    dsp56k_write_memory(&dsp->core, space_id, address, value);
}

/**
 * Output memory values between given addresses in given DSP address space.
 * Return next DSP address value.
 */
uint32_t dsp_disasm_memory(DSPState* dsp, uint32_t dsp_memdump_addr, uint32_t dsp_memdump_upper, char space)
{
    uint32_t mem, value;

    for (mem = dsp_memdump_addr; mem <= dsp_memdump_upper; mem++) {
        value = dsp_read_memory(dsp, space, mem);
        printf("%04x  %06x\n", mem, value);
    }
    return dsp_memdump_upper+1;
}

/**
 * Show information on DSP core state which isn't
 * shown by any of the other commands (dd, dm, dr).
 */
void dsp_info(DSPState* dsp)
{
    int i, j;
    const char *stackname[] = { "SSH", "SSL" };

    printf("DSP core information:\n");

    for (i = 0; i < ARRAY_SIZE(stackname); i++) {
        printf("- %s stack:", stackname[i]);
        for (j = 0; j < ARRAY_SIZE(dsp->core.stack[0]); j++) {
            printf(" %04x", dsp->core.stack[i][j]);
        }
        printf("\n");
    }

    printf("- Interrupt IPL:");
    for (i = 0; i < ARRAY_SIZE(dsp->core.interrupt_ipl); i++) {
        printf(" %04x", dsp->core.interrupt_ipl[i]);
    }
    printf("\n");

    printf("- Pending ints: ");
    for (i = 0; i < ARRAY_SIZE(dsp->core.interrupt_is_pending); i++) {
        printf(" %04hx", dsp->core.interrupt_is_pending[i]);
    }
    printf("\n");
}

/**
 * Show DSP register contents
 */
void dsp_print_registers(DSPState* dsp)
{
    uint32_t i;

    printf("A: A2: %02x  A1: %06x  A0: %06x\n",
        dsp->core.registers[DSP_REG_A2], dsp->core.registers[DSP_REG_A1], dsp->core.registers[DSP_REG_A0]);
    printf("B: B2: %02x  B1: %06x  B0: %06x\n",
        dsp->core.registers[DSP_REG_B2], dsp->core.registers[DSP_REG_B1], dsp->core.registers[DSP_REG_B0]);

    printf("X: X1: %06x  X0: %06x\n", dsp->core.registers[DSP_REG_X1], dsp->core.registers[DSP_REG_X0]);
    printf("Y: Y1: %06x  Y0: %06x\n", dsp->core.registers[DSP_REG_Y1], dsp->core.registers[DSP_REG_Y0]);

    for (i=0; i<8; i++) {
        printf("R%01x: %04x   N%01x: %04x   M%01x: %04x\n",
            i, dsp->core.registers[DSP_REG_R0+i],
            i, dsp->core.registers[DSP_REG_N0+i],
            i, dsp->core.registers[DSP_REG_M0+i]);
    }

    printf("LA: %04x   LC: %04x   PC: %04x\n", dsp->core.registers[DSP_REG_LA], dsp->core.registers[DSP_REG_LC], dsp->core.pc);
    printf("SR: %04x  OMR: %02x\n", dsp->core.registers[DSP_REG_SR], dsp->core.registers[DSP_REG_OMR]);
    printf("SP: %02x    SSH: %04x  SSL: %04x\n",
        dsp->core.registers[DSP_REG_SP], dsp->core.registers[DSP_REG_SSH], dsp->core.registers[DSP_REG_SSL]);
}


/**
 * Get given DSP register address and required bit mask.
 * Works for A0-2, B0-2, LA, LC, M0-7, N0-7, R0-7, X0-1, Y0-1, PC, SR, SP,
 * OMR, SSH & SSL registers, but note that the SP, SSH & SSL registers
 * need special handling (in DSP*SetRegister()) when they are set.
 * Return the register width in bits or zero for an error.
 */
int dsp_get_register_address(DSPState* dsp, const char *regname, uint32_t **addr, uint32_t *mask)
{
#define MAX_REGNAME_LEN 4
    typedef struct {
        const char name[MAX_REGNAME_LEN];
        uint32_t *addr;
        size_t bits;
        uint32_t mask;
    } reg_addr_t;

    /* sorted by name so that this can be bisected */
    const reg_addr_t registers[] = {

        /* 56-bit A register */
        { "A0",  &dsp->core.registers[DSP_REG_A0],  32, BITMASK(24) },
        { "A1",  &dsp->core.registers[DSP_REG_A1],  32, BITMASK(24) },
        { "A2",  &dsp->core.registers[DSP_REG_A2],  32, BITMASK(8) },

        /* 56-bit B register */
        { "B0",  &dsp->core.registers[DSP_REG_B0],  32, BITMASK(24) },
        { "B1",  &dsp->core.registers[DSP_REG_B1],  32, BITMASK(24) },
        { "B2",  &dsp->core.registers[DSP_REG_B2],  32, BITMASK(8) },

        /* 16-bit LA & LC registers */
        { "LA",  &dsp->core.registers[DSP_REG_LA],  32, BITMASK(16) },
        { "LC",  &dsp->core.registers[DSP_REG_LC],  32, BITMASK(16) },

        /* 16-bit M registers */
        { "M0",  &dsp->core.registers[DSP_REG_M0],  32, BITMASK(16) },
        { "M1",  &dsp->core.registers[DSP_REG_M1],  32, BITMASK(16) },
        { "M2",  &dsp->core.registers[DSP_REG_M2],  32, BITMASK(16) },
        { "M3",  &dsp->core.registers[DSP_REG_M3],  32, BITMASK(16) },
        { "M4",  &dsp->core.registers[DSP_REG_M4],  32, BITMASK(16) },
        { "M5",  &dsp->core.registers[DSP_REG_M5],  32, BITMASK(16) },
        { "M6",  &dsp->core.registers[DSP_REG_M6],  32, BITMASK(16) },
        { "M7",  &dsp->core.registers[DSP_REG_M7],  32, BITMASK(16) },

        /* 16-bit N registers */
        { "N0",  &dsp->core.registers[DSP_REG_N0],  32, BITMASK(16) },
        { "N1",  &dsp->core.registers[DSP_REG_N1],  32, BITMASK(16) },
        { "N2",  &dsp->core.registers[DSP_REG_N2],  32, BITMASK(16) },
        { "N3",  &dsp->core.registers[DSP_REG_N3],  32, BITMASK(16) },
        { "N4",  &dsp->core.registers[DSP_REG_N4],  32, BITMASK(16) },
        { "N5",  &dsp->core.registers[DSP_REG_N5],  32, BITMASK(16) },
        { "N6",  &dsp->core.registers[DSP_REG_N6],  32, BITMASK(16) },
        { "N7",  &dsp->core.registers[DSP_REG_N7],  32, BITMASK(16) },

        { "OMR", &dsp->core.registers[DSP_REG_OMR], 32, 0x5f },

        /* 16-bit program counter */
        { "PC",  (uint32_t*)(&dsp->core.pc),  24, BITMASK(24) },

        /* 16-bit DSP R (address) registers */
        { "R0",  &dsp->core.registers[DSP_REG_R0],  32, BITMASK(16) },
        { "R1",  &dsp->core.registers[DSP_REG_R1],  32, BITMASK(16) },
        { "R2",  &dsp->core.registers[DSP_REG_R2],  32, BITMASK(16) },
        { "R3",  &dsp->core.registers[DSP_REG_R3],  32, BITMASK(16) },
        { "R4",  &dsp->core.registers[DSP_REG_R4],  32, BITMASK(16) },
        { "R5",  &dsp->core.registers[DSP_REG_R5],  32, BITMASK(16) },
        { "R6",  &dsp->core.registers[DSP_REG_R6],  32, BITMASK(16) },
        { "R7",  &dsp->core.registers[DSP_REG_R7],  32, BITMASK(16) },

        { "SSH", &dsp->core.registers[DSP_REG_SSH], 32, BITMASK(16) },
        { "SSL", &dsp->core.registers[DSP_REG_SSL], 32, BITMASK(16) },
        { "SP",  &dsp->core.registers[DSP_REG_SP],  32, BITMASK(6) },

        /* 16-bit status register */
        { "SR",  &dsp->core.registers[DSP_REG_SR],  32, 0xefff },

        /* 48-bit X register */
        { "X0",  &dsp->core.registers[DSP_REG_X0],  32, BITMASK(24) },
        { "X1",  &dsp->core.registers[DSP_REG_X1],  32, BITMASK(24) },

        /* 48-bit Y register */
        { "Y0",  &dsp->core.registers[DSP_REG_Y0],  32, BITMASK(24) },
        { "Y1",  &dsp->core.registers[DSP_REG_Y1],  32, BITMASK(24) }
    };
    /* left, right, middle, direction */
    int l, r, m, dir = 0;
    unsigned int i, len;
    char reg[MAX_REGNAME_LEN];

    for (i = 0; i < sizeof(reg) && regname[i]; i++) {
        reg[i] = toupper(regname[i]);
    }
    if (i < 2 || regname[i]) {
        /* too short or longer than any of the names */
        return 0;
    }
    len = i;

    /* bisect */
    l = 0;
    r = ARRAY_SIZE(registers) - 1;
    do {
        m = (l+r) >> 1;
        for (i = 0; i < len; i++) {
            dir = (int)reg[i] - registers[m].name[i];
            if (dir) {
                break;
            }
        }
        if (dir == 0) {
            *addr = registers[m].addr;
            *mask = registers[m].mask;
            return registers[m].bits;
        }
        if (dir < 0) {
            r = m-1;
        } else {
            l = m+1;
        }
    } while (l <= r);
#undef MAX_REGNAME_LEN
    return 0;
}


/**
 * Set given DSP register value, return false if unknown register given
 */
bool dsp_disasm_set_register(DSPState* dsp, const char *arg, uint32_t value)
{
    uint32_t *addr, mask, sp_value;
    int bits;

    /* first check registers needing special handling... */
    if (arg[0]=='S' || arg[0]=='s') {
        if (arg[1]=='P' || arg[1]=='p') {
            dsp->core.registers[DSP_REG_SP] = value & BITMASK(6);
            value &= BITMASK(4);
            dsp->core.registers[DSP_REG_SSH] = dsp->core.stack[0][value];
            dsp->core.registers[DSP_REG_SSL] = dsp->core.stack[1][value];
            return true;
        }
        if (arg[1]=='S' || arg[1]=='s') {
            sp_value = dsp->core.registers[DSP_REG_SP] & BITMASK(4);
            if (arg[2]=='H' || arg[2]=='h') {
                if (sp_value == 0) {
                    dsp->core.registers[DSP_REG_SSH] = 0;
                    dsp->core.stack[0][sp_value] = 0;
                } else {
                    dsp->core.registers[DSP_REG_SSH] = value & BITMASK(16);
                    dsp->core.stack[0][sp_value] = value & BITMASK(16);
                }
                return true;
            }
            if (arg[2]=='L' || arg[2]=='l') {
                if (sp_value == 0) {
                    dsp->core.registers[DSP_REG_SSL] = 0;
                    dsp->core.stack[1][sp_value] = 0;
                } else {
                    dsp->core.registers[DSP_REG_SSL] = value & BITMASK(16);
                    dsp->core.stack[1][sp_value] = value & BITMASK(16);
                }
                return true;
            }
        }
    }

    /* ...then registers where address & mask are enough */
    bits = dsp_get_register_address(dsp, arg, &addr, &mask);
    switch (bits) {
    case 32:
        *addr = value & mask;
        return true;
    case 16:
        *(uint16_t*)addr = value & mask;
        return true;
    }
    return false;
}
