#include "ibm.h"
#include "io.h"
#include "pic.h"
#include "plat-midi.h"
#include "sound_mpu401_uart.h"

enum {
	STATUS_OUTPUT_NOT_READY = 0x40,
	STATUS_INPUT_NOT_READY = 0x80
};

static void mpu401_uart_raise_irq(void *p) {
	mpu401_uart_t *mpu = (mpu401_uart_t *)p;

	if (mpu->irq != -1)
		picint(1 << mpu->irq);
}

static void mpu401_uart_write(uint16_t addr, uint8_t val, void *p) {
	mpu401_uart_t *mpu = (mpu401_uart_t *)p;

	if (addr & 1) /*Command*/
	{
		switch (val) {
		case 0xff: /*Reset*/
			// From Roland: "An ACK will not be sent back upon sending a SYSTEM RESET to leave the UART MODE ($3F)."
			// But actual behaviour is weird. For example, the MPU401 port test in the AZT1605 drivers for Windows NT
			// want this to return an Ack but the IRQ test in the same driver wants this to raise no interrupts!
			mpu->rx_data = 0xfe; /*Acknowledge*/
			mpu->uart_mode = 0;
			if (mpu->is_aztech)
				mpu->status = STATUS_OUTPUT_NOT_READY;
			else {
				mpu->status = 0;
				mpu401_uart_raise_irq(p);
			}
			break;

		case 0x3f: /*Enter UART mode*/
			mpu->rx_data = 0xfe; /*Acknowledge*/
			mpu->uart_mode = 1;
			if (mpu->is_aztech) {
				mpu->status = STATUS_OUTPUT_NOT_READY;
				mpu401_uart_raise_irq(p);
			} else
				mpu->status = 0;
			break;
		}
		return;
	}

	/*Data*/
	if (mpu->uart_mode)
		midi_write(val);
}

static uint8_t mpu401_uart_read(uint16_t addr, void *p) {
	mpu401_uart_t *mpu = (mpu401_uart_t *)p;

	if (addr & 1) /*Status*/
		return mpu->status;

	/*Data*/
	mpu->status = STATUS_INPUT_NOT_READY;
	return mpu->rx_data;
}

void mpu401_uart_init(mpu401_uart_t *mpu, uint16_t addr, int irq, int is_aztech) {
	mpu->status = STATUS_INPUT_NOT_READY;
	mpu->uart_mode = 0;
	mpu->addr = addr;
	mpu->irq = irq;
	mpu->is_aztech = is_aztech;

	io_sethandler(addr, 0x0002, mpu401_uart_read, NULL, NULL, mpu401_uart_write, NULL, NULL, mpu);
}

void mpu401_uart_update_addr(mpu401_uart_t *mpu, uint16_t addr) {
	io_removehandler(mpu->addr, 0x0002, mpu401_uart_read, NULL, NULL, mpu401_uart_write, NULL, NULL, mpu);
	mpu->addr = addr;
	if (addr != 0)
		io_sethandler(addr, 0x0002, mpu401_uart_read, NULL, NULL, mpu401_uart_write, NULL, NULL, mpu);
}

void mpu401_uart_update_irq(mpu401_uart_t *mpu, int irq) {
	mpu->irq = irq;
}

