/*
 * Copyright (C) 2008-2012 by egnite GmbH
 * Copyright (C) 2003-2005 by egnite Software GmbH
 *
 * 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 THE COPYRIGHT HOLDERS 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 THE
 * COPYRIGHT OWNER 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.
 *
 * For additional information see http://www.ethernut.de/
 */

/*
 * $Id$
 */

#include <cfg/os.h>
#include <cfg/dev.h>
#include <cfg/arch/gpio.h>

#include <toolchain.h>

#include <stdlib.h>
#include <string.h>

#include <sys/event.h>
#include <sys/timer.h>
#include <sys/thread.h>

#include <netinet/if_ether.h>
#include <net/ether.h>

#include <dev/irqreg.h>
#include <dev/phy.h>
#include <dev/dm9000.h>

#ifdef NUTDEBUG
#include <stdio.h>
#endif

#ifndef NUT_THREAD_NICRXSTACK
#define NUT_THREAD_NICRXSTACK   384
#endif

#if !defined(DM9000_DATA_ADDR) && defined(DM9000_BASE_ADDR)
#define DM9000_DATA_ADDR   (DM9000_BASE_ADDR + 4)
#endif

#ifdef NIC_RESET_BIT

#if (NIC_RESET_AVRPORT == AVRPORTB)
#define NIC_RESET_PORT   PORTB
#define NIC_RESET_DDR    DDRB

#elif (NIC_RESET_AVRPORT == AVRPORTD)
#define NIC_RESET_PORT   PORTD
#define NIC_RESET_DDR    DDRD

#elif (NIC_RESET_AVRPORT == AVRPORTE)
#define NIC_RESET_PORT   PORTE
#define NIC_RESET_DDR    DDRE

#elif (NIC_RESET_AVRPORT == AVRPORTF)
#define NIC_RESET_PORT   PORTF
#define NIC_RESET_DDR    DDRF

#endif /* NIC_RESET_AVRPORT */

#endif /* NIC_RESET_BIT */

/*
 * Determine interrupt settings.
 */
#ifdef DM9000_SIGNAL_IRQ

#ifndef INT0
#define INT0    0
#define INT1    1
#define INT2    2
#define INT3    3
#define INT4    4
#define INT5    5
#define INT6    6
#define INT7    7
#endif

#if (DM9000_SIGNAL_IRQ == INT0)
#define DM9000_SIGNAL          sig_INTERRUPT0

#elif (DM9000_SIGNAL_IRQ == INT1)
#define DM9000_SIGNAL          sig_INTERRUPT1

#elif (DM9000_SIGNAL_IRQ == INT2)
#define DM9000_SIGNAL          sig_INTERRUPT2

#elif (DM9000_SIGNAL_IRQ == INT3)
#define DM9000_SIGNAL          sig_INTERRUPT3

#elif (DM9000_SIGNAL_IRQ == INT4)
#define DM9000_SIGNAL          sig_INTERRUPT4

#elif (DM9000_SIGNAL_IRQ == INT5)
#define DM9000_SIGNAL          sig_INTERRUPT5

#elif (DM9000_SIGNAL_IRQ == INT6)
#define DM9000_SIGNAL          sig_INTERRUPT6

#elif (DM9000_SIGNAL_IRQ == INT7)
#define DM9000_SIGNAL          sig_INTERRUPT7

#endif
#endif

/*!
 * \addtogroup xgDm9000Regs
 */
/*@{*/

#define NIC_NCR     0x00        /* Network control register (0x00). */
#define NIC_NCR_LBM     0x06    /* Loopback mode. */
#define NIC_NCR_LBNORM  0x00    /* Normal mode. */
#define NIC_NCR_LBMAC   0x02    /* MAC loopback. */
#define NIC_NCR_LBPHY   0x04    /* PHY loopback. */
#define NIC_NCR_RST     0x01    /* Software reset, auto clear. */

#define NIC_NSR     0x01        /* Network status register (0x00). */
#define NIC_NSR_SPEED   0x80
#define NIC_NSR_LINKST  0x40
#define NIC_NSR_WAKEST  0x20
#define NIC_NSR_TX2END  0x08
#define NIC_NSR_TX1END  0x04
#define NIC_NSR_RXOV    0x02

#define NIC_TCR     0x02        /* TX control register (0x00). */
#define NIC_TCR_TXREQ    0x01   /* TX request */

#define NIC_TSR1    0x03        /* TX status register I (0x00). */

#define NIC_TSR2    0x04        /* TX status register II (0x00). */

#define NIC_RCR     0x05        /* RX control register (0x00). */
#define NIC_RCR_DIS_LONG 0x20   /* Discard long packets. */
#define NIC_RCR_DIS_CRC 0x10    /* Discard CRC error packets. */
#define NIC_RCR_ALL     0x08    /* Pass all multicast */
#define NIC_RCR_PRMSC   0x02    /* Enable promiscuous mode. */
#define NIC_RCR_RXEN    0x01    /* Enable receiver. */

#define NIC_RSR     0x06        /* RX status register (0x00). */
#define NIC_RSR_ERRORS  0xBF    /* Error bit mask. */
#define NIC_RSR_RF      0x80    /* Runt frame. */
#define NIC_RSR_MF      0x40    /* Multicast frame. */
#define NIC_RSR_LCS     0x20    /* Late collision. */
#define NIC_RSR_RWTO    0x10    /* Receiver watchdog time out. */
#define NIC_RSR_PLE     0x08    /* Physical layer error. */
#define NIC_RSR_AE      0x04    /* Alignment error. */
#define NIC_RSR_CE      0x02    /* CRC error. */
#define NIC_RSR_FOE     0x01    /* FIFO overflow error. */

#define NIC_ROCR    0x07        /* Receive overflow counter register (0x00). */

#define NIC_BPTR    0x08        /* Back pressure threshold register (0x37). */

#define NIC_FCTR    0x09        /* Flow control threshold register (0x38). */

#define NIC_FCR     0x0A        /* RX flow control register (0x00). */

#define NIC_EPCR    0x0B        /* EEPROM and PHY control register. */

#define NIC_EPAR    0x0C        /* EEPROM and PHY address register. */

#define NIC_EPDRL   0x0D        /* EEPROM and PHY low byte data register. */

#define NIC_EPDRH   0x0E        /* EEPROM and PHY high byte data register. */

#define NIC_WCR     0x0F        /* Wake up control register (0x00). */

#define NIC_PAR     0x10        /* 6 byte physical address register. */

#define NIC_MAR     0x16        /* 8 byte multicast address register. */

#define NIC_GPCR    0x1E        /* General purpose control register (?). */

#define NIC_GPR     0x1F        /* General purpose register (?). */

#define NIC_TRPA    0x22        /* 2 byte TX SRAM read pointer address, low/high (0x0000). */

#define NIC_RWPA    0x24        /* 2 byte RX SRAM write pointer address, low/high (0x0000). */

#define NIC_VID     0x28        /* 2 byte vendor ID (0x0A46). */

#define NIC_PID     0x2A        /* 2 byte product ID (0x0900). */

#define NIC_CHIPR   0x2C        /* Chip revision (0x00). */

#define NIC_SMCR    0x2F        /* Special mode register (0x00). */

#define NIC_MRCMDX  0xF0        /* Memory data read command w/o increment (?). */

#define NIC_MRCMD   0xF2        /* Memory data read command with increment (?). */

#define NIC_MRR     0xF4        /* 2 byte memory data read register, low/high (?). */

#define NIC_MWCMDX  0xF6        /* Memory data write command register w/o increment (?). */

#define NIC_MWCMD   0xF8        /* Memory data write command register with increment (?). */

#define NIC_MWR     0xFA        /* Memory data write command register with increment (?). */

#define NIC_TXPL    0xFC        /* 2 byte TX packet length register. (?). */

#define NIC_ISR     0xFE        /* Interrupt status register (0x00). */
#define NIC_ISR_IOM     0xC0    /* I/O mode mask */
#define NIC_ISR_M16     0x00    /* 16-bit I/O mode */
#define NIC_ISR_M32     0x40    /* 32-bit I/O mode */
#define NIC_ISR_M8      0x80    /* 8-bit I/O mode */
#define NIC_ISR_ROOS    0x08    /* Receiver overflow counter interrupt. */
#define NIC_ISR_ROS     0x04    /* Receiver overflow interrupt. */
#define NIC_ISR_PTS     0x02    /* Transmitter interrupt. */
#define NIC_ISR_PRS     0x01    /* Receiver interrupt. */

#define NIC_IMR     0xFF        /* Interrupt mask register (0x00). */
#define NIC_IMR_PAR     0x80    /* Enable read/write pointer wrap around. */
#define NIC_IMR_ROOM    0x08    /* Enable receiver overflow counter interrupts. */
#define NIC_IMR_ROM     0x04    /* Enable receiver overflow interrupts. */
#define NIC_IMR_PTM     0x02    /* Enable transmitter interrupts. */
#define NIC_IMR_PRM     0x01    /* Enable receiver interrupts. */

/*!
 * \brief Network interface controller information structure.
 */
struct _NICINFO {
    HANDLE volatile ni_rx_rdy;  /*!< Receiver event queue. */
    HANDLE volatile ni_tx_rdy;  /*!< Transmitter event queue. */
    HANDLE ni_mutex;            /*!< Access mutex semaphore. */
    volatile int ni_tx_queued;  /*!< Number of packets in transmission queue. */
    volatile int ni_tx_quelen;  /*!< Number of bytes in transmission queue not sent. */
    volatile int ni_insane;     /*!< Set by error detection. */
    int ni_iomode;              /*!< 8 or 16 bit access. 32 bit is not supported. */
    uint8_t ni_mar[8];          /*!< Multicast Address Register. */
};

/*!
 * \brief Network interface controller information type.
 */
typedef struct _NICINFO NICINFO;

/*@}*/

/*!
 * \addtogroup xgNicDm9000
 */
/*@{*/

/*
 * ether_crc32_le based on FreeBSD code from if_ethersubr.c
 */
static const uint32_t crctab[] = {
    0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
    0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
    0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
    0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c
};

static uint32_t ether_crc32_le(const uint8_t * buf, uint8_t len)
{
    uint32_t crc;
    uint8_t i;

    /* Set initial value. */
    crc = 0xffffffff;

    for (i = 0; i < len; i++) {
        crc ^= buf[i];
        crc = (crc >> 4) ^ crctab[crc & 0xf];
        crc = (crc >> 4) ^ crctab[crc & 0xf];
    }

    return crc;
}

static INLINE void nic_outb(uint8_t reg, uint8_t val)
{
#ifdef DM9000_BASE_ADDR
    outb(DM9000_BASE_ADDR, reg);
    outb(DM9000_DATA_ADDR, val);
#endif
}

static INLINE uint8_t nic_inb(uint16_t reg)
{
#ifdef DM9000_BASE_ADDR
    outb(DM9000_BASE_ADDR, reg);
    return inb(DM9000_DATA_ADDR);
#else
    return 0;
#endif
}

/*!
 * \brief Read contents of PHY register.
 *
 * \param reg PHY register number.
 * \param val Pointer to value to read to.
 *
 * \return 0 for success, -1 on fail (not implemented)
 */
static uint16_t phy_inw( uint8_t reg)
{
    /* Select PHY register */
    nic_outb(NIC_EPAR, 0x40 | reg);

    /* PHY read command. */
    nic_outb(NIC_EPCR, 0x0C);
    NutDelay(1);
    nic_outb(NIC_EPCR, 0x00);

    /* Get data from PHY data register. */
    return ((uint16_t) nic_inb(NIC_EPDRH) << 8) | (uint16_t) nic_inb(NIC_EPDRL);
}

/*!
 * \brief Write value to PHY register.
 *
 * \note NIC interrupts must have been disabled before calling this routine.
 *
 * \param reg PHY register number.
 * \param val Pointer to value to write.
 *
 * \return 0 for success, -1 on fail (not implemented)
 */
static void phy_outw(uint8_t reg, uint16_t val)
{
    /* Select PHY register */
    nic_outb(NIC_EPAR, 0x40 | reg);

    /* Store value in PHY data register. */
    nic_outb(NIC_EPDRL, (uint8_t) (val));
    nic_outb(NIC_EPDRH, (uint8_t) (val >> 8));

    /* PHY write command. */
    nic_outb(NIC_EPCR, 0x0A);
    NutDelay(1);
    nic_outb(NIC_EPCR, 0x00);
}

/*!
 * \brief Initialize PHY.
 *
 * \note NutRegisterPhy() has to be called before and NIC has to be
 *       configured to be able to access the PHY through the MDIO
 *       bus interface.
 */
static int NicPhyInit(void)
{
    uint32_t phy = 1;
    /* Restart auto negotiation. */
    NutPhyCtl(PHY_CTL_AUTONEG_RE, &phy);

    nic_outb(NIC_GPCR, 1);
    nic_outb(NIC_GPR, 0);

    return 0;
}

/*!
 * \brief Reset the Ethernet controller.
 *
 * \return 0 on success, -1 otherwise.
 */
static int NicReset(void)
{
    /* Hardware reset. */
#ifdef undef_NIC_RESET_BIT
    sbi(NIC_RESET_DDR, NIC_RESET_BIT);
    sbi(NIC_RESET_PORT, NIC_RESET_BIT);
    NutDelay(WAIT100);
    cbi(NIC_RESET_PORT, NIC_RESET_BIT);
    NutDelay(WAIT250);
    NutDelay(WAIT250);
#else
    /* Software reset. */
    nic_outb(NIC_NCR, NIC_NCR_RST | NIC_NCR_LBMAC);
    NutDelay(1);
    /* FIXME: Delay required. */
#endif

    return NicPhyInit();
}

#ifdef DM9000_SIGNAL
/*
 * NIC interrupt entry.
 */
static void NicInterrupt(void *arg)
{
    uint8_t isr;
    NICINFO *ni = (NICINFO *) ((NUTDEVICE *) arg)->dev_dcb;

    /* Read interrupt status and disable interrupts. */
    isr = nic_inb(NIC_ISR);

    /* Receiver interrupt. */
    if (isr & NIC_ISR_PRS) {
        nic_outb(NIC_ISR, NIC_ISR_PRS);
        NutEventPostFromIrq(&ni->ni_rx_rdy);
    }

    /* Transmitter interrupt. */
    if (isr & NIC_ISR_PTS) {
        if (ni->ni_tx_queued) {
            if (ni->ni_tx_quelen) {
                /* Initiate transfer of a queued packet. */
                nic_outb(NIC_TXPL, (uint8_t) ni->ni_tx_quelen);
                nic_outb(NIC_TXPL + 1, (uint8_t) (ni->ni_tx_quelen >> 8));
                ni->ni_tx_quelen = 0;
                nic_outb(NIC_TCR, NIC_TCR_TXREQ);
            }
            ni->ni_tx_queued--;
        }
        nic_outb(NIC_ISR, NIC_ISR_PTS);
        NutEventPostFromIrq(&ni->ni_tx_rdy);
    }

    /* Receiver overflow interrupt. */
    if (isr & NIC_ISR_ROS) {
        nic_outb(NIC_ISR, NIC_ISR_ROS);
        ni->ni_insane = 1;
        NutEventPostFromIrq(&ni->ni_rx_rdy);
    }

    /* Receiver overflow counter interrupt. */
    if (isr & NIC_ISR_ROOS) {
        nic_outb(NIC_ISR, NIC_ISR_ROOS);
        NutEventPostFromIrq(&ni->ni_rx_rdy);
    }
}
#endif

#ifdef DM9000_BASE_ADDR
/*!
 * \brief Write data block to the NIC.
 *
 * NIC interrupts must be disabled when calling this function.
 */
static void NicWrite8(uint8_t * buf, uint16_t len)
{
    while (len--) {
        outb(DM9000_DATA_ADDR, *buf);
        buf++;
    }
}

/*!
 * \brief Write data block to the NIC.
 *
 * NIC interrupts must be disabled when calling this function.
 */
static void NicWrite16(uint8_t * buf, uint16_t len)
{
    uint16_t *wp = (uint16_t *) buf;

    len = (len + 1) / 2;
    while (len--) {
        outw(DM9000_DATA_ADDR, *wp);
        wp++;
    }
}

/*!
 * \brief Read data block from the NIC.
 *
 * NIC interrupts must be disabled when calling this function.
 */
static void NicRead8(uint8_t * buf, uint16_t len)
{
    while (len--) {
        *buf++ = inb(DM9000_DATA_ADDR);
    }
}

/*!
 * \brief Read data block from the NIC.
 *
 * NIC interrupts must be disabled when calling this function.
 */
static void NicRead16(uint8_t * buf, uint16_t len)
{
    uint16_t *wp = (uint16_t *) buf;

    len = (len + 1) / 2;
    while (len--) {
        *wp++ = inw(DM9000_DATA_ADDR);
    }
}
#endif /* DM9000_BASE_ADDR */

/*!
 * \brief Fetch the next packet out of the receive ring buffer.
 *
 * \return Pointer to an allocated ::NETBUF. If there is no
 *         no data available, then the function returns a
 *         null pointer. If the NIC's buffer seems to be
 *         corrupted, a pointer to 0xFFFF is returned.
 */
static int NicGetPacket(NICINFO * ni, NETBUF ** nbp)
{
    int rc = -1;
#ifdef DM9000_BASE_ADDR
    uint16_t fsw;
    uint16_t fbc;

    *nbp = NULL;

#ifdef DM9000_SIGNAL
    /* Disable NIC interrupts. */
    NutIrqDisable(&DM9000_SIGNAL);
#endif

    /*
     * Read the status word w/o auto increment. If zero, no packet is
     * available. Otherwise it should be set to one. Any other value
     * indicates a weird chip crying for reset.
     */
    nic_inb(NIC_MRCMDX);
    /* Add some delay before reading the status of the received packet. */
    _NOP();
    _NOP();
    _NOP();
    _NOP();
    fsw = inb(DM9000_DATA_ADDR);
    if (fsw > 1) {
        ni->ni_insane = 1;
    } else if (fsw) {
        /* Now read status word and byte count with auto increment. */
        outb(DM9000_BASE_ADDR, NIC_MRCMD);
        if (ni->ni_iomode == NIC_ISR_M16) {
            fsw = inw(DM9000_DATA_ADDR);
            _NOP();
            _NOP();
            _NOP();
            _NOP();
            fbc = inw(DM9000_DATA_ADDR);
        } else {
            fsw = inb(DM9000_DATA_ADDR) + ((uint16_t) inb(DM9000_DATA_ADDR) << 8);
            _NOP();
            _NOP();
            _NOP();
            _NOP();
            fbc = inb(DM9000_DATA_ADDR) + ((uint16_t) inb(DM9000_DATA_ADDR) << 8);
        }

        /*
         * Receiving long packets is unexpected, because we disabled
         * this during initialization. Let's declare the chip insane.
         * Short packets will be handled by the caller.
         */
        if (fbc > 1536) {
            ni->ni_insane = 1;
        } else {
            /*
             * The high byte of the status word contains a copy of the
             * receiver status register.
             */
            fsw >>= 8;
            fsw &= NIC_RSR_ERRORS;
#ifdef NUT_PERMON
            /* Update statistics. */
            if (fsw) {
                if (RxStatus & NIC_RSR_CE) {
                    ni->ni_crc_errors++;
                } else if (RxStatus & NIC_RSR_FOE) {
                    ni->ni_overruns++;
                } else {
                    ni->ni_rx_missed_errors++;
                }
            } else {
                ni->ni_rx_packets++;
            }
#endif
            /*
             * If we got an error packet or failed to allocated the
             * buffer, then silently discard the packet.
             */
            if (fsw || (*nbp = NutNetBufAlloc(0, NBAF_DATALINK + (2 & (NUTMEM_ALIGNMENT - 1)), fbc - 4)) == NULL) {
                if (ni->ni_iomode == NIC_ISR_M16) {
                    fbc = (fbc + 1) / 2;
                    while (fbc--) {
                        fsw = inw(DM9000_DATA_ADDR);
                    }
                } else {
                    while (fbc--) {
                        fsw = inb(DM9000_DATA_ADDR);
                    }
                }
            } else {
                if (ni->ni_iomode == NIC_ISR_M16) {
                    /* Read packet data from 16 bit bus. */
                    NicRead16((*nbp)->nb_dl.vp, (*nbp)->nb_dl.sz);
                    /* Read packet CRC. */
                    fsw = inw(DM9000_DATA_ADDR);
                    fsw = inw(DM9000_DATA_ADDR);
                } else {
                    /* Read packet data from 8 bit bus. */
                    NicRead8((*nbp)->nb_dl.vp, (*nbp)->nb_dl.sz);
                    /* Read packet CRC. */
                    fsw = inb(DM9000_DATA_ADDR);
                    fsw = inb(DM9000_DATA_ADDR);
                    fsw = inb(DM9000_DATA_ADDR);
                    fsw = inb(DM9000_DATA_ADDR);
                }
                /* Return success. */
                rc = 0;
            }
        }
    }

    /* Enable NIC interrupts if the chip is sane. */
#ifdef DM9000_SIGNAL
    if (ni->ni_insane == 0) {
        NutIrqEnable(&DM9000_SIGNAL);
    }
#endif
#endif
    return rc;
}

/*!
 * \brief Load a packet into the NIC's transmit ring buffer.
 *
 * \param nb Network buffer structure containing the packet to be sent.
 *           The structure must have been allocated by a previous
 *           call NutNetBufAlloc(). This routine will automatically
 *           release the buffer in case of an error.
 *
 * \return 0 on success, -1 in case of any errors. Errors
 *         will automatically release the network buffer
 *         structure.
 */
static int NicPutPacket(NICINFO * ni, NETBUF * nb, uint_fast16_t sz)
{
    int rc = -1;
#ifdef DM9000_BASE_ADDR

#ifdef DM9000_SIGNAL
    /* Disable interrupts. */
    NutIrqDisable(&DM9000_SIGNAL);
#endif

    /* TODO: Check for link. */
    if (ni->ni_insane == 0) {
        /* Enable data write. */
        outb(DM9000_BASE_ADDR, NIC_MWCMD);

        /* Transfer the Ethernet frame. */
        if (ni->ni_iomode == NIC_ISR_M16) {
            NicWrite16(nb->nb_dl.vp, nb->nb_dl.sz);
            NicWrite16(nb->nb_nw.vp, nb->nb_nw.sz);
            NicWrite16(nb->nb_tp.vp, nb->nb_tp.sz);
            NicWrite16(nb->nb_ap.vp, nb->nb_ap.sz);
        } else {
            NicWrite8(nb->nb_dl.vp, nb->nb_dl.sz);
            NicWrite8(nb->nb_nw.vp, nb->nb_nw.sz);
            NicWrite8(nb->nb_tp.vp, nb->nb_tp.sz);
            NicWrite8(nb->nb_ap.vp, nb->nb_ap.sz);
        }

        /* If no packet is queued, start the transmission. */
        if (ni->ni_tx_queued == 0) {
            nic_outb(NIC_TXPL, (uint8_t) sz);
            nic_outb(NIC_TXPL + 1, (uint8_t) (sz >> 8));
            nic_outb(NIC_TCR, NIC_TCR_TXREQ);
        }
        /* ...otherwise mark this packet queued. */
        else {
            ni->ni_tx_quelen = sz;
        }
        ni->ni_tx_queued++;
        rc = 0;
    }

#ifdef DM9000_SIGNAL
    /* Enable interrupts. */
    NutIrqEnable(&DM9000_SIGNAL);
#endif

    /* If the controller buffer is filled with two packets, then
       wait for the first being sent out. */
    if (rc == 0 && ni->ni_tx_queued > 1) {
        NutEventWait(&ni->ni_tx_rdy, 500);
    }
#endif
    return rc;
}

/*!
 * \brief Update the multicast register.
 *
 * \param Network interface controller information.
 */
static void NicUpdateMCHardware(NICINFO * ni)
{
    int i;

    /* Enable broadcast receive. */
    ni->ni_mar[7] |= 0x80;

    /* Set multicast address register */
    for (i = 0; i < 7; i++) {
        nic_outb(NIC_MAR + i, ni->ni_mar[i]);
    }
}

/*!
 * \brief Fire up the network interface.
 *
 * NIC interrupts must be disabled when calling this function.
 *
 * \param mac Six byte unique MAC address.
 */
static int NicStart(const uint8_t * mac, NICINFO * ni)
{
    int i;
    int link_wait = 20;
    uint32_t phy;

    /* Power up the PHY. */
    nic_outb(NIC_GPR, 0);
    NutDelay(5);

    /* Software reset with MAC loopback. */
    nic_outb(NIC_NCR, NIC_NCR_RST | NIC_NCR_LBMAC);
    NutDelay(5);
    nic_outb(NIC_NCR, NIC_NCR_RST | NIC_NCR_LBMAC);
    NutDelay(5);

    /*
     * PHY power down followed by PHY power up. This should activate
     * the auto sense link.
     */
    nic_outb(NIC_GPR, 1);
    nic_outb(NIC_GPR, 0);

    /* Set MAC address. */
    for (i = 0; i < 6; i++) {
        nic_outb(NIC_PAR + i, mac[i]);
    }

    /* Set multicast address register */
    NicUpdateMCHardware(ni);

    /* Clear interrupts. */
    nic_outb(NIC_ISR, NIC_ISR_ROOS | NIC_ISR_ROS | NIC_ISR_PTS | NIC_ISR_PRS);

    /* Enable late collision retries on the DM9000A. */
    if (nic_inb(NIC_CHIPR) == 0x19) {
        nic_outb(0x2D, 0x40);
    }

    /* Enable receiver. */
    nic_outb(NIC_RCR, NIC_RCR_DIS_LONG | NIC_RCR_DIS_CRC | NIC_RCR_RXEN | NIC_RCR_ALL);

    /* Restart autonegotiation */
    phy = 1;
    NutPhyCtl(PHY_CTL_AUTONEG_RE, &phy);

    /* Wait for auto negotiation completed and link established. */
    for (link_wait = 25;; link_wait--) {
        NutPhyCtl(PHY_GET_STATUS, &phy);
        if((phy & PHY_STATUS_HAS_LINK) && (phy & PHY_STATUS_AUTONEG_OK)) {
            break;
        }
        if (link_wait == 0) {
            return -1;
        }
        NutSleep(200);
    }

    /* Enable interrupts. */
    nic_outb(NIC_IMR, NIC_IMR_PAR | NIC_IMR_PTM | NIC_IMR_PRM);

    return 0;
}

/*! \fn NicRxLanc(void *arg)
 * \brief NIC receiver thread.
 *
 */
THREAD(NicRxLanc, arg)
{
    NUTDEVICE *dev;
    IFNET *ifn;
    NICINFO *ni;
    NETBUF *nb;

    dev = arg;
    ifn = (IFNET *) dev->dev_icb;
    ni = (NICINFO *) dev->dev_dcb;

    /*
     * This is a temporary hack. Due to a change in initialization,
     * we may not have got a MAC address yet. Wait until a valid one
     * has been set.
     */
    while (ETHER_IS_ZERO(ifn->if_mac) || ETHER_IS_BROADCAST(ifn->if_mac)) {
        NutSleep(10);
    }

    /*
     * Do not continue unless we managed to start the NIC. We are
     * trapped here if the Ethernet link cannot be established.
     * This happens, for example, if no Ethernet cable is plugged
     * in.
     */
    while (NicStart(ifn->if_mac, ni)) {
        NutSleep(1000);
    }

    /* Initialize the access mutex. */
    NutEventPost(&ni->ni_mutex);

    /* Run at high priority. */
    NutThreadSetPriority(9);

    /* Enable interrupts. */
#ifdef NIC_SIGNAL_XSR
    outr(NIC_SIGNAL_XSR, _BV(NIC_SIGNAL_BIT));
#if defined(ELEKTOR_IR1)
    /* Ugly code alarm: Should be configurable. */
    outr(PMC_PCER, _BV(IRQ0_ID));
#endif
#endif
#ifdef NIC_SIGNAL_BIT
    outr(NIC_SIGNAL_PDR, _BV(NIC_SIGNAL_BIT));
#endif
#ifdef DM9000_SIGNAL
    NutIrqEnable(&DM9000_SIGNAL);
#endif
#if defined(ELEKTOR_IR1)
    /* Ugly code alarm: Should be configurable. */
    NutIrqSetMode(&DM9000_SIGNAL, NUT_IRQMODE_HIGHLEVEL);
#endif

    for (;;) {
        /*
         * Wait for the arrival of new packets or poll the receiver
         * every two seconds.
         */
        NutEventWait(&ni->ni_rx_rdy, 2000);

        /*
         * Fetch all packets from the NIC's internal buffer and pass
         * them to the registered handler.
         */
        while (NicGetPacket(ni, &nb) == 0) {
            /* Update monitoring counters. */
            NUT_PERFMON_ADD(ifn->if_in_octets, nb->nb_dl.sz);
            if (nic_inb(NIC_RSR) & NIC_RSR_MF) {
                NUT_PERFMON_INC(ifn->if_in_n_ucast_pkts);
            } else {
                NUT_PERFMON_INC(ifn->if_in_ucast_pkts);
            }
            /* Discard short packets. */
            if (nb->nb_dl.sz < 60) {
                NutNetBufFree(nb);
                NUT_PERFMON_INC(ifn->if_in_errors);
            } else {
                (*ifn->if_recv) (dev, nb);
            }
        }

        /* We got a weird chip, try to restart it. */
        while (ni->ni_insane) {
            if (NicStart(ifn->if_mac, ni) == 0) {
                ni->ni_insane = 0;
                ni->ni_tx_queued = 0;
                ni->ni_tx_quelen = 0;
#ifdef DM9000_SIGNAL
                NutIrqEnable(&DM9000_SIGNAL);
#endif
            } else {
                NutSleep(1000);
            }
        }
    }
}

/*!
 * \brief Send Ethernet packet.
 *
 * \param dev Identifies the device to use.
 * \param nb  Network buffer structure containing the packet to be sent.
 *            The structure must have been allocated by a previous
 *            call NutNetBufAlloc().
 *
 * \return 0 on success, -1 in case of any errors.
 */
int DmOutput(NUTDEVICE * dev, NETBUF * nb)
{
    /* After initialization we are waiting for a long time to give the
       PHY a chance to establish an Ethernet link. */
    static uint32_t mx_wait = 5000;
    int rc = -1;
    NICINFO *ni = (NICINFO *) dev->dev_dcb;
    uint_fast16_t sz;
    IFNET *nif = (IFNET *) dev->dev_icb;

    /* Calculate the number of bytes to be send. Do not send packets
       larger than the Ethernet maximum transfer unit. The MTU consist
       of 1500 data bytes plus the 14 byte Ethernet header plus 4 bytes
       CRC. We check the data bytes only. */
    sz = nb->nb_nw.sz + nb->nb_tp.sz + nb->nb_ap.sz;
    if (sz > nif->if_mtu) {
        return -1;
    }
    /* Add the Ethernet header and make the length even. */
    sz += nb->nb_dl.sz + 1;
    sz &= ~1;

    while (rc) {
        if (ni->ni_insane || NutEventWait(&ni->ni_mutex, mx_wait)) {
            /* Discard the packet, if the controller is out of order. */
            NUT_PERFMON_INC(nif->if_out_discards);
            break;
        }

        /* Check for packet queue space. */
        if (ni->ni_tx_queued > 1) {
            if (NutEventWait(&ni->ni_tx_rdy, 500)) {
                /* No queue space. Release the lock and give up. */
                NutEventPost(&ni->ni_mutex);
                NUT_PERFMON_INC(nif->if_out_discards);
                break;
            }
        } else if (NicPutPacket(ni, nb, sz) == 0) {
            /* Ethernet works. Set a long waiting time in case we
               temporarly lose the link next time. */
            rc = 0;
            mx_wait = 5000;
            NUT_PERFMON_ADD(nif->if_out_octets, sz);
        } else {
            NUT_PERFMON_INC(nif->if_out_errors);
        }
        NutEventPost(&ni->ni_mutex);
    }
    /*
     * Probably no Ethernet link. Significantly reduce the waiting
     * time, so following transmission will soon return an error.
     */
    if (rc) {
        mx_wait = 500;
    }
    return rc;
}

/*!
 * \brief Initialize Ethernet hardware.
 *
 * Resets the LAN91C111 Ethernet controller, initializes all required
 * hardware registers and starts a background thread for incoming
 * Ethernet traffic.
 *
 * Applications should do not directly call this function. It is
 * automatically executed during during device registration by
 * NutRegisterDevice().
 *
 * If the network configuration hasn't been set by the application
 * before registering the specified device, this function will
 * call NutNetLoadConfig() to get the MAC address.
 *
 * \param dev Identifies the device to initialize.
 */
int DmInit(NUTDEVICE * dev)
{
    uint32_t id;
    NICINFO *ni = (NICINFO *) dev->dev_dcb;

    /* Probe chip by verifying the identifier registers. */
    id = (uint32_t) nic_inb(NIC_VID);
    id |= (uint32_t) nic_inb(NIC_VID + 1) << 8;
    id |= (uint32_t) nic_inb(NIC_PID) << 16;
    id |= (uint32_t) nic_inb(NIC_PID + 1) << 24;
    if (id != 0x90000A46) {
        return -1;
    }

    /* Register PHY */
    NutRegisterPhy( 1, phy_outw, phy_inw);

    /* Reset chip. */
    if (NicReset()) {
        return -1;
    }

    /* Clear NICINFO structure. */
    memset(ni, 0, sizeof(NICINFO));

    /* Determine bus mode. We do not support 32 bit access. */
    ni->ni_iomode = nic_inb(NIC_ISR) & NIC_ISR_IOM;
    if (ni->ni_iomode == NIC_ISR_M32) {
        return -1;
    }

#ifdef DM9000_SIGNAL
    /* Register interrupt handler. */
    if (NutRegisterIrqHandler(&DM9000_SIGNAL, NicInterrupt, dev)) {
        return -1;
    }
#endif

    /* Start the receiver thread. */
    if (NutThreadCreate("rxi1", NicRxLanc, dev,
        (NUT_THREAD_NICRXSTACK * NUT_THREAD_STACK_MULT) + NUT_THREAD_STACK_ADD) == NULL) {
        return -1;
    }
    return 0;
}

static int DmIOCtl(NUTDEVICE * dev, int req, void *conf)
{
    int rc = 0;
    int i;
    IFNET *nif = (IFNET *) dev->dev_icb;
    NICINFO *ni = (NICINFO *) dev->dev_dcb;
    uint32_t index;
    uint32_t ip_addr;
    MCASTENTRY *mcast;
    MCASTENTRY *mcast_prev;
    MCASTENTRY *mcast_next;

    uint8_t mac[6];

    switch (req) {
    case SIOCSIFADDR:
        /* Set interface hardware address. */
        memcpy(nif->if_mac, conf, sizeof(nif->if_mac));
        break;

    case SIOCADDMULTI:
        /* Add multicast address. */
        ip_addr = *((uint32_t *) conf);
        /* Test if the address is still in the list */
        mcast = nif->if_mcast;
        while (mcast) {
            if (ip_addr == mcast->mca_ip) {
                /* The address is still in the list, do nothing */
                return -1;
            }
            mcast = mcast->mca_next;
        }
        /* Create MAC address */
        mac[0] = 0x01;
        mac[1] = 0x00;
        mac[2] = 0x5E;
        mac[3] = ((uint8_t *) conf)[1] & 0x7f;
        mac[4] = ((uint8_t *) conf)[2];
        mac[5] = ((uint8_t *) conf)[3];
        mcast = malloc(sizeof(MCASTENTRY));
        if (mcast) {
            /* Calculate MAR index, range 0...63 */
            index = ether_crc32_le(&mac[0], 6);
            index &= 0x3F;
            /* Set multicast bit */
            ni->ni_mar[index / 8] |= (1 << (index % 8));
            /* Add new mcast to the mcast list */
            memcpy(mcast->mca_ha, mac, 6);
            mcast->mca_ip = *((uint32_t *) conf);
            mcast->mca_next = nif->if_mcast;
            nif->if_mcast = mcast;
            /* Update the MC hardware */
            NicUpdateMCHardware(ni);
        } else {
            rc = -1;
        }
        break;

    case SIOCDELMULTI:
        /* Delete multicast address. */
        ip_addr = *((uint32_t *) conf);

        /* Test if the address is still in the list */
        mcast = nif->if_mcast;
        mcast_prev = mcast;
        while (mcast) {
            if (ip_addr == mcast->mca_ip) {
                /* The address is in the list, leave the loop */
                break;
            }
            mcast_prev = mcast;
            mcast = mcast->mca_next;
        }
        if (NULL == mcast) {
            /* The address is not in the list */
            return -1;
        }

        /*
         * Remove the address from the list
         */
        mcast_next = mcast->mca_next;

        /* Check if the first element must be removed */
        if (nif->if_mcast == mcast) {
            /* The element is the first one. The first element is now
               the "next" element */
            nif->if_mcast = mcast_next;
            free(mcast);
        } else {
            /* The element is in the middle of the list. The next
               element of the previous is the "next" element */
            mcast_prev->mca_next = mcast_next;
            free(mcast);
        }

        /* Clear the multicast filter. */
        for (i = 0; i < 7; i++) {
            ni->ni_mar[i] = 0;
        }
        /* Rebuild the multicast filter. */
        mcast = nif->if_mcast;
        while (mcast) {
            /* Calculate MAR index, range 0...63 */
            index = ether_crc32_le(&mcast->mca_ha[0], 6);
            index &= 0x3F;
            /* Set multicast bit */
            ni->ni_mar[index / 8] |= (1 << (index % 8));
            mcast = mcast->mca_next;
        }
        /* Update the MC hardware */
        NicUpdateMCHardware(ni);
        break;

    default:
        rc = -1;
        break;
    }

    return rc;
}

static NICINFO dcb_eth0;

/*!
 * \brief Network interface information structure.
 *
 * Used to call.
 */
static IFNET ifn_eth0 = {
    IFT_ETHER,                  /*!< \brief Interface type, if_type. */
    0,                          /*!< \brief Interface flags, if_flags. */
    { 0, 0, 0, 0, 0, 0 },       /*!< \brief Hardware net address, if_mac. */
    0,                          /*!< \brief IP address, if_local_ip. */
    0,                          /*!< \brief Remote IP address for point to point, if_remote_ip. */
    0,                          /*!< \brief IP network mask, if_mask. */
    ETHERMTU,                   /*!< \brief Maximum size of a transmission unit, if_mtu. */
    0,                          /*!< \brief Packet identifier, if_pkt_id. */
    0,                          /*!< \brief Linked list of ARP entries, arpTable. */
    0,                          /*!< \brief Linked list of multicast address entries, if_mcast. */
    NutEtherInput,              /*!< \brief Routine to pass received data to, if_recv(). */
    DmOutput,                   /*!< \brief Driver output routine, if_send(). */
    NutEtherOutput              /*!< \brief Media output routine, if_output(). */
};

/*!
 * \brief Device information structure.
 *
 * A pointer to this structure must be passed to NutRegisterDevice()
 * to bind this Ethernet device driver to the Nut/OS kernel.
 * An application may then call NutNetIfConfig() with the name \em eth0
 * of this driver to initialize the network interface.
 *
 */
NUTDEVICE devDm9000 = {
    0,                          /*!< \brief Pointer to next device. */
    {'e', 't', 'h', '0', 0, 0, 0, 0, 0},        /*!< \brief Unique device name. */
    IFTYP_NET,                  /*!< \brief Type of device. */
    0,                          /*!< \brief Base address. */
    0,                          /*!< \brief First interrupt number. */
    &ifn_eth0,                  /*!< \brief Interface control block. */
    &dcb_eth0,                  /*!< \brief Driver control block. */
    DmInit,                     /*!< \brief Driver initialization routine. */
    DmIOCtl,                    /*!< \brief Driver specific control function. */
    0,                          /*!< \brief Read from device. */
    0,                          /*!< \brief Write to device. */
#ifdef __HARVARD_ARCH__
    0,                          /*!< \brief Write from program space data to device. */
#endif
    0,                          /*!< \brief Open a device or file. */
    0,                          /*!< \brief Close a device or file. */
    0,                          /*!< \brief Request file size. */
    0,                          /*!< \brief Select function, optional, not yet implemented */
};

/*@}*/
