//  [c]
error_t enc28j60Init(NetInterface *interface)
{
   uint8_t revisionId;
   Enc28j60Context *context;

   //Debug message
   TRACE_INFO("Initializing ENC28J60 Ethernet controller...\r\n");

   //Initialize SPI
   interface->spiDriver->init();
   //Initialize external interrupt line
   interface->extIntDriver->init();

   //Issue a system reset
   enc28j60SoftReset(interface);

   //After issuing the reset command, wait at least 1ms in firmware
   //for the device to be ready
   sleep(10);

   //Point to the driver context
   context = (Enc28j60Context *) interface->nicContext;

   //Initialize driver specific variables
   context->currentBank = UINT16_MAX;
   context->nextPacket = ENC28J60_RX_BUFFER_START;

   //Allocate RX buffer
   context->rxBuffer = memPoolAlloc(ETH_MAX_FRAME_SIZE);
   //Failed to allocate memory?
   if(context->rxBuffer == NULL)
   {
      return ERROR_OUT_OF_MEMORY;
   }

   //Read silicon revision ID
   revisionId = enc28j60ReadReg(interface, ENC28J60_EREVID);

   //Debug message
   TRACE_INFO("ENC28J60 revision ID: 0x%02X\r\n", revisionId);

   //Disable CLKOUT output
   enc28j60WriteReg(interface, ENC28J60_ECOCON, ENC28J60_ECOCON_COCON_DISABLED);

   //Set the MAC address of the station
   enc28j60WriteReg(interface, ENC28J60_MAADR5, interface->macAddr.b[0]);
   enc28j60WriteReg(interface, ENC28J60_MAADR4, interface->macAddr.b[1]);
   enc28j60WriteReg(interface, ENC28J60_MAADR3, interface->macAddr.b[2]);
   enc28j60WriteReg(interface, ENC28J60_MAADR2, interface->macAddr.b[3]);
   enc28j60WriteReg(interface, ENC28J60_MAADR1, interface->macAddr.b[4]);
   enc28j60WriteReg(interface, ENC28J60_MAADR0, interface->macAddr.b[5]);

   //Set receive buffer location
   enc28j60WriteReg(interface, ENC28J60_ERXSTL, LSB(ENC28J60_RX_BUFFER_START));
   enc28j60WriteReg(interface, ENC28J60_ERXSTH, MSB(ENC28J60_RX_BUFFER_START));
   enc28j60WriteReg(interface, ENC28J60_ERXNDL, LSB(ENC28J60_RX_BUFFER_STOP));
   enc28j60WriteReg(interface, ENC28J60_ERXNDH, MSB(ENC28J60_RX_BUFFER_STOP));

   //The ERXRDPT register defines a location within the FIFO where the receive
   //hardware is forbidden to write to
   enc28j60WriteReg(interface, ENC28J60_ERXRDPTL, LSB(ENC28J60_RX_BUFFER_STOP));
   enc28j60WriteReg(interface, ENC28J60_ERXRDPTH, MSB(ENC28J60_RX_BUFFER_STOP));

   //Configure the receive filters
   enc28j60WriteReg(interface, ENC28J60_ERXFCON, ENC28J60_ERXFCON_UCEN |
      ENC28J60_ERXFCON_CRCEN | ENC28J60_ERXFCON_HTEN | ENC28J60_ERXFCON_BCEN);

   //Initialize the hash table
   enc28j60WriteReg(interface, ENC28J60_EHT0, 0x00);
   enc28j60WriteReg(interface, ENC28J60_EHT1, 0x00);
   enc28j60WriteReg(interface, ENC28J60_EHT2, 0x00);
   enc28j60WriteReg(interface, ENC28J60_EHT3, 0x00);
   enc28j60WriteReg(interface, ENC28J60_EHT4, 0x00);
   enc28j60WriteReg(interface, ENC28J60_EHT5, 0x00);
   enc28j60WriteReg(interface, ENC28J60_EHT6, 0x00);
   enc28j60WriteReg(interface, ENC28J60_EHT7, 0x00);

   //Pull the MAC out of reset
   enc28j60WriteReg(interface, ENC28J60_MACON2, 0x00);

   //Enable the MAC to receive frames
   enc28j60WriteReg(interface, ENC28J60_MACON1, ENC28J60_MACON1_TXPAUS |
      ENC28J60_MACON1_RXPAUS | ENC28J60_MACON1_MARXEN);

   //Enable automatic padding, always append a valid CRC and check frame
   //length. MAC can operate in half-duplex or full-duplex mode
#if (ENC28J60_FULL_DUPLEX_SUPPORT == ENABLED)
   enc28j60WriteReg(interface, ENC28J60_MACON3, ENC28J60_MACON3_PADCFG_AUTO |
      ENC28J60_MACON3_TXCRCEN | ENC28J60_MACON3_FRMLNEN |
      ENC28J60_MACON3_FULDPX);
#else
   enc28j60WriteReg(interface, ENC28J60_MACON3, ENC28J60_MACON3_PADCFG_AUTO |
      ENC28J60_MACON3_TXCRCEN | ENC28J60_MACON3_FRMLNEN);
#endif

   //When the medium is occupied, the MAC will wait indefinitely for it to
   //become free when attempting to transmit
   enc28j60WriteReg(interface, ENC28J60_MACON4, ENC28J60_MACON4_DEFER);

   //Maximum frame length that can be received or transmitted
   enc28j60WriteReg(interface, ENC28J60_MAMXFLL, LSB(ETH_MAX_FRAME_SIZE));
   enc28j60WriteReg(interface, ENC28J60_MAMXFLH, MSB(ETH_MAX_FRAME_SIZE));

   //Configure the back-to-back inter-packet gap register
#if (ENC28J60_FULL_DUPLEX_SUPPORT == ENABLED)
   enc28j60WriteReg(interface, ENC28J60_MABBIPG, ENC28J60_MABBIPG_DEFAULT_FD);
#else
   enc28j60WriteReg(interface, ENC28J60_MABBIPG, ENC28J60_MABBIPG_DEFAULT_HD);
#endif

   //Configure the non-back-to-back inter-packet gap register
   enc28j60WriteReg(interface, ENC28J60_MAIPGL, ENC28J60_MAIPGL_DEFAULT);
   enc28j60WriteReg(interface, ENC28J60_MAIPGH, ENC28J60_MAIPGH_DEFAULT);

   //Collision window register
   enc28j60WriteReg(interface, ENC28J60_MACLCON2,
      ENC28J60_MACLCON2_COLWIN_DEFAULT);

   //Set the PHY to the proper duplex mode
#if (ENC28J60_FULL_DUPLEX_SUPPORT == ENABLED)
   enc28j60WritePhyReg(interface, ENC28J60_PHCON1, ENC28J60_PHCON1_PDPXMD);
#else
   enc28j60WritePhyReg(interface, ENC28J60_PHCON1, 0x0000);
#endif

   //Disable half-duplex loopback in PHY
   enc28j60WritePhyReg(interface, ENC28J60_PHCON2, ENC28J60_PHCON2_HDLDIS);

   //LEDA displays link status and LEDB displays TX/RX activity
   enc28j60WritePhyReg(interface, ENC28J60_PHLCON,
      ENC28J60_PHLCON_LACFG_LINK | ENC28J60_PHLCON_LBCFG_TX_RX |
      ENC28J60_PHLCON_LFRQ_40_MS | ENC28J60_PHLCON_STRCH);

   //Clear interrupt flags
   enc28j60WriteReg(interface, ENC28J60_EIR, 0x00);

   //Configure interrupts as desired
   enc28j60WriteReg(interface, ENC28J60_EIE, ENC28J60_EIE_INTIE |
      ENC28J60_EIE_PKTIE | ENC28J60_EIE_LINKIE | ENC28J60_EIE_TXIE |
      ENC28J60_EIE_TXERIE);

   //Configure PHY interrupts as desired
   enc28j60WritePhyReg(interface, ENC28J60_PHIE, ENC28J60_PHIE_PLNKIE |
      ENC28J60_PHIE_PGEIE);

   //Set RXEN to enable reception
   enc28j60SetBit(interface, ENC28J60_ECON1, ENC28J60_ECON1_RXEN);

   //Dump registers for debugging purpose
   enc28j60DumpReg(interface);
   enc28j60DumpPhyReg(interface);

   //Accept any packets from the upper layer
   osSetEvent(&interface->nicTxEvent);

   //Force the TCP/IP stack to poll the link state at startup
   interface->nicEvent = TRUE;
   //Notify the TCP/IP stack of the event
   osSetEvent(&netEvent);

   //Successful initialization
   return NO_ERROR;
}