/*
 * abcpun80.c
 *
 * Emulate a PUN80 network card
 */

#include "compiler.h"
#include "fw.h"
#include "io.h"
#include "abcio.h"

#define PUN_IOSEL	60

/* ACM channel */
#define PUN_TTY_CHAN	1
#if PUN_TTY_CHAN >= TTY_CHANNELS
# error "PUN_TTY_CHAN out of range"
#endif

#define PUN_DATA	TTY_DATA(PUN_TTY_CHAN)
#define PUN_WATERCTL	TTY_WATERCTL(PUN_TTY_CHAN)
#define PUN_STATUS	TTY_STATUS(PUN_TTY_CHAN)
#define PUN_IRQEN	TTY_IRQEN(PUN_TTY_CHAN)
#define PUN_IRQPOL	TTY_IRQPOL(PUN_TTY_CHAN)
#define PUN_IRQ		TTY_NIRQ(PUN_TTY_CHAN)

#define PUN_IRQ_MASK	(TTY_STATUS_TX_HIGH|TTY_STATUS_RX_LOW|\
			 TTY_STATUS_RX_EMPTY|TTY_STATUS_DTR_IN)

static struct abc_dev pun80_iodev;

/*
 * Convert to FT232H-compatible status codes, and advance
 * the receive FIFO if applicable.
 */
static __hot void
pun80_refresh_input(struct abc_dev *dev, bool advance)
{
    static bool data_loaded;
    unsigned int status;
    uint8_t  pun80_status;

    status = PUN_STATUS;

    if (!data_loaded || advance) {
	data_loaded = !(status & TTY_STATUS_RX_EMPTY);
	if (data_loaded) {
	    dev->inp_data_def = dev->inp_data[0] = PUN_DATA;
	    status = PUN_STATUS;
	}
    }

    PUN_IRQPOL = status;

    pun80_status = 0xe6;
    pun80_status ^= !!(status & TTY_STATUS_RX_LOW) << 4;
    pun80_status ^= !!(status & TTY_STATUS_USB_CONFIG) << 3;
    pun80_status ^= !!(status & TTY_STATUS_DTR_IN) << 2;
    pun80_status ^= !!(status & TTY_STATUS_TX_HIGH) << 1;
    pun80_status ^= data_loaded;

    dev->inp_data[1] = pun80_status;

    if (abc_selected_dev() == dev)
	ABC_INP = dev->inp_data_w;
}

static ABC_CALLBACK(pun80_callback_out)
{
    PUN_DATA = data;
    CON_DATA = '>';
    pun80_refresh_input(dev, false);
}

static ABC_CALLBACK(pun80_callback_inp)
{
    CON_DATA = '<';
    pun80_refresh_input(dev, true);
}

IRQHANDLER(tty,PUN_TTY_CHAN)
{
    CON_DATA = '#';
    pun80_refresh_input(&pun80_iodev, false);
}

/* The "flush" command in FT232H (C1#) is not needed with our USB stack */
static struct abc_dev pun80_iodev = {
    .callback_mask		= (1 << 0)|(1 << 8),
    .inp_en			= 3,
    .inp_data[1]                = 0xf6,
    .status_first_out_mask	= ~0,
    .status_first_inp_mask	= ~0,
    .callback_out[0]		= pun80_callback_out,
    .callback_inp[0]		= pun80_callback_inp
};

void pun80_init(void)
{
    mask_irq(PUN_IRQ);

    /*
     * Set the TX high water mark to 1/4 = 256 bytes free. This allows
     * OTIR to be used unconditionally for large transfers after
     * polling the TX buffer status only once. The buffer is large
     * enough that this should always be true in practice.
     *
     * The RX low water mark is set to 1/8 = 128 bytes occupied for
     * similar reasons. This is added as a non-FT232H status bit 4 if
     * 0 (FT232H always has 1 in this bit position.)
     */
    PUN_WATERCTL = TTY_WATERCTL_TX_LOW(0x3) | TTY_WATERCTL_TX_HIGH(0xc) |
	TTY_WATERCTL_RX_LOW(0x1) | TTY_WATERCTL_TX_HIGH(0xc);

    /*
     * Immediately interrupt on any of:
     *
     * USB configured
     * DTR asserted (host connected)
     * TX not past high water mark
     * RX not empty
     */
    PUN_IRQPOL = TTY_STATUS_RX_EMPTY | TTY_STATUS_RX_LOW | TTY_STATUS_TX_HIGH;
    PUN_IRQEN  = PUN_IRQ_MASK;
    unmask_irq(PUN_IRQ);

    abc_register(&pun80_iodev, PUN_IOSEL);
}
