/****************************************************************************
 * drivers/net/dm90x0.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/* References: Davicom data sheets (DM9000-DS-F03-041906.pdf,
 *   DM9010-DS-F01-103006.pdf) and looking at lots of other DM90x0
 *   drivers.
 */

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

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

/* Only one hardware interface supported at present (although there are
 * hooks throughout the design to that extending the support to multiple
 * interfaces should not be that difficult)
 */

#undef  CONFIG_DM9X_NINTERFACES
#define CONFIG_DM9X_NINTERFACES 1

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

#include <arpa/inet.h>
#include <net/ethernet.h>

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

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

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

/* If processing is not done at the interrupt level, then work queue support
 * is required.
 */

#if !defined(CONFIG_SCHED_WORKQUEUE)
#  error Work queue support is required in this configuration (CONFIG_SCHED_WORKQUEUE)
#endif

/* The low priority work queue is preferred.  If it is not enabled, LPWORK
 * will be the same as HPWORK.
 *
 * NOTE:  However, the network should NEVER run on the high priority work
 * queue!  That queue is intended only to service short back end interrupt
 * processing that never suspends.  Suspending the high priority work queue
 * may bring the system to its knees!
 */

#define ETHWORK LPWORK

/* DM90000 and DM9010 register offsets */

#define DM9X_NETC          0x00 /* Network control register */
#define DM9X_NETS          0x01 /* Network Status register */
#define DM9X_TXC           0x02 /* TX control register */
#define DM9X_TXS1          0x03 /* TX status register 1 */
#define DM9X_TXS2          0x03 /* TX status register 2 */
#define DM9X_RXC           0x05 /* RX control register */
#define DM9X_RXS           0x06 /* RX status register */
#define DM9X_RXOVF         0x07 /* Receive overflow counter register */
#define DM9X_BPTHRES       0x08 /* Back pressure threshold register */
#define DM9X_FCTHRES       0x09 /* Flow control threshold register */
#define DM9X_FC            0x0a /* RX/TX flow control register */
#define DM9X_EEPHYC        0x0b /* EEPROM & PHY control register */
#define DM9X_EEPHYA        0x0c /* EEPROM & PHY address register */
#define DM9X_EEPHYDL       0x0d /* EEPROM & PHY data register (lo) */
#define DM9X_EEPHYDH       0x0e /* EEPROM & PHY data register (hi) */
#define DM9X_WAKEUP        0x0f /* Wake-up control register */
#define DM9X_PAB0          0x10 /* Physical address register (byte 0) */
#define DM9X_PAB1          0x11 /* Physical address register (byte 1) */
#define DM9X_PAB2          0x12 /* Physical address register (byte 2) */
#define DM9X_PAB3          0x13 /* Physical address register (byte 3) */
#define DM9X_PAB4          0x14 /* Physical address register (byte 4) */
#define DM9X_PAB5          0x15 /* Physical address register (byte 5) */
#define DM9X_MAB0          0x16 /* Multicast address register (byte 0) */
#define DM9X_MAB1          0x17 /* Multicast address register (byte 1) */
#define DM9X_MAB2          0x18 /* Multicast address register (byte 2) */
#define DM9X_MAB3          0x19 /* Multicast address register (byte 3) */
#define DM9X_MAB4          0x1a /* Multicast address register (byte 4) */
#define DM9X_MAB5          0x1b /* Multicast address register (byte 5) */
#define DM9X_MAB6          0x1c /* Multicast address register (byte 6) */
#define DM9X_MAB7          0x1d /* Multicast address register (byte 7) */
#define DM9X_GPC           0x1e /* General purpose control register */
#define DM9X_GPD           0x1f /* General purpose register */

#define DM9X_TRPAL         0x22 /* TX read pointer address (lo) */
#define DM9X_TRPAH         0x23 /* TX read pointer address (hi) */
#define DM9X_RWPAL         0x24 /* RX write pointer address (lo) */
#define DM9X_RWPAH         0x25 /* RX write pointer address (hi) */

#define DM9X_VIDL          0x28 /* Vendor ID (lo) */
#define DM9X_VIDH          0x29 /* Vendor ID (hi) */
#define DM9X_PIDL          0x2a /* Product ID (lo) */
#define DM9X_PIDH          0x2b /* Product ID (hi) */
#define DM9X_CHIPR         0x2c /* Product ID (lo) */
#define DM9X_TXC2          0x2d /* Transmit control register 2 (dm9010) */
#define DM9X_OTC           0x2e /* Operation test control register (dm9010) */
#define DM9X_SMODEC        0x2f /* Special mode control register */
#define DM9X_ETXCSR        0x30 /* Early transmit control/status register (dm9010) */
#define DM9X_TCCR          0x31 /* Transmit checksum control register (dm9010) */
#define DM9X_RCSR          0x32 /* Receive checksum control/status register (dm9010) */
#define DM9X_EPHYA         0x33 /* External PHY address register (dm9010) */
#define DM9X_GPC2          0x34 /* General purpose control register 2 (dm9010) */
#define DM9X_GPD2          0x35 /* General purpose register 2 */
#define DM9X_GPC3          0x36 /* General purpose control register 3 (dm9010) */
#define DM9X_GPD3          0x37 /* General purpose register 3 */
#define DM9X_PBUSC         0x38 /* Processor bus control register (dm9010) */
#define DM9X_IPINC         0x39 /* INT pin control register (dm9010) */

#define DM9X_MON1          0x40 /* Monitor register 1 (dm9010) */
#define DM9X_MON2          0x41 /* Monitor register 2 (dm9010) */

#define DM9X_SCLKC         0x50 /* System clock turn ON control register (dm9010) */
#define DM9X_SCLKR         0x51 /* Resume system clock control register (dm9010) */

#define DM9X_MRCMDX        0xf0 /* Memory data pre-fetch read command without address increment */
#define DM9X_MRCMDX1       0xf1 /* memory data read command without address increment (dm9010) */
#define DM9X_MRCMD         0xf2 /* Memory data read command with address increment */
#define DM9X_MDRAL         0xf4 /* Memory data read address register (lo) */
#define DM9X_MDRAH         0xf5 /* Memory data read address register (hi) */
#define DM9X_MWCMDX        0xf6 /* Memory data write command without address increment */
#define DM9X_MWCMD         0xf8 /* Memory data write command with address increment */
#define DM9X_MDWAL         0xfa /* Memory data write address register (lo) */
#define DM9X_MDWAH         0xfb /* Memory data write address register (lo) */
#define DM9X_TXPLL         0xfc /* Memory data write address register (lo) */
#define DM9X_TXPLH         0xfd /* Memory data write address register (hi) */
#define DM9X_ISR           0xfe /* Interrupt status register */
#define DM9X_IMR           0xff /* Interrupt mask register */

/* Network control register bit definitions */

#define DM9X_NETC_RST      (1 << 0) /* Software reset */
#define DM9X_NETC_LBKM     (3 << 1) /* Loopback mode mask */
#define DM9X_NETC_LBK0     (0 << 1) /*   0: Normal */
#define DM9X_NETC_LBK1     (1 << 1) /*   1: MAC internal loopback */
#define DM9X_NETC_LBK2     (2 << 1) /*   2: Internal PHY 100M mode loopback */
#define DM9X_NETC_FDX      (1 << 3) /* Full dupliex mode */
#define DM9X_NETC_FCOL     (1 << 4) /* Force collision mode */
#define DM9X_NETC_WAKEEN   (1 << 6) /* Wakeup event enable */
#define DM9X_NETC_EXTPHY   (1 << 7) /* Select external PHY */

/* Network status bit definitions */

#define DM9X_NETS_RXOV     (1 << 1) /* RX Fifo overflow */
#define DM9X_NETS_TX1END   (1 << 2) /* TX packet 1 complete status */
#define DM9X_NETS_TX2END   (1 << 3) /* TX packet 2 complete status */
#define DM9X_NETS_WAKEST   (1 << 5) /* Wakeup event status */
#define DM9X_NETS_LINKST   (1 << 6) /* Link status */
#define DM9X_NETS_SPEED    (1 << 7) /* Media speed */

/* IMR/ISR bit definitions */

#define DM9X_INT_PR        (1 << 0) /* Packet received interrupt */
#define DM9X_INT_PT        (1 << 1) /* Packet transmitted interrupt */
#define DM9X_INT_RO        (1 << 2) /* Receive overflow interrupt */
#define DM9X_INT_ROO       (1 << 3) /* Receive overflow counter overflow int */
#define DM9X_INT_UDRUN     (1 << 4) /* Transmit underrun interrupt */
#define DM9X_INT_LNKCHG    (1 << 5) /* Link status change interrupt */
#define DM9X_INT_ALL       (0x3f)

#define DM9X_IMR_UNUSED    (1 << 6) /* (not used) */
#define DM9X_IMR_PAR       (1 << 7) /* Enable auto R/W pointer reset */

#define DM9X_ISR_IOMODEM   (3 << 6) /* IO mode mask */
#define DM9X_ISR_IOMODE8   (2 << 6) /*   IO mode = 8 bit */
#define DM9X_ISR_IOMODE16  (0 << 6) /*   IO mode = 16 bit */
#define DM9X_ISR_IOMODE32  (1 << 6) /*   IO mode = 32 bit */

#define DM9X_IMRENABLE     (DM9X_INT_PR | DM9X_INT_PT | DM9X_INT_LNKCHG | DM9X_IMR_PAR)
#define DM9X_IMRRXDISABLE  (DM9X_INT_PT | DM9X_INT_LNKCHG | DM9X_IMR_PAR)
#define DM9X_IMRDISABLE    (DM9X_IMR_PAR)

/* EEPROM/PHY control register bits */

#define DM9X_EEPHYC_ERRE   (1 << 0) /* EEPROM (vs PHY) access status */
#define DM9X_EEPHYC_ERPRW  (1 << 1) /* EEPROM/PHY write access */
#define DM9X_EEPHYC_ERPRR  (1 << 2) /* EEPROM/PHY read access */
#define DM9X_EEPHYC_EPOS   (1 << 3) /* EEPROM/PHY operation select */
#define DM9X_EEPHYC_WEP    (1 << 4) /* Write EEPROM enable */
#define DM9X_EEPHYC_REEP   (1 << 5) /* Reload EEPROM */

/* Supported values from the vendor and product ID register */

#define DM9X_DAVICOMVID    0x0a46
#define DM9X_DM9000PID     0x9000
#define DM9X_DM9010PID     0x9010

/* RX control register bit settings */

#define DM9X_RXC_RXEN      (1 << 0) /* RX enable */
#define DM9X_RXC_PRMSC     (1 << 1) /* Promiscuous mode */
#define DM9X_RXC_RUNT      (1 << 2) /* Pass runt packet */
#define DM9X_RXC_ALL       (1 << 3) /* Pass all multicast */
#define DM9X_RXC_DISCRC    (1 << 4) /* Discard CRC error packets */
#define DM9X_RXC_DISLONG   (1 << 5) /* Discard long packets */
#define DM9X_RXC_WTDIS     (1 << 6) /* Disable watchdog timer */
#define DM9X_RXC_HASHALL   (1 << 7) /* Filter all addresses in hash table */

#define DM9X_RXCSETUP      (DM9X_RXC_DISCRC | DM9X_RXC_DISLONG)

/* EEPHY bit settings */

#define DM9X_EEPHYA_EROA   0x40 /* PHY register address 0x01 */

#define DM9X_PKTRDY        0x01 /* Packet ready to receive */

/* The RX interrupt will be disabled if more than the following RX
 * interrupts are received back-to-back.
 */

#define DM9X_CRXTHRES 10

/* All access is via an index register and a data register.  Select accecss
 * according to user supplied base address and bus width.
 */

#if defined(CONFIG_DM9X_BUSWIDTH8)
#  define DM9X_INDEX *(volatile uint8_t*)(CONFIG_DM9X_BASE)
#  define DM9X_DATA  *(volatile uint8_t*)(CONFIG_DM9X_BASE + 2)
#elif defined(CONFIG_DM9X_BUSWIDTH16)
#  define DM9X_INDEX *(volatile uint16_t*)(CONFIG_DM9X_BASE)
#  define DM9X_DATA  *(volatile uint16_t*)(CONFIG_DM9X_BASE + 2)
#elif defined(CONFIG_DM9X_BUSWIDTH32)
#  define DM9X_INDEX *(volatile uint32_t*)(CONFIG_DM9X_BASE)
#  define DM9X_DATA  *(volatile uint32_t*)(CONFIG_DM9X_BASE + 2)
#endif

/* Phy operating mode.  Default is AUTO, but this setting can be overridden
 * in the NuttX configuration file.
 */

#if !defined(CONFIG_DM9X_MODE_AUTO)   && !defined(CONFIG_DM9X_MODE_10MHD) && \
    !defined(CONFIG_DM9X_MODE_100MHD) && !defined(CONFIG_DM9X_MODE_10MFD) && \
    !defined(CONFIG_DM9X_MODE_100MFD)
#   define CONFIG_DM9X_MODE_AUTO 1
#endif

/* TX timeout = 1 minute */

#define DM6X_TXTIMEOUT (60*CLK_TCK)

/* Packet buffer size */

#define PKTBUF_SIZE (MAX_NETDEV_PKTSIZE + CONFIG_NET_GUARDSIZE)

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

#define BUF ((FAR struct eth_hdr_s *)priv->dm_dev.d_buf)

/****************************************************************************
 * Private Types
 ****************************************************************************/

union rx_desc_u
{
  uint8_t rx_buf[4];
  struct
  {
    uint8_t  rx_byte;
    uint8_t  rx_status;
    uint16_t rx_len;
  } desc;
};

/* The dm9x_driver_s encapsulates all DM90x0 state information for a single
 * DM90x0 hardware interface
 */

struct dm9x_driver_s
{
  bool dm_bifup;               /* true:ifup false:ifdown */
  bool dm_b100m;               /* true:speed == 100M; false:speed == 10M */
  uint8_t dm_ntxpending;       /* Count of packets pending transmission */
  uint8_t ncrxpackets;         /* Number of continuous rx packets  */
  struct wdog_s dm_txtimeout;  /* TX timeout timer */
  struct work_s dm_irqwork;    /* For deferring interrupt work to the work queue */
  struct work_s dm_pollwork;   /* For deferring poll work to the work queue */

  /* Mode-dependent function to move data in 8/16/32 I/O modes */

  void (*dm_read)(uint8_t *ptr, int len);
  void (*dm_write)(const uint8_t *ptr, int len);
  void (*dm_discard)(int len);

  /* This holds the information visible to the NuttX network */

  struct net_driver_s dm_dev;
};

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

/* A single packet buffer is used */

static uint16_t g_pktbuf[CONFIG_DM9X_NINTERFACES][(PKTBUF_SIZE + 1) / 2];

/* At present, only a single DM90x0 device is supported. */

static struct dm9x_driver_s g_dm9x[CONFIG_DM9X_NINTERFACES];

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

/* Utility functions */

static uint8_t getreg(int reg);
static void putreg(int reg, uint8_t value);
static void read8(uint8_t *ptr, int len);
static void read16(uint8_t *ptr, int len);
static void read32(uint8_t *ptr, int len);
static void discard8(int len);
static void discard16(int len);
static void discard32(int len);
static void write8(const uint8_t *ptr, int len);
static void write16(const uint8_t *ptr, int len);
static void write32(const uint8_t *ptr, int len);

#if 0 /* Not used */
static uint16_t dm9x_readsrom(FAR struct dm9x_driver_s *priv, int offset);
#endif
static uint16_t dm9x_phyread(FAR struct dm9x_driver_s *priv, int reg);
static void dm9x_phywrite(FAR struct dm9x_driver_s *priv, int reg,
              uint16_t value);

#if defined(CONFIG_DM9X_CHECKSUM)
static bool dm9x_rxchecksumready(uint8_t);
#else
#  define dm9x_rxchecksumready(a) ((a) == 0x01)
#endif

/* Common TX logic */

static int  dm9x_transmit(FAR struct dm9x_driver_s *priv);
static int  dm9x_txpoll(FAR struct net_driver_s *dev);

/* Interrupt handling */

static void dm9x_receive(FAR struct dm9x_driver_s *priv);
static void dm9x_txdone(FAR struct dm9x_driver_s *priv);

static void dm9x_interrupt_work(FAR void *arg);
static int  dm9x_interrupt(int irq, FAR void *context, FAR void *arg);

/* Watchdog timer expirations */

static void dm9x_txtimeout_work(FAR void *arg);
static void dm9x_txtimeout_expiry(wdparm_t arg);

/* NuttX callback functions */

static int dm9x_ifup(FAR struct net_driver_s *dev);
static int dm9x_ifdown(FAR struct net_driver_s *dev);

static void dm9x_txavail_work(FAR void *arg);
static int dm9x_txavail(FAR struct net_driver_s *dev);

#ifdef CONFIG_NET_MCASTGROUP
static int dm9x_addmac(FAR struct net_driver_s *dev, FAR const uint8_t *mac);
static int dm9x_rmmac(FAR struct net_driver_s *dev, FAR const uint8_t *mac);
#endif

/* Initialization functions */

static void dm9x_bringup(FAR struct dm9x_driver_s *priv);
static void dm9x_reset(FAR struct dm9x_driver_s *priv);

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

/****************************************************************************
 * Name: getreg and setreg
 *
 * Description:
 *   Access to memory-mapped DM90x0 8-bit registers
 *
 * Input Parameters:
 *   reg - Register number
 *   value - Value to write to the register (setreg only)
 *
 * Returned Value:
 *   Value read from the register (getreg only)
 *
 * Assumptions:
 *
 ****************************************************************************/

static uint8_t getreg(int reg)
{
  DM9X_INDEX = reg;
  return DM9X_DATA & 0xff;
}

static void putreg(int reg, uint8_t value)
{
  DM9X_INDEX = reg;
  DM9X_DATA  = value & 0xff;
}

/****************************************************************************
 * Name: read8, read16, read32
 *
 * Description:
 *   Read packet data from the DM90x0 SRAM based on its current I/O mode
 *
 * Input Parameters:
 *   ptr - Location to write the packet data
 *   len - The number of bytes to read
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *
 ****************************************************************************/

static void read8(FAR uint8_t *ptr, int len)
{
  ninfo("Read %d bytes (8-bit mode)\n", len);
  for (; len > 0; len--)
    {
      *ptr++ = DM9X_DATA;
    }
}

static void read16(FAR uint8_t *ptr, int len)
{
  FAR uint16_t *ptr16 = (FAR uint16_t *)ptr;

  ninfo("Read %d bytes (16-bit mode)\n", len);
  for (; len > 0; len -= sizeof(uint16_t))
    {
      *ptr16++ = DM9X_DATA;
    }
}

static void read32(FAR uint8_t *ptr, int len)
{
  FAR uint32_t *ptr32 = (FAR uint32_t *)ptr;

  ninfo("Read %d bytes (32-bit mode)\n", len);
  for (; len > 0; len -= sizeof(uint32_t))
    {
      *ptr32++ = DM9X_DATA;
    }
}

/****************************************************************************
 * Name: discard8, discard16, discard32
 *
 * Description:
 *   Read and discard packet data in the DM90x0 SRAM based on its current
 *   I/O mode
 *
 * Input Parameters:
 *   len - The number of bytes to discard
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *
 ****************************************************************************/

static void discard8(int len)
{
  ninfo("Discard %d bytes (8-bit mode)\n", len);
  for (; len > 0; len--)
    {
      DM9X_DATA;
    }
}

static void discard16(int len)
{
  ninfo("Discard %d bytes (16-bit mode)\n", len);
  for (; len > 0; len -= sizeof(uint16_t))
    {
      DM9X_DATA;
    }
}

static void discard32(int len)
{
  ninfo("Discard %d bytes (32-bit mode)\n", len);
  for (; len > 0; len -= sizeof(uint32_t))
    {
      DM9X_DATA;
    }
}

/****************************************************************************
 * Name: write8, write16, write32
 *
 * Description:
 *   Write packet data into the DM90x0 SRAM based on its current I/O mode
 *
 * Input Parameters:
 *   ptr - Location to write the packet data
 *   len - The number of bytes to read
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *
 ****************************************************************************/

static void write8(FAR const uint8_t *ptr, int len)
{
  ninfo("Write %d bytes (8-bit mode)\n", len);

  for (; len > 0; len--)
    {
      DM9X_DATA = (*ptr++ & 0xff);
    }
}

static void write16(const uint8_t *ptr, int len)
{
  FAR uint16_t *ptr16 = (FAR uint16_t *)ptr;

  ninfo("Write %d bytes (16-bit mode)\n", len);

  for (; len > 0; len -= sizeof(uint16_t))
    {
       DM9X_DATA = *ptr16++;
    }
}

static void write32(FAR const uint8_t *ptr, int len)
{
  FAR uint32_t *ptr32 = (FAR uint32_t *)ptr;

  ninfo("Write %d bytes (32-bit mode)\n", len);

  for (; len > 0; len -= sizeof(uint32_t))
    {
      DM9X_DATA = *ptr32++;
    }
}

/****************************************************************************
 * Name: dm9x_readsrom
 *
 * Description:
 *   Read a word from SROM
 *
 * Input Parameters:
 *   priv - Reference to the driver state structure
 *   offset - SROM offset to read from
 *
 * Returned Value:
 *   SROM content at that offset
 *
 * Assumptions:
 *
 ****************************************************************************/

#if 0 /* Not used */
static uint16_t dm9x_readsrom(FAR struct dm9x_driver_s *priv, int offset)
{
  putreg(DM9X_EEPHYA, offset);
  putreg(DM9X_EEPHYC, DM9X_EEPHYC_ERPRR);
  up_udelay(200);
  putreg(DM9X_EEPHYC, 0x00);
  return (getreg(DM9X_EEPHYDL) + (getreg(DM9X_EEPHYDH) << 8));
}
#endif

/****************************************************************************
 * Name: dm9x_phyread and dm9x_phywrite
 *
 * Description:
 *   Read/write data from/to the PHY
 *
 * Input Parameters:
 *   priv  - Reference to the driver state structure
 *   reg   - PHY register offset
 *   value - The value to write to the PHY register (dm9x_write only)
 *
 * Returned Value:
 *   The value read from the PHY (dm9x_read only)
 *
 * Assumptions:
 *
 ****************************************************************************/

static uint16_t dm9x_phyread(FAR struct dm9x_driver_s *priv, int reg)
{
  /* Setup DM9X_EEPHYA, the EEPROM/PHY address register */

  putreg(DM9X_EEPHYA, DM9X_EEPHYA_EROA | reg);

  /* Issue PHY read command pulse in the EEPROM/PHY control register */

  putreg(DM9X_EEPHYC, (DM9X_EEPHYC_ERPRR | DM9X_EEPHYC_EPOS));
  up_udelay(100);
  putreg(DM9X_EEPHYC, 0x00);

  /* Return the data from the EEPROM/PHY data register pair */

  return (((uint16_t)getreg(DM9X_EEPHYDH)) << 8) |
           (uint16_t)getreg(DM9X_EEPHYDL);
}

static void dm9x_phywrite(FAR struct dm9x_driver_s *priv, int reg,
                          uint16_t value)
{
  /* Setup DM9X_EEPHYA, the EEPROM/PHY address register */

  putreg(DM9X_EEPHYA, DM9X_EEPHYA_EROA | reg);

  /* Put the data to write in the EEPROM/PHY data register pair */

  putreg(DM9X_EEPHYDL, (value & 0xff));
  putreg(DM9X_EEPHYDH, ((value >> 8) & 0xff));

  /* Issue PHY write command pulse in the EEPROM/PHY control register */

  putreg(DM9X_EEPHYC, (DM9X_EEPHYC_ERPRW | DM9X_EEPHYC_EPOS));
  up_udelay(500);
  putreg(DM9X_EEPHYC, 0x0);
}

/****************************************************************************
 * Name: dm9x_rxchecksumready
 *
 * Description:
 *   Return true if the RX checksum is available
 *
 * Input Parameters:
 *   rxbyte
 *
 * Returned Value:
 *   true: checksum is ready
 *
 * Assumptions:
 *
 ****************************************************************************/

#if defined(CONFIG_DM9X_CHECKSUM)
static inline bool dm9x_rxchecksumready(uint8_t rxbyte)
{
  if ((rxbyte & 0x01) == 0)
    {
      return false;
    }

  return ((rxbyte >> 4) | 0x01) != 0;
}
#endif

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

static int dm9x_transmit(FAR struct dm9x_driver_s *priv)
{
  /* Check if there is room in the DM90x0 to hold another packet.  In 100M
   * mode, that can be 2 packets, otherwise it is a single packet.
   */

  if (priv->dm_ntxpending < 1 || (priv->dm_b100m && priv->dm_ntxpending < 2))
    {
      /* Increment count of packets transmitted */

      priv->dm_ntxpending++;
      NETDEV_TXPACKETS(&dm9x0->dm_dev);

      /* Disable all DM90x0 interrupts */

      putreg(DM9X_IMR, DM9X_IMRDISABLE);

      /* Set the TX length */

      putreg(DM9X_TXPLL, (priv->dm_dev.d_len & 0xff));
      putreg(DM9X_TXPLH, (priv->dm_dev.d_len >> 8) & 0xff);

      /* Move the data to be sent into TX SRAM */

      DM9X_INDEX = DM9X_MWCMD;
      priv->dm_write(priv->dm_dev.d_buf, priv->dm_dev.d_len);

#if !defined(CONFIG_DM9X_ETRANS)
      /* Issue TX polling command */

      putreg(DM9X_TXC, 0x1); /* Cleared after TX complete */
#endif

      /* Clear count of back-to-back RX packet transfers */

      priv->ncrxpackets = 0;

      /* Re-enable DM90x0 interrupts */

      putreg(DM9X_IMR, DM9X_IMRENABLE);

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

      wd_start(&priv->dm_txtimeout, DM6X_TXTIMEOUT,
               dm9x_txtimeout_expiry, (wdparm_t)priv);
      return OK;
    }

  return -EBUSY;
}

/****************************************************************************
 * Name: dm9x_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 DM90x0 is reset
 *   3. During normal TX polling
 *
 * Input Parameters:
 *   dev  - Reference to the NuttX driver state structure
 *
 * Returned Value:
 *   OK on success; a negated errno on failure
 *
 * Assumptions:
 *
 ****************************************************************************/

static int dm9x_txpoll(FAR struct net_driver_s *dev)
{
  FAR struct dm9x_driver_s *priv =
    (FAR struct dm9x_driver_s *)dev->d_private;

  /* Send the packet */

  dm9x_transmit(priv);

  /* Check if there is room in the DM90x0 to hold another packet.
   * In 100M mode, that can be 2 packets, otherwise it is a single
   * packet.
   */

  if (priv->dm_ntxpending > 1 || !priv->dm_b100m)
    {
      /* Returning a non-zero value terminate the poll operation */

      return 1;
    }

  return 0;
}

/****************************************************************************
 * Name: dm9x_receive
 *
 * Description:
 *   An interrupt was received indicating the availability of a new RX packet
 *
 * Input Parameters:
 *   priv  - Reference to the driver state structure
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *
 ****************************************************************************/

static void dm9x_receive(FAR struct dm9x_driver_s *priv)
{
  union rx_desc_u rx;
  bool bchecksumready;
  uint8_t rxbyte;

  ninfo("Packet received\n");

  do
    {
      /* Store the value of memory data read address register */

      getreg(DM9X_MDRAH);
      getreg(DM9X_MDRAL);

      getreg(DM9X_MRCMDX);         /* Dummy read */
      rxbyte = (uint8_t)DM9X_DATA; /* Get the most up-to-date data */

      /* Packet ready for receive check */

      bchecksumready = dm9x_rxchecksumready(rxbyte);
      if (!bchecksumready)
        {
          break;
        }

      /* A packet is ready now. Get status/length */

      DM9X_INDEX = DM9X_MRCMD; /* set read ptr ++ */

      /* Read packet status & length */

      priv->dm_read((FAR uint8_t *)&rx, 4);

      /* Check if any errors were reported by the hardware */

      if (rx.desc.rx_status & 0xbf)
        {
          /* Bad RX packet... update statistics */

          nerr("ERROR: Received packet with errors: %02x\n",
               rx.desc.rx_status);
          NETDEV_RXERRORS(&priv->dm_dev);

          /* Drop this packet and continue to check the next packet */

          priv->dm_discard(rx.desc.rx_len);
        }

      /* Also check if the packet is a valid size for the configuration */

      else if (rx.desc.rx_len < ETH_HDRLEN ||
               rx.desc.rx_len > (CONFIG_NET_ETH_PKTSIZE + 2))
        {
          nerr("ERROR: RX length error\n");
          NETDEV_RXERRORS(&priv->dm_dev);

          /* Drop this packet and continue to check the next packet */

          priv->dm_discard(rx.desc.rx_len);
        }
      else
        {
          /* Good packet...
           * Copy the packet data out of SRAM and pass it one to the network
           */

          priv->dm_dev.d_len = rx.desc.rx_len;
          priv->dm_read(priv->dm_dev.d_buf, rx.desc.rx_len);

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

          pkt_input(&priv->dm_dev);
#endif

          /* We 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->dm_dev);

              /* Receive an IPv4 packet from the network device */

              ipv4_input(&priv->dm_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 (priv->dm_dev.d_len > 0)
                {
                  /* And send the packet */

                  dm9x_transmit(priv);
                }
            }
          else
#endif
#ifdef CONFIG_NET_IPv6
          if (BUF->type == HTONS(ETHTYPE_IP6))
            {
              ninfo("IPv6 frame\n");
              NETDEV_RXIPV6(&priv->dm_dev);

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

              ipv6_input(&priv->dm_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 (priv->dm_dev.d_len > 0)
                {
                  /* And send the packet */

                  dm9x_transmit(priv);
                }
            }
          else
#endif
#ifdef CONFIG_NET_ARP
          if (BUF->type == HTONS(ETHTYPE_ARP))
            {
              arp_input(&priv->dm_dev);
              NETDEV_RXARP(&priv->dm_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 (priv->dm_dev.d_len > 0)
                {
                  dm9x_transmit(priv);
                }
            }
#endif
          else
            {
              NETDEV_RXDROPPED(&priv->dm_dev);
            }
        }

      NETDEV_RXPACKETS(&priv->dm_dev);
      priv->ncrxpackets++;
    }
  while ((rxbyte & 0x01) == DM9X_PKTRDY &&
         priv->ncrxpackets < DM9X_CRXTHRES);
  ninfo("All RX packets processed\n");
}

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

static void dm9x_txdone(FAR struct dm9x_driver_s *priv)
{
  int  nsr;

  ninfo("TX done\n");

  /* Another packet has completed transmission.  Decrement the count of
   * of pending TX transmissions.
   */

  nsr = getreg(DM9X_NETS);
  if (nsr & DM9X_NETS_TX1END)
    {
      if (priv->dm_ntxpending)
        {
          priv->dm_ntxpending--;
        }
      else
        {
          nerr("ERROR: Bad TX count (TX1END)\n");
        }
    }

  if (nsr & DM9X_NETS_TX2END)
    {
      if (priv->dm_ntxpending)
        {
          priv->dm_ntxpending--;
        }
      else
        {
          nerr("ERROR: Bad TX count (TX2END)\n");
        }
    }

  /* Cancel the TX timeout */

  if (priv->dm_ntxpending == 0)
    {
      wd_cancel(&priv->dm_txtimeout);
    }

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

  devif_poll(&priv->dm_dev, dm9x_txpoll);
}

/****************************************************************************
 * Name: dm9x_interrupt_work
 *
 * Description:
 *   Perform interrupt related work from the worker thread
 *
 * Input Parameters:
 *   arg - The argument passed when work_queue() was called.
 *
 * Returned Value:
 *   OK on success
 *
 * Assumptions:
 *   The network is locked.
 *
 ****************************************************************************/

static void dm9x_interrupt_work(FAR void *arg)
{
  FAR struct dm9x_driver_s *priv = (FAR struct dm9x_driver_s *)arg;
  uint8_t isr;
  uint8_t save;
  int i;

  /* Process pending Ethernet interrupts */

  net_lock();

  /* Save previous register address */

  save = (uint8_t)DM9X_INDEX;

  /* Disable all DM90x0 interrupts */

  putreg(DM9X_IMR, DM9X_IMRDISABLE);

  /* Get and clear the DM90x0 interrupt status bits */

  isr = getreg(DM9X_ISR);
  putreg(DM9X_ISR, isr);
  ninfo("Interrupt status: %02x\n", isr);

  /* Check for link status change */

  if (isr & DM9X_INT_LNKCHG)
    {
      /* Wait up to 0.5s for link OK */

      for (i = 0; i < 500; i++)
        {
          dm9x_phyread(priv, 0x1);
          if (dm9x_phyread(priv, 0x1) & 0x4) /* Link OK */
            {
              /* Wait to get detected speed */

              for (i = 0; i < 200; i++)
                {
                  up_mdelay(1);
                }

              /* Set the new network speed */

              if (dm9x_phyread(priv, 0) & 0x2000)
                {
                  priv->dm_b100m = true;
                }
              else
                {
                  priv->dm_b100m = false;
                }
              break;
            }

          up_mdelay(1);
        }

      nerr("ERROR: delay: %dmS speed: %s\n",
           i, priv->dm_b100m ? "100M" : "10M");
    }

  /* Check if we received an incoming packet */

  if (isr & DM9X_INT_PR)
    {
      dm9x_receive(priv);
    }

  /* Check if we are able to transmit a packet */

  if (isr & DM9X_INT_PT)
    {
      dm9x_txdone(priv);
    }

  /* If the number of consecutive receive packets exceeds a threshold,
   * then disable the RX interrupt.
   */

  if (priv->ncrxpackets >= DM9X_CRXTHRES)
    {
      /* Enable all DM90x0 interrupts EXCEPT for RX */

      putreg(DM9X_IMR, DM9X_IMRRXDISABLE);
    }
  else
    {
      /* Enable all DM90x0 interrupts */

      putreg(DM9X_IMR, DM9X_IMRENABLE);
    }

  /* Restore previous register address */

  DM9X_INDEX = save;
  net_unlock();

  /* Re-enable Ethernet interrupts */

  up_enable_irq(CONFIG_DM9X_IRQ);
}

/****************************************************************************
 * Name: dm9x_interrupt
 *
 * Description:
 *   Hardware interrupt handler
 *
 * Input 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 dm9x_interrupt(int irq, FAR void *context, FAR void *arg)
{
#if CONFIG_DM9X_NINTERFACES == 1
  FAR struct dm9x_driver_s *priv = &g_dm9x[0];
#else
# error "Additional logic needed to support multiple interfaces"
#endif
  uint8_t isr;

  /* Disable further Ethernet interrupts.  Because Ethernet interrupts are
   * also disabled if the TX timeout event occurs, there can be no race
   * condition here.
   */

  up_disable_irq(CONFIG_DM9X_IRQ);

  /* Determine if a TX transfer just completed */

  isr = getreg(DM9X_ISR);
  if ((isr & DM9X_INT_PT) != 0)
    {
      /* If a TX transfer just completed, then cancel the TX timeout so
       * there will be no race condition between any subsequent timeout
       * expiration and the deferred interrupt processing.
       */

       wd_cancel(&priv->dm_txtimeout);
    }

  /* Schedule to perform the interrupt processing on the worker thread. */

  work_queue(ETHWORK, &priv->dm_irqwork, dm9x_interrupt_work, priv, 0);
  return OK;
}

/****************************************************************************
 * Name: dm9x_txtimeout_work
 *
 * Description:
 *   Perform TX timeout related work from the worker thread
 *
 * Input Parameters:
 *   arg - The argument passed when work_queue() as called.
 *
 * Returned Value:
 *   OK on success
 *
 * Assumptions:
 *   The network is locked.
 *
 ****************************************************************************/

static void dm9x_txtimeout_work(FAR void *arg)
{
  FAR struct dm9x_driver_s *priv = (FAR struct dm9x_driver_s *)arg;

  nerr("ERROR: TX timeout\n");

  /* Increment statistics and dump debug info */

  net_lock();
  NETDEV_TXTIMEOUTS(priv->dm_dev);

  ninfo("  TX packet count:           %d\n", priv->dm_ntxpending);
  ninfo("  TX read pointer address:   0x%02x:%02x\n",
        getreg(DM9X_TRPAH), getreg(DM9X_TRPAL));
  ninfo("  Memory data write address: 0x%02x:%02x (DM9010)\n",
        getreg(DM9X_MDWAH), getreg(DM9X_MDWAL));

  /* Then reset the DM90x0 */

  dm9x_reset(priv);

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

  devif_poll(&priv->dm_dev, dm9x_txpoll);
  net_unlock();
}

/****************************************************************************
 * Name: dm9x_txtimeout_expiry
 *
 * Description:
 *   Our TX watchdog timed out.  Called from the timer interrupt handler.
 *   The last TX never completed.  Reset the hardware and start again.
 *
 * Input Parameters:
 *   arg  - The argument
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *   Global interrupts are disabled by the watchdog logic.
 *
 ****************************************************************************/

static void dm9x_txtimeout_expiry(wdparm_t arg)
{
  FAR struct dm9x_driver_s *priv = (FAR struct dm9x_driver_s *)arg;

  /* Disable further Ethernet interrupts.  This will prevent some race
   * conditions with interrupt work.  There is still a potential race
   * condition with interrupt work that is already queued and in progress.
   */

  up_disable_irq(CONFIG_DM9X_IRQ);

  /* Schedule to perform the TX timeout processing on the worker thread. */

  work_queue(ETHWORK, &priv->dm_irqwork, dm9x_txtimeout_work, priv, 0);
}

/****************************************************************************
 * Name: dm9x_phymode
 *
 * Description:
 *   Configure the PHY operating mode
 *
 * Input Parameters:
 *   priv  - Reference to the driver state structure
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *
 ****************************************************************************/

static inline void dm9x_phymode(FAR struct dm9x_driver_s *priv)
{
  uint16_t phyreg0;
  uint16_t phyreg4;

#ifdef CONFIG_DM9X_MODE_AUTO
  phyreg0 = 0x1200;  /* Auto-negotiation & Restart Auto-negotiation */
  phyreg4 = 0x01e1;  /* Default flow control disable */
#elif defined(CONFIG_DM9X_MODE_10MHD)
  phyreg4 = 0x21;
  phyreg0 = 0x1000;
#elif defined(CONFIG_DM9X_MODE_10MFD)
  phyreg4 = 0x41;
  phyreg0 = 0x1100;
#elif defined(CONFIG_DM9X_MODE_100MHD)
  phyreg4 = 0x81;
  phyreg0 = 0x3000;
#elif defined(CONFIG_DM9X_MODE_100MFD)
  phyreg4 = 0x101;
  phyreg0 = 0x3100;
#else
# error "Recognized PHY mode"
#endif

  dm9x_phywrite(priv, 0, phyreg0);
  dm9x_phywrite(priv, 4, phyreg4);
}

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

static int dm9x_ifup(FAR struct net_driver_s *dev)
{
  FAR struct dm9x_driver_s *priv =
    (FAR struct dm9x_driver_s *)dev->d_private;
  uint8_t netstatus;
  int i;

  ninfo("Bringing up: %u.%u.%u.%u\n",
        ip4_addr1(dev->d_ipaddr), ip4_addr2(dev->d_ipaddr),
        ip4_addr3(dev->d_ipaddr), ip4_addr4(dev->d_ipaddr));

  /* Initialize DM90x0 chip */

  dm9x_bringup(priv);

  /* Check link state and media speed (waiting up to 3s for link OK) */

  priv->dm_b100m = false;
  for (i = 0; i < 3000; i++)
    {
      netstatus = getreg(DM9X_NETS);
      if (netstatus & DM9X_NETS_LINKST)
        {
          /* Link OK... Wait a bit before getting the detected speed */

          up_mdelay(200);
          netstatus = getreg(DM9X_NETS);
          if ((netstatus & DM9X_NETS_SPEED) == 0)
            {
              priv->dm_b100m = true;
            }

          break;
        }

      i++;
      up_mdelay(1);
    }

  ninfo("delay: %dmS speed: %s\n", i, priv->dm_b100m ? "100M" : "10M");

  /* Enable the DM9X interrupt */

  priv->dm_bifup = true;
  up_enable_irq(CONFIG_DM9X_IRQ);
  return OK;
}

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

static int dm9x_ifdown(FAR struct net_driver_s *dev)
{
  FAR struct dm9x_driver_s *priv =
    (FAR struct dm9x_driver_s *)dev->d_private;
  irqstate_t flags;

  ninfo("Stopping\n");

  /* Disable the DM9X interrupt */

  flags = enter_critical_section();
  up_disable_irq(CONFIG_DM9X_IRQ);

  /* Cancel the TX timeout timers */

  wd_cancel(&priv->dm_txtimeout);

  /* Reset the device */

  dm9x_phywrite(priv, 0x00, 0x8000);  /* PHY reset */
  putreg(DM9X_GPD, 0x01);             /* Power-down PHY (GEPIO0=1) */
  putreg(DM9X_IMR, DM9X_IMRDISABLE);  /* Disable all interrupts */
  putreg(DM9X_RXC, 0x00);             /* Disable RX */
  putreg(DM9X_ISR, DM9X_INT_ALL);     /* Clear interrupt status */

  priv->dm_bifup = false;
  leave_critical_section(flags);
  return OK;
}

/****************************************************************************
 * Name: dm9x_txavail_work
 *
 * Description:
 *   Perform an out-of-cycle poll on the worker thread.
 *
 * Input Parameters:
 *   arg - Reference to the NuttX driver state structure (cast to void*)
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *   Called on the higher priority worker thread.
 *
 ****************************************************************************/

static void dm9x_txavail_work(FAR void *arg)
{
  FAR struct dm9x_driver_s *priv =
    (FAR struct dm9x_driver_s *)arg;

  ninfo("Polling\n");

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

  net_lock();
  if (priv->dm_bifup)
    {
      /* Check if there is room in the DM90x0 to hold another packet. In 100M
       * mode, that can be 2 packets, otherwise it is a single packet.
       */

      if (priv->dm_ntxpending < 1 ||
          (priv->dm_b100m && priv->dm_ntxpending < 2))
        {
          /* If so, then poll the network for new XMIT data */

          devif_poll(&priv->dm_dev, dm9x_txpoll);
        }
    }

  net_unlock();
}

/****************************************************************************
 * Name: dm9x_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.
 *
 * Input Parameters:
 *   dev - Reference to the NuttX driver state structure
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *   Called in normal user mode
 *
 ****************************************************************************/

static int dm9x_txavail(FAR struct net_driver_s *dev)
{
  FAR struct dm9x_driver_s *priv =
    (FAR struct dm9x_driver_s *)dev->d_private;

  /* Is our single work structure available?  It may not be if there are
   * pending interrupt actions and we will have to ignore the Tx
   * availability action.
   */

  if (work_available(&priv->dm_pollwork))
    {
      /* Schedule to serialize the poll on the worker thread. */

      work_queue(ETHWORK, &priv->dm_pollwork, dm9x_txavail_work, priv, 0);
    }

  return OK;
}

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

#ifdef CONFIG_NET_MCASTGROUP
static int dm9x_addmac(FAR struct net_driver_s *dev, FAR const uint8_t *mac)
{
  FAR struct dm9x_driver_s *priv =
    (FAR struct dm9x_driver_s *)dev->d_private;

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

  /* #warning "Multicast MAC support not implemented" */

  return -ENOSYS;
}
#endif

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

#ifdef CONFIG_NET_MCASTGROUP
static int dm9x_rmmac(FAR struct net_driver_s *dev, FAR const uint8_t *mac)
{
  FAR struct dm9x_driver_s *priv =
    (FAR struct dm9x_driver_s *)dev->d_private;

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

  /* #warning "Multicast MAC support not implemented" */

  return -ENOSYS;
}
#endif

/****************************************************************************
 * Name: dm9x_bringup
 *
 * Description:
 *   Initialize the dm90x0 chip
 *
 * Input Parameters:
 *   priv  - Reference to the driver state structure
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *
 ****************************************************************************/

static void dm9x_bringup(FAR struct dm9x_driver_s *priv)
{
  ninfo("Initializing\n");

  /* Set the internal PHY power-on, GPIOs normal, and wait 2ms */

  putreg(DM9X_GPD, 0x01);  /* Power-down the PHY (GEPIO0=1) */
  up_udelay(500);
  putreg(DM9X_GPD, 0x00);  /* Preactivate PHY (GPIO0=0 */
  up_udelay(20);           /* Wait 20us for PHY power-on ready */

  /* Do a software reset and wait 20us (twice).  The reset autoclears
   * in 10us; 20us guarantees completion of the reset
   */

  putreg(DM9X_NETC, (DM9X_NETC_RST | DM9X_NETC_LBK1));
  up_udelay(20);
  putreg(DM9X_NETC, (DM9X_NETC_RST | DM9X_NETC_LBK1));
  up_udelay(20);

  /* Configure I/O mode */

  switch (getreg(DM9X_ISR) & DM9X_ISR_IOMODEM)
    {
      case DM9X_ISR_IOMODE8:
        priv->dm_read    = read8;
        priv->dm_write   = write8;
        priv->dm_discard = discard8;
        break;

      case DM9X_ISR_IOMODE16:
        priv->dm_read    = read16;
        priv->dm_write   = write16;
        priv->dm_discard = discard16;
        break;

      case DM9X_ISR_IOMODE32:
        priv->dm_read    = read32;
        priv->dm_write   = write32;
        priv->dm_discard = discard32;
        break;

      default:
        break;
    }

  /* Program PHY operating mode */

  dm9x_phymode(priv);

  /* Program operating mode */

  putreg(DM9X_NETC, 0x00);        /* Network control */
  putreg(DM9X_TXC, 0x00);         /* Clear TX Polling */
  putreg(DM9X_BPTHRES, 0x3f);     /* Less 3kb, 600us */
  putreg(DM9X_SMODEC, 0x00);      /* Special mode */
                                  /* Clear TX status */
  putreg(DM9X_NETS, DM9X_NETS_WAKEST | DM9X_NETS_TX1END | DM9X_NETS_TX2END);
  putreg(DM9X_ISR, DM9X_INT_ALL); /* Clear interrupt status */

#if defined(CONFIG_DM9X_CHECKSUM)
  putreg(DM9X_TCCR, 0x07);        /* TX UDP/TCP/IP checksum enable */
  putreg(DM9X_RCSR, 0x02);        /* Receive checksum enable */
#endif

#if defined(CONFIG_DM9X_ETRANS)
  putreg(DM9X_ETXCSR, 0x83);
#endif

  /* Initialize statistics */

  priv->ncrxpackets   = 0; /* Number of continuous RX packets  */
  priv->dm_ntxpending = 0; /* Number of pending TX packets */
  NETDEV_RESET_STATISTICS(&priv->dm_dev);

  /* Activate DM9000A/DM9010 */

  putreg(DM9X_RXC, DM9X_RXCSETUP | 1); /* RX enable */
  putreg(DM9X_IMR, DM9X_IMRENABLE);    /* Enable TX/RX interrupts */
}

/****************************************************************************
 * Name: dm9x_reset
 *
 * Description:
 *   Stop, reset, re-initialize, and restart the DM90x0 chip and driver.  At
 *   present, the chip is only reset after a TX timeout.
 *
 * Input Parameters:
 *   priv  - Reference to the driver state structure
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *
 ****************************************************************************/

static void dm9x_reset(FAR struct dm9x_driver_s *priv)
{
  uint8_t save;
  int i;

  /* Cancel the TX timeout timers */

  wd_cancel(&priv->dm_txtimeout);

  /* Save previous register address */

  save = (uint8_t)DM9X_INDEX;
  dm9x_bringup(priv);

  /* Wait up to 1 second for the link to be OK */

  priv->dm_b100m = false;
  for (i = 0; i < 1000; i++)
    {
      if (dm9x_phyread(priv, 0x1) & 0x4)
        {
          if (dm9x_phyread(priv, 0) & 0x2000)
            {
              priv->dm_b100m = true;
            }

          break;
        }

      up_mdelay(1);
    }

  /* Restore previous register address */

  DM9X_INDEX = save;
}

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

/****************************************************************************
 * Name: dm9x_initialize
 *
 * Description:
 *   Initialize the DM90x0 driver
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   OK on success; Negated errno on failure.
 *
 * Assumptions:
 *
 ****************************************************************************/

/* Initialize the DM90x0 chip and driver */

int dm9x_initialize(void)
{
  uint8_t *mptr;
  uint16_t vid;
  uint16_t pid;
  int i;
  int j;

  /* Get the chip vendor ID and product ID */

  vid = (((uint16_t)getreg(DM9X_VIDH)) << 8) | (uint16_t)getreg(DM9X_VIDL);
  pid = (((uint16_t)getreg(DM9X_PIDH)) << 8) | (uint16_t)getreg(DM9X_PIDL);

  ninfo("I/O base: %08x VID: %04x PID: %04x\n", CONFIG_DM9X_BASE, vid, pid);

  /* Check if a DM90x0 chip is recognized at this I/O base */

  if (vid != DM9X_DAVICOMVID ||
      (pid != DM9X_DM9000PID && pid != DM9X_DM9010PID))
    {
      nerr("ERROR: vendor/product ID not found at this base address\n");
      return -ENODEV;
    }

  /* Attach the IRQ to the driver */

  if (irq_attach(CONFIG_DM9X_IRQ, dm9x_interrupt, NULL))
    {
      /* We could not attach the ISR to the ISR */

      nerr("ERROR: irq_attach() failed\n");
      return -EAGAIN;
    }

  /* Initialize the driver structure */

  memset(g_dm9x, 0, CONFIG_DM9X_NINTERFACES*sizeof(struct dm9x_driver_s));
  g_dm9x[0].dm_dev.d_buf     = (FAR uint8_t *)g_pktbuf[0]; /* Single packet buffer */
  g_dm9x[0].dm_dev.d_ifup    = dm9x_ifup;                  /* I/F down callback */
  g_dm9x[0].dm_dev.d_ifdown  = dm9x_ifdown;                /* I/F up (new IP address) callback */
  g_dm9x[0].dm_dev.d_txavail = dm9x_txavail;               /* New TX data callback */
#ifdef CONFIG_NET_MCASTGROUP
  g_dm9x[0].dm_dev.d_addmac  = dm9x_addmac;                /* Add multicast MAC address */
  g_dm9x[0].dm_dev.d_rmmac   = dm9x_rmmac;                 /* Remove multicast MAC address */
#endif
  g_dm9x[0].dm_dev.d_private = g_dm9x;                     /* Used to recover private state from dev */

  /* Read the MAC address */

  mptr = g_dm9x[0].dm_dev.d_mac.ether.ether_addr_octet;
  for (i = 0, j = DM9X_PAB0; i < ETHER_ADDR_LEN; i++, j++)
    {
      mptr[i] = getreg(j);
    }

  ninfo("MAC: %0x:%0x:%0x:%0x:%0x:%0x\n",
       mptr[0], mptr[1], mptr[2], mptr[3], mptr[4], mptr[5]);

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

  netdev_register(&g_dm9x[0].dm_dev, NET_LL_ETHERNET);
  return OK;
}

#endif /* CONFIG_NET && CONFIG_NET_DM90x0 */