2021-11-30 14:51:24 +01:00

1037 lines
33 KiB
C

/**
* \file
*
* \brief GMAC (Ethernet MAC) driver for SAM.
*
* Copyright (c) 2013 Atmel Corporation. All rights reserved.
*
* \asf_license_start
*
* \page License
*
* 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. The name of Atmel may not be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* 4. This software may only be redistributed and used in connection with an
* Atmel microcontroller product.
*
* THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
* EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL 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.
*
* \asf_license_stop
*
*/
/* Standard includes. */
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/* FreeRTOS includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "FreeRTOSIPConfig.h"
#include "compiler.h"
#include "instance/gmac.h"
#include "ethernet_phy.h"
/*/ @cond 0 */
/**INDENT-OFF**/
#ifdef __cplusplus
extern "C" {
#endif
/**INDENT-ON**/
/*/ @endcond */
#ifndef ARRAY_SIZE
#define ARRAY_SIZE( x ) ( int ) ( sizeof( x ) / sizeof( x )[ 0 ] )
#endif
/**
* \defgroup gmac_group Ethernet Media Access Controller
*
* See \ref gmac_quickstart.
*
* Driver for the GMAC (Ethernet Media Access Controller).
* This file contains basic functions for the GMAC, with support for all modes, settings
* and clock speeds.
*
* \section dependencies Dependencies
* This driver does not depend on other modules.
*
* @{
*/
/** TX descriptor lists */
COMPILER_ALIGNED( 8 )
static gmac_tx_descriptor_t gs_tx_desc[ GMAC_TX_BUFFERS ];
#if ( GMAC_USES_TX_CALLBACK != 0 )
/** TX callback lists */
static gmac_dev_tx_cb_t gs_tx_callback[ GMAC_TX_BUFFERS ];
#endif
/** RX descriptors lists */
COMPILER_ALIGNED( 8 )
static gmac_rx_descriptor_t gs_rx_desc[ GMAC_RX_BUFFERS ];
#if ( ipconfigZERO_COPY_TX_DRIVER == 0 )
/** Send Buffer. Section 3.6 of AMBA 2.0 spec states that burst should not cross the
* 1K Boundaries. Receive buffer manager write operations are burst of 2 words => 3 lsb bits
* of the address shall be set to 0.
*/
COMPILER_ALIGNED( 8 )
static uint8_t gs_uc_tx_buffer[ GMAC_TX_BUFFERS * GMAC_TX_UNITSIZE ];
#endif /* ipconfigZERO_COPY_TX_DRIVER */
/** Receive Buffer */
COMPILER_ALIGNED( 8 )
static uint8_t gs_uc_rx_buffer[ GMAC_RX_BUFFERS * GMAC_RX_UNITSIZE ];
/**
* GMAC device memory management struct.
*/
typedef struct gmac_dev_mem
{
/* Pointer to allocated buffer for RX. The address should be 8-byte aligned
* and the size should be GMAC_RX_UNITSIZE * wRxSize. */
uint8_t * p_rx_buffer;
/* Pointer to allocated RX descriptor list. */
gmac_rx_descriptor_t * p_rx_dscr;
/* RX size, in number of registered units (RX descriptors). */
/* Increased size from 16- to 32-bits, because it's more efficient */
uint32_t us_rx_size;
/* Pointer to allocated buffer for TX. The address should be 8-byte aligned
* and the size should be GMAC_TX_UNITSIZE * wTxSize. */
uint8_t * p_tx_buffer;
/* Pointer to allocated TX descriptor list. */
gmac_tx_descriptor_t * p_tx_dscr;
/* TX size, in number of registered units (TX descriptors). */
uint32_t us_tx_size;
} gmac_dev_mem_t;
/** Return count in buffer */
#define CIRC_CNT( head, tail, size ) ( ( ( head ) - ( tail ) ) % ( size ) )
/*
* Return space available, from 0 to size-1.
* Always leave one free char as a completely full buffer that has (head == tail),
* which is the same as empty.
*/
#define CIRC_SPACE( head, tail, size ) CIRC_CNT( ( tail ), ( ( head ) + 1 ), ( size ) )
/** Circular buffer is empty ? */
#define CIRC_EMPTY( head, tail ) ( head == tail )
/** Clear circular buffer */
#define CIRC_CLEAR( head, tail ) do { ( head ) = 0; ( tail ) = 0; } while( ipFALSE_BOOL )
/** Increment head or tail */
static __inline void circ_inc32( int32_t * lHeadOrTail,
uint32_t ulSize )
{
( *lHeadOrTail )++;
if( ( *lHeadOrTail ) >= ( int32_t ) ulSize )
{
( *lHeadOrTail ) = 0;
}
}
/**
* \brief Wait PHY operation to be completed.
*
* \param p_gmac HW controller address.
* \param ul_retry The retry times, 0 to wait forever until completeness.
*
* Return GMAC_OK if the operation is completed successfully.
*/
static uint8_t gmac_wait_phy( Gmac * p_gmac,
const uint32_t ul_retry )
{
volatile uint32_t ul_retry_count = 0;
const uint32_t xPHYPollDelay = pdMS_TO_TICKS( 1ul );
while( !gmac_is_phy_idle( p_gmac ) )
{
if( ul_retry == 0 )
{
continue;
}
ul_retry_count++;
if( ul_retry_count >= ul_retry )
{
return GMAC_TIMEOUT;
}
/* Block the task to allow other tasks to execute while the PHY
* is not connected. */
vTaskDelay( xPHYPollDelay );
}
return GMAC_OK;
}
/**
* \brief Disable transfer, reset registers and descriptor lists.
*
* \param p_dev Pointer to GMAC driver instance.
*
*/
static void gmac_reset_tx_mem( gmac_device_t * p_dev )
{
Gmac * p_hw = p_dev->p_hw;
uint8_t * p_tx_buff = p_dev->p_tx_buffer;
gmac_tx_descriptor_t * p_td = p_dev->p_tx_dscr;
uint32_t ul_index;
uint32_t ul_address;
/* Disable TX */
gmac_enable_transmit( p_hw, 0 );
/* Set up the TX descriptors */
CIRC_CLEAR( p_dev->l_tx_head, p_dev->l_tx_tail );
for( ul_index = 0; ul_index < p_dev->ul_tx_list_size; ul_index++ )
{
#if ( ipconfigZERO_COPY_TX_DRIVER != 0 )
{
ul_address = ( uint32_t ) 0u;
}
#else
{
ul_address = ( uint32_t ) ( &( p_tx_buff[ ul_index * GMAC_TX_UNITSIZE ] ) );
}
#endif /* ipconfigZERO_COPY_TX_DRIVER */
p_td[ ul_index ].addr = ul_address;
p_td[ ul_index ].status.val = GMAC_TXD_USED;
}
p_td[ p_dev->ul_tx_list_size - 1 ].status.val =
GMAC_TXD_USED | GMAC_TXD_WRAP;
/* Set transmit buffer queue */
gmac_set_tx_queue( p_hw, ( uint32_t ) p_td );
}
/**
* \brief Disable receiver, reset registers and descriptor list.
*
* \param p_drv Pointer to GMAC Driver instance.
*/
static void gmac_reset_rx_mem( gmac_device_t * p_dev )
{
Gmac * p_hw = p_dev->p_hw;
uint8_t * p_rx_buff = p_dev->p_rx_buffer;
gmac_rx_descriptor_t * pRd = p_dev->p_rx_dscr;
uint32_t ul_index;
uint32_t ul_address;
/* Disable RX */
gmac_enable_receive( p_hw, 0 );
/* Set up the RX descriptors */
p_dev->ul_rx_idx = 0;
for( ul_index = 0; ul_index < p_dev->ul_rx_list_size; ul_index++ )
{
ul_address = ( uint32_t ) ( &( p_rx_buff[ ul_index * GMAC_RX_UNITSIZE ] ) );
pRd[ ul_index ].addr.val = ul_address & GMAC_RXD_ADDR_MASK;
pRd[ ul_index ].status.val = 0;
}
pRd[ p_dev->ul_rx_list_size - 1 ].addr.val |= GMAC_RXD_WRAP;
/* Set receive buffer queue */
gmac_set_rx_queue( p_hw, ( uint32_t ) pRd );
}
/**
* \brief Initialize the allocated buffer lists for GMAC driver to transfer data.
* Must be invoked after gmac_dev_init() but before RX/TX starts.
*
* \note If input address is not 8-byte aligned, the address is automatically
* adjusted and the list size is reduced by one.
*
* \param p_gmac Pointer to GMAC instance.
* \param p_gmac_dev Pointer to GMAC device instance.
* \param p_dev_mm Pointer to the GMAC memory management control block.
* \param p_tx_cb Pointer to allocated TX callback list.
*
* \return GMAC_OK or GMAC_PARAM.
*/
static uint8_t gmac_init_mem( Gmac * p_gmac,
gmac_device_t * p_gmac_dev,
gmac_dev_mem_t * p_dev_mm
#if ( GMAC_USES_TX_CALLBACK != 0 )
,
gmac_dev_tx_cb_t * p_tx_cb
#endif
)
{
if( ( p_dev_mm->us_rx_size <= 1 ) || p_dev_mm->us_tx_size <= 1
#if ( GMAC_USES_TX_CALLBACK != 0 )
|| p_tx_cb == NULL
#endif
)
{
return GMAC_PARAM;
}
/* Assign RX buffers */
if( ( ( uint32_t ) p_dev_mm->p_rx_buffer & 0x7 ) ||
( ( uint32_t ) p_dev_mm->p_rx_dscr & 0x7 ) )
{
p_dev_mm->us_rx_size--;
}
p_gmac_dev->p_rx_buffer =
( uint8_t * ) ( ( uint32_t ) p_dev_mm->p_rx_buffer & 0xFFFFFFF8 );
p_gmac_dev->p_rx_dscr =
( gmac_rx_descriptor_t * ) ( ( uint32_t ) p_dev_mm->p_rx_dscr
& 0xFFFFFFF8 );
p_gmac_dev->ul_rx_list_size = p_dev_mm->us_rx_size;
/* Assign TX buffers */
if( ( ( uint32_t ) p_dev_mm->p_tx_buffer & 0x7 ) ||
( ( uint32_t ) p_dev_mm->p_tx_dscr & 0x7 ) )
{
p_dev_mm->us_tx_size--;
}
p_gmac_dev->p_tx_buffer =
( uint8_t * ) ( ( uint32_t ) p_dev_mm->p_tx_buffer & 0xFFFFFFF8 );
p_gmac_dev->p_tx_dscr =
( gmac_tx_descriptor_t * ) ( ( uint32_t ) p_dev_mm->p_tx_dscr
& 0xFFFFFFF8 );
p_gmac_dev->ul_tx_list_size = p_dev_mm->us_tx_size;
#if ( GMAC_USES_TX_CALLBACK != 0 )
p_gmac_dev->func_tx_cb_list = p_tx_cb;
#endif
/* Reset TX & RX */
gmac_reset_rx_mem( p_gmac_dev );
gmac_reset_tx_mem( p_gmac_dev );
/* Enable Rx and Tx, plus the statistics register */
gmac_enable_transmit( p_gmac, true );
gmac_enable_receive( p_gmac, true );
gmac_enable_statistics_write( p_gmac, true );
/* Set up the interrupts for transmission and errors */
gmac_enable_interrupt( p_gmac,
GMAC_IER_RXUBR | /* Enable receive used bit read interrupt. */
GMAC_IER_TUR | /* Enable transmit underrun interrupt. */
GMAC_IER_RLEX | /* Enable retry limit exceeded interrupt. */
GMAC_IER_TFC | /* Enable transmit buffers exhausted in mid-frame interrupt. */
GMAC_IER_TCOMP | /* Enable transmit complete interrupt. */
GMAC_IER_ROVR | /* Enable receive overrun interrupt. */
GMAC_IER_HRESP | /* Enable Hresp not OK interrupt. */
GMAC_IER_PFNZ | /* Enable pause frame received interrupt. */
GMAC_IER_PTZ ); /* Enable pause time zero interrupt. */
return GMAC_OK;
}
/**
* \brief Read the PHY register.
*
* \param p_gmac Pointer to the GMAC instance.
* \param uc_phy_address PHY address.
* \param uc_address Register address.
* \param p_value Pointer to a 32-bit location to store read data.
*
* \Return GMAC_OK if successfully, GMAC_TIMEOUT if timeout.
*/
uint8_t gmac_phy_read( Gmac * p_gmac,
uint8_t uc_phy_address,
uint8_t uc_address,
uint32_t * p_value )
{
gmac_maintain_phy( p_gmac, uc_phy_address, uc_address, 1, 0 );
if( gmac_wait_phy( p_gmac, MAC_PHY_RETRY_MAX ) == GMAC_TIMEOUT )
{
return GMAC_TIMEOUT;
}
*p_value = gmac_get_phy_data( p_gmac );
return GMAC_OK;
}
/**
* \brief Write the PHY register.
*
* \param p_gmac Pointer to the GMAC instance.
* \param uc_phy_address PHY Address.
* \param uc_address Register Address.
* \param ul_value Data to write, actually 16-bit data.
*
* \Return GMAC_OK if successfully, GMAC_TIMEOUT if timeout.
*/
uint8_t gmac_phy_write( Gmac * p_gmac,
uint8_t uc_phy_address,
uint8_t uc_address,
uint32_t ul_value )
{
gmac_maintain_phy( p_gmac, uc_phy_address, uc_address, 0, ul_value );
if( gmac_wait_phy( p_gmac, MAC_PHY_RETRY_MAX ) == GMAC_TIMEOUT )
{
return GMAC_TIMEOUT;
}
return GMAC_OK;
}
/**
* \brief Initialize the GMAC driver.
*
* \param p_gmac Pointer to the GMAC instance.
* \param p_gmac_dev Pointer to the GMAC device instance.
* \param p_opt GMAC configure options.
*/
void gmac_dev_init( Gmac * p_gmac,
gmac_device_t * p_gmac_dev,
gmac_options_t * p_opt )
{
gmac_dev_mem_t gmac_dev_mm;
/* Disable TX & RX and more */
gmac_network_control( p_gmac, 0 );
gmac_disable_interrupt( p_gmac, ~0u );
gmac_clear_statistics( p_gmac );
/* Clear all status bits in the receive status register. */
gmac_clear_rx_status( p_gmac, GMAC_RSR_RXOVR | GMAC_RSR_REC | GMAC_RSR_BNA );
/* Clear all status bits in the transmit status register */
gmac_clear_tx_status( p_gmac, GMAC_TSR_UBR | GMAC_TSR_COL | GMAC_TSR_RLE
| GMAC_TSR_TFC | GMAC_TSR_TXCOMP | GMAC_TSR_UND );
/* Clear interrupts */
gmac_get_interrupt_status( p_gmac );
#if !defined( ETHERNET_CONF_DATA_OFFSET )
/* Receive Buffer Offset
* Indicates the number of bytes by which the received data
* is offset from the start of the receive buffer
* which can be handy for alignment reasons */
/* Note: FreeRTOS+TCP wants to have this offset set to 2 bytes */
#error ETHERNET_CONF_DATA_OFFSET not defined, assuming 0
#endif
/* Enable the copy of data into the buffers
* ignore broadcasts, and not copy FCS. */
gmac_set_configure( p_gmac,
( gmac_get_configure( p_gmac ) & ~GMAC_NCFGR_RXBUFO_Msk ) |
GMAC_NCFGR_RFCS | /* Remove FCS, frame check sequence (last 4 bytes) */
GMAC_NCFGR_PEN | /* Pause Enable */
GMAC_NCFGR_RXBUFO( ETHERNET_CONF_DATA_OFFSET ) |
GMAC_RXD_RXCOEN );
/*
* GMAC_DCFGR_TXCOEN: (GMAC_DCFGR) Transmitter Checksum Generation Offload Enable.
* Note: that SAM4E does have RX checksum offloading
* but TX checksum offloading has NOT been implemented.
*/
gmac_set_dma( p_gmac,
gmac_get_dma( p_gmac ) | GMAC_DCFGR_TXCOEN );
gmac_enable_copy_all( p_gmac, p_opt->uc_copy_all_frame );
gmac_disable_broadcast( p_gmac, p_opt->uc_no_boardcast );
/* Fill in GMAC device memory management */
gmac_dev_mm.p_rx_buffer = gs_uc_rx_buffer;
gmac_dev_mm.p_rx_dscr = gs_rx_desc;
gmac_dev_mm.us_rx_size = GMAC_RX_BUFFERS;
#if ( ipconfigZERO_COPY_TX_DRIVER != 0 )
{
gmac_dev_mm.p_tx_buffer = NULL;
}
#else
{
gmac_dev_mm.p_tx_buffer = gs_uc_tx_buffer;
}
#endif
gmac_dev_mm.p_tx_dscr = gs_tx_desc;
gmac_dev_mm.us_tx_size = GMAC_TX_BUFFERS;
gmac_init_mem( p_gmac, p_gmac_dev, &gmac_dev_mm
#if ( GMAC_USES_TX_CALLBACK != 0 )
, gs_tx_callback
#endif
);
gmac_set_address( p_gmac, 0, p_opt->uc_mac_addr );
}
/**
* \brief Frames can be read from the GMAC in multiple sections.
*
* Returns > 0 if a complete frame is available
* It also it cleans up incomplete older frames
*/
static uint32_t gmac_dev_poll( gmac_device_t * p_gmac_dev )
{
uint32_t ulReturn = 0;
int32_t ulIndex = p_gmac_dev->ul_rx_idx;
gmac_rx_descriptor_t * pxHead = &p_gmac_dev->p_rx_dscr[ ulIndex ];
/* Discard any incomplete frames */
while( ( pxHead->addr.val & GMAC_RXD_OWNERSHIP ) &&
( pxHead->status.val & GMAC_RXD_SOF ) == 0 )
{
pxHead->addr.val &= ~( GMAC_RXD_OWNERSHIP );
circ_inc32( &ulIndex, p_gmac_dev->ul_rx_list_size );
pxHead = &p_gmac_dev->p_rx_dscr[ ulIndex ];
p_gmac_dev->ul_rx_idx = ulIndex;
#if ( GMAC_STATS != 0 )
{
gmacStats.incompCount++;
}
#endif
}
while( ( pxHead->addr.val & GMAC_RXD_OWNERSHIP ) != 0 )
{
if( ( pxHead->status.val & GMAC_RXD_EOF ) != 0 )
{
/* Here a complete frame has been seen with SOF and EOF */
ulReturn = pxHead->status.bm.len;
break;
}
circ_inc32( &ulIndex, p_gmac_dev->ul_rx_list_size );
pxHead = &p_gmac_dev->p_rx_dscr[ ulIndex ];
if( ( pxHead->addr.val & GMAC_RXD_OWNERSHIP ) == 0 )
{
/* CPU is not the owner (yet) */
break;
}
if( ( pxHead->status.val & GMAC_RXD_SOF ) != 0 )
{
/* Strange, we found a new Start Of Frame
* discard previous segments */
int32_t ulPrev = p_gmac_dev->ul_rx_idx;
pxHead = &p_gmac_dev->p_rx_dscr[ ulPrev ];
do
{
pxHead->addr.val &= ~( GMAC_RXD_OWNERSHIP );
circ_inc32( &ulPrev, p_gmac_dev->ul_rx_list_size );
pxHead = &p_gmac_dev->p_rx_dscr[ ulPrev ];
#if ( GMAC_STATS != 0 )
{
gmacStats.truncCount++;
}
#endif
} while( ulPrev != ulIndex );
p_gmac_dev->ul_rx_idx = ulIndex;
}
}
return ulReturn;
}
/**
* \brief Frames can be read from the GMAC in multiple sections.
* Read ul_frame_size bytes from the GMAC receive buffers to pcTo.
* p_rcv_size is the size of the entire frame. Generally gmac_read
* will be repeatedly called until the sum of all the ul_frame_size equals
* the value of p_rcv_size.
*
* \param p_gmac_dev Pointer to the GMAC device instance.
* \param p_frame Address of the frame buffer.
* \param ul_frame_size Length of the frame.
* \param p_rcv_size Received frame size.
*
* \return GMAC_OK if receiving frame successfully, otherwise failed.
*/
uint32_t gmac_dev_read( gmac_device_t * p_gmac_dev,
uint8_t * p_frame,
uint32_t ul_frame_size,
uint32_t * p_rcv_size )
{
int32_t nextIdx; /* A copy of the Rx-index 'ul_rx_idx' */
int32_t bytesLeft = gmac_dev_poll( p_gmac_dev );
gmac_rx_descriptor_t * pxHead;
if( bytesLeft == 0 )
{
return GMAC_RX_NULL;
}
/* gmac_dev_poll has confirmed that there is a complete frame at
* the current position 'ul_rx_idx'
*/
nextIdx = p_gmac_dev->ul_rx_idx;
/* Read +2 bytes because buffers are aligned at -2 bytes */
bytesLeft = min( bytesLeft + 2, ( int32_t ) ul_frame_size );
/* The frame will be copied in 1 or 2 memcpy's */
if( ( p_frame != NULL ) && ( bytesLeft != 0 ) )
{
const uint8_t * source;
int32_t left;
int32_t toCopy;
source = p_gmac_dev->p_rx_buffer + nextIdx * GMAC_RX_UNITSIZE;
left = bytesLeft;
toCopy = ( p_gmac_dev->ul_rx_list_size - nextIdx ) * GMAC_RX_UNITSIZE;
if( toCopy > left )
{
toCopy = left;
}
memcpy( p_frame, source, toCopy );
left -= toCopy;
if( left != 0ul )
{
memcpy( p_frame + toCopy, ( void * ) p_gmac_dev->p_rx_buffer, left );
}
}
do
{
pxHead = &p_gmac_dev->p_rx_dscr[ nextIdx ];
pxHead->addr.val &= ~( GMAC_RXD_OWNERSHIP );
circ_inc32( &nextIdx, p_gmac_dev->ul_rx_list_size );
} while( ( pxHead->status.val & GMAC_RXD_EOF ) == 0 );
p_gmac_dev->ul_rx_idx = nextIdx;
*p_rcv_size = bytesLeft;
return GMAC_OK;
}
extern void vGMACGenerateChecksum( uint8_t * apBuffer );
/**
* \brief Send ulLength bytes from pcFrom. This copies the buffer to one of the
* GMAC Tx buffers, and then indicates to the GMAC that the buffer is ready.
* If lEndOfFrame is true then the data being copied is the end of the frame
* and the frame can be transmitted.
*
* \param p_gmac_dev Pointer to the GMAC device instance.
* \param p_buffer Pointer to the data buffer.
* \param ul_size Length of the frame.
* \param func_tx_cb Transmit callback function.
*
* \return Length sent.
*/
uint32_t gmac_dev_write( gmac_device_t * p_gmac_dev,
void * p_buffer,
uint32_t ul_size,
gmac_dev_tx_cb_t func_tx_cb )
{
volatile gmac_tx_descriptor_t * p_tx_td;
#if ( GMAC_USES_TX_CALLBACK != 0 )
volatile gmac_dev_tx_cb_t * p_func_tx_cb;
#endif
Gmac * p_hw = p_gmac_dev->p_hw;
#if ( GMAC_USES_TX_CALLBACK == 0 )
( void ) func_tx_cb;
#endif
/* Check parameter */
if( ul_size > GMAC_TX_UNITSIZE )
{
return GMAC_PARAM;
}
/* Pointers to the current transmit descriptor */
p_tx_td = &p_gmac_dev->p_tx_dscr[ p_gmac_dev->l_tx_head ];
/* If no free TxTd, buffer can't be sent, schedule the wakeup callback */
/* if (CIRC_SPACE(p_gmac_dev->l_tx_head, p_gmac_dev->l_tx_tail, */
/* p_gmac_dev->ul_tx_list_size) == 0) */
{
if( ( p_tx_td->status.val & GMAC_TXD_USED ) == 0 )
{
return GMAC_TX_BUSY;
}
}
#if ( GMAC_USES_TX_CALLBACK != 0 )
/* Pointers to the current Tx callback */
p_func_tx_cb = &p_gmac_dev->func_tx_cb_list[ p_gmac_dev->l_tx_head ];
#endif
/* Set up/copy data to transmission buffer */
if( p_buffer && ul_size )
{
/* Driver manages the ring buffer */
/* Calculating the checksum here is faster than calculating it from the GMAC buffer
* because within p_buffer, it is well aligned */
#if ( ipconfigZERO_COPY_TX_DRIVER != 0 )
{
/* Zero-copy... */
p_tx_td->addr = ( uint32_t ) p_buffer;
}
#else
{
/* Or Memcopy... */
memcpy( ( void * ) p_tx_td->addr, p_buffer, ul_size );
}
#endif /* ipconfigZERO_COPY_TX_DRIVER */
vGMACGenerateChecksum( ( uint8_t * ) p_tx_td->addr );
}
#if ( GMAC_USES_TX_CALLBACK != 0 )
/* Tx callback */
*p_func_tx_cb = func_tx_cb;
#endif
/* Update transmit descriptor status */
/* The buffer size defined is the length of ethernet frame,
* so it's always the last buffer of the frame. */
if( p_gmac_dev->l_tx_head == ( int32_t ) ( p_gmac_dev->ul_tx_list_size - 1 ) )
{
/* No need to 'and' with GMAC_TXD_LEN_MASK because ul_size has been checked */
p_tx_td->status.val =
ul_size | GMAC_TXD_LAST | GMAC_TXD_WRAP;
}
else
{
p_tx_td->status.val =
ul_size | GMAC_TXD_LAST;
}
circ_inc32( &p_gmac_dev->l_tx_head, p_gmac_dev->ul_tx_list_size );
/* Now start to transmit if it is still not done */
gmac_start_transmission( p_hw );
return GMAC_OK;
}
/**
* \brief Get current load of transmit.
*
* \param p_gmac_dev Pointer to the GMAC device instance.
*
* \return Current load of transmit.
*/
#if ( GMAC_USES_TX_CALLBACK != 0 )
/* Without defining GMAC_USES_TX_CALLBACK, l_tx_tail won't be updated */
uint32_t gmac_dev_get_tx_load( gmac_device_t * p_gmac_dev )
{
uint16_t us_head = p_gmac_dev->l_tx_head;
uint16_t us_tail = p_gmac_dev->l_tx_tail;
return CIRC_CNT( us_head, us_tail, p_gmac_dev->ul_tx_list_size );
}
#endif
/**
* \brief Register/Clear RX callback. Callback will be invoked after the next received
* frame.
*
* When gmac_dev_read() returns GMAC_RX_NULL, the application task calls
* gmac_dev_set_rx_callback() to register func_rx_cb() callback and enters suspend state.
* The callback is in charge to resume the task once a new frame has been
* received. The next time gmac_dev_read() is called, it will be successful.
*
* This function is usually invoked from the RX callback itself with NULL
* callback, to unregister. Once the callback has resumed the application task,
* there is no need to invoke the callback again.
*
* \param p_gmac_dev Pointer to the GMAC device instance.
* \param func_tx_cb Receive callback function.
*/
void gmac_dev_set_rx_callback( gmac_device_t * p_gmac_dev,
gmac_dev_rx_cb_t func_rx_cb )
{
Gmac * p_hw = p_gmac_dev->p_hw;
if( func_rx_cb == NULL )
{
gmac_disable_interrupt( p_hw, GMAC_IDR_RCOMP );
p_gmac_dev->func_rx_cb = NULL;
}
else
{
p_gmac_dev->func_rx_cb = func_rx_cb;
gmac_enable_interrupt( p_hw, GMAC_IER_RCOMP );
}
}
/**
* \brief Register/Clear TX wakeup callback.
*
* When gmac_dev_write() returns GMAC_TX_BUSY (all transmit descriptor busy), the application
* task calls gmac_dev_set_tx_wakeup_callback() to register func_wakeup() callback and
* enters suspend state. The callback is in charge to resume the task once
* several transmit descriptors have been released. The next time gmac_dev_write() will be called,
* it shall be successful.
*
* This function is usually invoked with NULL callback from the TX wakeup
* callback itself, to unregister. Once the callback has resumed the
* application task, there is no need to invoke the callback again.
*
* \param p_gmac_dev Pointer to GMAC device instance.
* \param func_wakeup Pointer to wakeup callback function.
* \param uc_threshold Number of free transmit descriptor before wakeup callback invoked.
*
* \return GMAC_OK, GMAC_PARAM on parameter error.
*/
#if ( GMAC_USES_WAKEUP_CALLBACK )
uint8_t gmac_dev_set_tx_wakeup_callback( gmac_device_t * p_gmac_dev,
gmac_dev_wakeup_cb_t func_wakeup_cb,
uint8_t uc_threshold )
{
if( func_wakeup_cb == NULL )
{
p_gmac_dev->func_wakeup_cb = NULL;
}
else
{
if( uc_threshold <= p_gmac_dev->ul_tx_list_size )
{
p_gmac_dev->func_wakeup_cb = func_wakeup_cb;
p_gmac_dev->uc_wakeup_threshold = uc_threshold;
}
else
{
return GMAC_PARAM;
}
}
return GMAC_OK;
}
#endif /* GMAC_USES_WAKEUP_CALLBACK */
/**
* \brief Reset TX & RX queue & statistics.
*
* \param p_gmac_dev Pointer to GMAC device instance.
*/
void gmac_dev_reset( gmac_device_t * p_gmac_dev )
{
Gmac * p_hw = p_gmac_dev->p_hw;
gmac_reset_rx_mem( p_gmac_dev );
gmac_reset_tx_mem( p_gmac_dev );
gmac_network_control( p_hw, GMAC_NCR_TXEN | GMAC_NCR_RXEN
| GMAC_NCR_WESTAT | GMAC_NCR_CLRSTAT );
}
void gmac_dev_halt( Gmac * p_gmac );
void gmac_dev_halt( Gmac * p_gmac )
{
gmac_network_control( p_gmac, GMAC_NCR_WESTAT | GMAC_NCR_CLRSTAT );
gmac_disable_interrupt( p_gmac, ~0u );
}
/**
* \brief GMAC Interrupt handler.
*
* \param p_gmac_dev Pointer to GMAC device instance.
*/
#if ( GMAC_STATS != 0 )
extern int logPrintf( const char * pcFormat,
... );
void gmac_show_irq_counts()
{
int index;
for( index = 0; index < ARRAY_SIZE( intPairs ); index++ )
{
if( gmacStats.intStatus[ intPairs[ index ].index ] )
{
logPrintf( "%s : %6u\n", intPairs[ index ].name, gmacStats.intStatus[ intPairs[ index ].index ] );
}
}
}
#endif /* if ( GMAC_STATS != 0 ) */
void gmac_handler( gmac_device_t * p_gmac_dev )
{
Gmac * p_hw = p_gmac_dev->p_hw;
#if ( GMAC_USES_TX_CALLBACK != 0 )
gmac_tx_descriptor_t * p_tx_td;
gmac_dev_tx_cb_t * p_tx_cb = NULL;
uint32_t ul_tx_status_flag;
#endif
#if ( GMAC_STATS != 0 )
int index;
#endif
/* volatile */ uint32_t ul_isr;
/* volatile */ uint32_t ul_rsr;
/* volatile */ uint32_t ul_tsr;
ul_isr = gmac_get_interrupt_status( p_hw );
ul_rsr = gmac_get_rx_status( p_hw );
ul_tsr = gmac_get_tx_status( p_hw );
/* Why clear bits that are ignored anyway ? */
/* ul_isr &= ~(gmac_get_interrupt_mask(p_hw) | 0xF8030300); */
#if ( GMAC_STATS != 0 )
{
for( index = 0; index < ARRAY_SIZE( intPairs ); index++ )
{
if( ul_isr & intPairs[ index ].mask )
{
gmacStats.intStatus[ intPairs[ index ].index ]++;
}
}
}
#endif /* GMAC_STATS != 0 */
/* RX packet */
if( ( ul_isr & GMAC_ISR_RCOMP ) || ( ul_rsr & ( GMAC_RSR_REC | GMAC_RSR_RXOVR | GMAC_RSR_BNA ) ) )
{
/* Clear status */
gmac_clear_rx_status( p_hw, ul_rsr );
if( ul_isr & GMAC_ISR_RCOMP )
{
ul_rsr |= GMAC_RSR_REC;
}
/* Invoke callbacks which can be useful to wake up a task */
if( p_gmac_dev->func_rx_cb )
{
p_gmac_dev->func_rx_cb( ul_rsr );
}
}
/* TX packet */
if( ( ul_isr & GMAC_ISR_TCOMP ) || ( ul_tsr & ( GMAC_TSR_TXCOMP | GMAC_TSR_COL | GMAC_TSR_RLE | GMAC_TSR_UND ) ) )
{
#if ( GMAC_USES_TX_CALLBACK != 0 )
ul_tx_status_flag = GMAC_TSR_TXCOMP;
#endif
/* A frame transmitted */
/* Check RLE */
if( ul_tsr & GMAC_TSR_RLE )
{
/* Status RLE & Number of discarded buffers */
#if ( GMAC_USES_TX_CALLBACK != 0 )
ul_tx_status_flag = GMAC_TSR_RLE | CIRC_CNT( p_gmac_dev->l_tx_head,
p_gmac_dev->l_tx_tail, p_gmac_dev->ul_tx_list_size );
p_tx_cb = &p_gmac_dev->func_tx_cb_list[ p_gmac_dev->l_tx_tail ];
#endif
gmac_reset_tx_mem( p_gmac_dev );
gmac_enable_transmit( p_hw, 1 );
}
/* Clear status */
gmac_clear_tx_status( p_hw, ul_tsr );
#if ( GMAC_USES_TX_CALLBACK != 0 )
if( !CIRC_EMPTY( p_gmac_dev->l_tx_head, p_gmac_dev->l_tx_tail ) )
{
/* Check the buffers */
do
{
p_tx_td = &p_gmac_dev->p_tx_dscr[ p_gmac_dev->l_tx_tail ];
p_tx_cb = &p_gmac_dev->func_tx_cb_list[ p_gmac_dev->l_tx_tail ];
/* Any error? Exit if buffer has not been sent yet */
if( ( p_tx_td->status.val & GMAC_TXD_USED ) == 0 )
{
break;
}
/* Notify upper layer that a packet has been sent */
if( *p_tx_cb )
{
( *p_tx_cb )( ul_tx_status_flag, ( void * ) p_tx_td->addr );
#if ( ipconfigZERO_COPY_TX_DRIVER != 0 )
{
p_tx_td->addr = 0ul;
}
#endif /* ipconfigZERO_COPY_TX_DRIVER */
}
circ_inc32( &p_gmac_dev->l_tx_tail, p_gmac_dev->ul_tx_list_size );
} while( CIRC_CNT( p_gmac_dev->l_tx_head, p_gmac_dev->l_tx_tail,
p_gmac_dev->ul_tx_list_size ) );
}
if( ul_tsr & GMAC_TSR_RLE )
{
/* Notify upper layer RLE */
if( *p_tx_cb )
{
( *p_tx_cb )( ul_tx_status_flag, NULL );
}
}
#endif /* GMAC_USES_TX_CALLBACK */
#if ( GMAC_USES_WAKEUP_CALLBACK )
/* If a wakeup has been scheduled, notify upper layer that it can
* send other packets, and the sending will be successful. */
if( ( CIRC_SPACE( p_gmac_dev->l_tx_head, p_gmac_dev->l_tx_tail,
p_gmac_dev->ul_tx_list_size ) >= p_gmac_dev->uc_wakeup_threshold ) &&
p_gmac_dev->func_wakeup_cb )
{
p_gmac_dev->func_wakeup_cb();
}
#endif
}
}
/*@} */
/*/ @cond 0 */
/**INDENT-OFF**/
#ifdef __cplusplus
}
#endif
/**INDENT-ON**/
/*/ @endcond */