// license:BSD-3-Clause
// copyright-holders:Curt Coder
/***************************************************************************

    Zilog Z80 Parallel Input/Output Controller implementation

***************************************************************************
                            _____   _____
                    D2   1 |*    \_/     | 40  D3
                    D7   2 |             | 39  D4
                    D6   3 |             | 38  D5
                   _CE   4 |             | 37  _M1
                  C/_D   5 |             | 36  _IORQ
                  B/_A   6 |             | 35  RD
                   PA7   7 |             | 34  PB7
                   PA6   8 |             | 33  PB6
                   PA5   9 |             | 32  PB5
                   PA4  10 |    Z8420    | 31  PB4
                   GND  11 |             | 30  PB3
                   PA3  12 |             | 29  PB2
                   PA2  13 |             | 28  PB1
                   PA1  14 |             | 27  PB0
                   PA0  15 |             | 26  +5V
                 _ASTB  16 |             | 25  CLK
                 _BSTB  17 |             | 24  IEI
                  ARDY  18 |             | 23  _INT
                    D0  19 |             | 22  IEO
                    D1  20 |_____________| 21  BRDY

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

#ifndef MAME_MACHINE_Z80PIO_H
#define MAME_MACHINE_Z80PIO_H

#pragma once

#include "cpu/z80/z80daisy.h"


//**************************************************************************
//  DEVICE CONFIGURATION MACROS
//**************************************************************************

#define MCFG_Z80PIO_OUT_INT_CB(_devcb) \
	devcb = &z80pio_device::set_out_int_callback(*device, DEVCB_##_devcb);

#define MCFG_Z80PIO_IN_PA_CB(_devcb) \
	devcb = &z80pio_device::set_in_pa_callback(*device, DEVCB_##_devcb);

#define MCFG_Z80PIO_OUT_PA_CB(_devcb) \
	devcb = &z80pio_device::set_out_pa_callback(*device, DEVCB_##_devcb);

#define MCFG_Z80PIO_OUT_ARDY_CB(_devcb) \
	devcb = &z80pio_device::set_out_ardy_callback(*device, DEVCB_##_devcb);

#define MCFG_Z80PIO_IN_PB_CB(_devcb) \
	devcb = &z80pio_device::set_in_pb_callback(*device, DEVCB_##_devcb);

#define MCFG_Z80PIO_OUT_PB_CB(_devcb) \
	devcb = &z80pio_device::set_out_pb_callback(*device, DEVCB_##_devcb);

#define MCFG_Z80PIO_OUT_BRDY_CB(_devcb) \
	devcb = &z80pio_device::set_out_brdy_callback(*device, DEVCB_##_devcb);


//**************************************************************************
//  TYPE DEFINITIONS
//**************************************************************************


// ======================> z80pio_device

class z80pio_device :   public device_t,
						public device_z80daisy_interface
{
public:
	enum
	{
		PORT_A = 0,
		PORT_B,
		PORT_COUNT
	};

	// construction/destruction
	z80pio_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);

	template <class Object> static devcb_base &set_out_int_callback(device_t &device, Object &&cb) { return downcast<z80pio_device &>(device).m_out_int_cb.set_callback(std::forward<Object>(cb)); }
	template <class Object> static devcb_base &set_in_pa_callback(device_t &device, Object &&cb) { return downcast<z80pio_device &>(device).m_in_pa_cb.set_callback(std::forward<Object>(cb)); }
	template <class Object> static devcb_base &set_out_pa_callback(device_t &device, Object &&cb) { return downcast<z80pio_device &>(device).m_out_pa_cb.set_callback(std::forward<Object>(cb)); }
	template <class Object> static devcb_base &set_out_ardy_callback(device_t &device, Object &&cb) { return downcast<z80pio_device &>(device).m_out_ardy_cb.set_callback(std::forward<Object>(cb)); }
	template <class Object> static devcb_base &set_in_pb_callback(device_t &device, Object &&cb) { return downcast<z80pio_device &>(device).m_in_pb_cb.set_callback(std::forward<Object>(cb)); }
	template <class Object> static devcb_base &set_out_pb_callback(device_t &device, Object &&cb) { return downcast<z80pio_device &>(device).m_out_pb_cb.set_callback(std::forward<Object>(cb)); }
	template <class Object> static devcb_base &set_out_brdy_callback(device_t &device, Object &&cb) { return downcast<z80pio_device &>(device).m_out_brdy_cb.set_callback(std::forward<Object>(cb)); }

	// I/O line access
	int rdy(int which) { return m_port[which].rdy(); }
	void strobe(int which, bool state) { m_port[which].strobe(state); }
	DECLARE_READ_LINE_MEMBER( rdy_a ) { return rdy(PORT_A); }
	DECLARE_READ_LINE_MEMBER( rdy_b ) { return rdy(PORT_B); }
	DECLARE_WRITE_LINE_MEMBER( strobe_a ) { strobe(PORT_A, state); }
	DECLARE_WRITE_LINE_MEMBER( strobe_b ) { strobe(PORT_B, state); }

	// control register I/O
	uint8_t control_read();
	void control_write(int offset, uint8_t data) { m_port[offset & 1].control_write(data); }
	void control_a_write(uint8_t data) { control_write(PORT_A, data); }
	void control_b_write(uint8_t data) { control_write(PORT_B, data); }

	// data register I/O
	uint8_t data_read(int offset) { return m_port[offset & 1].data_read(); }
	void data_write(int offset, uint8_t data) { m_port[offset & 1].data_write(data); }
	uint8_t data_a_read() { return data_read(PORT_A); }
	uint8_t data_b_read() { return data_read(PORT_B); }
	void data_a_write(uint8_t data) { data_write(PORT_A, data); }
	void data_b_write(uint8_t data) { data_write(PORT_B, data); }

	// port I/O
	uint8_t port_read(int offset) { return m_port[offset & 1].read(); }
	void port_write(int offset, uint8_t data) { m_port[offset & 1].write(data); }
	void port_write(int offset, int bit, int state) { port_write(offset, (m_port[offset & 1].m_input & ~(1 << bit)) | (state << bit));  }
	uint8_t port_a_read() { return port_read(PORT_A); }
	uint8_t port_b_read() { return port_read(PORT_B); }
	void port_a_write(uint8_t data) { port_write(PORT_A, data); }
	void port_b_write(uint8_t data) { port_write(PORT_B, data); }
	DECLARE_WRITE8_MEMBER( pa_w ) { port_a_write(data); }
	DECLARE_READ8_MEMBER( pa_r ) { return port_a_read(); }
	DECLARE_WRITE8_MEMBER( pb_w ) { port_b_write(data); }
	DECLARE_READ8_MEMBER( pb_r ) { return port_b_read(); }
	DECLARE_WRITE_LINE_MEMBER( pa0_w ) { port_write(PORT_A, 0, state); }
	DECLARE_WRITE_LINE_MEMBER( pa1_w ) { port_write(PORT_A, 1, state); }
	DECLARE_WRITE_LINE_MEMBER( pa2_w ) { port_write(PORT_A, 2, state); }
	DECLARE_WRITE_LINE_MEMBER( pa3_w ) { port_write(PORT_A, 3, state); }
	DECLARE_WRITE_LINE_MEMBER( pa4_w ) { port_write(PORT_A, 4, state); }
	DECLARE_WRITE_LINE_MEMBER( pa5_w ) { port_write(PORT_A, 5, state); }
	DECLARE_WRITE_LINE_MEMBER( pa6_w ) { port_write(PORT_A, 6, state); }
	DECLARE_WRITE_LINE_MEMBER( pa7_w ) { port_write(PORT_A, 7, state); }
	DECLARE_WRITE_LINE_MEMBER( pb0_w ) { port_write(PORT_B, 0, state); }
	DECLARE_WRITE_LINE_MEMBER( pb1_w ) { port_write(PORT_B, 1, state); }
	DECLARE_WRITE_LINE_MEMBER( pb2_w ) { port_write(PORT_B, 2, state); }
	DECLARE_WRITE_LINE_MEMBER( pb3_w ) { port_write(PORT_B, 3, state); }
	DECLARE_WRITE_LINE_MEMBER( pb4_w ) { port_write(PORT_B, 4, state); }
	DECLARE_WRITE_LINE_MEMBER( pb5_w ) { port_write(PORT_B, 5, state); }
	DECLARE_WRITE_LINE_MEMBER( pb6_w ) { port_write(PORT_B, 6, state); }
	DECLARE_WRITE_LINE_MEMBER( pb7_w ) { port_write(PORT_B, 7, state); }

	// standard read/write, with C/D in bit 1, B/A in bit 0
	DECLARE_READ8_MEMBER( read );
	DECLARE_WRITE8_MEMBER( write );

	// alternate read/write, with C/D in bit 0, B/A in bit 1
	DECLARE_READ8_MEMBER( read_alt );
	DECLARE_WRITE8_MEMBER( write_alt );

private:
	enum
	{
		MODE_OUTPUT = 0,
		MODE_INPUT,
		MODE_BIDIRECTIONAL,
		MODE_BIT_CONTROL
	};

	enum
	{
		ANY = 0,
		IOR,
		MASK
	};

	enum
	{
		ICW_ENABLE_INT    = 0x80,
		ICW_AND_OR        = 0x40,
		ICW_AND           = 0x40,
		ICW_OR            = 0x00,
		ICW_HIGH_LOW      = 0x20,
		ICW_HIGH          = 0x20,
		ICW_LOW           = 0x00,
		ICW_MASK_FOLLOWS  = 0x10
	};

	// device-level overrides
	virtual void device_start() override;
	virtual void device_reset() override;

	// device_z80daisy_interface overrides
	virtual int z80daisy_irq_state() override;
	virtual int z80daisy_irq_ack() override;
	virtual void z80daisy_irq_reti() override;

	// internal helpers
	void check_interrupts();

	// a single PIO port
	class pio_port
	{
		friend class z80pio_device;

	public:
		pio_port();

		void start(z80pio_device *device, int index);
		void reset();

		void trigger_interrupt();

		int rdy() const { return m_rdy; }
		void set_rdy(bool state);
		void set_mode(int mode);
		void strobe(bool state);

		uint8_t read();
		void write(uint8_t data);

		void control_write(uint8_t data);

		uint8_t data_read();
		void data_write(uint8_t data);

	private:
		void check_interrupts() { m_device->check_interrupts(); }

		z80pio_device *             m_device;
		int                         m_index;

		int m_mode;                 // mode register
		int m_next_control_word;    // next control word
		uint8_t m_input;              // input latch
		uint8_t m_output;             // output latch
		uint8_t m_ior;                // input/output register
		bool m_rdy;                 // ready
		bool m_stb;                 // strobe

		// interrupts
		bool m_ie;                  // interrupt enabled
		bool m_ip;                  // interrupt pending
		bool m_ius;                 // interrupt under service
		uint8_t m_icw;                // interrupt control word
		uint8_t m_vector;             // interrupt vector
		uint8_t m_mask;               // interrupt mask
		bool m_match;               // logic equation match
	};

	// internal state
	pio_port             m_port[2];
	devcb_write_line    m_out_int_cb;

	devcb_read8         m_in_pa_cb;
	devcb_write8        m_out_pa_cb;
	devcb_write_line    m_out_ardy_cb;

	devcb_read8         m_in_pb_cb;
	devcb_write8        m_out_pb_cb;
	devcb_write_line    m_out_brdy_cb;
};


// device type definition
DECLARE_DEVICE_TYPE(Z80PIO, z80pio_device)

#endif // MAME_MACHINE_Z80PIO_H
