/*
 * abcio.c
 *
 * Handle ABC-bus I/O operations
 */

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

static struct abc_dev *abc_device[65]; /* 65 == post-RST# = always NULL */
static struct abc_dev *selected_dev;
static uint8_t abc_devsel = 64;

#define EVENT_MASK_ALWAYS 0x0082 /* RST# and CS# */

static inline __attribute__((always_inline))
uint16_t event_mask(const struct abc_dev *dev)
{
    if (!dev)
	return EVENT_MASK_ALWAYS;

    return EVENT_MASK_ALWAYS | dev->callback_mask |
	(dev->out_cnt ? 1 : 0) | (dev->inp_cnt ? 0x100 : 0);
}

/* Not inlining this makes the code larger...! */
static inline __attribute__((always_inline))
void refresh_dev(struct abc_dev *dev)
{
    if (dev == selected_dev) {
	ABC_BUSY_MASK = event_mask(dev);
	ABC_INP = dev ? dev->inp_data_w : 0;
    }
}

static inline __attribute__((always_inline))
void abc_select(struct abc_dev *dev)
{
    selected_dev = dev;
    refresh_dev(dev);
}

IRQHANDLER(abc)
{
    unsigned int what = ABC_BUSY_STATUS;
    struct abc_dev *dev = selected_dev;
    unsigned int callback = dev ? (what & dev->callback_mask) : 0;

    if (what & 0xff) {
	unsigned int addr = ABC_OUT_ADDR;
	unsigned int data = ABC_OUT_DATA;

	switch (addr) {
	case 0:
	    if (!dev)
		break;

	    if (dev->out_cnt) {
		*dev->out_buf++ = data;
		dev->inp_data[1] &= dev->status_first_out_mask;
		ABC_INP1_DATA = dev->inp_data[1];
		if (--dev->out_cnt)
		    break;	/* No callback, don't set out_data[] */
	    }
	    goto handle_out;

	case 1:
	    dev = abc_device[abc_devsel = data & 0x3f];
	    abc_select(dev);

	    if (!dev)
		break;

	    callback = dev->callback_mask & what;
	    goto handle_out;

	case 2 ... 5:
	    goto handle_out;

	handle_out:
	    dev->out_data[addr] = data;
	    if (callback & (1 << addr))
		dev->callback_out[addr](dev, data, addr);
	    break;

	case 7:
	{
	    struct abc_dev **devp;
	    uint8_t old_devsel = abc_devsel;

	    abc_devsel = DEVSEL_NONE;
	    abc_select(NULL);

	    devp = &abc_device[0];
	    while (devp <= &abc_device[63]) {
		dev = *devp++;
		if (dev && (dev->callback_mask & 0x0080))
		    dev->callback_rst(dev, old_devsel, 7);
	    }
	    dev = NULL;
	    break;
	}

	default:
	    break;
	}
    }

    if (what & 0x100) {
	if (dev->inp_cnt && --dev->inp_cnt) {
	    dev->inp_data[1] &= dev->status_first_inp_mask;
	    ABC_INP1_DATA = dev->inp_data[1];
	    ABC_INP0_DATA = dev->inp_data[0] = *dev->inp_buf++;
	    /* No callback */
	} else {
	    uint8_t old_data = ABC_INP0_DATA;
	    ABC_INP0_DATA = dev->inp_data[0] = dev->inp_data_def;
	    if (callback & 0x100)
		dev->callback_inp[0](dev, old_data, 0);
	}
    }

    if (what & 0x200) {
	uint8_t old_data = ABC_INP1_DATA;
	ABC_INP1_DATA = dev->inp_data[1];
	if (callback & 0x200)
	    dev->callback_inp[1](dev, old_data, 1);
    }

    /* May need to change event_mask here */
    ABC_BUSY = what | (event_mask(dev) << 16);
}

void abc_setup_out_queue(struct abc_dev *dev, void *buf, size_t len,
			 uint8_t status)
{
    irqmask_t irqmask = mask_irq(ABC_IRQ);

    dev->out_buf = buf;
    dev->out_cnt = len;
    dev->inp_data[1] = status;
    refresh_dev(dev);		/* Update registers as needed */

    restore_irq(irqmask, ABC_IRQ);
}

void abc_setup_inp_queue(struct abc_dev *dev, const void *buf, size_t len,
			 uint8_t status)
{
    irqmask_t irqmask = mask_irq(ABC_IRQ);

    dev->inp_buf = buf;
    dev->inp_cnt = len;
    dev->inp_data[0] = *dev->inp_buf++;
    dev->inp_data[1] = status;
    refresh_dev(dev);		/* Update registers as needed */

    restore_irq(irqmask, ABC_IRQ);
}

void abc_set_inp_default(struct abc_dev *dev, uint8_t val)
{
    irqmask_t irqmask = mask_irq(ABC_IRQ);

    dev->inp_data_def = val;
    if (!dev->inp_cnt) {
	dev->inp_data[0] = val;
	if (dev == selected_dev)
	    ABC_INP0_DATA = val;
    }

    restore_irq(irqmask, ABC_IRQ);
}

void abc_set_inp_status(struct abc_dev *dev, uint8_t val)
{
    irqmask_t irqmask = mask_irq(ABC_IRQ);

    dev->inp_data[1] = val;
    if (dev == selected_dev)
	ABC_INP1_DATA = val;

    restore_irq(irqmask, ABC_IRQ);
}

/*
 * This can be called before or after abc_init()!
 */
void abc_register(struct abc_dev *dev, unsigned int devsel)
{
    if (devsel > 63)
	return;

    irqmask_t irqmask = mask_irq(ABC_IRQ);

    if (!dev->inp_cnt)
	dev->inp_data[0] = dev->inp_data_def;

    abc_device[devsel] = dev;
    if (devsel == abc_devsel)
	abc_select(dev);

    restore_irq(irqmask, ABC_IRQ);
}

void abc_init(void)
{
    unsigned int devsel;

    mask_irq(ABC_IRQ);

    devsel = ABC_IOSEL;
    abc_devsel = (devsel & 0x100) ? DEVSEL_NONE : (devsel & 63);
    abc_select(abc_device[abc_devsel]);

    unmask_irq(ABC_IRQ);	/* Start ABC bus unconditionally */
}
