/*
 * Copyright (C) 2005-2017 FAUmachine Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

#define DEBUG_CONTROL_FLOW	0

#ifdef INCLUDE

/* Nothing... */

#endif /* INCLUDE */
#ifdef STATE

struct {
	unsigned short base_address[4];
	unsigned short base_counter[4];
	unsigned short address[4];
	unsigned short count[4];
	unsigned char status;	/* Not implemented yet. FIXME VOSSI */
	unsigned char command;	/* Not implemented yet. FIXME VOSSI */
	unsigned char temporary;/* Not implemented yet. FIXME VOSSI */
	unsigned char mode[4];
	unsigned char mask;
	unsigned char request;	/* Not implemented yet. FIXME VOSSI */

	unsigned char inout_index;
} NAME;

#endif /* STATE */
#ifdef BEHAVIOR

/*
 * NOTES about DMA transfers:
 *
 *  controller 1: channels 0-3, byte operations, ports 00-1F
 *  controller 2: channels 4-7, word operations, ports C0-DF
 *
 *  - ALL registers are 8 bits only, regardless of transfer size
 *  - channel 4 is not used - cascades 1 into 2.
 *  - channels 0-3 are byte - addresses/counts are for physical bytes
 *  - channels 5-7 are word - addresses/counts are for physical words
 *  - transfers must not cross physical 64K (0-3) or 128K (5-7) boundaries
 *  - transfer count loaded to registers is 1 less than actual count
 *  - controller 2 offsets are all even (2x offsets for controller 1)
 *  - page registers for 5-7 don't use data bit 0, represent 128K pages
 *  - page registers for 0-3 use bit 0, represent 64K pages
 *
 * DMA transfers are limited to the lower 16MB of _physical_ memory.
 * Note that addresses loaded into registers must be _physical_ addresses,
 * not logical addresses (which may differ if paging is active).
 *
 *  Address mapping for channels 0-3:
 *
 *   A23 ... A16 A15 ... A8  A7 ... A0    (Physical addresses)
 *    |  ...  |   |  ... |   |  ... |
 *    |  ...  |   |  ... |   |  ... |
 *    |  ...  |   |  ... |   |  ... |
 *   P7  ...  P0  A7 ... A0  A7 ... A0
 * |    Page    | Addr MSB | Addr LSB |   (DMA registers)
 *
 *  Address mapping for channels 5-7:
 *
 *   A23 ... A17 A16 A15 ... A9 A8 A7 ... A1 A0    (Physical addresses)
 *    |  ...  |   \   \   ... \  \  \  ... \  \
 *    |  ...  |    \   \   ... \  \  \  ... \  (not used)
 *    |  ...  |     \   \   ... \  \  \  ... \
 *   P7  ...  P1 (0) A7 A6  ... A0 A7 A6 ... A0
 * |      Page      |  Addr MSB   |  Addr LSB  |   (DMA registers)
 *
 * Again, channels 5-7 transfer _physical_ words (16 bits), so addresses
 * and counts _must_ be word-aligned (the lowest address bit is _ignored_ at
 * the hardware level, so odd-byte transfers aren't possible).
 *
 * Transfer count (_not # bytes_) is limited to 64K, represented as actual
 * count - 1 : 64K => 0xFFFF, 1 => 0x0000.  Thus, count is always 1 or more,
 * and up to 128K bytes may be transferred on channels 5-7 in one operation.
 *
 */

static void
NAME_(req)(struct cpssp *css, unsigned char chan)
{
	assert(/* 0 <= chan && */ chan < 4);

	switch ((css->NAME.mode[chan] >> 2) & 3) {
	case 0:
		/* Verify transfer. */
		NAME_(ack_verify)(css, chan, css->NAME.count[chan] == 0);
		break;
	case 1:
		/* Write transfer. */
		NAME_(ack_in)(css, css->NAME.address[chan],
				chan, css->NAME.count[chan] == 0);
		break;
	case 2:
		/* Read transfer. */
		NAME_(ack_out)(css, css->NAME.address[chan],
				chan, css->NAME.count[chan] == 0);
		break;
	case 3:
		/* Illegal. */
		assert(0);	/* FIXME VOSSI */
		break;
	default:
		assert(0);	/* Cannot happen. */
	}

	if ((css->NAME.mode[chan] >> 5) & 1) {
		/* Decrement address. */
		css->NAME.address[chan]--;
	} else {
		/* Increment address. */
		css->NAME.address[chan]++;
	}
	css->NAME.count[chan]--;

	if ((css->NAME.mode[chan] >> 4) & 1) {
		/* Autoinit mode. */
		if (css->NAME.count[chan] == 0xffff) {
			css->NAME.count[chan] = css->NAME.base_counter[chan];
			css->NAME.address[chan] = css->NAME.base_address[chan];
		}
	}
}

static void
NAME_(inb)(struct cpssp *css, unsigned char *valuep, unsigned short port)
{
	unsigned char chan;

#if DEBUG_CONTROL_FLOW
	fprintf(stderr, "%s called(port=0x%04x)\n", __FUNCTION__, port);
#endif

	switch (port) {
	case 0x00: /* current address, channel 0 */
	case 0x02: /* current address, channel 1 */
	case 0x04: /* current address, channel 2 */
	case 0x06: /* current address, channel 3 */
		chan = port / 2;
		if (css->NAME.inout_index == 0){
			css->NAME.inout_index++;
			*valuep = css->NAME.address[chan] & 0xff;
		} else {
			css->NAME.inout_index = 0;
			*valuep = css->NAME.address[chan] >> 8 ;
		}
		break;

	case 0x01: /* current count, channel 0 */
	case 0x03: /* current count, channel 1 */
	case 0x05: /* current count, channel 2 */
	case 0x07: /* current count, channel 3 */
		chan = port / 2;
		if (css->NAME.inout_index == 0) {
			css->NAME.inout_index++;
			*valuep = css->NAME.count[chan] & 0xff;
		} else {
			css->NAME.inout_index = 0;
			*valuep = css->NAME.count[chan] >> 8 ;
		}
		break;

	case 0x08:
		/* Read status register. */
		*valuep = css->NAME.status;
		break;

	case 0x09:
	case 0x0a:
	case 0x0b:
	case 0x0c:
		goto write_only;

	case 0x0d:
		/* Read temporary register. */
		*valuep = css->NAME.temporary;
		break;

	case 0x0e:
	case 0x0f:
	write_only:;
		faum_log(FAUM_LOG_WARNING, "DMA", "",
				"Reading write-only register 0x%02x.\n", port);
		*valuep = 0x00;
		break;

	default:
		assert(0);
	}

#if DEBUG_CONTROL_FLOW
	fprintf(stderr, "%s return(value=0x%02x)\n", __FUNCTION__, *valuep);
#endif
}

static void
NAME_(outb)(struct cpssp *css, unsigned char value, unsigned short port)
{
	unsigned char chan;

#if DEBUG_CONTROL_FLOW
	fprintf(stderr, "%s called(value=0x%02x, port=0x%04x)\n", __FUNCTION__,
			value, port);
#endif

	switch (port) {
	case 0x00: /* current address, channel 0 */
	case 0x02: /* current address, channel 1 */
	case 0x04: /* current address, channel 2 */
	case 0x06: /* current address, channel 3 */
		chan = port / 2;
		if (css->NAME.inout_index == 0) {
			css->NAME.inout_index++;
			css->NAME.address[chan] = value;
		} else {
			css->NAME.inout_index = 0;
			css->NAME.address[chan] |= ((unsigned short) value) << 8;
			css->NAME.base_address[chan] = css->NAME.address[chan];
		}
		break;
	case 0x01: /* current count, channel 0 */
	case 0x03: /* current count, channel 1 */
	case 0x05: /* current count, channel 2 */
	case 0x07: /* current count, channel 3 */
		chan = port / 2;
		if (css->NAME.inout_index == 0) {
			css->NAME.inout_index++;
			css->NAME.count[chan] = value;
		} else {
			css->NAME.inout_index = 0;
			css->NAME.count[chan] |= ((unsigned short) value) << 8;
			css->NAME.base_counter[chan] = css->NAME.count[chan];
		}
		break;

	case 0x08:
		/* Write command register. */
		css->NAME.command = value;
		break;

	case 0x09:
		/* Write single request register bit. */
		chan = value & 3;
		css->NAME.request &= ~(1 << chan);
		css->NAME.request |= ((value >> 2) & 1) << chan;
		break;

	case 0x0a:
		/* Write single mask register bit. */
		chan = value & 3;
		css->NAME.mask &= ~(1 << chan);
		css->NAME.mask |= ((value >> 2) & 1) << chan;
		break;

	case 0x0b:
		/* Write mode register. */
		chan = value & 3;
		css->NAME.mode[chan] = value & ~3;
		break;

	case 0x0c:
		/* Clear byte pointer flip/flop. */
		css->NAME.inout_index = 0;
		break;
	
	case 0x0d:
		/* Master clear (reset). */
		faum_log(FAUM_LOG_WARNING, "DMA", "",
				"Reset called (not implemented yet).\n");
		break;
	
	case 0x0e:
		/* Clear mask register. */
		css->NAME.mask = 0;
		break;
	
	case 0x0f:
		/* Write all mask register bits. */
		css->NAME.mask = value & 0xf;
		break;

	default:
		assert(0);
	}
}

static void
NAME_(reset)(struct cpssp *css)
{
}

static void
NAME_(init)(struct cpssp *css)
{
	/* Nothing to do... */
}

#endif /* BEHAVIOR */

#undef DEBUG_CONTROL_FLOW
