// license:BSD-3-Clause
// copyright-holders:Nigel Barnes
/******************************************************************************

    Psion ASIC5

    ASIC5 is a general purpose I/O chip with a built-in UART that can be set to
    run in a number of different modes thereby simplifying the task of peripheral
    design. For example, it is possible to set up ASIC5 to run as a Centronics
    parallel port interface, an 8-bit parallel I/O port, a serial bar code
    controller or a serial RS232 converter.

         PC6         PA7 PA6 PA5                           PA4 PA3             PA2 PA1 PA0
      Pack Mode      Memory Type                           No. devices         Memory Size
          1           0   0   0  RAM                        0   0  1 device     0   0   0  No memory
                      0   0   1  Type 1 Flash               0   1  2 devices    0   0   1  32K
                      0   1   0  Type 2 Flash               1   0  3 devices    0   1   0  64K
                      1   1   0  ROM                        1   1  4 devices    0   1   1  128K
                      1   1   1  Write protect                                  1   0   0  256K
                                                                                1   0   1  512K
                                                                                1   1   0  1M
    Peripheral Mode                                  Type
          0           x   x   0   x   x   x   x   1  RS232 port
                      x   x   0   x   x   x   1   x  Centronics (Parallel) port
                      x   x   0   x   x   1   x   x  ROM
                      x   x   0   x   1   x   x   x  Magnetic Card Reader
                      x   x   0   0   x   x   x   x  Barcode reader
                      x   x   0   1   x   x   x   x  USA modem
                      x   1   0   x   x   x   x   x  Modem
                      1   x   0   x   x   x   x   x  RS232 TTL

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

#include "emu.h"
#include "psion_asic5.h"

#define VERBOSE 0
//#define LOG_OUTPUT_FUNC osd_printf_info
#include "logmacro.h"


DEFINE_DEVICE_TYPE(PSION_ASIC5, psion_asic5_device, "psion_asic5", "Psion ASIC5")


//**************************************************************************
//  LIVE DEVICE
//**************************************************************************

psion_asic5_device::psion_asic5_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: device_t(mconfig, PSION_ASIC5, tag, owner, clock)
	, device_serial_interface(mconfig, *this)
	, m_mode(~uint8_t(0))
	, m_in_a_handler(*this, 0)
	, m_in_b_handler(*this, 0)
	, m_in_c_handler(*this, 0)
	, m_out_a_handler(*this)
	, m_out_b_handler(*this)
	, m_out_c_handler(*this)
	, m_out_d_handler(*this)
	, m_out_cs_handler(*this)
	, m_int_handler(*this)
	, m_txd_handler(*this)
	, m_rts_handler(*this)
	, m_dtr_handler(*this)
	, m_rxd(0)
	, m_cts(0)
	, m_dsr(0)
	, m_dcd(0)
{
}


//-------------------------------------------------
//  device_validity_check - device-specific checks
//-------------------------------------------------

void psion_asic5_device::device_validity_check(validity_checker &valid) const
{
	if ((PACK_MODE != m_mode) && (PERIPHERAL_MODE != m_mode))
		osd_printf_error("Power on reset pin PC6 not configured\n");
}


//-------------------------------------------------
//  device_start - device-specific startup
//-------------------------------------------------

void psion_asic5_device::device_start()
{
	save_item(NAME(m_port_b_counter));
	save_item(NAME(m_port_b_latch));
	save_item(NAME(m_port_b_mode));
	save_item(NAME(m_port_dc_select));
	save_item(NAME(m_int_mask));
	save_item(NAME(m_int_status));
	save_item(NAME(m_control));
	save_item(NAME(m_bdr));
}

//-------------------------------------------------
//  device_reset - device-specific reset
//-------------------------------------------------

void psion_asic5_device::device_reset()
{
	m_port_b_counter = 0x00;
	m_port_b_latch   = 0x00;
	m_port_b_mode    = 0x00;
	m_port_dc_select = false;
	m_int_mask       = 0x00;
	m_int_status     = 0x02; // UART transmitter empty
	m_control        = 0x00;
	m_bdr            = -1;

	receive_register_reset();
	transmit_register_reset();
}


void psion_asic5_device::update_interrupts()
{
	int irq = m_int_status & m_int_mask;

	m_int_handler(irq ? ASSERT_LINE : CLEAR_LINE);
}


void psion_asic5_device::rcv_callback()
{
	if (BIT(m_port_b_mode, 0)) // UART enabled
		receive_register_update_bit(m_rxd);
}

void psion_asic5_device::rcv_complete()
{
	receive_register_extract();

	if (is_receive_framing_error() || is_receive_parity_error())
		m_int_status |= 0x04;

	m_int_status |= 0x01;
	update_interrupts();
}

void psion_asic5_device::tra_callback()
{
	if (BIT(m_port_b_mode, 0)) // UART enabled
		m_txd_handler(transmit_register_get_data_bit());
}

void psion_asic5_device::tra_complete()
{
	m_int_status |= 0x02;
	update_interrupts();
}


//**************************************************************************
//  READ/WRITE HANDLERS
//**************************************************************************

void psion_asic5_device::data_w(uint16_t data)
{
	switch (data & 0x300)
	{
	case NULL_FRAME:
		device_reset();
		break;

	case CONTROL_FRAME:
		m_sibo_control = data & 0xff;

		switch (m_sibo_control & 0xc0)
		{
		case 0x80: // SerialWrite
			if (m_sibo_control == 0x93) // Multi Port D and C writes
				m_port_dc_select = false;
			break;
		}
		break;

	case DATA_FRAME:
		data &= 0xff;
		switch (m_sibo_control & 0x0f)
		{
		case 0x00: // Port A write data
			LOG("%s data_w: Port A write data %02x\n", machine().describe_context(), data);
			m_out_a_handler(data);
			if (BIT(m_port_b_mode, 1, 2) == 0) // Counter mode
			{
				m_out_b_handler(++m_port_b_counter);
			}
			break;

		case 0x01: // Port B write data
			LOG("%s data_w: Port B write data %02x\n", machine().describe_context(), data);
			if (BIT(m_port_b_mode, 1, 2) == 1 && !BIT(m_control, 3)) // Latch mode
			{
				m_out_b_handler(m_port_b_latch = data);
			}
			break;

		case 0x02: // Port B control
			// b0 0 Memory mode, 1 Peripheral mode - enables UART
			// b1 Port B mode
			// b2 Port B mode
			// b3 0 Normal mode, 1 Test mode
			// b4 Not used
			// b5 Not used
			// b6 Not used
			// b7 Not used
			//
			// Port B mode
			// b2  b1  Mode
			// 0   0   Counter mode
			// 0   1   Latch mode
			// 1   0   Baud rate out on port B
			// 1   1   Test bus output on port B
			LOG("%s data_w: Port B mode %02x\n", machine().describe_context(), data);
			m_port_b_mode = data;
			break;

		case 0x03: // Port D and C write data
			switch (BIT(m_port_b_mode, 0))
			{
			case 0:
				if (m_port_dc_select)
				{
					LOG("%s data_w: Port C write data %02x\n", machine().describe_context(), data);
					m_out_c_handler(data);
				}
				else
				{
					LOG("%s data_w: Port D write data %02x\n", machine().describe_context(), data);
					m_out_d_handler(data);
					if (BIT(m_port_b_mode, 1, 2) == 0) // Counter mode
					{
						m_out_b_handler(m_port_b_counter = 0);
					}
				}
				m_port_dc_select = true;
				break;
			case 1: // UART enabled
				m_rts_handler(BIT(data, 1));
				m_dtr_handler(BIT(data, 2));
				break;
			}
			break;

		case 0x06: // Interrupt mask write
			// b0 UART - UART character received
			// b1 UART - UART transmitter empty
			// b2 UART - UART error or modem line status change
			// b3 PC7  - Barcode switch/general interrupt
			// b4 SR   - Synchronous port1 character received
			// b5 SR   - Synchronous port2 character received
			// b6 PC4  - Barcode data/general interrupt
			// b7 PA4  - Centronics busy low/general interrupt
			LOG("%s data_w: Interrupt mask write %02x\n", machine().describe_context(), data);
			m_int_mask = data;
			update_interrupts();
			break;

		case 0x07: // Control register
			LOG("%s data_w: Control register %02x\n", machine().describe_context(), data);
			m_control = data;
			m_out_cs_handler(data & 7);
			break;

		case 0x08: // UART Control register
			// b0 Generate break character
			// b1 Character length 1
			// b2 Character length 2
			// b3 Parity enabled if set
			// b4 Odd parity if, even if clear
			// b5 Set for two stop bits, clear for one
			// b6 Not used
			// b7 Not used
			LOG("%s data_w: UART Control register %02x\n", machine().describe_context(), data);
			set_data_frame(1, 5 + BIT(data, 1, 2), BIT(data, 3) ? (BIT(data, 4) ? PARITY_ODD : PARITY_EVEN) : PARITY_NONE, BIT(data, 5) ? STOP_BITS_2 : STOP_BITS_1);
			receive_register_reset();
			transmit_register_reset();
			break;

		case 0x09: // UART Transmit holding register
			LOG("%s data_w: UART Transmit holding register %02x\n", machine().describe_context(), data);
			transmit_register_setup(data);
			m_int_status &= ~0x02;
			update_interrupts();
			break;

		case 0x0a:
		case 0x0b: // UART Baud rate
			switch (m_sibo_control & 1)
			{
			case 0: m_bdr = (m_bdr & 0xff00) | (data << 0); break; // LSB
			case 1: m_bdr = (m_bdr & 0x00ff) | (data << 8); break; // MSB
			}
			LOG("%s data_w: UART Baud rate divisor %04x, Baud rate %d\n", machine().describe_context(), m_bdr, clock() / 16 / (1 - m_bdr));
			set_rate(clock() / 16 / (1 - m_bdr));
			break;

		case 0x0c: // Port 1 and 2 reset
			LOG("%s data_w: Port 1 and 2 reset %02x\n", machine().describe_context(), data);
			break;

		default:
			LOG("%s data_w: unknown control %02x data %02x\n", machine().describe_context(), m_sibo_control, data);
			break;
		}
		break;
	}
}


uint8_t psion_asic5_device::data_r()
{
	uint8_t data = 0x00;

	switch (m_sibo_control & 0xc0)
	{
	case 0x40: // SerialSelect
		switch (m_sibo_control & 0x0f)
		{
		case 0x02: // Asic5PackId
		case 0x03: // Asic5NormalId
			if (BIT(m_sibo_control, 0) == m_mode)
				data = m_info_byte; // A5InfoByte
			break;
		}
		break;

	case 0xc0: // SerialRead
		switch (m_sibo_control & 0x0f)
		{
		case 0x00: // Port A read data
			data = m_in_a_handler();
			LOG("%s data_r: Port A read data %02x\n", machine().describe_context(), data);
			if (BIT(m_port_b_mode, 1, 2) == 0) // Counter mode
			{
				m_out_b_handler(++m_port_b_counter);
			}
			break;

		case 0x01: // Port B read data
			if (BIT(m_port_b_mode, 1, 2) == 1) // Latch mode
			{
				data = m_port_b_latch;
			}
			LOG("%s data_r: Port B read data %02x\n", machine().describe_context(), data);
			break;

		case 0x02: // Port B counter
			if (BIT(m_port_b_mode, 1, 2) == 0) // Counter mode
			{
				m_out_b_handler(++m_port_b_counter);
			}
			LOG("%s data_r: Port B counter %02x\n", machine().describe_context(), m_port_b_counter);
			break;

		case 0x06: // Interrupt mask read
			data = m_int_mask;
			LOG("%s data_r: Interrupt mask read %02x\n", machine().describe_context(), data);
			break;

		case 0x07: // Interrupt status read
			data = m_int_status & m_int_mask;
			LOG("%s data_r: Interrupt status read %02x\n", machine().describe_context(), data);
			break;

		case 0x08: // UART Status register
			// b0 State of the CTS line (PA1)
			// b1 State of the DSR line (PA2)
			// b2 State of the DCD line (PA3)
			// b3 Transmitter buffer empty
			// b4 Transmitter busy
			// b5 Receive data waiting
			// b6 Overrun or framing error
			// b7 Parity error
			data |= m_cts << 0;
			data |= m_dsr << 1;
			data |= m_dcd << 2;
			data |= is_transmit_register_empty()  ? 1 << 3 : 0;
			data |= !is_transmit_register_empty() ? 1 << 4 : 0;
			data |= is_receive_register_full()    ? 1 << 5 : 0;
			data |= is_receive_framing_error()    ? 1 << 6 : 0;
			data |= is_receive_parity_error()     ? 1 << 7 : 0;
			LOG("%s data_r: UART Status register %02x\n", machine().describe_context(), data);
			m_int_status &= ~0x04;
			update_interrupts();
			break;

		case 0x09: // UART Receive register
			data = get_received_char();
			LOG("%s data_r: UART Receive register %02x\n", machine().describe_context(), data);
			m_int_status &= ~0x01;
			update_interrupts();
			break;

		case 0x0c: // Synchronous Port 1 read
			LOG("%s data_r: Synchronous Port 1 read %02x\n", machine().describe_context(), data);
			break;

		case 0x0d: // Barcode read data
			//  b0  PC4
			//  b1  PC7
			//  b2  PB2
			//  b3  PB3
			//  b4  PB4
			//  b5  PB5
			//  b6  PB6
			//  b7  PB7
			LOG("%s data_r: Barcode read data %02x\n", machine().describe_context(), data);
			break;

		case 0x0e: // Synchronous Port 2 read
			LOG("%s data_r: Synchronous Port 2 read %02x\n", machine().describe_context(), data);
			break;

		default:
			LOG("%s data_r: unknown control %02x data %02x\n", machine().describe_context(), m_sibo_control, data);
			break;
		}
		break;
	}

	return data;
}


void psion_asic5_device::write_rxd(int state)
{
	m_rxd = state;
	device_serial_interface::rx_w(state);
}

void psion_asic5_device::write_cts(int state)
{
	if (m_cts != state)
		m_int_status |= 0x04;
	m_cts = state;
	update_interrupts();

}
void psion_asic5_device::write_dsr(int state)
{
	if (m_dsr != state)
		m_int_status |= 0x04;
	m_dsr = state;
	update_interrupts();
}

void psion_asic5_device::write_dcd(int state)
{
	if (m_dcd != state)
		m_int_status |= 0x04;
	m_dcd = state;
	update_interrupts();
}
