/*
 *  Copyright (C) 2006 Stas Sergeev <stsp@users.sourceforge.net>
 *
 * The below copyright strings have to be distributed unchanged together
 * with this file. This prefix can not be modified or separated.
 */

/*
 *  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.
 */

/*
 * DMA controller emulation.
 *
 * Author: Stas Sergeev <stsp@users.sourceforge.net>
 */

#include "emu.h"
#include "init.h"
#include "utilities.h"
#include "port.h"
#include "timers.h"
#include "dmanew.h"
#include "dmaregs.h"
#include <string.h>

typedef union {
    Bit8u byte[2];
    Bit16u value;
} dma_reg16;

struct dma_channel {
    dma_reg16 base_addr;
    dma_reg16 base_count;
    dma_reg16 cur_addr;
    dma_reg16 cur_count;
    Bit8u page;
    Bit8u mode;
};

struct dma_controller {
    struct dma_channel chans[4];
#if 0
    /* looks like we don't need those */
    Bit16u tmp_addr;
    Bit16u tmp_count;
#endif
    Bit8u tmp_reg;
    Bit8u status;
    Bit8u command;
    Bit8u mask;
    Bit8u request;
    Bit8u ff;
} dma[2];

static Bit8u dma_data_bus[2];

#define DMA1  0
#define DMA2  1

/* need this as soon as dosemu is threaded */
#define DMA_LOCK()
#define DMA_UNLOCK()

#define DI(c) (((c) & 4) >> 2)
#define CI(c) ((c) & 3)

#define HAVE_DRQ(contr, chan) (dma[contr].status & (1 << ((chan) + 4)))
#define REACHED_TC(contr, chan) (dma[contr].status & (1 << (chan)))
#define MASKED(contr, chan) (dma[contr].mask & (1 << (chan)))
#define HAVE_SRQ(contr, chan) (dma[contr].request & (1 << (chan)))
#define SW_ACTIVE(contr, chan) \
  (HAVE_SRQ(contr, chan) && \
  (DMA_TRANSFER_MODE(dma[contr].chans[chan].mode) == BLOCK))


static void dma_soft_reset(int dma_idx)
{
    dma[dma_idx].command = 0;
    dma[dma_idx].status = 0;
    dma[dma_idx].request = 0;
    dma[dma_idx].tmp_reg = 0;
    dma[dma_idx].ff = 0;
    dma[dma_idx].mask = 0x0f;
    q_printf("DMA: Soft Reset on controller %i\n", dma_idx);
}

static void dma_poll_DRQ(int dma_idx, int chan_idx)
{
    /* Since our model is simplified (API has only pulse_DRQ() instead
     * of assert_DRQ()/release_DRQ()), poll never sees the DRQ asserted. */
    dma[dma_idx].status &= ~(1 << (chan_idx + 4));
}

static void dma_update_DRQ(int dma_idx, int chan_idx)
{
    switch (DMA_TRANSFER_MODE(dma[dma_idx].chans[chan_idx].mode)) {
    case DEMAND:
	dma_poll_DRQ(dma_idx, chan_idx);
	break;
    case SINGLE:
	dma[dma_idx].status &= ~(1 << (chan_idx + 4));
	break;
    case BLOCK:
	if (REACHED_TC(dma_idx, chan_idx))
	    dma_poll_DRQ(dma_idx, chan_idx);
	break;
    case CASCADE:
	dma_poll_DRQ(dma_idx, chan_idx);
	break;
    }
}

static void dma_process_channel(int dma_idx, int chan_idx)
{
    struct dma_channel *chan = &dma[dma_idx].chans[chan_idx];
    void *addr = physaddr_to_unixaddr(
	    (chan->page << 16) | (chan->cur_addr.value << dma_idx));

    /* first, do the transfer */
    switch (DMA_TRANSFER_OP(chan->mode)) {
    case VERIFY:
	q_printf("DMA: verify mode does nothing\n");
	break;
    case WRITE:
	memcpy(addr, dma_data_bus, 1 << dma_idx);
	break;
    case READ:
	memcpy(dma_data_bus, addr, 1 << dma_idx);
	break;
    case INVALID:
	q_printf("DMA: invalid mode does nothing\n");
	break;
    }

    /* now advance the address */
    if ((dma[dma_idx].command & 3) != 3)
	chan->cur_addr.value += (DMA_ADDR_DEC(chan->mode) ? -1 : 1);

    /* and the counter */
    chan->cur_count.value--;
    if (chan->cur_count.value == 0xffff) {	/* overflow */
	if (DMA_AUTOINIT(chan->mode)) {
	    q_printf("DMA: controller %i, channel %i reinitialized\n",
		     dma_idx, chan_idx);
	    chan->cur_addr.value = chan->base_addr.value;
	    chan->cur_count.value = chan->base_count.value;
	} else {		/* TC */
	    q_printf("DMA: controller %i, channel %i TC\n", dma_idx,
		     chan_idx);
	    dma[dma_idx].status |= 1 << chan_idx;
	    dma[dma_idx].request &= ~(1 << chan_idx);
	    /* the datasheet says it gets automatically masked too */
	    dma[dma_idx].mask |= 1 << chan_idx;
	}
    }
}

static void dma_run_channel(int dma_idx, int chan_idx)
{
    int done = 0;
    long ticks = 0;
    while (!done &&
	   (HAVE_DRQ(dma_idx, chan_idx) || SW_ACTIVE(dma_idx, chan_idx))) {
	if (!MASKED(dma_idx, chan_idx) &&
	    !REACHED_TC(dma_idx, chan_idx) &&
	    !(dma[dma_idx].command & 4) &&
	    (DMA_TRANSFER_MODE(dma[dma_idx].chans[chan_idx].mode) != CASCADE)) {
	    dma_process_channel(dma_idx, chan_idx);
	    ticks++;
	} else {
	    done = 1;
	}
	dma_update_DRQ(dma_idx, chan_idx);
    }
    if (ticks > 1)
	q_printf("DMA: processed %lu (left %u) cycles on controller %i channel %i\n",
	     ticks, dma[dma_idx].chans[chan_idx].cur_count.value, dma_idx,
	     chan_idx);
}

static void dma_process(void)
{
    int contr_num, chan_num;

    DMA_LOCK();
    for (contr_num = 0; contr_num < 2; contr_num++) {
	for (chan_num = 0; chan_num < 4; chan_num++)
	    dma_run_channel(contr_num, chan_num);
    }
    DMA_UNLOCK();
}

int dma_pulse_DRQ(int ch, Bit8u * buf)
{
    int ret = DMA_DACK;
    if (MASKED(DI(ch), CI(ch))) {
	q_printf("DMA: channel %i masked, DRQ ignored\n", ch);
	ret = DMA_NO_DACK;
    }
    if ((dma[DI(ch)].status & 0xf0) || dma[DI(ch)].request) {
	error("DMA: channel %i already active! (m=%#x s=%#x r=%#x)\n",
	      ch, dma[DI(ch)].chans[CI(ch)].mode, dma[DI(ch)].status,
	      dma[DI(ch)].request);
	ret = DMA_NO_DACK;
    }
#if 0
    q_printf("DMA: pulse DRQ on channel %d\n", ch);
#endif
    if (ret == DMA_DACK) {
	DMA_LOCK();
	dma[DI(ch)].status |= 1 << (CI(ch) + 4);
	memcpy(dma_data_bus, buf, 1 << DI(ch));
	dma_run_channel(DI(ch), CI(ch));
	memcpy(buf, dma_data_bus, 1 << DI(ch));
	DMA_UNLOCK();
    } else {
	memset(buf, 0xff, 1 << DI(ch));
    }
    return ret;
}


/* lets ride on the cpp ass */
#define d(x) (x-1)
#define HANDLE_X(n) \
    HANDLE_##n(1, 1); \
    HANDLE_##n(1, 2); \
    HANDLE_##n(1, 3); \
    HANDLE_##n(1, 4); \
    HANDLE_##n(2, 1); \
    HANDLE_##n(2, 2); \
    HANDLE_##n(2, 3); \
    HANDLE_##n(2, 4)
static Bit8u dma_io_read(ioport_t port)
{
    Bit8u r = 0xff;
    switch (port) {

#define HANDLE_CUR_ADDR_READ(d_n, c_n) \
    case DMA##d_n##_ADDR_##c_n: \
	r = dma[d(d_n)].chans[d(c_n)].cur_addr.byte[dma[d(d_n)].ff]; \
	q_printf("DMA%i: cur_addr read: %#x from Channel %d byte %d\n", \
		d_n, r, d(c_n), dma[d(d_n)].ff); \
	dma[d(d_n)].ff ^= 1; \
	break
	HANDLE_X(CUR_ADDR_READ);

#define HANDLE_CUR_CNT_READ(d_n, c_n) \
    case DMA##d_n##_CNT_##c_n: \
	r = dma[d(d_n)].chans[d(c_n)].cur_count.byte[dma[d(d_n)].ff]; \
	q_printf("DMA%i: cur_cnt read: %#x from Channel %d byte %d\n", \
		d_n, r, d(c_n), dma[d(d_n)].ff); \
	dma[d(d_n)].ff ^= 1; \
	break
	HANDLE_X(CUR_CNT_READ);

#define HANDLE_PAGE_READ(d_n, c_n) \
    case DMA##d_n##_PAGE_##c_n: \
	r = dma[d(d_n)].chans[d(c_n)].page; \
	q_printf("DMA%i: page read: %#x from Channel %d\n", \
		d_n, r, d(c_n)); \
	break
	HANDLE_X(PAGE_READ);

    case DMA1_STAT_REG:
	r = dma[DMA1].status;
	q_printf("DMA1: Read %u from Status reg\n", r);
	dma[DMA1].status &= 0xf0;	/* clear status bits */
	break;
    case DMA2_STAT_REG:
	r = dma[DMA2].status;
	q_printf("DMA2: Read %u from Status reg\n", r);
	dma[DMA2].status &= 0xf0;	/* clear status bits */
	break;

    case DMA1_TEMP_REG:
	r = dma[DMA1].tmp_reg;
	q_printf("DMA1: Read %u from temporary register unimplemented\n",
		 r);
	break;
    case DMA2_TEMP_REG:
	r = dma[DMA2].tmp_reg;
	q_printf("DMA2: Read %u from temporary register unimplemented\n",
		 r);
	break;

    default:
	q_printf("DMA: Unhandled Read on 0x%04x\n", (Bit16u) port);
    }

    dma_process();		// Not needed in fact

    return r;
}

static void dma_io_write(ioport_t port, Bit8u value)
{
    switch (port) {

#define HANDLE_ADDR_WRITE(d_n, c_n) \
    case DMA##d_n##_ADDR_##c_n: \
	dma[d(d_n)].chans[d(c_n)].base_addr.byte[dma[d(d_n)].ff] = value; \
	dma[d(d_n)].chans[d(c_n)].cur_addr.byte[dma[d(d_n)].ff] = value; \
        q_printf("DMA%i: addr write: %#x to Channel %d byte %d\n", \
		d_n, value, d(c_n), dma[d(d_n)].ff); \
	dma[d(d_n)].ff ^= 1; \
	break
	HANDLE_X(ADDR_WRITE);

#define HANDLE_CNT_WRITE(d_n, c_n) \
    case DMA##d_n##_CNT_##c_n: \
	dma[d(d_n)].chans[d(c_n)].base_count.byte[dma[d(d_n)].ff] = value; \
	dma[d(d_n)].chans[d(c_n)].cur_count.byte[dma[d(d_n)].ff] = value; \
	q_printf("DMA%i: count write: %#x to Channel %d byte %d\n", \
		d_n, value, d(c_n), dma[d(d_n)].ff); \
	dma[d(d_n)].ff ^= 1; \
	break
	HANDLE_X(CNT_WRITE);

#define HANDLE_PAGE_WRITE(d_n, c_n) \
    case DMA##d_n##_PAGE_##c_n: \
	dma[d(d_n)].chans[d(c_n)].page = value; \
	q_printf("DMA%i: page write: %#x to Channel %d\n", \
		d_n, value, d(c_n)); \
	break
	HANDLE_X(PAGE_WRITE);

    case DMA1_MASK_REG:
	if (value & 4) {
	    q_printf("DMA1: mask channel %i\n", value & 3);
	    dma[DMA1].mask |= 1 << (value & 3);
	} else {
	    q_printf("DMA1: unmask channel %i\n", value & 3);
	    dma[DMA1].mask &= ~(1 << (value & 3));
	    dma[DMA1].status &= ~(1 << (value & 3));
	}
	break;
    case DMA2_MASK_REG:
	if (value & 4) {
	    q_printf("DMA2: mask channel %i\n", value & 3);
	    dma[DMA2].mask |= 1 << (value & 3);
	} else {
	    q_printf("DMA2: unmask channel %i\n", value & 3);
	    dma[DMA2].mask &= ~(1 << (value & 3));
	    dma[DMA2].status &= ~(1 << (value & 3));
	}
	break;

    case DMA1_MODE_REG:
	dma[DMA1].chans[value & 3].mode = value >> 2;
	q_printf("DMA1: Write mode 0x%x to Channel %u\n", value >> 2,
		 value & 3);
	break;
    case DMA2_MODE_REG:
	dma[DMA2].chans[value & 3].mode = value >> 2;
	q_printf("DMA2: Write mode 0x%x to Channel %u\n", value >> 2,
		 value & 3);
	break;

    case DMA1_CMD_REG:
	dma[DMA1].command = value;
	q_printf("DMA1: Write 0x%x to Command reg\n", value);
	break;
    case DMA2_CMD_REG:
	dma[DMA2].command = value;
	q_printf("DMA2: Write 0x%x to Command reg\n", value);
	break;

    case DMA1_CLEAR_FF_REG:
	q_printf("DMA1: Clearing Output Flip-Flop\n");
	dma[DMA1].ff = 0;
	break;
    case DMA2_CLEAR_FF_REG:
	q_printf("DMA2: Clearing Output Flip-Flop\n");
	dma[DMA2].ff = 0;
	break;

    case DMA1_RESET_REG:
	q_printf("DMA1: Reset\n");
	dma_soft_reset(DMA1);
	break;
    case DMA2_RESET_REG:
	q_printf("DMA2: Reset\n");
	dma_soft_reset(DMA2);
	break;

    case DMA1_REQ_REG:
	if (value & 4) {
	    q_printf("DMA1: Setting request state %#x\n", value);
	    dma[DMA1].request |= 1 << (value & 3);
	} else {
	    q_printf("DMA1: Clearing request state %#x\n", value);
	    dma[DMA1].request &= ~(1 << (value & 3));
	}
	break;
    case DMA2_REQ_REG:
	if (value & 4) {
	    q_printf("DMA2: Setting request state %#x\n", value);
	    dma[DMA2].request |= 1 << (value & 3);
	} else {
	    q_printf("DMA2: Clearing request state %#x\n", value);
	    dma[DMA2].request &= ~(1 << (value & 3));
	}
	break;

    case DMA1_CLR_MASK_REG:
	q_printf("DMA1: Clearing masks\n");
	dma[DMA1].mask = 0;
	dma[DMA1].status &= 0xf0;
	break;
    case DMA2_CLR_MASK_REG:
	q_printf("DMA2: Clearing masks\n");
	dma[DMA2].mask = 0;
	dma[DMA2].status &= 0xf0;
	break;

    case DMA1_MASK_ALL_REG:
	q_printf("DMA1: Setting masks %#x\n", value);
	dma[DMA1].mask = value;
	dma[DMA1].status &= 0xf0;
	break;
    case DMA2_MASK_ALL_REG:
	q_printf("DMA2: Setting masks %#x\n", value);
	dma[DMA2].mask = value;
	dma[DMA2].status &= 0xf0;
	break;

    default:
	q_printf("DMA: Unhandled Write on 0x%04x\n", (Bit16u) port);
    }

    dma_process();		// Not needed in fact
}

void dma_new_reset(void)
{
    dma_soft_reset(DMA1);
    dma_soft_reset(DMA2);
}

void dma_new_init(void)
{
    emu_iodev_t io_device;

    /* 8237 DMA controller */
    io_device.read_portb = dma_io_read;
    io_device.write_portb = dma_io_write;
    io_device.read_portw = NULL;
    io_device.write_portw = NULL;
    io_device.read_portd = NULL;
    io_device.write_portd = NULL;
    io_device.irq = EMU_NO_IRQ;
    io_device.fd = -1;

    /*
     * XT Controller
     */
    io_device.start_addr = 0x0000;
    io_device.end_addr = 0x000F;
    io_device.handler_name = "DMA - XT Controller";
    port_register_handler(io_device, 0);

    /*
     * Page Registers (XT)
     */
    io_device.start_addr = 0x0081;
    io_device.end_addr = 0x0087;
    io_device.handler_name = "DMA - XT Pages";
    port_register_handler(io_device, 0);

    /*
     * AT Controller
     */
    io_device.start_addr = 0x00C0;
    io_device.end_addr = 0x00DE;
    io_device.handler_name = "DMA - AT Controller";
    port_register_handler(io_device, 0);

    /*
     * Page Registers (AT)
     */
    io_device.start_addr = 0x0089;
    io_device.end_addr = 0x008F;
    io_device.handler_name = "DMA - AT Pages";
    port_register_handler(io_device, 0);

    q_printf("DMA: DMA Controller initialized - 8 & 16 bit modes\n");
}

CONSTRUCTOR(static void dma_early_init(void))
{
    /* HACK - putting this into dma_init() should work, but doesn't */
    register_debug_class('q', NULL, "DMA");
}
