blob: b551875172a1b3f7faa591347c0af5c0ceca6f68 [file] [log] [blame]
/* ----------------------------------------------------------------------------
* SAM Software Package License
* ----------------------------------------------------------------------------
* Copyright (c) 2015, Atmel Corporation
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the disclaimer below.
*
* Atmel's name may not be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* DISCLAIMER: 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
* 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.
* ----------------------------------------------------------------------------
*/
/*---------------------------------------------------------------------------
* Headers
*---------------------------------------------------------------------------*/
#include "chip.h"
#include "trace.h"
#include "ring.h"
#include "peripherals/aic.h"
#include "peripherals/gmacd.h"
#include "peripherals/gmac.h"
#include "peripherals/l2cc.h"
#include "peripherals/pmc.h"
#include <string.h>
#include <assert.h>
//------------------------------------------------------------------------------
// Definitions
//------------------------------------------------------------------------------
// Interrupt bits
#define GMAC_INT_RX_BITS (GMAC_IER_RCOMP | GMAC_IER_RXUBR | GMAC_IER_ROVR)
#define GMAC_INT_TX_ERR_BITS (GMAC_IER_TUR | GMAC_IER_RLEX | GMAC_IER_TFC)
#define GMAC_INT_TX_BITS (GMAC_INT_TX_ERR_BITS | GMAC_IER_TCOMP)
/*---------------------------------------------------------------------------
* Types
*---------------------------------------------------------------------------*/
struct _gmacd_irq_handler {
Gmac* addr;
struct _gmacd** gmacd;
uint32_t irq;
aic_handler_t handler;
};
/*---------------------------------------------------------------------------
* IRQ Handlers
*---------------------------------------------------------------------------*/
#ifdef CONFIG_HAVE_GMAC_QUEUES
#if GMAC_NUM_QUEUES != 3
#error This driver assumes that GMAC_NUM_QUEUES is 3
#endif
#endif
static struct _gmacd* _gmacd0;
#ifdef GMAC1
static struct _gmacd* _gmacd1;
#endif
static void _gmacd_handler(struct _gmacd* gmacd, uint8_t queue);
static void _gmacd_gmac0_irq_handler(void)
{
_gmacd_handler(_gmacd0, 0);
}
#ifdef CONFIG_HAVE_GMAC_QUEUES
static void _gmacd_gmac0q1_irq_handler(void)
{
_gmacd_handler(_gmacd0, 1);
}
static void _gmacd_gmac0q2_irq_handler(void)
{
_gmacd_handler(_gmacd0, 2);
}
#endif
#ifdef GMAC1
static void _gmacd_gmac1_irq_handler(void)
{
_gmacd_handler(_gmacd1, 0);
}
#ifdef CONFIG_HAVE_GMAC_QUEUES
static void _gmacd_gmac1q1_irq_handler(void)
{
_gmacd_handler(_gmacd1, 1);
}
static void _gmacd_gmac1q2_irq_handler(void)
{
_gmacd_handler(_gmacd1, 2);
}
#endif
#endif
static const struct _gmacd_irq_handler _gmacd_irq_handlers[] = {
{ GMAC0, &_gmacd0, ID_GMAC0, _gmacd_gmac0_irq_handler },
#ifdef CONFIG_HAVE_GMAC_QUEUES
{ GMAC0, &_gmacd0, ID_GMAC0_Q1, _gmacd_gmac0q1_irq_handler },
{ GMAC0, &_gmacd0, ID_GMAC0_Q2, _gmacd_gmac0q2_irq_handler },
#endif
#ifdef GMAC1
{ GMAC1, &_gmacd1, ID_GMAC1, _gmacd_gmac1_irq_handler },
#ifdef CONFIG_HAVE_GMAC_QUEUES
{ GMAC1, &_gmacd1, ID_GMAC1_Q1, _gmacd_gmac1q1_irq_handler },
{ GMAC1, &_gmacd1, ID_GMAC1_Q2, _gmacd_gmac1q2_irq_handler },
#endif
#endif
};
/*---------------------------------------------------------------------------
* Dummy Buffers for unconfigured queues
*---------------------------------------------------------------------------*/
#define DUMMY_BUFFERS 2
#define DUMMY_UNITSIZE 128
/** TX descriptors list */
ALIGNED(8) SECTION(".region_ddr_nocache")
static struct _gmac_desc dummy_tx_desc[DUMMY_BUFFERS];
/** RX descriptors list */
ALIGNED(8) SECTION(".region_ddr_nocache")
static struct _gmac_desc dummy_rx_desc[DUMMY_BUFFERS];
/** Send Buffer */
ALIGNED(32)
static uint8_t dummy_buffer[DUMMY_BUFFERS * DUMMY_UNITSIZE];
/*---------------------------------------------------------------------------
* Local functions
*---------------------------------------------------------------------------*/
/**
* \brief Disable TX & reset registers and descriptor list
* \param gmacd Pointer to GMAC Driver instance.
*/
static void _gmacd_reset_tx(struct _gmacd* gmacd, uint8_t queue)
{
struct _gmacd_queue* q = &gmacd->queues[queue];
uint32_t addr = (uint32_t)q->tx_buffer;
uint32_t i;
/* Disable TX */
gmac_transmit_enable(gmacd->gmac, false);
/* Setup the TX descriptors */
RING_CLEAR(q->tx_head, q->tx_tail);
for (i = 0; i < q->tx_size; i++) {
q->tx_desc[i].addr = addr;
DSB();
q->tx_desc[i].status = GMAC_TX_STATUS_USED;
addr += GMAC_TX_UNITSIZE;
}
q->tx_desc[q->tx_size - 1].status |= GMAC_TX_STATUS_WRAP;
/* Transmit Buffer Queue Pointer Register */
gmac_set_tx_desc(gmacd->gmac, queue, q->tx_desc);
}
/**
* \brief Disable RX & reset registers and descriptor list
* \param gmacd Pointer to GMAC Driver instance.
*/
static void _gmacd_reset_rx(struct _gmacd* gmacd, uint8_t queue)
{
struct _gmacd_queue* q = &gmacd->queues[queue];
uint32_t addr = (uint32_t)q->rx_buffer;
uint32_t i;
/* Disable RX */
gmac_receive_enable(gmacd->gmac, false);
/* Setup the RX descriptors */
q->rx_head = 0;
for (i = 0; i < q->rx_size; i++) {
q->rx_desc[i].addr = addr & GMAC_RX_ADDR_MASK;
DSB();
q->rx_desc[i].status = 0;
addr += GMAC_RX_UNITSIZE;
}
q->rx_desc[q->rx_size - 1].addr |= GMAC_RX_ADDR_WRAP;
/* Receive Buffer Queue Pointer Register */
gmac_set_rx_desc(gmacd->gmac, queue, q->rx_desc);
}
/**
* \brief Process successfully sent packets
* \param gmacd Pointer to GMAC Driver instance.
*/
static void _gmacd_tx_complete_handler(struct _gmacd* gmacd, uint8_t queue)
{
Gmac* gmac = gmacd->gmac;
struct _gmacd_queue* q = &gmacd->queues[queue];
struct _gmac_desc *desc;
gmacd_callback_t callback;
uint32_t tsr;
//printf("<TX>\r\n");
/* Clear status */
tsr = gmac_get_tx_status(gmac);
gmac_clear_tx_status(gmac, tsr);
while (!RING_EMPTY(q->tx_head, q->tx_tail)) {
desc = &q->tx_desc[q->tx_tail];
/* Exit if frame has not been sent yet:
* On TX completion, the GMAC set the USED bit only into the
* very first buffer descriptor of the sent frame.
* Otherwise it updates this descriptor with status error bits.
* This is the descriptor writeback.
*/
if ((desc->status & GMAC_TX_STATUS_USED) == 0)
break;
/* Process all buffers of the current transmitted frame */
while ((desc->status & GMAC_TX_STATUS_LASTBUF) == 0) {
RING_INC(q->tx_tail, q->tx_size);
desc = &q->tx_desc[q->tx_tail];
}
/* Notify upper layer that a frame has been sent */
if (q->tx_callbacks) {
callback = q->tx_callbacks[q->tx_tail];
if (callback)
callback(queue, tsr);
}
/* Go to next frame */
RING_INC(q->tx_tail, q->tx_size);
}
/* If a wakeup callback has been set, notify upper layer that it can
send more packets now */
if (q->tx_wakeup_callback) {
if (RING_SPACE(q->tx_head, q->tx_tail, q->tx_size) >=
q->tx_wakeup_threshold) {
q->tx_wakeup_callback(queue);
}
}
}
/**
* \brief Reset TX queue when errors are detected
* \param gmacd Pointer to GMAC Driver instance.
*/
static void _gmacd_tx_error_handler(struct _gmacd* gmacd, uint8_t queue)
{
Gmac *gmac = gmacd->gmac;
struct _gmacd_queue* q = &gmacd->queues[queue];
struct _gmac_desc* desc;
gmacd_callback_t callback;
uint32_t tsr;
printf("<TXERR>\r\n");
/* Clear TXEN bit into the Network Configuration Register:
* this is a workaround to recover from TX lockups that
* occur on sama5d4 gmac (r1p24f2) when using scatter-gather.
* This issue has never been seen on sama5d4 gmac (r1p31).
*/
gmac_transmit_enable(gmac, false);
/* The following step should be optional since this function is called
* directly by the IRQ handler. Indeed, according to Cadence
* documentation, the transmission is halted on errors such as
* too many retries or transmit under run.
* However it would become mandatory if the call of this function
* were scheduled as a task by the IRQ handler (this is how Linux
* driver works). Then this function might compete with GMACD_Send().
*
* Setting bit 10, tx_halt, of the Network Control Register is not enough:
* We should wait for bit 3, tx_go, of the Transmit Status Register to
* be cleared at transmit completion if a frame is being transmitted.
*/
gmac_halt_transmission(gmac);
while (gmac_get_tx_status(gmac) & GMAC_TSR_TXGO);
/* Treat frames in TX queue including the ones that caused the error. */
while (!RING_EMPTY(q->tx_head, q->tx_tail)) {
int tx_completed = 0;
desc = &q->tx_desc[q->tx_tail];
/* Check USED bit on the very first buffer descriptor to validate
* TX completion.
*/
if (desc->status & GMAC_TX_STATUS_USED)
tx_completed = 1;
/* Go to the last buffer descriptor of the frame */
while ((desc->status & GMAC_TX_STATUS_LASTBUF) == 0) {
RING_INC(q->tx_tail, q->tx_size);
desc = &q->tx_desc[q->tx_tail];
}
/* Notify upper layer that a frame status */
// TODO: which error to notify?
if (q->tx_callbacks) {
callback = q->tx_callbacks[q->tx_tail];
if (callback)
callback(queue, tx_completed ? GMAC_TSR_TXCOMP : 0);
}
/* Go to next frame */
RING_INC(q->tx_tail, q->tx_size);
}
/* Reset TX queue */
_gmacd_reset_tx(gmacd, queue);
/* Clear status */
tsr = gmac_get_tx_status(gmac);
gmac_clear_tx_status(gmac, tsr);
/* Now we are ready to start transmission again */
gmac_transmit_enable(gmac, true);
if (q->tx_wakeup_callback)
q->tx_wakeup_callback(queue);
}
/*---------------------------------------------------------------------------
* Exported functions
*---------------------------------------------------------------------------*/
/**
* \brief GMAC Interrupt handler
* \param gmacd Pointer to GMAC Driver instance.
*/
static void _gmacd_handler(struct _gmacd * gmacd, uint8_t queue)
{
Gmac *gmac = gmacd->gmac;
struct _gmacd_queue* q = &gmacd->queues[queue];
uint32_t isr;
uint32_t rsr;
/* Interrupt Status Register is cleared on read */
while ((isr = gmac_get_it_status(gmac, queue)) != 0) {
/* RX packet */
if (isr & GMAC_INT_RX_BITS) {
/* Clear status */
rsr = gmac_get_rx_status(gmac);
gmac_clear_rx_status(gmac, rsr);
/* Invoke callback */
if (q->rx_callback)
q->rx_callback(queue, rsr);
}
/* TX error */
if (isr & GMAC_INT_TX_ERR_BITS) {
_gmacd_tx_error_handler(gmacd, queue);
break;
}
/* TX packet */
if (isr & GMAC_IER_TCOMP) {
_gmacd_tx_complete_handler(gmacd, queue);
}
/* HRESP not OK */
if (isr & GMAC_IER_HRESP) {
trace_error("HRESP not OK\n\r");
}
}
}
/**
* \brief Initialize the GMAC with the Gmac controller address
* \param gmacd Pointer to GMAC Driver instance.
* \param gmac Pointer to HW address for registers.
* \param enableCAF Enable/Disable CopyAllFrame.
* \param enableNBC Enable/Disable NoBroadCast.
*/
void gmacd_configure(struct _gmacd * gmacd,
Gmac * gmac, uint8_t enableCAF, uint8_t enableNBC)
{
uint32_t ncfgr;
int i;
/* Initialize struct */
gmacd->gmac = gmac;
gmac_configure(gmac);
uint32_t id = get_gmac_id_from_addr(gmac);
for (i = 0; i < ARRAY_SIZE(_gmacd_irq_handlers); i++) {
if (_gmacd_irq_handlers[i].addr == gmac) {
*_gmacd_irq_handlers[i].gmacd = gmacd;
aic_set_source_vector(_gmacd_irq_handlers[i].irq,
_gmacd_irq_handlers[i].handler);
}
}
aic_enable(id);
/* Enable the copy of data into the buffers
ignore broadcasts, and don't copy FCS. */
ncfgr = gmac_get_network_config_register(gmac);
ncfgr |= GMAC_NCFGR_FD;
if (enableCAF) {
ncfgr |= GMAC_NCFGR_CAF;
}
if (enableNBC) {
ncfgr |= GMAC_NCFGR_NBC;
}
gmac_set_network_config_register(gmac, ncfgr);
for (i = 0; i < GMAC_NUM_QUEUES; i++) {
gmacd_setup_queue(gmacd, i,
DUMMY_BUFFERS, dummy_buffer, dummy_rx_desc,
DUMMY_BUFFERS, dummy_buffer, dummy_tx_desc,
NULL);
}
}
/**
* Initialize necessary allocated buffer lists for GMAC Driver to transfer data.
* Must be invoked after GMACD_Init() but before RX/TX start.
* \param gmacd Pointer to GMAC Driver instance.
* \param rx_buffer Pointer to allocated buffer for RX. The address should
* be 8-byte aligned and the size should be
* GMAC_RX_UNITSIZE * wRxSize.
* \param rx_desc Pointer to allocated RX descriptor list.
* \param wRxSize RX size, in number of registered units (RX descriptors).
* \param tx_buffer Pointer to allocated buffer for TX. The address should
* be 8-byte aligned and the size should be
* GMAC_TX_UNITSIZE * wTxSize.
* \param tx_desc Pointer to allocated TX descriptor list.
* \param pTxCb Pointer to allocated TX callback list.
* \param wTxSize TX size, in number of registered units (TX descriptors).
* \return GMACD_OK or GMACD_PARAM.
* \note If input address is not 8-byte aligned the address is automatically
* adjusted and the list size is reduced by one.
*/
uint8_t gmacd_setup_queue(struct _gmacd* gmacd, uint8_t queue,
uint16_t rx_size, uint8_t* rx_buffer, struct _gmac_desc* rx_desc,
uint16_t tx_size, uint8_t* tx_buffer, struct _gmac_desc* tx_desc,
gmacd_callback_t *tx_callbacks)
{
Gmac *gmac = gmacd->gmac;
struct _gmacd_queue* q = &gmacd->queues[queue];
if (rx_size <= 1 || tx_size <= 1)
return GMACD_PARAM;
/* Assign RX buffers */
if (((uint32_t)rx_buffer & 0x7)
|| ((uint32_t)rx_desc & 0x7)) {
rx_size--;
trace_debug("RX list address adjusted\n\r");
}
q->rx_buffer = (uint8_t*)((uint32_t)rx_buffer & 0xFFFFFFF8);
q->rx_desc = (struct _gmac_desc *)((uint32_t)rx_desc & 0xFFFFFFF8);
q->rx_size = rx_size;
q->rx_callback = NULL;
/* Assign TX buffers */
if (((uint32_t)tx_buffer & 0x7)
|| ((uint32_t)tx_desc & 0x7)) {
tx_size--;
trace_debug("TX list address adjusted\n\r");
}
q->tx_buffer = (uint8_t*)((uint32_t)tx_buffer & 0xFFFFFFF8);
q->tx_desc = (struct _gmac_desc*)((uint32_t)tx_desc & 0xFFFFFFF8);
q->tx_size = tx_size;
q->tx_callbacks = tx_callbacks;
q->tx_wakeup_callback = NULL;
/* Reset TX & RX */
_gmacd_reset_rx(gmacd, queue);
_gmacd_reset_tx(gmacd, queue);
/* Setup the interrupts for RX/TX completion (and errors) */
gmac_enable_it(gmac, queue, GMAC_INT_RX_BITS | GMAC_INT_TX_BITS | GMAC_IER_HRESP);
return GMACD_OK;
}
void gmacd_start(struct _gmacd * gmacd)
{
/* Enable Rx and Tx, plus the stats register. */
gmac_transmit_enable(gmacd->gmac, true);
gmac_receive_enable(gmacd->gmac, true);
gmac_enable_statistics_write(gmacd->gmac, true);
}
/**
* Reset TX & RX queue & statistics
* \param gmacd Pointer to GMAC Driver instance.
*/
void gmacd_reset(struct _gmacd* gmacd)
{
int i;
for (i = 0; i < GMAC_NUM_QUEUES; i++) {
_gmacd_reset_rx(gmacd, i);
_gmacd_reset_tx(gmacd, i);
}
gmac_set_network_control_register(gmacd->gmac, GMAC_NCR_TXEN | GMAC_NCR_RXEN |
GMAC_NCR_WESTAT | GMAC_NCR_CLRSTAT);
}
/**
* \brief Send a frame splitted into buffers. If the frame size is larger than transfer buffer size
* error returned. If frame transfer status is monitored, specify callback for each frame.
* \param gmacd Pointer to GMAC Driver instance.
* \param sgl Pointer to a scatter-gather list describing the buffers of the ethernet frame.
* \param fTxCb Pointer to callback function.
*/
uint8_t gmacd_send_sg(struct _gmacd* gmacd, uint8_t queue,
const struct _gmac_sg_list* sgl, gmacd_callback_t callback)
{
Gmac* gmac = gmacd->gmac;
struct _gmacd_queue* q = &gmacd->queues[queue];
struct _gmac_desc* desc;
uint16_t idx, tx_head;
int i;
if (callback && !q->tx_callbacks) {
trace_error("Cannot set send callback, no tx_callbacks "\
"buffer configured for queue %u", queue);
}
/* Check parameter */
if (!sgl->size) {
trace_error("gmacd_send_sg: ethernet frame is empty.\r\n");
return GMACD_PARAM;
}
if (sgl->size >= q->tx_size) {
trace_error("gmacd_send_sg: ethernet frame has too many buffers.\r\n");
return GMACD_PARAM;
}
/* Check available space */
if (RING_SPACE(q->tx_head, q->tx_tail, q->tx_size) < sgl->size) {
trace_error("gmacd_send_sg: not enough free buffers in TX queue.\r\n");
return GMACD_TX_BUSY;
}
/* Tag end of TX queue */
tx_head = fixed_mod(q->tx_head + sgl->size, q->tx_size);
idx = tx_head;
if (q->tx_callbacks)
q->tx_callbacks[idx] = NULL;
desc = &q->tx_desc[idx];
desc->status |= GMAC_TX_STATUS_USED;
/* Update buffer descriptors in reverse order to avoid a race
* condition with hardware.
*/
for (i = sgl->size - 1; i >= 0; i--) {
const struct _gmac_sg *sg = &sgl->entries[i];
uint32_t status;
if (sg->size > GMAC_TX_UNITSIZE) {
trace_error("gmacd_send_sg: buffer size is too big.\r\n");
return GMACD_PARAM;
}
RING_DEC(idx, q->tx_size);
/* Reset TX callback */
if (q->tx_callbacks)
q->tx_callbacks[idx] = NULL;
desc = &q->tx_desc[idx];
/* Copy data into transmittion buffer */
if (sg->buffer && sg->size) {
memcpy((void*)desc->addr, sg->buffer, sg->size);
l2cc_clean_region(desc->addr, desc->addr + sg->size);
}
/* Compute buffer descriptor status word */
status = sg->size & GMAC_RX_STATUS_LENGTH_MASK;
if (i == (sgl->size - 1)) {
status |= GMAC_TX_STATUS_LASTBUF;
if (q->tx_callbacks)
q->tx_callbacks[idx] = callback;
}
if (idx == (q->tx_size - 1)) {
status |= GMAC_TX_STATUS_WRAP;
}
/* Update buffer descriptor status word: clear USED bit */
desc->status = status;
DSB();
}
/* Update TX ring buffer pointers */
q->tx_head = tx_head;
/* Now start to transmit if it is not already done */
gmac_start_transmission(gmac);
return GMACD_OK;
}
/**
* \brief Send a packet with GMAC. If the packet size is larger than transfer buffer size
* error returned. If packet transfer status is monitored, specify callback for each packet.
* \param gmacd Pointer to GMAC Driver instance.
* \param pBuffer The buffer to be send
* \param size The size of buffer to be send
* \param fTxCb Threshold Wakeup callback
* \return OK, Busy or invalid packet
*/
uint8_t gmacd_send(struct _gmacd* gmacd, uint8_t queue, void *buffer,
uint32_t size, gmacd_callback_t callback)
{
struct _gmac_sg sg;
struct _gmac_sg_list sgl;
/* Init single entry scatter-gather list */
sg.size = size;
sg.buffer = buffer;
sgl.size = 1;
sgl.entries = &sg;
return gmacd_send_sg(gmacd, queue, &sgl, callback);
}
/**
* Return current load of TX.
* \param gmacd Pointer to GMAC Driver instance.
*/
uint32_t gmacd_get_tx_load(struct _gmacd* gmacd, uint8_t queue)
{
struct _gmacd_queue* q = &gmacd->queues[queue];
return RING_CNT(q->tx_head, q->tx_tail, q->tx_size);
}
/**
* \brief Receive a packet with GMAC.
* If not enough buffer for the packet, the remaining data is lost but right
* frame length is returned.
* \param gmacd Pointer to GMAC Driver instance.
* \param pFrame Buffer to store the frame
* \param frameSize Size of the frame
* \param pRcvSize Received size
* \return OK, no data, or frame too small
*/
uint8_t gmacd_poll(struct _gmacd* gmacd, uint8_t queue,
uint8_t* buffer, uint32_t buffer_size, uint32_t* recv_size)
{
struct _gmacd_queue* q = &gmacd->queues[queue];
struct _gmac_desc *desc;
uint32_t idx;
uint32_t cur_frame_size = 0;
uint8_t *cur_frame = 0;
if (!buffer)
return GMACD_PARAM;
/* Set the default return value */
*recv_size = 0;
/* Process RX descriptors */
idx = q->rx_head;
desc = &q->rx_desc[idx];
while (desc->addr & GMAC_RX_ADDR_OWN) {
/* A start of frame has been received, discard previous fragments */
if (desc->status & GMAC_RX_STATUS_SOF) {
/* Skip previous fragment */
while (idx != q->rx_head) {
desc = &q->rx_desc[q->rx_head];
desc->addr &= ~GMAC_RX_ADDR_OWN;
RING_INC(q->rx_head, q->rx_size);
}
cur_frame = buffer;
cur_frame_size = 0;
}
/* Increment the index */
RING_INC(idx, q->rx_size);
/* Copy data in the frame buffer */
if (cur_frame) {
if (idx == q->rx_head) {
trace_info("no EOF (buffers probably too small)\r\n");
do {
desc = &q->rx_desc[q->rx_head];
desc->addr &= ~GMAC_RX_ADDR_OWN;
RING_INC(q->rx_head, q->rx_size);
} while (idx != q->rx_head);
return GMACD_RX_NULL;
}
/* Copy the buffer into the application frame */
uint32_t length = GMAC_RX_UNITSIZE;
if ((cur_frame_size + length) > buffer_size) {
length = buffer_size - cur_frame_size;
}
uint32_t addr = desc->addr & GMAC_RX_ADDR_MASK;
l2cc_invalidate_region(addr, addr + length);
memcpy(cur_frame, (void*)addr, length);
cur_frame += length;
cur_frame_size += length;
/* An end of frame has been received, return the data */
if (desc->status & GMAC_RX_STATUS_EOF) {
/* Frame size from the GMAC */
*recv_size = desc->status & GMAC_RX_STATUS_LENGTH_MASK;
/* Application frame buffer is too small all
* data have not been copied */
if (cur_frame_size < *recv_size) {
return GMACD_SIZE_TOO_SMALL;
}
/* All data have been copied in the application
* frame buffer => release descriptors */
while (q->rx_head != idx) {
desc = &q->rx_desc[q->rx_head];
desc->addr &= ~GMAC_RX_ADDR_OWN;
RING_INC(q->rx_head, q->rx_size);
}
return GMACD_OK;
}
}
/* SOF has not been detected, skip the fragment */
else {
desc->addr &= ~GMAC_RX_ADDR_OWN;
q->rx_head = idx;
}
/* Process the next buffer */
desc = &q->rx_desc[idx];
}
return GMACD_RX_NULL;
}
/**
* \brief Registers pRxCb callback. Callback will be invoked after the next received
* frame. When GMAC_Poll() returns GMAC_RX_NO_DATA the application task call GMAC_Set_RxCb()
* to register pRxCb() 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_Poll()
* is called, it will be successful.
* \param gmacd Pointer to GMAC Driver instance.
* \param fRxCb Pointer to callback function
* \return OK, no data, or frame too small
*/
void gmacd_set_rx_callback(struct _gmacd* gmacd, uint8_t queue, gmacd_callback_t callback)
{
struct _gmacd_queue* q = &gmacd->queues[queue];
if (!callback) {
gmac_disable_it(gmacd->gmac, queue, GMAC_IDR_RCOMP);
q->rx_callback = NULL;
} else {
q->rx_callback = callback;
gmac_enable_it(gmacd->gmac, queue, GMAC_IER_RCOMP);
}
}
/**
* Register/Clear TX wakeup callback.
*
* When GMACD_Send() returns GMACD_TX_BUSY (all TD busy) the application
* task calls GMACD_SetTxWakeupCallback() to register fWakeup() callback and
* enters suspend state. The callback is in charge to resume the task once
* several TD have been released. The next time GMACD_Send() 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 gmacd Pointer to GMAC Driver instance.
* \param fWakeup Wakeup callback.
* \param bThreshold Number of free TD before wakeup callback invoked.
* \return GMACD_OK, GMACD_PARAM on parameter error.
*/
uint8_t gmacd_set_tx_wakeup_callback(struct _gmacd* gmacd, uint8_t queue,
gmacd_wakeup_cb_t callback, uint16_t threshold)
{
struct _gmacd_queue* q = &gmacd->queues[queue];
if (!callback) {
q->tx_wakeup_callback = NULL;
} else {
if (threshold <= q->tx_size) {
q->tx_wakeup_callback = callback;
q->tx_wakeup_threshold = threshold;
} else {
return GMACD_PARAM;
}
}
return GMACD_OK;
}
/**@}*/