/***************************************************************************

    IBM AT Compatibles

***************************************************************************/

#include "includes/at.h"

#define LOG_PORT80	0

/*************************************************************
 *
 * pic8259 configuration
 *
 *************************************************************/
READ8_MEMBER( at_state::get_slave_ack )
{
	if (offset==2) // IRQ = 2
		return pic8259_acknowledge(m_pic8259_slave);

	return 0x00;
}

const struct pic8259_interface at_pic8259_master_config =
{
	DEVCB_CPU_INPUT_LINE("maincpu", 0),
	DEVCB_LINE_VCC,
	DEVCB_DRIVER_MEMBER(at_state, get_slave_ack)
};

const struct pic8259_interface at_pic8259_slave_config =
{
	DEVCB_DEVICE_LINE("pic8259_master", pic8259_ir2_w),
	DEVCB_LINE_GND,
	DEVCB_NULL
};



/*************************************************************************
 *
 *      PC Speaker related
 *
 *************************************************************************/

void at_state::at_speaker_set_spkrdata(UINT8 data)
{
	m_at_spkrdata = data ? 1 : 0;
	speaker_level_w( m_speaker, m_at_spkrdata & m_at_speaker_input);
}

void at_state::at_speaker_set_input(UINT8 data)
{
	m_at_speaker_input = data ? 1 : 0;
	speaker_level_w( m_speaker, m_at_spkrdata & m_at_speaker_input);
}



/*************************************************************
 *
 * pit8254 configuration
 *
 *************************************************************/

WRITE_LINE_MEMBER( at_state::at_pit8254_out0_changed )
{
	if (m_pic8259_master)
		pic8259_ir0_w(m_pic8259_master, state);
}


WRITE_LINE_MEMBER( at_state::at_pit8254_out2_changed )
{
	at_speaker_set_input( state );
}


const struct pit8253_config at_pit8254_config =
{
	{
		{
			4772720/4,				/* heartbeat IRQ */
			DEVCB_NULL,
			DEVCB_DRIVER_LINE_MEMBER(at_state, at_pit8254_out0_changed)
		}, {
			4772720/4,				/* dram refresh */
			DEVCB_NULL,
			DEVCB_NULL
		}, {
			4772720/4,				/* pio port c pin 4, and speaker polling enough */
			DEVCB_NULL,
			DEVCB_DRIVER_LINE_MEMBER(at_state, at_pit8254_out2_changed)
		}
	}
};


/*************************************************************************
 *
 *      PC DMA stuff
 *
 *************************************************************************/

READ8_MEMBER( at_state::at_page8_r )
{
	UINT8 data = m_at_pages[offset % 0x10];

	switch(offset % 8)
	{
	case 1:
		data = m_dma_offset[BIT(offset, 3)][2];
		break;
	case 2:
		data = m_dma_offset[BIT(offset, 3)][3];
		break;
	case 3:
		data = m_dma_offset[BIT(offset, 3)][1];
		break;
	case 7:
		data = m_dma_offset[BIT(offset, 3)][0];
		break;
	}
	return data;
}


WRITE8_MEMBER( at_state::at_page8_w )
{
	m_at_pages[offset % 0x10] = data;

	if (LOG_PORT80 && (offset == 0))
	{
		logerror(" at_page8_w(): Port 80h <== 0x%02x (PC=0x%08x)\n", data,
							(unsigned) cpu_get_reg(m_maincpu, STATE_GENPC));
	}

	switch(offset % 8)
	{
	case 1:
		m_dma_offset[BIT(offset, 3)][2] = data;
		break;
	case 2:
		m_dma_offset[BIT(offset, 3)][3] = data;
		break;
	case 3:
		m_dma_offset[BIT(offset, 3)][1] = data;
		break;
	case 7:
		m_dma_offset[BIT(offset, 3)][0] = data;
		break;
	}
}


WRITE_LINE_MEMBER( at_state::pc_dma_hrq_changed )
{
	device_set_input_line(m_maincpu, INPUT_LINE_HALT, state ? ASSERT_LINE : CLEAR_LINE);

	/* Assert HLDA */
	i8237_hlda_w( m_dma8237_2, state );
}

static READ8_HANDLER( pc_dma_read_byte )
{
	at_state *st = space->machine().driver_data<at_state>();
	UINT8 result;
	offs_t page_offset = (((offs_t) st->m_dma_offset[0][st->m_dma_channel]) << 16) & 0xFF0000;

	result = space->read_byte(page_offset + offset);
	return result;
}


static WRITE8_HANDLER( pc_dma_write_byte )
{
	at_state *st = space->machine().driver_data<at_state>();
	offs_t page_offset = (((offs_t) st->m_dma_offset[0][st->m_dma_channel]) << 16) & 0xFF0000;

	space->write_byte(page_offset + offset, data);
}


static READ8_HANDLER( pc_dma_read_word )
{
	at_state *st = space->machine().driver_data<at_state>();
	UINT16 result;
	offs_t page_offset = (((offs_t) st->m_dma_offset[1][st->m_dma_channel & 3]) << 16) & 0xFF0000;

	result = space->read_word(page_offset + ( offset << 1 ) );
	st->m_dma_high_byte = result & 0xFF00;

	return result & 0xFF;
}


static WRITE8_HANDLER( pc_dma_write_word )
{
	at_state *st = space->machine().driver_data<at_state>();
	offs_t page_offset = (((offs_t) st->m_dma_offset[1][st->m_dma_channel & 3]) << 16) & 0xFF0000;

	space->write_word(page_offset + ( offset << 1 ), st->m_dma_high_byte | data);
}


READ8_MEMBER( at_state::pc_dma8237_0_dack_r ) { return m_isabus->dack_r(0); }
READ8_MEMBER( at_state::pc_dma8237_1_dack_r ) { return m_isabus->dack_r(1); }
READ8_MEMBER( at_state::pc_dma8237_2_dack_r ) { return m_isabus->dack_r(2); }
READ8_MEMBER( at_state::pc_dma8237_3_dack_r ) { return m_isabus->dack_r(3); }
READ8_MEMBER( at_state::pc_dma8237_5_dack_r ) { return m_isabus->dack_r(5); }
READ8_MEMBER( at_state::pc_dma8237_6_dack_r ) { return m_isabus->dack_r(6); }
READ8_MEMBER( at_state::pc_dma8237_7_dack_r ) { return m_isabus->dack_r(7); }


WRITE8_MEMBER( at_state::pc_dma8237_0_dack_w ){ m_isabus->dack_w(0, data); }
WRITE8_MEMBER( at_state::pc_dma8237_1_dack_w ){ m_isabus->dack_w(1, data); }
WRITE8_MEMBER( at_state::pc_dma8237_2_dack_w ){ m_isabus->dack_w(2, data); }
WRITE8_MEMBER( at_state::pc_dma8237_3_dack_w ){ m_isabus->dack_w(3, data); }
WRITE8_MEMBER( at_state::pc_dma8237_5_dack_w ){ m_isabus->dack_w(5, data); }
WRITE8_MEMBER( at_state::pc_dma8237_6_dack_w ){ m_isabus->dack_w(6, data); }
WRITE8_MEMBER( at_state::pc_dma8237_7_dack_w ){ m_isabus->dack_w(7, data); }

WRITE_LINE_MEMBER( at_state::at_dma8237_out_eop ) { m_isabus->eop_w(state == ASSERT_LINE ? 0 : 1 ); }

static void set_dma_channel(device_t *device, int channel, int state)
{
	at_state *st = device->machine().driver_data<at_state>();
	if (!state)
		st->m_dma_channel = channel;
}

WRITE_LINE_MEMBER( at_state::pc_dack0_w ) { set_dma_channel(m_dma8237_1, 0, state); }
WRITE_LINE_MEMBER( at_state::pc_dack1_w ) { set_dma_channel(m_dma8237_1, 1, state); }
WRITE_LINE_MEMBER( at_state::pc_dack2_w ) { set_dma_channel(m_dma8237_1, 2, state); }
WRITE_LINE_MEMBER( at_state::pc_dack3_w ) { set_dma_channel(m_dma8237_1, 3, state); }
WRITE_LINE_MEMBER( at_state::pc_dack4_w ) { i8237_hlda_w( m_dma8237_1, state ? 0 : 1); } // it's inverted
WRITE_LINE_MEMBER( at_state::pc_dack5_w ) { set_dma_channel(m_dma8237_2, 5, state); }
WRITE_LINE_MEMBER( at_state::pc_dack6_w ) { set_dma_channel(m_dma8237_2, 6, state); }
WRITE_LINE_MEMBER( at_state::pc_dack7_w ) { set_dma_channel(m_dma8237_2, 7, state); }

I8237_INTERFACE( at_dma8237_1_config )
{
	DEVCB_DEVICE_LINE("dma8237_2",i8237_dreq0_w),
	DEVCB_DRIVER_LINE_MEMBER(at_state, at_dma8237_out_eop),
	DEVCB_MEMORY_HANDLER("maincpu", PROGRAM, pc_dma_read_byte),
	DEVCB_MEMORY_HANDLER("maincpu", PROGRAM, pc_dma_write_byte),
	{ DEVCB_DRIVER_MEMBER(at_state, pc_dma8237_0_dack_r), DEVCB_DRIVER_MEMBER(at_state, pc_dma8237_1_dack_r), DEVCB_DRIVER_MEMBER(at_state, pc_dma8237_2_dack_r), DEVCB_DRIVER_MEMBER(at_state, pc_dma8237_3_dack_r) },
	{ DEVCB_DRIVER_MEMBER(at_state, pc_dma8237_0_dack_w), DEVCB_DRIVER_MEMBER(at_state, pc_dma8237_1_dack_w), DEVCB_DRIVER_MEMBER(at_state, pc_dma8237_2_dack_w), DEVCB_DRIVER_MEMBER(at_state, pc_dma8237_3_dack_w) },
	{ DEVCB_DRIVER_LINE_MEMBER(at_state, pc_dack0_w), DEVCB_DRIVER_LINE_MEMBER(at_state, pc_dack1_w), DEVCB_DRIVER_LINE_MEMBER(at_state, pc_dack2_w), DEVCB_DRIVER_LINE_MEMBER(at_state, pc_dack3_w) }
};


I8237_INTERFACE( at_dma8237_2_config )
{
	DEVCB_DRIVER_LINE_MEMBER(at_state, pc_dma_hrq_changed),
	DEVCB_NULL,
	DEVCB_MEMORY_HANDLER("maincpu", PROGRAM, pc_dma_read_word),
	DEVCB_MEMORY_HANDLER("maincpu", PROGRAM, pc_dma_write_word),
	{ DEVCB_NULL, DEVCB_DRIVER_MEMBER(at_state, pc_dma8237_5_dack_r), DEVCB_DRIVER_MEMBER(at_state, pc_dma8237_6_dack_r), DEVCB_DRIVER_MEMBER(at_state, pc_dma8237_7_dack_r) },
	{ DEVCB_NULL, DEVCB_DRIVER_MEMBER(at_state, pc_dma8237_5_dack_w), DEVCB_DRIVER_MEMBER(at_state, pc_dma8237_6_dack_w), DEVCB_DRIVER_MEMBER(at_state, pc_dma8237_7_dack_w) },
	{ DEVCB_DRIVER_LINE_MEMBER(at_state, pc_dack4_w), DEVCB_DRIVER_LINE_MEMBER(at_state, pc_dack5_w), DEVCB_DRIVER_LINE_MEMBER(at_state, pc_dack6_w), DEVCB_DRIVER_LINE_MEMBER(at_state, pc_dack7_w) }
};

READ8_MEMBER( at_state::at_portb_r )
{
	UINT8 data = m_at_speaker;
	data &= ~0xc0; /* AT BIOS don't likes this being set */

	/* This needs fixing/updating not sure what this is meant to fix */
	if ( --m_poll_delay < 0 )
	{
		m_poll_delay = 3;
		m_at_offset1 ^= 0x10;
	}
	data = (data & ~0x10) | ( m_at_offset1 & 0x10 );

	if ( pit8253_get_output(m_pit8254, 2 ) )
		data |= 0x20;
	else
		data &= ~0x20; /* ps2m30 wants this */

	return data;
}

WRITE8_MEMBER( at_state::at_portb_w )
{
	m_at_speaker = data;
	pit8253_gate2_w(m_pit8254, BIT(data, 0));
	at_speaker_set_spkrdata( BIT(data, 1));
}


/**********************************************************
 *
 * Init functions
 *
 **********************************************************/

static void init_at_common(running_machine &machine)
{
	at_state *st = machine.driver_data<at_state>();
	address_space* space = machine.device("maincpu")->memory().space(AS_PROGRAM);

	// The CS4031 chipset does this itself
	if (machine.device("cs4031") == NULL)
	{
		/* MESS managed RAM */
		memory_set_bankptr(machine, "bank10", ram_get_ptr(machine.device(RAM_TAG)));

		if (ram_get_size(machine.device(RAM_TAG)) > 0x0a0000)
		{
			offs_t ram_limit = 0x100000 + ram_get_size(machine.device(RAM_TAG)) - 0x0a0000;
			space->install_read_bank(0x100000,  ram_limit - 1, "bank1");
			space->install_write_bank(0x100000,  ram_limit - 1, "bank1");
			memory_set_bankptr(machine, "bank1", ram_get_ptr(machine.device(RAM_TAG)) + 0xa0000);
		}
	}

	st->m_at_offset1 = 0xff;
}


static READ8_HANDLER( input_port_0_r ) { return input_port_read(space->machine(), "IN0"); }

static const struct pc_vga_interface vga_interface =
{
	NULL,
	NULL,
	input_port_0_r,
	AS_IO,
	0x0000
};


DRIVER_INIT( atcga )
{
	init_at_common(machine);
}

DRIVER_INIT( atvga)
{
	init_at_common(machine);
	pc_vga_init(machine, &vga_interface, NULL);
}

static IRQ_CALLBACK(at_irq_callback)
{
	at_state *st = device->machine().driver_data<at_state>();
	return pic8259_acknowledge(st->m_pic8259_master);
}

MACHINE_START( at )
{
	device_set_irq_callback(machine.device("maincpu"), at_irq_callback);
}

MACHINE_RESET( at )
{
	at_state *st = machine.driver_data<at_state>();
	st->m_poll_delay = 4;
	st->m_at_spkrdata = 0;
	st->m_at_speaker_input = 0;
}
