Webserver+3d print

Dependents:   Nucleo

cyclone_tcp/drivers/dm9000.c

Committer:
Sergunb
Date:
2017-02-04
Revision:
0:8918a71cdbe9

File content as of revision 0:8918a71cdbe9:

/**
 * @file dm9000.c
 * @brief DM9000A/B Ethernet controller
 *
 * @section License
 *
 * Copyright (C) 2010-2017 Oryx Embedded SARL. All rights reserved.
 *
 * This file is part of CycloneTCP Open.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * @author Oryx Embedded SARL (www.oryx-embedded.com)
 * @version 1.7.6
 **/

//Switch to the appropriate trace level
#define TRACE_LEVEL NIC_TRACE_LEVEL

//Dependencies
#include "core/net.h"
#include "core/ethernet.h"
#include "drivers/dm9000.h"
#include "debug.h"


/**
 * @brief DM9000 driver
 **/

const NicDriver dm9000Driver =
{
   NIC_TYPE_ETHERNET,
   ETH_MTU,
   dm9000Init,
   dm9000Tick,
   dm9000EnableIrq,
   dm9000DisableIrq,
   dm9000EventHandler,
   dm9000SendPacket,
   dm9000SetMulticastFilter,
   NULL,
   NULL,
   NULL,
   TRUE,
   TRUE,
   TRUE,
   FALSE
};


/**
 * @brief DM9000 controller initialization
 * @param[in] interface Underlying network interface
 * @return Error code
 **/

error_t dm9000Init(NetInterface *interface)
{
   uint_t i;
   uint16_t vendorId;
   uint16_t productId;
   uint8_t chipRevision;
   Dm9000Context *context;

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

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

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

   //Initialize driver specific variables
   context->queuedPackets = 0;

   //Allocate TX and RX buffers
   context->txBuffer = memPoolAlloc(ETH_MAX_FRAME_SIZE);
   context->rxBuffer = memPoolAlloc(ETH_MAX_FRAME_SIZE);

   //Failed to allocate memory?
   if(context->txBuffer == NULL || context->rxBuffer == NULL)
   {
      //Clean up side effects
      memPoolFree(context->txBuffer);
      memPoolFree(context->rxBuffer);

      //Report an error
      return ERROR_OUT_OF_MEMORY;
   }

   //Retrieve vendorID, product ID and chip revision
   vendorId = (dm9000ReadReg(DM9000_REG_VIDH) << 8) | dm9000ReadReg(DM9000_REG_VIDL);
   productId = (dm9000ReadReg(DM9000_REG_PIDH) << 8) | dm9000ReadReg(DM9000_REG_PIDL);
   chipRevision = dm9000ReadReg(DM9000_REG_CHIPR);

   //Check vendor ID and product ID
   if(vendorId != DM9000_VID || productId != DM9000_PID)
      return ERROR_WRONG_IDENTIFIER;
   //Check chip revision
   if(chipRevision != DM9000A_CHIP_REV && chipRevision != DM9000B_CHIP_REV)
      return ERROR_WRONG_IDENTIFIER;

   //Power up the internal PHY by clearing PHYPD
   dm9000WriteReg(DM9000_REG_GPR, 0x00);
   //Wait for the PHY to be ready
   sleep(10);

   //Software reset
   dm9000WriteReg(DM9000_REG_NCR, NCR_RST);
   //Wait for the reset to complete
   while(dm9000ReadReg(DM9000_REG_NCR) & NCR_RST);

   //PHY software reset
   dm9000WritePhyReg(DM9000_PHY_REG_BMCR, BMCR_RST);
   //Wait for the PHY reset to complete
   while(dm9000ReadPhyReg(DM9000_PHY_REG_BMCR) & BMCR_RST);

   //Debug message
   TRACE_INFO("  VID = 0x%04" PRIX16 "\r\n", vendorId);
   TRACE_INFO("  PID = 0x%04" PRIX16 "\r\n", productId);
   TRACE_INFO("  CHIPR = 0x%02" PRIX8 "\r\n", chipRevision);
   TRACE_INFO("  PHYIDR1 = 0x%04" PRIX16 "\r\n", dm9000ReadPhyReg(DM9000_PHY_REG_PHYIDR1));
   TRACE_INFO("  PHYIDR2 = 0x%04" PRIX16 "\r\n", dm9000ReadPhyReg(DM9000_PHY_REG_PHYIDR2));

   //Enable loopback mode?
#if (DM9000_LOOPBACK_MODE == ENABLED)
   dm9000WriteReg(DM9000_REG_NCR, DM9000_LBK_PHY);
   dm9000WritePhyReg(DM9000_PHY_REG_BMCR, BMCR_LOOPBACK | BMCR_SPEED_SEL | BMCR_AN_EN | BMCR_DUPLEX_MODE);
#endif

   //Set host MAC address
   for(i = 0; i < 6; i++)
      dm9000WriteReg(DM9000_REG_PAR0 + i, interface->macAddr.b[i]);

   //Initialize hash table
   for(i = 0; i < 8; i++)
      dm9000WriteReg(DM9000_REG_MAR0 + i, 0x00);

   //Always accept broadcast packets
   dm9000WriteReg(DM9000_REG_MAR7, 0x80);

   //Enable the Pointer Auto Return function
   dm9000WriteReg(DM9000_REG_IMR, IMR_PAR);
   //Clear NSR status bits
   dm9000WriteReg(DM9000_REG_NSR, NSR_WAKEST | NSR_TX2END | NSR_TX1END);
   //Clear interrupt flags
   dm9000WriteReg(DM9000_REG_ISR, ISR_LNKCHG | ISR_UDRUN | ISR_ROO | ISR_ROS | ISR_PT | ISR_PR);
   //Enable interrupts
   dm9000WriteReg(DM9000_REG_IMR, IMR_PAR | IMR_LNKCHGI | IMR_PTI | IMR_PRI);
   //Enable the receiver by setting RXEN
   dm9000WriteReg(DM9000_REG_RCR, RCR_DIS_LONG | RCR_DIS_CRC | RCR_RXEN);

   //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;
}


/**
 * @brief DM9000 timer handler
 * @param[in] interface Underlying network interface
 **/

void dm9000Tick(NetInterface *interface)
{
}


/**
 * @brief Enable interrupts
 * @param[in] interface Underlying network interface
 **/

void dm9000EnableIrq(NetInterface *interface)
{
   //Enable interrupts
   interface->extIntDriver->enableIrq();
}


/**
 * @brief Disable interrupts
 * @param[in] interface Underlying network interface
 **/

void dm9000DisableIrq(NetInterface *interface)
{
   //Disable interrupts
   interface->extIntDriver->disableIrq();
}


/**
 * @brief DM9000 interrupt service routine
 * @param[in] interface Underlying network interface
 * @return TRUE if a higher priority task must be woken. Else FALSE is returned
 **/

bool_t dm9000IrqHandler(NetInterface *interface)
{
   bool_t flag;
   uint8_t status;
   uint8_t mask;
   Dm9000Context *context;

   //This flag will be set if a higher priority task must be woken
   flag = FALSE;

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

   //Read interrupt status register
   status = dm9000ReadReg(DM9000_REG_ISR);

   //Link status change?
   if(status & ISR_LNKCHG)
   {
      //Read interrupt mask register
      mask = dm9000ReadReg(DM9000_REG_IMR);
      //Disable LNKCHGI interrupt
      dm9000WriteReg(DM9000_REG_IMR, mask & ~IMR_LNKCHGI);

      //Set event flag
      interface->nicEvent = TRUE;
      //Notify the TCP/IP stack of the event
      flag |= osSetEventFromIsr(&netEvent);
   }

   //Packet transmission complete?
   if(status & ISR_PT)
   {
      //Check TX complete status bits
      if(dm9000ReadReg(DM9000_REG_NSR) & (NSR_TX2END | NSR_TX1END))
      {
         //The transmission of the current packet is complete
         if(context->queuedPackets > 0)
            context->queuedPackets--;

         //Notify the TCP/IP stack that the transmitter is ready to send
         flag |= osSetEventFromIsr(&interface->nicTxEvent);
      }

      //Clear interrupt flag
      dm9000WriteReg(DM9000_REG_ISR, ISR_PT);
   }

   //Packet received?
   if(status & ISR_PR)
   {
      //Read interrupt mask register
      mask = dm9000ReadReg(DM9000_REG_IMR);
      //Disable PRI interrupt
      dm9000WriteReg(DM9000_REG_IMR, mask & ~IMR_PRI);

      //Set event flag
      interface->nicEvent = TRUE;
      //Notify the TCP/IP stack of the event
      flag |= osSetEventFromIsr(&netEvent);
   }

   //A higher priority task must be woken?
   return flag;
}


/**
 * @brief DM9000 event handler
 * @param[in] interface Underlying network interface
 **/

void dm9000EventHandler(NetInterface *interface)
{
   error_t error;
   uint8_t status;

   //Read interrupt status register
   status = dm9000ReadReg(DM9000_REG_ISR);

   //Check whether the link status has changed?
   if(status & ISR_LNKCHG)
   {
      //Clear interrupt flag
      dm9000WriteReg(DM9000_REG_ISR, ISR_LNKCHG);
      //Read network status register
      status = dm9000ReadReg(DM9000_REG_NSR);

      //Check link state
      if(status & NSR_LINKST)
      {
         //Get current speed
         if(status & NSR_SPEED)
            interface->linkSpeed = NIC_LINK_SPEED_10MBPS;
         else
            interface->linkSpeed = NIC_LINK_SPEED_100MBPS;

         //Read network control register
         status = dm9000ReadReg(DM9000_REG_NCR);

         //Determine the new duplex mode
         if(status & NCR_FDX)
            interface->duplexMode = NIC_FULL_DUPLEX_MODE;
         else
            interface->duplexMode = NIC_HALF_DUPLEX_MODE;

         //Link is up
         interface->linkState = TRUE;
      }
      else
      {
         //Link is down
         interface->linkState = FALSE;
      }

      //Process link state change event
      nicNotifyLinkChange(interface);
   }

   //Check whether a packet has been received?
   if(status & ISR_PR)
   {
      //Clear interrupt flag
      dm9000WriteReg(DM9000_REG_ISR, ISR_PR);

      //Process all pending packets
      do
      {
         //Read incoming packet
         error = dm9000ReceivePacket(interface);

         //No more data in the receive buffer?
      } while(error != ERROR_BUFFER_EMPTY);
   }

   //Re-enable LNKCHGI and PRI interrupts
   dm9000WriteReg(DM9000_REG_IMR, IMR_PAR | IMR_LNKCHGI | IMR_PTI | IMR_PRI);
}


/**
 * @brief Send a packet to DM9000
 * @param[in] interface Underlying network interface
 * @param[in] buffer Multi-part buffer containing the data to send
 * @param[in] offset Offset to the first data byte
 * @return Error code
 **/

error_t dm9000SendPacket(NetInterface *interface,
   const NetBuffer *buffer, size_t offset)
{
   size_t i;
   size_t length;
   uint16_t *p;
   Dm9000Context *context;

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

   //Retrieve the length of the packet
   length = netBufferGetLength(buffer) - offset;

   //Check the frame length
   if(length > ETH_MAX_FRAME_SIZE)
   {
      //The transmitter can accept another packet
      osSetEvent(&interface->nicTxEvent);
      //Report an error
      return ERROR_INVALID_LENGTH;
   }

   //Copy user data
   netBufferRead(context->txBuffer, buffer, offset, length);

   //A dummy write is required before accessing FIFO
   dm9000WriteReg(DM9000_REG_MWCMDX, 0);
   //Select MWCMD register
   DM9000_INDEX_REG = DM9000_REG_MWCMD;

   //Point to the beginning of the buffer
   p = (uint16_t *) context->txBuffer;

   //Write data to the FIFO using 16-bit mode
   for(i = length; i > 1; i -= 2)
      DM9000_DATA_REG = *(p++);

   //Odd number of bytes?
   if(i > 0)
      DM9000_DATA_REG = *((uint8_t *) p);

   //Write the number of bytes to send
   dm9000WriteReg(DM9000_REG_TXPLL, LSB(length));
   dm9000WriteReg(DM9000_REG_TXPLH, MSB(length));

   //Clear interrupt flag
   dm9000WriteReg(DM9000_REG_ISR, ISR_PT);
   //Start data transfer
   dm9000WriteReg(DM9000_REG_TCR, TCR_TXREQ);

   //The packet was successfully written to FIFO
   context->queuedPackets++;

   //Successful processing
   return NO_ERROR;
}


/**
 * @brief Receive a packet
 * @param[in] interface Underlying network interface
 * @return Error code
 **/

error_t dm9000ReceivePacket(NetInterface *interface)
{
   error_t error;
   size_t i;
   size_t n;
   size_t length;
   volatile uint8_t status;
   volatile uint16_t data;
   Dm9000Context *context;

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

   //A dummy read is required before accessing the 4-byte header
   data = dm9000ReadReg(DM9000_REG_MRCMDX);

   //Select MRCMDX1 register
   DM9000_INDEX_REG = DM9000_REG_MRCMDX1;
   //Read the first byte of the header
   status = LSB(DM9000_DATA_REG);

   //The first byte indicates if a packet has been received
   if(status == 0x01)
   {
      //Select MRCMD register
      DM9000_INDEX_REG = DM9000_REG_MRCMD;
      //The second byte is the RX status byte
      status = MSB(DM9000_DATA_REG);

      //Retrieve packet length
      length = DM9000_DATA_REG;
      //Limit the number of data to read
      n = MIN(length, ETH_MAX_FRAME_SIZE);

      //Point to the beginning of the buffer
      i = 0;

      //Make sure no error occurred
      if(!(status & (RSR_LCS | RSR_RWTO | RSR_PLE | RSR_AE | RSR_CE | RSR_FOE)))
      {
         //Read data from FIFO using 16-bit mode
         while((i + 1) < n)
         {
            data = DM9000_DATA_REG;
            context->rxBuffer[i++] = LSB(data);
            context->rxBuffer[i++] = MSB(data);
         }

         //Odd number of bytes to read?
         if((i + 1) == n)
         {
            data = DM9000_DATA_REG;
            context->rxBuffer[i] = LSB(data);
            i += 2;
         }

         //Valid packet received
         error = NO_ERROR;
      }
      else
      {
         //The received packet contains an error
         error = ERROR_INVALID_PACKET;
      }

      //Flush remaining bytes
      while(i < length)
      {
         data = DM9000_DATA_REG;
         i += 2;
      }
   }
   else
   {
      //No more data in the receive buffer
      error = ERROR_BUFFER_EMPTY;
   }

   //Check whether a valid packet has been received
   if(!error)
   {
      //Pass the packet to the upper layer
      nicProcessPacket(interface, context->rxBuffer, n);
   }

   //Return status code
   return error;
}


/**
 * @brief Configure multicast MAC address filtering
 * @param[in] interface Underlying network interface
 * @return Error code
 **/

error_t dm9000SetMulticastFilter(NetInterface *interface)
{
   uint_t i;
   uint_t k;
   uint32_t crc;
   uint8_t hashTable[8];
   MacFilterEntry *entry;

   //Debug message
   TRACE_DEBUG("Updating DM9000 hash table...\r\n");

   //Clear hash table
   memset(hashTable, 0, sizeof(hashTable));
   //Always accept broadcast packets regardless of the MAC filter table
   hashTable[7] = 0x80;

   //The MAC filter table contains the multicast MAC addresses
   //to accept when receiving an Ethernet frame
   for(i = 0; i < MAC_MULTICAST_FILTER_SIZE; i++)
   {
      //Point to the current entry
      entry = &interface->macMulticastFilter[i];

      //Valid entry?
      if(entry->refCount > 0)
      {
         //Compute CRC over the current MAC address
         crc = dm9000CalcCrc(&entry->addr, sizeof(MacAddr));
         //Calculate the corresponding index in the table
         k = crc & 0x3F;
         //Update hash table contents
         hashTable[k / 8] |= (1 << (k % 8));
      }
   }

   //Write the hash table to the DM9000 controller
   for(i = 0; i < 8; i++)
      dm9000WriteReg(DM9000_REG_MAR0 + i, hashTable[i]);

   //Debug message
   TRACE_DEBUG("  MAR = %02" PRIX8 " %02" PRIX8 " %02" PRIX8 " %02" PRIX8 " "
      "%02" PRIX8 " %02" PRIX8 " %02" PRIX8 " %02" PRIX8 "\r\n",
      dm9000ReadReg(DM9000_REG_MAR0), dm9000ReadReg(DM9000_REG_MAR1),
      dm9000ReadReg(DM9000_REG_MAR2), dm9000ReadReg(DM9000_REG_MAR3),
      dm9000ReadReg(DM9000_REG_MAR4), dm9000ReadReg(DM9000_REG_MAR5),
      dm9000ReadReg(DM9000_REG_MAR6), dm9000ReadReg(DM9000_REG_MAR7));

   //Successful processing
   return NO_ERROR;
}


/**
 * @brief Write DM9000 register
 * @param[in] address Register address
 * @param[in] data Register value
 **/

void dm9000WriteReg(uint8_t address, uint8_t data)
{
   //Write register address to INDEX register
   DM9000_INDEX_REG = address;
   //Write register value to DATA register
   DM9000_DATA_REG = data;
}


/**
 * @brief Read DM9000 register
 * @param[in] address Register address
 * @return Register value
 **/

uint8_t dm9000ReadReg(uint8_t address)
{
   //Write register address to INDEX register
   DM9000_INDEX_REG = address;
   //Read register value from DATA register
   return DM9000_DATA_REG;
}


/**
 * @brief Write DM9000 PHY register
 * @param[in] address PHY register address
 * @param[in] data Register value
 **/

void dm9000WritePhyReg(uint8_t address, uint16_t data)
{
   //Write PHY register address
   dm9000WriteReg(DM9000_REG_EPAR, 0x40 | address);
   //Write register value
   dm9000WriteReg(DM9000_REG_EPDRL, LSB(data));
   dm9000WriteReg(DM9000_REG_EPDRH, MSB(data));

   //Start the write operation
   dm9000WriteReg(DM9000_REG_EPCR, EPCR_EPOS | EPCR_ERPRW);
   //PHY access is still in progress?
   while(dm9000ReadReg(DM9000_REG_EPCR) & EPCR_ERRE);

   //Wait 5us minimum
   usleep(5);
   //Clear command register
   dm9000WriteReg(DM9000_REG_EPCR, EPCR_EPOS);
}


/**
 * @brief Read DM9000 PHY register
 * @param[in] address PHY register address
 * @return Register value
 **/

uint16_t dm9000ReadPhyReg(uint8_t address)
{
   //Write PHY register address
   dm9000WriteReg(DM9000_REG_EPAR, 0x40 | address);

   //Start the read operation
   dm9000WriteReg(DM9000_REG_EPCR, EPCR_EPOS | EPCR_ERPRR);
   //PHY access is still in progress?
   while(dm9000ReadReg(DM9000_REG_EPCR) & EPCR_ERRE);

   //Clear command register
   dm9000WriteReg(DM9000_REG_EPCR, EPCR_EPOS);
   //Wait 5us minimum
   usleep(5);

   //Return register value
   return (dm9000ReadReg(DM9000_REG_EPDRH) << 8) | dm9000ReadReg(DM9000_REG_EPDRL);
}


/**
 * @brief CRC calculation
 * @param[in] data Pointer to the data over which to calculate the CRC
 * @param[in] length Number of bytes to process
 * @return Resulting CRC value
 **/

uint32_t dm9000CalcCrc(const void *data, size_t length)
{
   uint_t i;
   uint_t j;

   //Point to the data over which to calculate the CRC
   const uint8_t *p = (uint8_t *) data;
   //CRC preset value
   uint32_t crc = 0xFFFFFFFF;

   //Loop through data
   for(i = 0; i < length; i++)
   {
      //Update CRC value
      crc ^= p[i];
      //The message is processed bit by bit
      for(j = 0; j < 8; j++)
      {
         if(crc & 0x00000001)
            crc = (crc >> 1) ^ 0xEDB88320;
         else
            crc = crc >> 1;
      }
   }

   //Return CRC value
   return crc;
}