//  [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_REG_EREVID);

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

   //Disable CLKOUT output
   enc28j60WriteReg(interface, ENC28J60_REG_ECOCON, 0x00);

   //Set the MAC address of the station
   enc28j60WriteReg(interface, ENC28J60_REG_MAADR1, interface->macAddr.b[0]);
   enc28j60WriteReg(interface, ENC28J60_REG_MAADR2, interface->macAddr.b[1]);
   enc28j60WriteReg(interface, ENC28J60_REG_MAADR3, interface->macAddr.b[2]);
   enc28j60WriteReg(interface, ENC28J60_REG_MAADR4, interface->macAddr.b[3]);
   enc28j60WriteReg(interface, ENC28J60_REG_MAADR5, interface->macAddr.b[4]);
   enc28j60WriteReg(interface, ENC28J60_REG_MAADR6, interface->macAddr.b[5]);

   //Set receive buffer location
   enc28j60WriteReg(interface, ENC28J60_REG_ERXSTL, LSB(ENC28J60_RX_BUFFER_START));
   enc28j60WriteReg(interface, ENC28J60_REG_ERXSTH, MSB(ENC28J60_RX_BUFFER_START));
   enc28j60WriteReg(interface, ENC28J60_REG_ERXNDL, LSB(ENC28J60_RX_BUFFER_STOP));
   enc28j60WriteReg(interface, ENC28J60_REG_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_REG_ERXRDPTL, LSB(ENC28J60_RX_BUFFER_STOP));
   enc28j60WriteReg(interface, ENC28J60_REG_ERXRDPTH, MSB(ENC28J60_RX_BUFFER_STOP));

   //Configure the receive filters
   enc28j60WriteReg(interface, ENC28J60_REG_ERXFCON, ERXFCON_UCEN |
      ERXFCON_CRCEN | ERXFCON_HTEN | ERXFCON_BCEN);

   //Initialize the hash table
   enc28j60WriteReg(interface, ENC28J60_REG_EHT0, 0x00);
   enc28j60WriteReg(interface, ENC28J60_REG_EHT1, 0x00);
   enc28j60WriteReg(interface, ENC28J60_REG_EHT2, 0x00);
   enc28j60WriteReg(interface, ENC28J60_REG_EHT3, 0x00);
   enc28j60WriteReg(interface, ENC28J60_REG_EHT4, 0x00);
   enc28j60WriteReg(interface, ENC28J60_REG_EHT5, 0x00);
   enc28j60WriteReg(interface, ENC28J60_REG_EHT6, 0x00);
   enc28j60WriteReg(interface, ENC28J60_REG_EHT7, 0x00);

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

   //Enable the MAC to receive frames
   enc28j60WriteReg(interface, ENC28J60_REG_MACON1,
      MACON1_TXPAUS | MACON1_RXPAUS | MACON1_MARXEN);

   //Enable automatic padding to at least 60 bytes, 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_REG_MACON3, MACON3_PADCFG(1) |
      MACON3_TXCRCEN | MACON3_FRMLNEN | MACON3_FULDPX);
#else
   enc28j60WriteReg(interface, ENC28J60_REG_MACON3, MACON3_PADCFG(1) |
      MACON3_TXCRCEN | 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_REG_MACON4, MACON4_DEFER);

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

   //Configure the back-to-back inter-packet gap register
#if (ENC28J60_FULL_DUPLEX_SUPPORT == ENABLED)
   enc28j60WriteReg(interface, ENC28J60_REG_MABBIPG, 0x15);
#else
   enc28j60WriteReg(interface, ENC28J60_REG_MABBIPG, 0x12);
#endif

   //Configure the non-back-to-back inter-packet gap register
   enc28j60WriteReg(interface, ENC28J60_REG_MAIPGL, 0x12);
   enc28j60WriteReg(interface, ENC28J60_REG_MAIPGH, 0x0C);

   //Collision window register
   enc28j60WriteReg(interface, ENC28J60_REG_MACLCON2, 63);

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

   //Disable half-duplex loopback in PHY
   enc28j60WritePhyReg(interface, ENC28J60_PHY_REG_PHCON2, PHCON2_HDLDIS);

   //LEDA displays link status and LEDB displays TX/RX activity
   enc28j60WritePhyReg(interface, ENC28J60_PHY_REG_PHLCON,
      PHLCON_LACFG(4) | PHLCON_LBCFG(7) | PHLCON_LFRQ(0) | PHLCON_STRCH);

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

   //Configure interrupts as desired
   enc28j60WriteReg(interface, ENC28J60_REG_EIE, EIE_INTIE |
      EIE_PKTIE | EIE_LINKIE | EIE_TXIE | EIE_TXERIE);

   //Configure PHY interrupts as desired
   enc28j60WritePhyReg(interface, ENC28J60_PHY_REG_PHIE,
      PHIE_PLNKIE | PHIE_PGEIE);

   //Set RXEN to enable reception
   enc28j60SetBit(interface, ENC28J60_REG_ECON1, 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;
}