/*
 * Copyright (C) 2001-2003 by Cyber Integration, LLC. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY CYBER INTEGRATION, LLC AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CYBER
 * INTEGRATION, LLC OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *
 */

/*!
 * \file arch/avr/dev/tlc16c550.c
 * \brief AVR driver for external TLC16C550.
 *
 * \verbatim
 * $Id: tlc16c550.c 5472 2013-12-06 00:16:28Z olereinhardt $
 * \endverbatim
 */

// system include files
#include <string.h>
#include <sys/atom.h>
#include <sys/heap.h>
#include <sys/event.h>
#include <sys/timer.h>
#include <sys/device.h>
#include <dev/irqreg.h>
#include <dev/tlc16c550.h>
#include <fcntl.h>
#include <stdio.h>

/*
 * Not nice because stdio already defined them. But in order to save memory,
 * we do the whole buffering and including stdio here would be more weird.
 */
#ifndef _IOFBF
#define _IOFBF      0x00
#define _IOLBF      0x01
#define _IONBF      0x02
#endif

/*!
 * \
 */
/*@{*/


// define the register offset
// define the UART Register offsets
#define ACE_RBR_OFS 0
#define ACE_THR_OFS 0
#define ACE_DLL_OFS 0
#define ACE_DLM_OFS 1
#define ACE_IER_OFS 1
#define ACE_FCR_OFS 2
#define ACE_IIR_OFS 2
#define ACE_LCR_OFS 3
#define ACE_MCR_OFS 4
#define ACE_LSR_OFS 5
#define ACE_MSR_OFS 6
#define ACE_SRC_OFS 7

// define the interrupt enable masks
#define IER_RDA_MSK 0x01    // receiver data avaialbe
#define IER_THE_MSK 0x02    // transmit holding empty
#define IER_LST_MSK 0x04    // line status
#define IER_MST_MSK 0x08    // modem status

// define the fifo control mask
#define FCR_ENABLE     0x01     //fifo enable
#define FCR_PURGE_I 0x02    //purge all data from input fifo
#define FCR_PURGE_O 0x04    //purge all data from output fifo
#define FCR_LEVEL_1    0x00     //receive trigger level 1
#define FCR_LEVEL_4    0x40     //receive trigger level 4
#define FCR_LEVEL_8    0x80     //receive trigger level 8
#define FCR_LEVEL_14   0xc0     //receive trigger level 14

// define the interrupt id masks
#define IIR_MST_MSK 0x00    // modem status interrupt
#define IIR_TXE_MSK 0x02    // transmit buffer empty
#define IIR_RDA_MSK 0x04    // receive data available
#define IIR_TDA_MSK 0x0c    // timeout receive data available
#define IIR_LST_MSK 0x06    // line status interrupt
#define IIR_NON_MSK 0x01    // no interrupt
#define IIR_FIFO_MSK   0xc0     // mask to eliminate the fifo status

// define the line control masks
#define LCR_WS0_MSK 0x01
#define LCR_WS1_MSK 0x02
#define LCR_STB_MSK 0x04
#define LCR_PEN_MSK 0x08
#define LCR_PRE_MSK 0x10
#define LCR_PRS_MSK 0x20
#define LCR_BRK_MSK 0x40
#define LCR_ENB_MSK 0x80

// define the modem control masks
#define MCR_DTR_MSK 0x01
#define MCR_RTS_MSK 0x02
#define MCR_GP1_MSK 0x04
#define MCR_GP2_MSK 0x08
#define MCR_LOP_MSK 0x10

// define the line status masks
#define LSR_RDR_MSK 0x01
#define LSR_OVR_MSK 0x02
#define LSR_PER_MSK 0x04
#define LSR_FER_MSK 0x08
#define LSR_BDT_MSK 0x10
#define LSR_THE_MSK 0x20
#define LSR_TXE_MSK 0x40
#define LSR_EIF_MSK 0x80

// define the modem status masks
#define MSR_DCTS_MSK    0x01
#define MSR_DDSR_MSK    0x02
#define MSR_DRI_MSK 0x04
#define MSR_DDCD_MSK    0x08
#define MSR_CTS_MSK 0x10
#define MSR_DSR_MSK 0x20
#define MSR_RI_MSK      0x40
#define MSR_DCD_MSK 0x80

// define the irq structure
typedef struct tagIRQDEFS {
    IRQ_HANDLER *pvIrq;
    volatile uint8_t *pnIrqMskPort;
    uint8_t nMask;
} IRQDEFS;

// define the interrupt handlers
static const PROGMEM IRQDEFS atIrqDefs[] = {
    {&sig_INTERRUPT0, &EICRA, 0x00},
    {&sig_INTERRUPT1, &EICRA, 0x00},
    {&sig_INTERRUPT2, &EICRA, 0x00},
    {&sig_INTERRUPT3, &EICRA, 0x00},
    {&sig_INTERRUPT4, &EICRB, 0x00},
    {&sig_INTERRUPT5, &EICRB, 0x00},
    {&sig_INTERRUPT6, &EICRB, 0x00},
    {&sig_INTERRUPT7, &EICRB, 0x00}
};

// define the dcb's asigned to the interrupt to have more than one device on the same interrupt
// NUT intrnal irq structure could be used instead but that would be a hack
static NUTDEVICE *pIrqDev[8] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
static uint8_t irqMask = 0;
#ifdef ACE_HDX_LINE
static unsigned int ByteOcrTime(NUTDEVICE * dev);
static void AceTmr3Init(void);
static void AceOutComp3AInt(void *arg);
static void AceAddHdxTime(ACEDCB * dev);
#endif

/*
 * Handle AVR ACE interrupts
 */
static void AceIrqHandler(void *arg)
{
    NUTDEVICE *dev = (NUTDEVICE *) arg;
    IFSTREAM *ifs;
    ACEDCB *dcb;
    volatile uint8_t event;
    uint8_t maxData;

    do {
        ifs = dev->dev_icb;
        dcb = dev->dev_dcb;

        // get the interrupt source
        while (((event = *(uint8_t *) (dev->dev_base + ACE_IIR_OFS)) & ~IIR_FIFO_MSK) != IIR_NON_MSK) {
            switch (event & ~IIR_FIFO_MSK) {
            case IIR_RDA_MSK:  // receive data available
            case IIR_TDA_MSK:  // timeout receive data available
                /* maxData can be avoided but it ensures that for slow system and fast uart we will not get stuck
                 * reading incomming data all the time.
                 */
                maxData = (dcb->dcb_rfifo == 0) ? 1 : dcb->dcb_rfifo;
                for (; (*(uint8_t *) (dev->dev_base + ACE_LSR_OFS) & LSR_RDR_MSK) && (maxData > 0); --maxData) {
                    // get the character and store it
                    ifs->if_rx_buf[ifs->if_rx_idx] = *(uint8_t *) (dev->dev_base + ACE_RBR_OFS);
                    /* if we have just received a first byte into the empty buffer */
                    if (ifs->if_rd_idx == ifs->if_rx_idx) {
                        NutEventPostFromIrq(&(dcb->dcb_rx_rdy));
                    }
                    /* Late increment fixes ICCAVR bug on volatile variables. */
                    ifs->if_rx_idx++;
                }
                break;

            case IIR_TXE_MSK:  // transmit buffer empty
                dcb->dcb_wfifo = (event & IIR_FIFO_MSK) ? ACE_FIFO_SIZE : 1;
                if (ifs->if_tx_idx != ifs->if_wr_idx) {
                    for (; (ifs->if_tx_idx != ifs->if_wr_idx) && (dcb->dcb_wfifo > 0); ++ifs->if_tx_idx) {
                        --dcb->dcb_wfifo;
                        // send a character
                        *(uint8_t *) (dev->dev_base + ACE_THR_OFS) = ifs->if_tx_buf[ifs->if_tx_idx];
                    }
                } else {
#ifdef ACE_HDX_LINE
                    if (dcb->dcb_modeflags & ACE_MF_HALFDUPLEX) {
                        AceAddHdxTime(dcb);
                    }
#endif
                    ifs->if_tx_act = 0;
                    NutEventPostFromIrq(&(dcb->dcb_tx_rdy));
                }
                break;

            case IIR_MST_MSK:  // modem status interrupt
                break;

            case IIR_LST_MSK:  // line status interrupt
                break;
            }
        }
        /* get the next device assigned to this interrupt */
        dev = dcb->dev_next;
    } while (dev != NULL);

}

#ifdef ACE_HDX_LINE
static unsigned int ByteOcrTime(NUTDEVICE * dev)
{
    uint8_t bv;
    uint8_t tb = 14;             /* twice of 1 start 5 char min. 1 stop */
    unsigned int sv;
    uint32_t s, c;

    /* get speed */
    *(uint8_t *) (dev->dev_base + ACE_LCR_OFS) |= LCR_ENB_MSK;
    sv = *(uint8_t *) (dev->dev_base + ACE_DLL_OFS);
    sv |= *(uint8_t *) (dev->dev_base + ACE_DLM_OFS) << 8;
    *(uint8_t *) (dev->dev_base + ACE_LCR_OFS) &= (uint8_t) ~ LCR_ENB_MSK;

    bv = *(uint8_t *) (dev->dev_base + ACE_LCR_OFS);

    /* character length *2 */
    tb += (bv & (LCR_WS0_MSK | LCR_WS1_MSK)) << 1;

    /* stop bits *2 */
    if (bv & LCR_STB_MSK) {
        tb += 1 + !!(bv & (LCR_WS0_MSK | LCR_WS1_MSK));
    }

    /* parity bit *2 */
    tb += (!!(bv & LCR_PEN_MSK)) << 1;

    s = ACE_CLOCK * 8UL;
    s = s / (uint32_t) (sv);

    c = NutGetCpuClock();
    c = c * (uint32_t) tb;

    sv = ((unsigned int) (c / s) & 0x0000ffff) - 1;

    return sv;
}
#endif

/*!
 * \brief Wait for input.
 *
 * This function checks the input buffer for any data. If
 * the buffer is empty, the calling \ref xrThread "thread"
 * will be blocked until at least one new character is
 * received or a timeout occurs.
 *
 * \param dev Indicates the ACE device.
 *
 * \return 0 on success, -1 on timeout.
 */
int AceInput(NUTDEVICE * dev)
{
    int rc = 0;
    IFSTREAM *ifs = dev->dev_icb;
    ACEDCB *dcb = dev->dev_dcb;

    if (ifs->if_rd_idx == ifs->if_rx_idx) {
        rc = NutEventWaitNext(&(dcb->dcb_rx_rdy), dcb->dcb_rtimeout);
    }
    return rc;
}

/*!
 * \brief Initiate output.
 *
 * This function checks the output buffer for any data. If
 * the buffer contains at least one character, the transmitter
 * is started, if not already running. The function returns
 * immediately, without waiting for the character being
 * completely transmitted. Any remaining characters in the
 * output buffer are transmitted in the background.
 *
 * \param dev Indicates the ACE device.
 *
 * \return 0 on success, -1 otherwise.
 */
int AceOutput(NUTDEVICE * dev)
{
    IFSTREAM *ifs = dev->dev_icb;
    ACEDCB *dcb = dev->dev_dcb;
    volatile uint8_t tmp;

    if ((ifs->if_tx_act == 0) && (ifs->if_tx_idx != ifs->if_wr_idx)) {
        ifs->if_tx_act = 1;
        --dcb->dcb_wfifo;
        tmp = ifs->if_tx_idx;
        ++ifs->if_tx_idx;
#ifdef ACE_HDX_LINE
        if (dcb->dcb_modeflags & ACE_MF_HALFDUPLEX) {
            ACE_HDX_TRANSMIT(dev->dev_base);
        }
#endif
        // send a character
        *(uint8_t *) (dev->dev_base + ACE_THR_OFS) = ifs->if_tx_buf[tmp];
        // no need to enable an interrupt here as it should be enabled all the time
    }

    return 0;
}

/*!
 * \brief Wait for output buffer empty.
 *
 * If the output buffer contains any data, the calling
 * thread is suspended until all data has been transmitted.
 *
 * \param dev Indicates the ACE device.
 *
 * \return 0 on success, -1 otherwise.
 */
int AceFlush(NUTDEVICE * dev)
{
    IFSTREAM *ifs = dev->dev_icb;
    ACEDCB *dcb = dev->dev_dcb;

    /*
     * Start any pending output.
     */
    if (AceOutput(dev))
        return -1;

    /*
     * Wait until output buffer empty.
     */
    while (ifs->if_tx_idx != ifs->if_wr_idx) {
        NutEventWaitNext(&dcb->dcb_tx_rdy, 100);
    }

    return 0;
}

/*
 *
 * \param dev Indicates the ACE device.
 *
 * \return 0 on success, -1 otherwise.
 */
static int AceGetStatus(NUTDEVICE * dev, uint32_t * status)
{
    IFSTREAM *ifs = dev->dev_icb;
    uint8_t us;

    *status = 0;
    us = *(uint8_t *) (dev->dev_base + ACE_LSR_OFS);
    if (us & LSR_FER_MSK)
        *status |= ACE_FRAMINGERROR;
    if (us & LSR_OVR_MSK)
        *status |= ACE_OVERRUNERROR;
    if (ifs->if_tx_idx == ifs->if_wr_idx)
        *status |= ACE_TXBUFFEREMPTY;
    if (ifs->if_rd_idx == ifs->if_rx_idx)
        *status |= ACE_RXBUFFEREMPTY;
    return 0;
}

/*
 * Carefully enable ACE functions.
 */
static void AceEnable(uint16_t base)
{
    /*volatile uint8_t* pnBase = *(volatile uint8_t* )base; */

    /*
     * Enable ACE interrupts.
     */
    NutEnterCritical();
    *(uint8_t *) (base + ACE_IER_OFS) = IER_RDA_MSK | IER_THE_MSK;
    NutExitCritical();
}

/*
 * Carefully disable ACE functions.
 */
static void AceDisable(uint16_t base)
{
    /*volatile uint8_t* pnBase = *(volatile uint8_t* )base; */

    /*
     * Disable ACE interrupts.
     */
    NutEnterCritical();
    *(uint8_t *) (base + ACE_IER_OFS) &= (uint8_t) ~ (IER_RDA_MSK);
    NutExitCritical();

    /*
     * Allow incoming or outgoing character to finish.
     */
    NutDelay(10);
}

/*!
 * \brief Perform ACE control functions.
 *
 * \param dev  Identifies the device that receives the device-control
 *             function.
 * \param req  Requested control function. May be set to one of the
 *             following constants:
 *             - ACE_SETSPEED, conf points to an uint32_t value containing the baudrate.
 *             - ACE_GETSPEED, conf points to an uint32_t value receiving the current baudrate.
 *             - ACE_SETDATABITS, conf points to an uint32_t value containing the number of data bits, 5, 6, 7 or 8.
 *             - ACE_GETDATABITS, conf points to an uint32_t value receiving the number of data bits, 5, 6, 7 or 8.
 *             - ACE_SETPARITY, conf points to an uint32_t value containing the parity, 0 (no), 1 (odd) or 2 (even).
 *             - ACE_GETPARITY, conf points to an uint32_t value receiving the parity, 0 (no), 1 (odd) or 2 (even).
 *             - ACE_SETSTOPBITS, conf points to an uint32_t value containing the number of stop bits 1 or 2.
 *             - ACE_GETSTOPBITS, conf points to an uint32_t value receiving the number of stop bits 1 or 2.
 *             - ACE_SETSTATUS
 *             - ACE_GETSTATUS
 *             - ACE_SETREADTIMEOUT, conf points to an uint32_t value containing the read timeout.
 *             - ACE_GETREADTIMEOUT, conf points to an uint32_t value receiving the read timeout.
 *             - ACE_SETWRITETIMEOUT, conf points to an uint32_t value containing the write timeout.
 *             - ACE_GETWRITETIMEOUT, conf points to an uint32_t value receiving the write timeout.
 *             - ACE_SETLOCALECHO, conf points to an uint32_t value containing 0 (off) or 1 (on).
 *             - ACE_GETLOCALECHO, conf points to an uint32_t value receiving 0 (off) or 1 (on).
 *             - ACE_SETFLOWCONTROL, conf points to an uint32_t value containing combined ACE_FCR_ values.
 *             - ACE_GETFLOWCONTROL, conf points to an uint32_t value containing receiving ACE_FCR_ values.
 *             - ACE_SETCOOKEDMODE, conf points to an uint32_t value containing 0 (off) or 1 (on).
 *             - ACE_GETCOOKEDMODE, conf points to an uint32_t value receiving 0 (off) or 1 (on).
 *
 * \param conf Points to a buffer that contains any data required for
 *             the given control function or receives data from that
 *             function.
 * \return 0 on success, -1 otherwise.
 *
 * \warning Timeout values are given in milliseconds and are limited to
 *          the granularity of the system timer. To disable timeout,
 *          set the parameter to NUT_WAIT_INFINITE.
 *
 *
 */
int AceIOCtl(NUTDEVICE * dev, int req, void *conf)
{
    int rc = 0;
    ACEDCB *dcb;
    IFSTREAM *ifs;
    uint32_t *lvp = (uint32_t *) conf;
    uint32_t lv = *lvp;
    uint8_t bv = (uint8_t) lv;
    uint16_t sv;
    uint16_t devnum;
    uint8_t tv;

    if (dev == 0) {
        return -1;
    }

    devnum = dev->dev_base;
    dcb = dev->dev_dcb;

    switch (req) {
    case ACE_SETSPEED:
        AceDisable(devnum);
        sv = (uint16_t) (ACE_CLOCK / (lv * 16UL));
        *(uint8_t *) (dev->dev_base + ACE_LCR_OFS) |= LCR_ENB_MSK;
        *(uint8_t *) (dev->dev_base + ACE_DLL_OFS) = (uint8_t) (sv & 0xFF);
        *(uint8_t *) (dev->dev_base + ACE_DLM_OFS) = (uint8_t) (sv >> 8);
        *(uint8_t *) (dev->dev_base + ACE_LCR_OFS) &= (uint8_t) ~ LCR_ENB_MSK;
#ifdef ACE_HDX_LINE
        dcb->hdxByteTime = ByteOcrTime(dev);
#endif
        AceEnable(devnum);
        break;

    case ACE_GETSPEED:
        *(uint8_t *) (dev->dev_base + ACE_LCR_OFS) |= LCR_ENB_MSK;
        sv = *(uint8_t *) (dev->dev_base + ACE_DLL_OFS);
        sv |= *(uint8_t *) (dev->dev_base + ACE_DLM_OFS) << 8;
        *(uint8_t *) (dev->dev_base + ACE_LCR_OFS) &= (uint8_t) ~ LCR_ENB_MSK;
        *lvp = ACE_CLOCK / (16UL * (uint32_t) (sv));
        break;

    case ACE_SETDATABITS:
        AceDisable(devnum);
        if ((bv >= 5) && (bv <= 8)) {
            bv -= 5;
            tv = *(uint8_t *) (dev->dev_base + ACE_LCR_OFS);
            tv &= (uint8_t) ~ (LCR_WS0_MSK | LCR_WS1_MSK);
            tv |= (bv & (LCR_WS0_MSK | LCR_WS1_MSK));
            *(uint8_t *) (dev->dev_base + ACE_LCR_OFS) = tv;
#ifdef ACE_HDX_LINE
            dcb->hdxByteTime = ByteOcrTime(dev);
#endif
        }
        AceEnable(devnum);
        break;

    case ACE_GETDATABITS:
        *lvp = *(uint8_t *) (dev->dev_base + ACE_LCR_OFS) & (LCR_WS0_MSK | LCR_WS1_MSK);
        break;

    case ACE_SETPARITY:
        AceDisable(devnum);
        if (bv <= 4) {
            switch (bv) {
            case 1:            // odd parity
                bv = LCR_PEN_MSK;
                break;

            case 2:            // event parity
                bv = LCR_PEN_MSK | LCR_PRE_MSK;
                break;

            case 3:            // space
                bv = LCR_PEN_MSK;
                break;

            case 4:            // mark
                bv = LCR_PEN_MSK | LCR_PRS_MSK;

            default:           // no parity
                bv = 0;
                break;
            }

            tv = *(uint8_t *) (dev->dev_base + ACE_LCR_OFS);
            tv &= (uint8_t) ~ (LCR_PEN_MSK | LCR_PRE_MSK | LCR_PRS_MSK);
            tv |= bv;
            *(uint8_t *) (dev->dev_base + ACE_LCR_OFS) = tv;
#ifdef ACE_HDX_LINE
            dcb->hdxByteTime = ByteOcrTime(dev);
#endif
        }
        AceEnable(devnum);
        break;

    case ACE_GETPARITY:
        tv = *(uint8_t *) (dev->dev_base + ACE_LCR_OFS) & (LCR_PEN_MSK | LCR_PRE_MSK | LCR_PRS_MSK);
        switch (tv) {
        case 0:
            *lvp = 0;           // no parity
            break;

        case LCR_PEN_MSK:
            *lvp = 1;           // odd parity
            break;

        case LCR_PEN_MSK | LCR_PRE_MSK:
            *lvp = 2;           // event parity
            break;

        case LCR_PEN_MSK | LCR_PRS_MSK:
            *lvp = 4;           // mark parity
            break;
        }
        break;

    case ACE_SETSTOPBITS:
        AceDisable(devnum);
        if (bv == 1 || bv == 2) {
            tv = *(uint8_t *) (dev->dev_base + ACE_LCR_OFS);
            tv &= (uint8_t) ~ (LCR_STB_MSK);
            tv |= (bv == 2) ? LCR_STB_MSK : 0;
            *(uint8_t *) (dev->dev_base + ACE_LCR_OFS) = tv;
#ifdef ACE_HDX_LINE
            dcb->hdxByteTime = ByteOcrTime(dev);
#endif
        }
        AceEnable(devnum);
        break;

    case ACE_GETSTOPBITS:
        tv = *(uint8_t *) (dev->dev_base + ACE_LCR_OFS);
        *lvp = (tv & LCR_STB_MSK) ? 2 : 1;
        break;

    case ACE_SETFIFO:
        AceDisable(devnum);
        dcb->dcb_wfifo = ACE_FIFO_SIZE;
        switch (bv) {
        case 1:
            tv = FCR_ENABLE | FCR_LEVEL_1 | FCR_PURGE_I | FCR_PURGE_O;
            break;

        case 4:
            tv = FCR_ENABLE | FCR_LEVEL_4 | FCR_PURGE_I | FCR_PURGE_O;
            break;

        case 8:
            tv = FCR_ENABLE | FCR_LEVEL_8 | FCR_PURGE_I | FCR_PURGE_O;
            break;

        case 14:
            tv = FCR_ENABLE | FCR_LEVEL_14 | FCR_PURGE_I | FCR_PURGE_O;
            break;

        default:
            bv = 0;
            tv = 0;
            dcb->dcb_wfifo = 1;
            break;
        }
        *(uint8_t *) (dev->dev_base + ACE_FCR_OFS) = tv;
        /* if enabling then must write the level after */
        *(uint8_t *) (dev->dev_base + ACE_FCR_OFS) = tv;
        dcb->dcb_rfifo = bv;

        /* must signal any active and waiting writer, discard pending data */
        ifs = dev->dev_icb;

        ifs->if_tx_act = 0;
        ifs->if_tx_idx = ifs->if_wr_idx;
        NutEventPostAsync(&(dcb->dcb_tx_rdy));

        AceEnable(devnum);
        break;

    case ACE_GETFIFO:
        *lvp = (uint32_t) (dcb->dcb_rfifo);
        break;

    case ACE_GETSTATUS:
        AceGetStatus(dev, lvp);
        break;

    case ACE_SETSTATUS:
        rc = -1;
        break;

    case ACE_SETREADTIMEOUT:
        dcb->dcb_rtimeout = lv;
        break;
    case ACE_GETREADTIMEOUT:
        *lvp = dcb->dcb_rtimeout;
        break;

    case ACE_SETWRITETIMEOUT:
        dcb->dcb_wtimeout = lv;
        break;
    case ACE_GETWRITETIMEOUT:
        *lvp = dcb->dcb_wtimeout;
        break;

    case ACE_SETLOCALECHO:
        if (bv)
            dcb->dcb_modeflags |= ACE_MF_LOCALECHO;
        else
            dcb->dcb_modeflags &= ~ACE_MF_LOCALECHO;
        break;
    case ACE_GETLOCALECHO:
        *lvp = (dcb->dcb_modeflags & ACE_MF_LOCALECHO) ? 1 : 0;
        break;

    case ACE_SETFLOWCONTROL:
#ifdef ACE_HDX_LINE
        if (lv & ACE_MF_HALFDUPLEX) {
            /* next transmission will use HDX pin */
            dcb->dcb_modeflags |= ACE_MF_HALFDUPLEX;
        } else {
            dcb->dcb_modeflags &= ~ACE_MF_HALFDUPLEX;
        }
        dcb->hdxOcrTime = 0;
        /* switch HDX pin off */
        ACE_HDX_RECEIVE(dev->dev_base);
#endif
        break;

    case ACE_GETFLOWCONTROL:
#ifdef ACE_HDX_LINE
        *lvp = (uint32_t) (dcb->dcb_modeflags & ACE_MF_HALFDUPLEX);
#endif
        break;

    case ACE_SETCOOKEDMODE:
        if (bv)
            dcb->dcb_modeflags |= ACE_MF_COOKEDMODE;
        else
            dcb->dcb_modeflags &= ~ACE_MF_COOKEDMODE;
        break;
    case ACE_GETCOOKEDMODE:
        *lvp = (dcb->dcb_modeflags & ACE_MF_COOKEDMODE) ? 1 : 0;
        break;

    default:
        rc = -1;
        break;
    }
    return rc;
}

/*!
 * \brief Initialize on chip ACE device.
 *
 * Prepares the device for subsequent reading or writing.
 * Enables ACE transmitter and receiver interrupts.
 *
 * \param dev  Identifies the device to initialize.
 *
 * \return 0 on success, -1 otherwise.
 */
int AceInit(NUTDEVICE * dev)
{
    IFSTREAM *ifs;
    ACEDCB *dcb, *pFirstDcb;
    uint32_t baudrate = 9600;
    uint32_t databits = 8;
    uint32_t parity = 0;
    uint32_t stopbits = 1;
    IRQ_HANDLER *irq;
    uint8_t *pnPort;
    uint8_t nMask;
#ifdef ACE_HDX_LINE
    uint32_t flowcontrol = 0;
#endif
    /*
     * We only support character devices for on-chip ACEs.
     */
    if (dev->dev_type != IFTYP_STREAM) {
        return -1;
    }

    /*
     * Initialize interface control block.
     */
    ifs = dev->dev_icb;
    memset(ifs, 0, sizeof(IFSTREAM));
    ifs->if_input = AceInput;
    ifs->if_output = AceOutput;
    ifs->if_flush = AceFlush;

    /*
     * Initialize driver control block.
     */
    dcb = dev->dev_dcb;
    memset(dcb, 0, sizeof(ACEDCB));
    dcb->dcb_modeflags = ACE_MF_NOBUFFER;
    dcb->dcb_rfifo = 0;
    dcb->dcb_wfifo = 1;
    dcb->dev_next = NULL;
#ifdef ACE_HDX_LINE
    dcb->hdxOcrTime = 0;
#endif

    /*
     * Register interrupt handler.
     */
    if (dev->dev_base) {
        /* if any ACE is already assigned to this interrupt */
        if (pIrqDev[dev->dev_irq] != NULL) {
            pFirstDcb = pIrqDev[dev->dev_irq]->dev_dcb;
            dcb->dev_next = pFirstDcb->dev_next;
            pFirstDcb->dev_next = dev;
        } else {
#ifdef ACE_HDX_LINE
            if (irqMask == 0) {
                /* Register interrupt handlers */
                if (NutRegisterIrqHandler(&sig_OUTPUT_COMPARE3A, AceOutComp3AInt, NULL)) {
                    return -1;
                }
                AceTmr3Init();
            }
#endif
            // get the appropriate irq handler
            irq = (IRQ_HANDLER *) pgm_read_word(&(atIrqDefs[dev->dev_irq].pvIrq));

            if (NutRegisterIrqHandler(irq, AceIrqHandler, dev)) {
                return -1;
            }
            // enable the interrupts
            pnPort = (uint8_t *) pgm_read_word(&(atIrqDefs[dev->dev_irq].pnIrqMskPort));
            nMask = pgm_read_byte(&(atIrqDefs[dev->dev_irq].nMask));
            *pnPort |= nMask;
            /* remember dcb of the recently initialized device */
            pIrqDev[dev->dev_irq] = dev;
            irqMask |= 0x01 << dev->dev_irq;
        }
    }

    /*
     * Set baudrate and handshake default. This will also
     * enable the ACE functions.
     */
    AceIOCtl(dev, ACE_SETSPEED, (void *) &baudrate);
    AceIOCtl(dev, ACE_SETDATABITS, (void *) &databits);
    AceIOCtl(dev, ACE_SETPARITY, (void *) &parity);
    AceIOCtl(dev, ACE_SETSTOPBITS, (void *) &stopbits);
#ifdef ACE_HDX_LINE
    /* set HDX pin off by defalt */
    AceIOCtl(dev, ACE_SETFLOWCONTROL, (void *) &flowcontrol);
#endif
    sbi(EIMSK, dev->dev_irq);   /* dev->dev_irq to IRQ_INTx map should be used for a clean implementation but it looks like an overhead */

    AceEnable(dev->dev_base);

    return 0;
}

/*!
 * \brief Read from device.
 */
int AceRead(NUTFILE * fp, void *buffer, int size)
{
    int rc;
    NUTDEVICE *dev;
    IFSTREAM *ifs;
    ACEDCB *dcb;
    uint8_t elmode;
    uint8_t ch;
    uint8_t *cp = buffer;

    dev = fp->nf_dev;
    ifs = (IFSTREAM *) dev->dev_icb;
    dcb = dev->dev_dcb;

    if (dcb->dcb_modeflags & ACE_MF_COOKEDMODE)
        elmode = 1;
    else
        elmode = 0;

    /*
     * Call without data pointer discards receive buffer.
     */
    if (buffer == 0) {
        ifs->if_rd_idx = ifs->if_rx_idx;
        return 0;
    }

    /*
     * Get characters from receive buffer.
     */
    for (rc = 0; rc < size;) {
        /* if nothing has been received yet */
        if (ifs->if_rd_idx == ifs->if_rx_idx) {
            /* while incomming buffer is empty */
            while (ifs->if_rd_idx == ifs->if_rx_idx) {
                /* wait (timeout) for incomming data */
                if (AceInput(dev)) {
                    /* if a timeout */
                    return 0;
                }
            }
        }
        ch = ifs->if_rx_buf[ifs->if_rd_idx++];
        if (elmode && (ch == '\r' || ch == '\n')) {
            if ((ifs->if_last_eol == 0) || (ifs->if_last_eol == ch)) {
                ifs->if_last_eol = ch;
                *cp++ = '\n';
                rc++;
            }
        } else {
            ifs->if_last_eol = 0;
            *cp++ = ch;
            rc++;
        }
    }
    return rc;
}

/*!
 * \brief Write to device.
 */
int AcePut(NUTDEVICE * dev, const void *buffer, int len, int pflg)
{
    int rc;
    IFSTREAM *ifs;
    ACEDCB *dcb;
    const uint8_t *cp;
    uint8_t lbmode;
    uint8_t elmode;
    uint8_t ch;

    ifs = dev->dev_icb;
    dcb = dev->dev_dcb;

    if (dcb->dcb_modeflags & ACE_MF_LINEBUFFER)
        lbmode = 1;
    else
        lbmode = 0;

    if (dcb->dcb_modeflags & ACE_MF_COOKEDMODE)
        elmode = 1;
    else
        elmode = 0;

    /*
     * Call without data pointer starts transmission.
     */
    if (buffer == 0) {
        rc = AceFlush(dev);
        return rc;
    }

    /*
     * Put characters in transmit buffer.
     */
    cp = buffer;
    for (rc = 0; rc < len;) {
        if ((uint8_t) (ifs->if_wr_idx + 1) == ifs->if_tx_idx) {
            if (AceFlush(dev)) {
                return -1;
            }
        }
        ch = pflg ? PRG_RDB(cp) : *cp;
        if (elmode == 1 && ch == '\n') {
            elmode = 2;
            if (lbmode == 1)
                lbmode = 2;
            ch = '\r';
        } else {
            if (elmode == 2)
                elmode = 1;
            cp++;
            rc++;
        }
        ifs->if_tx_buf[ifs->if_wr_idx++] = ch;
    }

    if (lbmode > 1 || (dcb->dcb_modeflags & ACE_MF_NOBUFFER) != 0) {
        if (AceFlush(dev))
            rc = -1;
    }
    return rc;
}

int AceWrite(NUTFILE * fp, const void *buffer, int len)
{
    return AcePut(fp->nf_dev, buffer, len, 0);
}

int AceWrite_P(NUTFILE * fp, PGM_P buffer, int len)
{
    return AcePut(fp->nf_dev, (const char *) buffer, len, 1);
}


/*!
 * \brief Open a device or file.
 */
NUTFILE *AceOpen(NUTDEVICE * dev, const char *name, int mode, int acc)
{
    NUTFILE *fp = NutHeapAlloc(sizeof(NUTFILE));
    ACEDCB *dcb;

    if (fp == 0)
        return NUTFILE_EOF;

    dcb = dev->dev_dcb;
    if (mode & _O_BINARY)
        dcb->dcb_modeflags &= ~ACE_MF_COOKEDMODE;
    else
        dcb->dcb_modeflags |= ACE_MF_COOKEDMODE;

    fp->nf_dev = dev;
    fp->nf_fcb = NULL;

    return fp;
}

/*!
 * \brief Close a device or file.
 */
int AceClose(NUTFILE * fp)
{
    NutHeapFree(fp);

    return 0;
}

/*!
 * \brief Request file size.
 */
long AceSize(NUTFILE * fp)
{
    NUTDEVICE *dev;
    IFSTREAM *ifs;

    dev = fp->nf_dev;
    ifs = (IFSTREAM *) dev->dev_icb;
    return ((uint8_t) (ifs->if_rx_idx - ifs->if_rd_idx));
}

#ifdef ACE_HDX_LINE
static void AceTmr3Init(void)
{
    /* TMR3 also runs in normal mode */
    TCCR3A &= ~(_BV(COM3A1) | _BV(COM3A0) | _BV(WGM31) | _BV(WGM30));   /* normal mode */

    TCCR3B &= ~(_BV(WGM33) | _BV(WGM32) | _BV(CS32) | _BV(CS31) | _BV(CS30));
    TCCR3B |= _BV(CS31) | _BV(CS30);    /* f/64 (155200Hz - 1.75Hz) */

    /* TMR3 output compare A match interrupt disable */
    ETIMSK &= ~_BV(OCIE3A);
}

static void AceOutComp3AInt(void *arg)
{
    NUTDEVICE *dev;
    ACEDCB *dcb;
    int i;
    unsigned int nextOcr = 0xffff, ocr;
    unsigned int timerOcrDiff;

    /* TMR3 stop counting */
    TCCR3B &= ~(_BV(CS31) | _BV(CS30));

    timerOcrDiff = (unsigned int /* modulo max */ )((unsigned int /* modulo max */ )TCNT3 - (unsigned int /* modulo max */ )OCR3A);

    /* Due to interrupt nesting and as TL interrupts are higher priority - disable them.
     * This routine cannot be interrupted by a cal to the AceAddHdxTime()
     */
    EIMSK &= ~irqMask;

    for (i = 0; i < 8; ++i) {
        for (dev = pIrqDev[i]; dev != NULL; dev = dcb->dev_next) {
            dcb = dev->dev_dcb;
            /* only if enabled */
            if (dcb->hdxOcrTime != 0) {
                if ((unsigned int /* modulo max */ )(dcb->hdxOcrTime - (unsigned int /* modulo max */ )OCR3A) <= timerOcrDiff) {
                    dcb->hdxOcrTime = 0;
                    ACE_HDX_RECEIVE(dev->dev_base);
                } else {
                    ocr = (unsigned int /* modulo max */ )(dcb->hdxOcrTime - (unsigned int /* modulo max */ )TCNT3);
                    if (ocr < nextOcr) {
                        nextOcr = ocr;
                    }
                }
            }
        }
    }

    if (nextOcr == 0xffff) {
        /* TMR3 output compare A match interrupt disable */
        ETIMSK &= ~_BV(OCIE3A);
    } else {
        OCR3A = nextOcr;
        /* start timer */
        TCCR3B |= _BV(CS31) | _BV(CS30);        /* f/64 (155200Hz - 1.75Hz) */
    }

    /* Re-enable TL interrupt. */
    EIMSK |= irqMask;
}

static void AceAddHdxTime(ACEDCB * dcb)
{
    /* TMR3 stop counting */
    TCCR3B &= ~(_BV(CS31) | _BV(CS30));

    /* if disabled */
    if ((ETIMSK & _BV(OCIE3A)) == 0) {
        /* initialize */
        OCR3A = 0;
        TCNT3 = 1;
    }

    /* set offset from current counter value */
    dcb->hdxOcrTime = (unsigned int /* modulo max */ )((unsigned int /* modulo max */ )TCNT3 + dcb->hdxByteTime);

    if (dcb->hdxOcrTime == 0) {
        dcb->hdxOcrTime = 1;    /* 0 means disabled, one bit delay is not a problem as there is interrupt latency anyway */
    }

    if (dcb->hdxByteTime < (unsigned int /* modulo max */ )((unsigned int /* modulo max */ )OCR3A - (unsigned int /* modulo max */ )TCNT3)) {
        OCR3A = dcb->hdxOcrTime;
    }
    /* TMR3 output compare A match interrupt enable */
    ETIMSK |= _BV(OCIE3A);

    /* start timer */
    TCCR3B |= _BV(CS31) | _BV(CS30);    /* f/64 (155200Hz - 1.75Hz) */
}
#endif

/*@}*/
