/*
 * QEMU Parallel PORT emulation
 * 
 * Copyright (c) 2003-2005 Fabrice Bellard
 * Copyright (c) 2007 Marko Kohtala
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
#include "vl.h"

//#define DEBUG_PARALLEL

#ifdef DEBUG_PARALLEL
#define pdebug(fmt, arg...) printf("pp: " fmt, ##arg)
#else
#define pdebug(fmt, arg...) ((void)0)
#endif

#define PARA_REG_DATA 0
#define PARA_REG_STS 1
#define PARA_REG_CTR 2
#define PARA_REG_EPP_ADDR 3
#define PARA_REG_EPP_DATA 4

/*
 * These are the definitions for the Printer Status Register
 */
#define PARA_STS_BUSY	0x80	/* Busy complement */
#define PARA_STS_ACK	0x40	/* Acknowledge */
#define PARA_STS_PAPER	0x20	/* Out of paper */
#define PARA_STS_ONLINE	0x10	/* Online */
#define PARA_STS_ERROR	0x08	/* Error complement */
#define PARA_STS_TMOUT	0x01	/* EPP timeout */

/*
 * These are the definitions for the Printer Control Register
 */
#define PARA_CTR_DIR	0x20	/* Direction (1=read, 0=write) */
#define PARA_CTR_INTEN	0x10	/* IRQ Enable */
#define PARA_CTR_SELECT	0x08	/* Select In complement */
#define PARA_CTR_INIT	0x04	/* Initialize Printer complement */
#define PARA_CTR_AUTOLF	0x02	/* Auto linefeed complement */
#define PARA_CTR_STROBE	0x01	/* Strobe complement */

#define PARA_CTR_SIGNAL (PARA_CTR_SELECT|PARA_CTR_INIT|PARA_CTR_AUTOLF|PARA_CTR_STROBE)

struct ParallelState {
    uint8_t dataw;
    uint8_t datar;
    uint8_t status;
    uint8_t control;
    int irq;
    int irq_pending;
    CharDriverState *chr;
    int hw_driver;
    int epp_timeout;
    uint32_t last_read_offset; /* For debugging */
};

static void parallel_update_irq(ParallelState *s)
{
    if (s->irq_pending)
        pic_set_irq(s->irq, 1);
    else
        pic_set_irq(s->irq, 0);
}

static void
parallel_ioport_write_sw(void *opaque, uint32_t addr, uint32_t val)
{
    ParallelState *s = opaque;
    
    pdebug("write addr=0x%02x val=0x%02x\n", addr, val);

    addr &= 7;
    switch(addr) {
    case PARA_REG_DATA:
	s->dataw = val;
	parallel_update_irq(s);
        break;
    case PARA_REG_CTR:
	if ((val & PARA_CTR_INIT) == 0 ) {
	    s->status = PARA_STS_BUSY;
	    s->status |= PARA_STS_ACK;
	    s->status |= PARA_STS_ONLINE;
	    s->status |= PARA_STS_ERROR;
	}
	else if (val & PARA_CTR_SELECT) {
	    if (val & PARA_CTR_STROBE) {
		s->status &= ~PARA_STS_BUSY;
		if ((s->control & PARA_CTR_STROBE) == 0)
		    qemu_chr_write(s->chr, &s->dataw, 1);
	    } else {
		if (s->control & PARA_CTR_INTEN) {
		    s->irq_pending = 1;
		}
	    }
	}
	parallel_update_irq(s);
	s->control = val;
        break;
    }
}

static void parallel_ioport_write_hw(void *opaque, uint32_t addr, uint32_t val)
{
    ParallelState *s = opaque;
    uint8_t parm = val;

    /* Sometimes programs do several writes for timing purposes on old
       HW. Take care not to waste time on writes that do nothing. */

    s->last_read_offset = ~0U;

    addr &= 7;
    switch(addr) {
    case PARA_REG_DATA:
        if (s->dataw == val)
	    return;
	pdebug("wd%02x\n", val);
	qemu_chr_ioctl(s->chr, CHR_IOCTL_PP_WRITE_DATA, &parm);
	s->dataw = val;
        break;
    case PARA_REG_STS:
	pdebug("ws%02x\n", val);
	if (val & PARA_STS_TMOUT)
	    s->epp_timeout = 0;
	break;
    case PARA_REG_CTR:
        val |= 0xc0;
        if (s->control == val)
	    return;
	pdebug("wc%02x\n", val);
	qemu_chr_ioctl(s->chr, CHR_IOCTL_PP_WRITE_CONTROL, &parm);
	s->control = val;
        break;
    case PARA_REG_EPP_ADDR:
	if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != PARA_CTR_INIT)
	    /* Controls not correct for EPP address cycle, so do nothing */
	    pdebug("wa%02x s\n", val);
	else {
	    struct ParallelIOArg ioarg = { .buffer = &parm, .count = 1 };
	    if (qemu_chr_ioctl(s->chr, CHR_IOCTL_PP_EPP_WRITE_ADDR, &ioarg)) {
		s->epp_timeout = 1;
		pdebug("wa%02x t\n", val);
	    }
	    else
		pdebug("wa%02x\n", val);
	}
	break;
    case PARA_REG_EPP_DATA:
	if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != PARA_CTR_INIT)
	    /* Controls not correct for EPP data cycle, so do nothing */
	    pdebug("we%02x s\n", val);
	else {
	    struct ParallelIOArg ioarg = { .buffer = &parm, .count = 1 };
	    if (qemu_chr_ioctl(s->chr, CHR_IOCTL_PP_EPP_WRITE, &ioarg)) {
		s->epp_timeout = 1;
		pdebug("we%02x t\n", val);
	    }
	    else
		pdebug("we%02x\n", val);
	}
	break;
    }
}

static void
parallel_ioport_eppdata_write_hw2(void *opaque, uint32_t addr, uint32_t val)
{
    ParallelState *s = opaque;
    uint16_t eppdata = cpu_to_le16(val);
    int err;
    struct ParallelIOArg ioarg = {
	.buffer = &eppdata, .count = sizeof(eppdata)
    };
    if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != PARA_CTR_INIT) {
	/* Controls not correct for EPP data cycle, so do nothing */
	pdebug("we%04x s\n", val);
	return;
    }
    err = qemu_chr_ioctl(s->chr, CHR_IOCTL_PP_EPP_WRITE, &ioarg);
    if (err) {
	s->epp_timeout = 1;
	pdebug("we%04x t\n", val);
    }
    else
	pdebug("we%04x\n", val);
}

static void
parallel_ioport_eppdata_write_hw4(void *opaque, uint32_t addr, uint32_t val)
{
    ParallelState *s = opaque;
    uint32_t eppdata = cpu_to_le32(val);
    int err;
    struct ParallelIOArg ioarg = {
	.buffer = &eppdata, .count = sizeof(eppdata)
    };
    if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != PARA_CTR_INIT) {
	/* Controls not correct for EPP data cycle, so do nothing */
	pdebug("we%08x s\n", val);
	return;
    }
    err = qemu_chr_ioctl(s->chr, CHR_IOCTL_PP_EPP_WRITE, &ioarg);
    if (err) {
	s->epp_timeout = 1;
	pdebug("we%08x t\n", val);
    }
    else
	pdebug("we%08x\n", val);
}

static uint32_t parallel_ioport_read_sw(void *opaque, uint32_t addr)
{
    ParallelState *s = opaque;
    uint32_t ret = 0xff;

    addr &= 7;
    switch(addr) {
    case PARA_REG_DATA:
	if (s->control & PARA_CTR_DIR)
	    ret = s->datar;
	else
	    ret = s->dataw;
        break;
    case PARA_REG_STS:
	ret = s->status;
	s->irq_pending = 0;
	if ((s->status & PARA_STS_BUSY) == 0 && (s->control & PARA_CTR_STROBE) == 0) {
	    /* XXX Fixme: wait 5 microseconds */
	    if (s->status & PARA_STS_ACK)
		s->status &= ~PARA_STS_ACK;
	    else {
		/* XXX Fixme: wait 5 microseconds */
		s->status |= PARA_STS_ACK;
		s->status |= PARA_STS_BUSY;
	    }
	}
	parallel_update_irq(s);
        break;
    case PARA_REG_CTR:
        ret = s->control;
        break;
    }
    pdebug("read addr=0x%02x val=0x%02x\n", addr, ret);
    return ret;
}

static uint32_t parallel_ioport_read_hw(void *opaque, uint32_t addr)
{
    ParallelState *s = opaque;
    uint8_t ret = 0xff;
    addr &= 7;
    switch(addr) {
    case PARA_REG_DATA:
	qemu_chr_ioctl(s->chr, CHR_IOCTL_PP_READ_DATA, &ret);
	if (s->last_read_offset != addr || s->datar != ret)
	    pdebug("rd%02x\n", ret);
        s->datar = ret;
        break;
    case PARA_REG_STS:
	qemu_chr_ioctl(s->chr, CHR_IOCTL_PP_READ_STATUS, &ret);
	ret &= ~PARA_STS_TMOUT;
	if (s->epp_timeout)
	    ret |= PARA_STS_TMOUT;
	if (s->last_read_offset != addr || s->status != ret)
	    pdebug("rs%02x\n", ret);
	s->status = ret;
        break;
    case PARA_REG_CTR:
        /* s->control has some bits fixed to 1. It is zero only when
	   it has not been yet written to.  */
	if (s->control == 0) {
	    qemu_chr_ioctl(s->chr, CHR_IOCTL_PP_READ_CONTROL, &ret);
	    if (s->last_read_offset != addr)
		pdebug("rc%02x\n", ret);
	    s->control = ret;
	}
	else {
	    ret = s->control;
	    if (s->last_read_offset != addr)
		pdebug("rc%02x\n", ret);
	}
        break;
    case PARA_REG_EPP_ADDR:
	if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != (PARA_CTR_DIR|PARA_CTR_INIT))
	    /* Controls not correct for EPP addr cycle, so do nothing */
	    pdebug("ra%02x s\n", ret);
	else {
	    struct ParallelIOArg ioarg = { .buffer = &ret, .count = 1 };
	    if (qemu_chr_ioctl(s->chr, CHR_IOCTL_PP_EPP_READ_ADDR, &ioarg)) {
		s->epp_timeout = 1;
		pdebug("ra%02x t\n", ret);
	    }
	    else
		pdebug("ra%02x\n", ret);
	}
	break;
    case PARA_REG_EPP_DATA:
	if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != (PARA_CTR_DIR|PARA_CTR_INIT))
	    /* Controls not correct for EPP data cycle, so do nothing */
	    pdebug("re%02x s\n", ret);
	else {
	    struct ParallelIOArg ioarg = { .buffer = &ret, .count = 1 };
	    if (qemu_chr_ioctl(s->chr, CHR_IOCTL_PP_EPP_READ, &ioarg)) {
		s->epp_timeout = 1;
		pdebug("re%02x t\n", ret);
	    }
	    else
		pdebug("re%02x\n", ret);
	}
	break;
    }
    s->last_read_offset = addr;
    return ret;
}

static uint32_t
parallel_ioport_eppdata_read_hw2(void *opaque, uint32_t addr)
{
    ParallelState *s = opaque;
    uint32_t ret;
    uint16_t eppdata = ~0;
    int err;
    struct ParallelIOArg ioarg = {
	.buffer = &eppdata, .count = sizeof(eppdata)
    };
    if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != (PARA_CTR_DIR|PARA_CTR_INIT)) {
	/* Controls not correct for EPP data cycle, so do nothing */
	pdebug("re%04x s\n", eppdata);
	return eppdata;
    }
    err = qemu_chr_ioctl(s->chr, CHR_IOCTL_PP_EPP_READ, &ioarg);
    ret = le16_to_cpu(eppdata);

    if (err) {
	s->epp_timeout = 1;
	pdebug("re%04x t\n", ret);
    }
    else
	pdebug("re%04x\n", ret);
    return ret;
}

static uint32_t
parallel_ioport_eppdata_read_hw4(void *opaque, uint32_t addr)
{
    ParallelState *s = opaque;
    uint32_t ret;
    uint32_t eppdata = ~0U;
    int err;
    struct ParallelIOArg ioarg = {
	.buffer = &eppdata, .count = sizeof(eppdata)
    };
    if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != (PARA_CTR_DIR|PARA_CTR_INIT)) {
	/* Controls not correct for EPP data cycle, so do nothing */
	pdebug("re%08x s\n", eppdata);
	return eppdata;
    }
    err = qemu_chr_ioctl(s->chr, CHR_IOCTL_PP_EPP_READ, &ioarg);
    ret = le32_to_cpu(eppdata);

    if (err) {
	s->epp_timeout = 1;
	pdebug("re%08x t\n", ret);
    }
    else
	pdebug("re%08x\n", ret);
    return ret;
}

static void parallel_ioport_ecp_write(void *opaque, uint32_t addr, uint32_t val)
{
    addr &= 7;
    pdebug("wecp%d=%02x\n", addr, val);
}

static uint32_t parallel_ioport_ecp_read(void *opaque, uint32_t addr)
{
    uint8_t ret = 0xff;
    addr &= 7;
    pdebug("recp%d:%02x\n", addr, ret);
    return ret;
}

/* If fd is zero, it means that the parallel device uses the console */
ParallelState *parallel_init(int base, int irq, CharDriverState *chr)
{
    ParallelState *s;
    uint8_t dummy;

    s = qemu_mallocz(sizeof(ParallelState));
    if (!s)
        return NULL;
    s->datar = ~0;
    s->dataw = ~0;
    s->status = PARA_STS_BUSY;
    s->status |= PARA_STS_ACK;
    s->status |= PARA_STS_ONLINE;
    s->status |= PARA_STS_ERROR;
    s->control = PARA_CTR_SELECT;
    s->control |= PARA_CTR_INIT;
    s->irq = irq;
    s->irq_pending = 0;
    s->chr = chr;
    s->hw_driver = 0;
    s->epp_timeout = 0;
    s->last_read_offset = ~0U;

    if (qemu_chr_ioctl(chr, CHR_IOCTL_PP_READ_STATUS, &dummy) == 0) {
        s->hw_driver = 1;
	s->status = dummy;
    }

    if (s->hw_driver) {
	register_ioport_write(base, 8, 1, parallel_ioport_write_hw, s);
	register_ioport_read(base, 8, 1, parallel_ioport_read_hw, s);
	register_ioport_write(base+4, 1, 2, parallel_ioport_eppdata_write_hw2, s);
	register_ioport_read(base+4, 1, 2, parallel_ioport_eppdata_read_hw2, s);
	register_ioport_write(base+4, 1, 4, parallel_ioport_eppdata_write_hw4, s);
	register_ioport_read(base+4, 1, 4, parallel_ioport_eppdata_read_hw4, s);
	register_ioport_write(base+0x400, 8, 1, parallel_ioport_ecp_write, s);
	register_ioport_read(base+0x400, 8, 1, parallel_ioport_ecp_read, s);
    }
    else {
	register_ioport_write(base, 8, 1, parallel_ioport_write_sw, s);
	register_ioport_read(base, 8, 1, parallel_ioport_read_sw, s);
    }
    return s;
}