/****************************************************************************
 * drivers/net/cs89x0.c
 *
 *   Copyright (C) 2009-2011, 2014-2016 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * 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 NuttX nor the names of its 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.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>
#if defined(CONFIG_NET) && defined(CONFIG_NET_CS89x0)

#include <stdint.h>
#include <stdbool.h>
#include <time.h>
#include <string.h>
#include <debug.h>
#include <errno.h>

#include <arpa/inet.h>

#include <nuttx/arch.h>
#include <nuttx/irq.h>
#include <nuttx/wdog.h>
#include <nuttx/net/arp.h>
#include <nuttx/net/netdev.h>

#ifdef CONFIG_NET_PKT
#  include <nuttx/net/pkt.h>
#endif

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#error "Under construction -- do not use"

/* CONFIG_CS89x0_NINTERFACES determines the number of physical interfaces
 * that will be supported.
 */

#ifndef CONFIG_CS89x0_NINTERFACES
# define CONFIG_CS89x0_NINTERFACES 1
#endif

/* TX poll delay = 1 seconds. CLK_TCK is the number of clock ticks per second */

#define CS89x0_WDDELAY   (1*CLK_TCK)

/* TX timeout = 1 minute */

#define CS89x0_TXTIMEOUT (60*CLK_TCK)

/* This is a helper pointer for accessing the contents of the Ethernet header */

#define BUF ((struct eth_hdr_s *)cs89x0->cs_dev.d_buf)

#define PKTBUF_SIZE (MAX_NET_DEV_MTU + CONFIG_NET_GUARDSIZE)

/****************************************************************************
 * Private Data
 ****************************************************************************/

static FAR struct cs89x0_driver_s *g_cs89x0[CONFIG_CS89x0_NINTERFACES];

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/* CS89x0 register access */

static uint16_t cs89x0_getreg(struct cs89x0_driver_s *cs89x0, int offset);
static void cs89x0_putreg(struct cs89x0_driver_s *cs89x0, int offset,
                          uint16_t value);
static uint16_t cs89x0_getppreg(struct cs89x0_driver_s *cs89x0, int addr);
static void cs89x0_putppreg(struct cs89x0_driver_s *cs89x0, int addr,
                            uint16_t value);

/* Common TX logic */

static int  cs89x0_transmit(struct cs89x0_driver_s *cs89x0);
static int  cs89x0_txpoll(struct net_driver_s *dev);

/* Interrupt handling */

static void cs89x0_receive(struct cs89x0_driver_s *cs89x0);
static void cs89x0_txdone(struct cs89x0_driver_s *cs89x0, uint16_t isq);
static int  cs89x0_interrupt(int irq, FAR void *context, FAR void *arg);

/* Watchdog timer expirations */

static void cs89x0_polltimer(int argc, uint32_t arg, ...);
static void cs89x0_txtimeout(int argc, uint32_t arg, ...);

/* NuttX callback functions */

static int cs89x0_ifup(struct net_driver_s *dev);
static int cs89x0_ifdown(struct net_driver_s *dev);
static int cs89x0_txavail(struct net_driver_s *dev);
#ifdef CONFIG_NET_IGMP
static int cs89x0_addmac(struct net_driver_s *dev, FAR const uint8_t *mac);
static int cs89x0_rmmac(struct net_driver_s *dev, FAR const uint8_t *mac);
#endif

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Function: cs89x0_getreg and cs89x0_putreg
 *
 * Description:
 *   Read from and write to a CS89x0 register
 *
 * Parameters:
 *   cs89x0 - Reference to the driver state structure
 *   offset - Offset to the CS89x0 register
 *   value  - Value to be written (cs89x0_putreg only)
 *
 * Returned Value:
 *   cs89x0_getreg: The 16-bit value of the register
 *   cs89x0_putreg: None
 *
 ****************************************************************************/

static uint16_t cs89x0_getreg(struct cs89x0_driver_s *cs89x0, int offset)
{
#ifdef CONFIG_CS89x0_ALIGN16
  return getreg16(s89x0->cs_base + offset);
#else
  return (uint16_t)getreg32(s89x0->cs_base + offset);
#endif
}

static void cs89x0_putreg(struct cs89x0_driver_s *cs89x0, int offset, uint16_t value)
{
#ifdef CONFIG_CS89x0_ALIGN16
  return putreg16(value, s89x0->cs_base + offset);
#else
  return (uint16_t)putreg32((uint32_t)value, s89x0->cs_base + offset);
#endif
}

/****************************************************************************
 * Function: cs89x0_getppreg and cs89x0_putppreg
 *
 * Description:
 *   Read from and write to a CS89x0 page packet register
 *
 * Parameters:
 *   cs89x0 - Reference to the driver state structure
 *   addr   - Address of the CS89x0 page packet register
 *   value  - Value to be written (cs89x0_putppreg only)
 *
 * Returned Value:
 *   cs89x0_getppreg: The 16-bit value of the page packet register
 *   cs89x0_putppreg: None
 *
 ****************************************************************************/

static uint16_t cs89x0_getppreg(struct cs89x0_driver_s *cs89x0, int addr)
{
  /* In memory mode, the CS89x0's internal registers and frame buffers are mapped
   * into a contiguous 4kb block providing direct access to the internal registers
   * and frame buffers.
   */

#ifdef CONFIG_CS89x0_MEMMODE
  if (cs89x0->cs_memmode)
    {
#ifdef CONFIG_CS89x0_ALIGN16
      return getreg16(s89x0->cs_ppbase + (CS89x0_PDATA_OFFSET << ??));
#else
      return (uint16_t)getreg32(s89x0->cs_ppbase + (CS89x0_PDATA_OFFSET << ??));
#endif
    }

  /* When configured in I/O mode, the CS89x0 is accessed through eight, 16-bit
   * I/O ports that in the host system's I/O space.
   */

  else
#endif
    {
#ifdef CONFIG_CS89x0_ALIGN16
      putreg16((uint16_t)addr, cs89x0->cs_base + CS89x0_PPTR_OFFSET);
      return getreg16(s89x0->cs_base + CS89x0_PDATA_OFFSET);
#else
      putreg32((uint32_t)addr, cs89x0->cs_base + CS89x0_PPTR_OFFSET);
      return (uint16_t)getreg32(s89x0->cs_base + CS89x0_PDATA_OFFSET);
#endif
    }
}

static void cs89x0_putppreg(struct cs89x0_driver_s *cs89x0, int addr, uint16_t value)
{
  /* In memory mode, the CS89x0's internal registers and frame buffers are mapped
   * into a contiguous 4kb block providing direct access to the internal registers
   * and frame buffers.
   */

#ifdef CONFIG_CS89x0_MEMMODE
  if (cs89x0->cs_memmode)
    {
#ifdef CONFIG_CS89x0_ALIGN16
      putreg16(value), cs89x0->cs_ppbase + (CS89x0_PDATA_OFFSET << ??));
#else
      putreg32((uint32_t)value, cs89x0->cs_ppbase + (CS89x0_PDATA_OFFSET << ??));
#endif
    }

  /* When configured in I/O mode, the CS89x0 is accessed through eight, 16-bit
   * I/O ports that in the host system's I/O space.
   */

  else
#endif
    {
#ifdef CONFIG_CS89x0_ALIGN16
      putreg16((uint16_t)addr, cs89x0->cs_base + CS89x0_PPTR_OFFSET);
      putreg16(value, cs89x0->cs_base + CS89x0_PDATA_OFFSET);
#else
      putreg32((uint32_t)addr, cs89x0->cs_base + CS89x0_PPTR_OFFSET);
      putreg32((uint32_t)value, cs89x0->cs_base + CS89x0_PDATA_OFFSET);
#endif
    }
}

/****************************************************************************
 * Function: cs89x0_transmit
 *
 * Description:
 *   Start hardware transmission.  Called either from the txdone interrupt
 *   handling or from watchdog based polling.
 *
 * Parameters:
 *   cs89x0  - Reference to the driver state structure
 *
 * Returned Value:
 *   OK on success; a negated errno on failure
 *
 * Assumptions:
 *
 ****************************************************************************/

static int cs89x0_transmit(struct cs89x0_driver_s *cs89x0)
{
  /* Verify that the hardware is ready to send another packet */
#warning "Missing logic"

  /* Increment statistics */
#warning "Missing logic"

  /* Disable Ethernet interrupts */
#warning "Missing logic"

  /* Send the packet: address=cs89x0->cs_dev.d_buf, length=cs89x0->cs_dev.d_len */
#warning "Missing logic"

  /* Restore Ethernet interrupts */
#warning "Missing logic"

  /* Setup the TX timeout watchdog (perhaps restarting the timer) */

  (void)wd_start(cs89x0->cs_txtimeout, CS89x0_TXTIMEOUT, cs89x0_txtimeout,
                 1, (wdparm_t)cs89x0);
  return OK;
}

/****************************************************************************
 * Function: cs89x0_txpoll
 *
 * Description:
 *   The transmitter is available, check if the network has any outgoing packets ready
 *   to send.  This is a callback from devif_poll().  devif_poll() may be called:
 *
 *   1. When the preceding TX packet send is complete,
 *   2. When the preceding TX packet send timesout and the interface is reset
 *   3. During normal TX polling
 *
 * Parameters:
 *   dev  - Reference to the NuttX driver state structure
 *
 * Returned Value:
 *   OK on success; a negated errno on failure
 *
 * Assumptions:
 *
 ****************************************************************************/

static int cs89x0_txpoll(struct net_driver_s *dev)
{
  struct cs89x0_driver_s *cs89x0 = (struct cs89x0_driver_s *)dev->d_private;

  /* If the polling resulted in data that should be sent out on the network,
   * the field d_len is set to a value > 0.
   */

  if (cs89x0->cs_dev.d_len > 0)
    {
      /* Look up the destination MAC address and add it to the Ethernet
       * header.
       */

#ifdef CONFIG_NET_IPv4
#ifdef CONFIG_NET_IPv6
      if (IFF_IS_IPv4(cs89x0->cs_dev.d_flags))
#endif
        {
          arp_out(&cs89x0->cs_dev);
        }
#endif /* CONFIG_NET_IPv4 */

#ifdef CONFIG_NET_IPv6
#ifdef CONFIG_NET_IPv4
      else
#endif
        {
          neighbor_out(&cs89x0->cs_dev);
        }
#endif /* CONFIG_NET_IPv6 */

      /* Send the packet */

      cs89x0_transmit(cs89x0);

      /* Check if there is room in the CS89x0 to hold another packet. If not,
       * return a non-zero value to terminate the poll.
       */
#warning "Missing logic"
    }

  /* If zero is returned, the polling will continue until all connections have
   * been examined.
   */

  return 0;
}

/****************************************************************************
 * Function: cs89x0_receive
 *
 * Description:
 *   An interrupt was received indicating the availability of a new RX packet
 *
 * Parameters:
 *   cs89x0  - Reference to the driver state structure
 *   isq     - Interrupt status queue value read by interrupt handler
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *
 ****************************************************************************/

static void cs89x0_receive(FAR struct cs89x0_driver_s *cs89x0, uint16_t isq)
{
  FAR uint16_t *dest;
  uint16_t rxlength;
  int nbytes;

  /* Check for errors and update statistics */

  rxlength = cs89x0_getreg(PPR_RXLENGTH);
  if ((isq & RX_OK) == 0)
    {
#ifdef CONFIG_NETDEV_STATISTICS
      NETDEV_RXERRORS(&cd89x0->cs_dev);

#if 0
      if ((isq & RX_RUNT) != 0)
        {
        }

      if ((isq & RX_EXTRA_DATA) != 0)
        {
        }

      if (isq & RX_CRC_ERROR) != 0)
        {
          if (!(isq & (RX_EXTRA_DATA | RX_RUNT)))
            {
            }
        }

      if ((isq & RX_DRIBBLE) != 0)
        {
        }
#endif
#endif

      return;
    }

  /* Check if the packet is a valid size for the network buffer configuration */

  if (rxlength > ???)
    {
      NETDEV_RXERRORS(&cd89x0->cs_dev);
      return;
    }

  /* Copy the data data from the hardware to cs89x0->cs_dev.d_buf.  Set
   * amount of data in cs89x0->cs_dev.d_len
   */

  dest = (FAR uint16_t *)cs89x0->cs_dev.d_buf;
  for (nbytes = 0; nbytes < rxlength; nbytes += sizeof(uint16_t))
    {
      *dest++ = cs89x0_getreg(PPR_RXFRAMELOCATION);
    }

  NETDEV_RXPACKETS(&cd89x0->cs_dev);

#ifdef CONFIG_NET_PKT
  /* When packet sockets are enabled, feed the frame into the packet tap */

   pkt_input(&cs89x0->cs_dev);
#endif

  /* We only accept IP packets of the configured type and ARP packets */

#ifdef CONFIG_NET_IPv4
  if (BUF->type == HTONS(ETHTYPE_IP))
    {
      ninfo("IPv4 frame\n");
      NETDEV_RXIPV4(&priv->cs_dev);

      /* Handle ARP on input then give the IPv4 packet to the network
       * layer
       */

      arp_ipin(&cs89x0->cs_dev);
      ipv4_input(&cs89x0->cs_dev);

      /* If the above function invocation resulted in data that should be
       * sent out on the network, the field  d_len will set to a value > 0.
       */

      if (cs89x0->cs_dev.d_len > 0)
        {
          /* Update the Ethernet header with the correct MAC address */

#ifdef CONFIG_NET_IPv6
          if (IFF_IS_IPv4(cs89x0->cs_dev.d_flags))
#endif
            {
              arp_out(&cs89x0->cs_dev);
            }
#ifdef CONFIG_NET_IPv6
          else
            {
              neighbor_out(&s89x0->cs_dev);
            }
#endif

          /* And send the packet */

          cs89x0_transmit(cs89x0);
        }
    }
  else
#endif
#ifdef CONFIG_NET_IPv6
  if (BUF->type == HTONS(ETHTYPE_IP6))
    {
      ninfo("Iv6 frame\n");
      NETDEV_RXIPV6(&priv->cs_dev);

      /* Give the IPv6 packet to the network layer */

      ipv6_input(&cs89x0->cs_dev);

      /* If the above function invocation resulted in data that should be
       * sent out on the network, the field  d_len will set to a value > 0.
       */

      if (cs89x0->cs_dev.d_len > 0)
        {
          /* Update the Ethernet header with the correct MAC address */

#ifdef CONFIG_NET_IPv4
          if (IFF_IS_IPv4(cs89x0->cs_dev.d_flags))
            {
              arp_out(&cs89x0->cs_dev);
            }
          else
#endif
#ifdef CONFIG_NET_IPv6
            {
              neighbor_out(&cs89x0->cs_dev);
            }
#endif

          /* And send the packet */

          cs89x0_transmit(cs89x0);
        }
    }
  else
#endif
#ifdef CONFIG_NET_ARP
  if (BUF->type == htons(ETHTYPE_ARP))
    {
      NETDEV_RXARP(&priv->cs_dev);
      arp_arpin(&cs89x0->cs_dev);

      /* If the above function invocation resulted in data that should be
       * sent out on the network, the field  d_len will set to a value > 0.
       */

      if (cs89x0->cs_dev.d_len > 0)
        {
          cs89x0_transmit(cs89x0);
        }
    }
  else
#endif
    {
      ninfo("Unrecognized packet type %02x\n", BUF->type);
      NETDEV_RXDROPPED(&priv->cs_dev);
    }
}

/****************************************************************************
 * Function: cs89x0_txdone
 *
 * Description:
 *   An interrupt was received indicating that the last TX packet(s) is done
 *
 * Parameters:
 *   cs89x0  - Reference to the driver state structure
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *
 ****************************************************************************/

static void cs89x0_txdone(struct cs89x0_driver_s *cs89x0, uint16_t isq)
{
  /* Check for errors and update statistics.  The lower 6-bits of the ISQ
   * hold the register address causing the interrupt.  We got here because
   * those bits indicated */

#ifdef CONFIG_NETDEV_STATISTICS
  NETDEV_TXPACKETS(&cd89x0->cs_dev);
  if ((isq & ISQ_TXEVENT_TXOK) == 0)
    {
      NETDEV_TXERRORS(&cd89x0->cs_dev);
    }

#if 0
  if ((isq & ISQ_TXEVENT_LOSSOFCRS) != 0)
    {
    }

  if ((isq & ISQ_TXEVENT_SQEERROR) != 0)
    {
    }

  if (i(sq & ISQ_TXEVENT_OUTWINDOW) != 0)
    {
    }

  if (isq & TX_16_COL)
    {
    }
#endif
#endif

  /* If no further xmits are pending, then cancel the TX timeout */

  wd_cancel(cs89x0->cs_txtimeout);

  /* Then poll the network for new XMIT data */

  (void)devif_poll(&cs89x0->cs_dev, cs89x0_txpoll);
}

/****************************************************************************
 * Function: cs89x0_interrupt
 *
 * Description:
 *   Hardware interrupt handler
 *
 * Parameters:
 *   irq     - Number of the IRQ that generated the interrupt
 *   context - Interrupt register state save info (architecture-specific)
 *
 * Returned Value:
 *   OK on success
 *
 * Assumptions:
 *
 ****************************************************************************/

static int cs89x0_interrupt(int irq, FAR void *context, FAR void *arg)
{
  FAR struct cs89x0_driver_s *cs89x0 = (FAR struct cs89x0_driver_s *)arg;
  uint16_t isq;

  DEBUGASSERT(cs89x0 != NULL);

  /* Read and process all of the events from the ISQ */

  while ((isq = cs89x0_getreg(dev, CS89x0_ISQ_OFFSET)) != 0)
    {
      ninfo("ISQ: %04x\n", isq);
      switch (isq & ISQ_EVENTMASK)
        {
        case ISQ_RXEVENT:
            cs89x0_receive(cs89x0);
            break;

        case ISQ_TXEVENT:
            cs89x0_txdone(cs89x0, isq);
            break;

        case ISQ_BUFEVENT:
            if ((isq & ISQ_BUFEVENT_TXUNDERRUN) != 0)
              {
                nerr("ERROR: Transmit underrun\n");
#ifdef CONFIG_CS89x0_XMITEARLY
                cd89x0->cs_txunderrun++;
                if (cd89x0->cs_txunderrun == 3)
                  {
                    cd89x0->cs_txstart = PPR_TXCMD_TXSTART381;
                  }
                else if (cd89x0->cs_txunderrun == 6)
                  {
                    cd89x0->cs_txstart = PPR_TXCMD_TXSTARTFULL;
                  }
#endif
              }
            break;

        case ISQ_RXMISSEVENT:
            NETDEV_RXERRORS(&cd89x0->cs_dev);
            break;

        case ISQ_TXCOLEVENT:
            NETDEV_TXERRORS(&cd89x0->cs_dev);
            break;
        }
    }
  return OK;
}

/****************************************************************************
 * Function: cs89x0_txtimeout
 *
 * Description:
 *   Our TX watchdog timed out.  Called from the timer interrupt handler.
 *   The last TX never completed.  Reset the hardware and start again.
 *
 * Parameters:
 *   argc - The number of available arguments
 *   arg  - The first argument
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *
 ****************************************************************************/

static void cs89x0_txtimeout(int argc, uint32_t arg, ...)
{
  struct cs89x0_driver_s *cs89x0 = (struct cs89x0_driver_s *)arg;

  /* Increment statistics and dump debug info */
#warning "Missing logic"

  /* Then reset the hardware */
#warning "Missing logic"

  /* Then poll the network for new XMIT data */

  (void)devif_poll(&cs89x0->cs_dev, cs89x0_txpoll);
}

/****************************************************************************
 * Function: cs89x0_polltimer
 *
 * Description:
 *   Periodic timer handler.  Called from the timer interrupt handler.
 *
 * Parameters:
 *   argc - The number of available arguments
 *   arg  - The first argument
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *
 ****************************************************************************/

static void cs89x0_polltimer(int argc, uint32_t arg, ...)
{
  struct cs89x0_driver_s *cs89x0 = (struct cs89x0_driver_s *)arg;

  /* Check if there is room in the send another TXr packet.  */
#warning "Missing logic"

  /* If so, update TCP timing states and poll the network for new XMIT data */

  (void)devif_timer(&cs89x0->cs_dev, cs89x0_txpoll);

  /* Setup the watchdog poll timer again */

  (void)wd_start(cs89x0->cs_txpoll, CS89x0_WDDELAY, cs89x0_polltimer, 1,
                 (wdparm_t)arg);
}

/****************************************************************************
 * Function: cs89x0_ifup
 *
 * Description:
 *   NuttX Callback: Bring up the Ethernet interface when an IP address is
 *   provided
 *
 * Parameters:
 *   dev  - Reference to the NuttX driver state structure
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *
 ****************************************************************************/

static int cs89x0_ifup(struct net_driver_s *dev)
{
  struct cs89x0_driver_s *cs89x0 = (struct cs89x0_driver_s *)dev->d_private;

  ninfo("Bringing up: %d.%d.%d.%d\n",
        dev->d_ipaddr & 0xff, (dev->d_ipaddr >> 8) & 0xff,
        (dev->d_ipaddr >> 16) & 0xff, dev->d_ipaddr >> 24);

  /* Initialize the Ethernet interface */
#warning "Missing logic"

  /* Set and activate a timer process */

  (void)wd_start(cs89x0->cs_txpoll, CS89x0_WDDELAY, cs89x0_polltimer, 1,
                 (wdparm_t)cs89x0);

  /* Enable the Ethernet interrupt */

  cs89x0->cs_bifup = true;
  up_enable_irq(CONFIG_CS89x0_IRQ);
  return OK;
}

/****************************************************************************
 * Function: cs89x0_ifdown
 *
 * Description:
 *   NuttX Callback: Stop the interface.
 *
 * Parameters:
 *   dev  - Reference to the NuttX driver state structure
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *
 ****************************************************************************/

static int cs89x0_ifdown(struct net_driver_s *dev)
{
  struct cs89x0_driver_s *cs89x0 = (struct cs89x0_driver_s *)dev->d_private;
  irqstate_t flags;

  /* Disable the Ethernet interrupt */

  flags = enter_critical_section();
  up_disable_irq(CONFIG_CS89x0_IRQ);

  /* Cancel the TX poll timer and TX timeout timers */

  wd_cancel(cs89x0->cs_txpoll);
  wd_cancel(cs89x0->cs_txtimeout);

  /* Reset the device */

  cs89x0->cs_bifup = false;
  leave_critical_section(flags);
  return OK;
}

/****************************************************************************
 * Function: cs89x0_txavail
 *
 * Description:
 *   Driver callback invoked when new TX data is available.  This is a
 *   stimulus perform an out-of-cycle poll and, thereby, reduce the TX
 *   latency.
 *
 * Parameters:
 *   dev  - Reference to the NuttX driver state structure
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *   Called in normal user mode
 *
 ****************************************************************************/

static int cs89x0_txavail(struct net_driver_s *dev)
{
  struct cs89x0_driver_s *cs89x0 = (struct cs89x0_driver_s *)dev->d_private;
  irqstate_t flags;

  flags = enter_critical_section();

  /* Ignore the notification if the interface is not yet up */

  if (cs89x0->cs_bifup)
    {
      /* Check if there is room in the hardware to hold another outgoing packet. */
#warning "Missing logic"

      /* If so, then poll the network for new XMIT data */

      (void)devif_poll(&cs89x0->cs_dev, cs89x0_txpoll);
    }

  leave_critical_section(flags);
  return OK;
}

/****************************************************************************
 * Function: cs89x0_addmac
 *
 * Description:
 *   NuttX Callback: Add the specified MAC address to the hardware multicast
 *   address filtering
 *
 * Parameters:
 *   dev  - Reference to the NuttX driver state structure
 *   mac  - The MAC address to be added
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *
 ****************************************************************************/

#ifdef CONFIG_NET_IGMP
static int cs89x0_addmac(struct net_driver_s *dev, FAR const uint8_t *mac)
{
  FAR struct cs89x0_driver_s *priv = (FAR struct cs89x0_driver_s *)dev->d_private;

  /* Add the MAC address to the hardware multicast routing table */

#warning "Multicast MAC support not implemented"
  return OK;
}
#endif

/****************************************************************************
 * Function: cs89x0_rmmac
 *
 * Description:
 *   NuttX Callback: Remove the specified MAC address from the hardware multicast
 *   address filtering
 *
 * Parameters:
 *   dev  - Reference to the NuttX driver state structure
 *   mac  - The MAC address to be removed
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *
 ****************************************************************************/

#ifdef CONFIG_NET_IGMP
static int cs89x0_rmmac(struct net_driver_s *dev, FAR const uint8_t *mac)
{
  FAR struct cs89x0_driver_s *priv = (FAR struct cs89x0_driver_s *)dev->d_private;

  /* Add the MAC address to the hardware multicast routing table */

#warning "Multicast MAC support not implemented"
  return OK;
}
#endif

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Function: cs89x0_initialize
 *
 * Description:
 *   Initialize the Ethernet driver
 *
 * Parameters:
 *   impl - decribes the implementation of the cs89x00 implementation.
 *     This reference is retained so so must remain stable throughout the
 *     life of the driver instance.
 *   devno - Identifies the device number.  This must be a number between
 *     zero CONFIG_CS89x0_NINTERFACES and the same devno must not be
 *     initialized twice.  The associated network device will be referred
 *     to with the name "eth" followed by this number (eth0, eth1, etc).
 *
 * Returned Value:
 *   OK on success; Negated errno on failure.
 *
 * Assumptions:
 *
 ****************************************************************************/

/* Initialize the CS89x0 chip and driver */

int cs89x0_initialize(FAR const cs89x0_driver_s *cs89x0, int devno)
{
  FAR uint8_t *pktbuf;

  /* Sanity checks -- only performed with debug enabled */

#ifdef CONFIG_DEBUG_FEATURES
  if (!cs89x0 || (unsigned)devno > CONFIG_CS89x0_NINTERFACES || g_cs89x00[devno])
    {
      return -EINVAL;
    }
#endif

  /* Check if a Ethernet chip is recognized at its I/O base */

#warning "Missing logic"

  /* Attach the IRQ to the driver */

  if (irq_attach(cs89x0->irq, cs89x0_interrupt, cs89x0))
    {
      /* We could not attach the ISR to the ISR */

      return -EAGAIN;
    }

  /* Allocate a packet buffer */

  pktbuf = (FAR uint_t *)kmm_alloc(MAX_NET_DEV_MTU + CONFIG_NET_GUARDSIZE);
  if (pktbuf == NULL)
    {
      return -ENOMEM;
    }

  /* Initialize the driver structure */

  g_cs89x[devno]           = cs89x0;             /* Used to map IRQ back to instance */
  cs89x0->cs_dev.d_buf     = g_pktbuf;           /* Single packet buffer */
  cs89x0->cs_dev.d_ifup    = cs89x0_ifup;        /* I/F down callback */
  cs89x0->cs_dev.d_ifdown  = cs89x0_ifdown;      /* I/F up (new IP address) callback */
  cs89x0->cs_dev.d_txavail = cs89x0_txavail;     /* New TX data callback */
#ifdef CONFIG_NET_IGMP
  cs89x0->cs_dev.d_addmac  = cs89x0_addmac;      /* Add multicast MAC address */
  cs89x0->cs_dev.d_rmmac   = cs89x0_rmmac;       /* Remove multicast MAC address */
#endif
  cs89x0->cs_dev.d_private = (FAR void *)cs89x0; /* Used to recover private state from dev */

  /* Create a watchdog for timing polling for and timing of transmisstions */

  cs89x0->cs_txpoll       = wd_create();         /* Create periodic poll timer */
  cs89x0->cs_txtimeout    = wd_create();         /* Create TX timeout timer */

  /* Read the MAC address from the hardware into cs89x0->cs_dev.d_mac.ether_addr_octet */

  /* Register the device with the OS so that socket IOCTLs can be performed */

  (void)netdev_register(&cs89x0->cs_dev, NET_LL_ETHERNET);
  return OK;
}

#endif /* CONFIG_NET && CONFIG_NET_CS89x0 */