787 lines
24 KiB
C
787 lines
24 KiB
C
/**
|
||
* dwc_eth.c
|
||
*
|
||
* Copyright (c) 2021 Semidrive Semiconductor.
|
||
* All rights reserved.
|
||
*
|
||
* Description: dw eth mac drv
|
||
*
|
||
* Revision History:
|
||
* -----------------
|
||
*/
|
||
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include "debug.h"
|
||
#include "param.h"
|
||
#include "armv7-r/barriers.h"
|
||
#include "armv7-r/cache.h"
|
||
#include "irq_num.h"
|
||
#include "irq.h"
|
||
#include "sdrv_mac_lld.h"
|
||
#include "sdrv_eth.h"
|
||
#include "phy/phy.h"
|
||
|
||
#include <wdt_refresh.h>
|
||
|
||
/* Eth frame header and crc length. */
|
||
#define ETH_FRAME_HEADER_LEN (6 + 6 + 2)
|
||
#define ETH_FRAME_OVERHEAD_LEN (ETH_FRAME_HEADER_LEN + 4)
|
||
|
||
#if !CONFIG_ETH_USE_HEAP
|
||
#define MAX_ETH_CNT 2
|
||
|
||
#ifndef CONFIG_ARCH_CACHE_LINE
|
||
#define CONFIG_ARCH_CACHE_LINE 32
|
||
#endif
|
||
|
||
typedef struct {
|
||
struct eqos_desc desc;
|
||
} __ALIGNED(EQOS_DESCRIPTOR_ALIGN) eqos_desc_t;
|
||
|
||
struct eqos_data {
|
||
bool used;
|
||
dwc_eth_dev_t dev;
|
||
struct eqos_priv eqos;
|
||
eqos_desc_t descs[EQOS_DESCRIPTORS_NUM];
|
||
uint8_t rx_dma_buf[EQOS_RX_BUFFER_SIZE] __ALIGNED(CONFIG_ARCH_CACHE_LINE);
|
||
uint8_t tx_dma_buf[EQOS_TX_BUFFER_SIZE] __ALIGNED(CONFIG_ARCH_CACHE_LINE);
|
||
};
|
||
|
||
static struct eqos_data g_eqos_data[MAX_ETH_CNT];
|
||
#endif
|
||
|
||
static void *eqos_alloc_descs(struct eqos_priv *eqos, unsigned int num)
|
||
{
|
||
/* Align dma descriptors base address & size to cache line to avoid
|
||
* descriptors corrupted by cacahe line invalidation needed by other
|
||
* address, if descriptors has not been wrote to ram.
|
||
*/
|
||
eqos->desc_size = ALIGN(sizeof(struct eqos_desc),
|
||
(unsigned int)EQOS_DESCRIPTOR_ALIGN);
|
||
|
||
#if !CONFIG_ETH_USE_HEAP
|
||
return containerof(eqos, struct eqos_data, eqos)->descs;
|
||
#else
|
||
#if __GNUC__
|
||
return (void *)memalign(eqos->desc_size, num * eqos->desc_size);
|
||
#else
|
||
return aligned_alloc(eqos->desc_size, num * eqos->desc_size);
|
||
#endif
|
||
#endif
|
||
}
|
||
|
||
#if CONFIG_ETH_USE_HEAP
|
||
static void eqos_free_descs(void *descs)
|
||
{
|
||
free(descs);
|
||
}
|
||
#endif
|
||
|
||
static struct eqos_desc *eqos_get_desc(struct eqos_priv *eqos, unsigned int num,
|
||
bool rx)
|
||
{
|
||
void *desc = (uint8_t *)eqos->descs +
|
||
((rx ? EQOS_DESCRIPTORS_TX : 0) + num) * eqos->desc_size;
|
||
return desc;
|
||
}
|
||
|
||
/**
|
||
* @brief Init the Controller DMA Block
|
||
*/
|
||
static inline void mac_init_dma(uint32_t regbase, struct eqos_priv *eqos)
|
||
{
|
||
uint8_t ch_cnt = 0U;
|
||
|
||
dwmac_dma_bus_init(regbase, DMA_AXI_MAX_OSR_LIMIT | DMA_SYS_BUS_AAL |
|
||
DMA_AXI_BLEN16 | DMA_AXI_BLEN8 | DMA_AXI_BLEN4);
|
||
|
||
for (ch_cnt = 0; ch_cnt < ETH_MAX_DMA_CHANNEL; ch_cnt++) {
|
||
// DMA Control
|
||
dwmac_dma_init_channel(regbase, 0, ch_cnt, eqos->dsl);
|
||
// rx ring length
|
||
dwmac_set_rx_ring_len(regbase, EQOS_DESCRIPTORS_RX - 1, ch_cnt);
|
||
// init_rx_chan
|
||
dwmac_dma_init_rx_chan(regbase, (paddr_t)eqos_get_desc(eqos, 0, true),
|
||
EQOS_MAX_PACKET_SIZE, ch_cnt);
|
||
// rx bufsize
|
||
dwmac_set_bfsize(regbase, 2048, ch_cnt);//2048 20250112<31><EFBFBD>Ϊ 4096
|
||
// tx ring length
|
||
dwmac_set_tx_ring_len(regbase, EQOS_DESCRIPTORS_TX - 1, ch_cnt);
|
||
// init_tx_chan
|
||
dwmac_dma_init_tx_chan(regbase, (paddr_t)eqos_get_desc(eqos, 0, false),
|
||
ch_cnt);
|
||
// set tx tail ptr to next address after the last tx descriptor, so that
|
||
// dma will never suspend.
|
||
dwmac_set_tx_tail_ptr(regbase, (uint32_t)eqos_get_desc(eqos, EQOS_DESCRIPTORS_TX, false),
|
||
ch_cnt);
|
||
// set rx tail ptr to next address after the last tx descriptor, so that
|
||
// dma will never suspend.
|
||
dwmac_set_rx_tail_ptr(regbase,
|
||
(paddr_t)eqos_get_desc(eqos, EQOS_DESCRIPTORS_RX, true),
|
||
ch_cnt);
|
||
|
||
dwmac_enable_dma_irq(regbase, ch_cnt, false, false);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief Init the Controller MTL Block
|
||
*/
|
||
static inline void mac_init_mtl(uint32_t regbase)
|
||
{
|
||
uint8_t ch_cnt = 0U;
|
||
|
||
dwmac_prog_mtl_rx_algorithms(regbase, MTL_RX_ALGORITHM_SP);
|
||
|
||
dwmac_prog_mtl_tx_algorithms(regbase, MTL_OPERATION_SCHALG_WRR);
|
||
|
||
for (ch_cnt = 0; ch_cnt < ETH_MAX_DMA_CHANNEL; ch_cnt++) {
|
||
// rx mtl map with dma
|
||
dwmac_map_mtl_dma(regbase, ch_cnt, ch_cnt);
|
||
dwmac_rx_queue_enable(regbase, MTL_QUEUE_MODE_DCB, ch_cnt);
|
||
dwmac_dma_tx_chan_op_mode(regbase, ch_cnt, SF_MODE, 10240, MTL_QUEUE_MODE_DCB);
|
||
dwmac_dma_rx_chan_op_mode(regbase, ch_cnt, SF_MODE, 10240, MTL_QUEUE_MODE_DCB);
|
||
}
|
||
}
|
||
|
||
static inline void mac_start(uint32_t regbase, bool start)
|
||
{
|
||
uint8_t ch_cnt = 0U;
|
||
|
||
for (ch_cnt = 0; ch_cnt < ETH_MAX_DMA_CHANNEL; ch_cnt++) {
|
||
if (start) {
|
||
// start rx
|
||
dwmac_dma_start_rx(regbase, ch_cnt);
|
||
// start tx
|
||
dwmac_dma_start_tx(regbase, ch_cnt);
|
||
}
|
||
else {
|
||
// stop rx
|
||
dwmac_dma_stop_rx(regbase, ch_cnt);
|
||
// stop tx
|
||
dwmac_dma_stop_tx(regbase, ch_cnt);
|
||
}
|
||
}
|
||
}
|
||
|
||
static void eth_mac_init(dwc_eth_config_t *cfg, struct eqos_priv *eqos)
|
||
{
|
||
//DMA core sw reset
|
||
dwmac_dma_reset(cfg->base);
|
||
|
||
uint32_t skip_len = eqos->desc_size - sizeof(struct eqos_desc);
|
||
uint8_t bus_width = cfg->dma_bus_width ? cfg->dma_bus_width : 64;
|
||
eqos->dsl = skip_len * 8 / bus_width;
|
||
|
||
//DMA init
|
||
mac_init_dma(cfg->base, eqos);
|
||
|
||
//MTL init
|
||
mac_init_mtl(cfg->base);
|
||
|
||
//core init
|
||
phy_speed_t speed = (cfg->phy[0].phy_addr == ~0) ?
|
||
PHY_SPEED_1000 : cfg->phy[0].speed;
|
||
dwmac_core_init(cfg->base, cfg->mtu, speed, cfg->mac_addr);
|
||
dwmac_mac_rx_queue_mcbc_routing(cfg->base, 0);
|
||
}
|
||
|
||
static inline void set_phyif_mode(dwc_eth_config_t *cfg)
|
||
{
|
||
if (cfg->set_phy_intf)
|
||
cfg->set_phy_intf(cfg->base, cfg->phy_intf_mode);
|
||
}
|
||
|
||
static inline void config_eth_clk(dwc_eth_config_t *cfg)
|
||
{
|
||
if (cfg->timer_sec_clk)
|
||
sdrv_ckgen_set_rate(cfg->timer_sec_clk, 125000000);
|
||
if (cfg->phy_ref_clk)
|
||
sdrv_ckgen_set_rate(cfg->phy_ref_clk, 125000000);
|
||
if (cfg->rmii_clk)
|
||
sdrv_ckgen_set_rate(cfg->rmii_clk, 50000000);
|
||
if (cfg->tx_clk)
|
||
sdrv_ckgen_set_rate(cfg->tx_clk, 250000000);
|
||
}
|
||
|
||
static inline void reset_eth(dwc_eth_config_t *cfg)
|
||
{
|
||
if (cfg->rst)
|
||
sdrv_rstgen_reset(cfg->rst);
|
||
}
|
||
|
||
static err_t dwc_eth_init(struct net_driver_s *dev)
|
||
{
|
||
int ret = 0;
|
||
int i;
|
||
dwc_eth_dev_t *eth_dev = (dwc_eth_dev_t *)dev->d_private;
|
||
dwc_eth_config_t *cfg = eth_dev->config;
|
||
struct eqos_priv *eqos;
|
||
|
||
if (eth_dev->init)
|
||
return 0;
|
||
else
|
||
eth_dev->init = true;
|
||
|
||
//set SCR ethernet operating mode, e.g. RGMII, SGMII, etc.
|
||
set_phyif_mode(cfg);
|
||
|
||
//set ethernet Clk
|
||
config_eth_clk(cfg);
|
||
|
||
//ethernet reset
|
||
reset_eth(cfg);
|
||
|
||
#if !CONFIG_ETH_USE_HEAP
|
||
struct eqos_data *eqosd = containerof(eth_dev, struct eqos_data, dev);
|
||
eqos = &eqosd->eqos;
|
||
#else
|
||
eqos = (struct eqos_priv *)malloc(sizeof(struct eqos_priv));
|
||
ASSERT(eqos);
|
||
memset(eqos, 0, sizeof(struct eqos_priv));
|
||
#endif
|
||
|
||
eqos->descs = eqos_alloc_descs(eqos, EQOS_DESCRIPTORS_NUM);
|
||
|
||
if (!eqos->descs) {
|
||
ssdk_printf(SSDK_INFO, "%s: eqos_alloc_descs() failed\n", __func__);
|
||
ret = -1;
|
||
}
|
||
|
||
eqos->tx_desc_idx = 0;
|
||
eqos->rx_desc_idx = 0;
|
||
|
||
#if !CONFIG_ETH_USE_HEAP
|
||
eqos->tx_dma_buf = eqosd->tx_dma_buf;
|
||
eqos->rx_dma_buf = eqosd->rx_dma_buf;
|
||
#else
|
||
/* Align tx dma buf base address & size to cache line to avoid tx
|
||
* dma buf corrupted by cacahe line invalidation needed by other
|
||
* address, if tx dma buf data has not been wrote to ram.
|
||
*/
|
||
#if __GNUC__
|
||
eqos->tx_dma_buf = (void *)memalign(CONFIG_ARCH_CACHE_LINE,
|
||
EQOS_TX_BUFFER_SIZE);
|
||
#else
|
||
eqos->tx_dma_buf = (void *)aligned_alloc(CONFIG_ARCH_CACHE_LINE,
|
||
EQOS_TX_BUFFER_SIZE);
|
||
#endif
|
||
ASSERT(eqos->tx_dma_buf);
|
||
memset(eqos->tx_dma_buf, 0, EQOS_TX_BUFFER_SIZE);
|
||
|
||
/* rx dma buf base address & size should be cache line aligned.
|
||
* Otherwise data before or after rx dma buf may locate in same
|
||
* cache line with rx dma buf, when invalidate rx dma buf cache,
|
||
* heap data before or after rx dma buf may be corrupted.
|
||
*/
|
||
#if __GNUC__
|
||
eqos->rx_dma_buf = (void *)memalign(CONFIG_ARCH_CACHE_LINE,
|
||
EQOS_RX_BUFFER_SIZE);
|
||
#else
|
||
eqos->rx_dma_buf = (void *)aligned_alloc(CONFIG_ARCH_CACHE_LINE,
|
||
EQOS_RX_BUFFER_SIZE);
|
||
#endif
|
||
ASSERT(eqos->rx_dma_buf);
|
||
memset(eqos->rx_dma_buf, 0, EQOS_RX_BUFFER_SIZE);
|
||
#endif
|
||
|
||
//init phy
|
||
phy_init(ð_dev->phy_bus);
|
||
|
||
//init mac
|
||
eth_mac_init(cfg, eqos);
|
||
|
||
// set up dma tx/rx descriptors
|
||
memset(eqos->descs, 0, eqos->desc_size * EQOS_DESCRIPTORS_NUM);
|
||
|
||
for (i = 0; i < EQOS_DESCRIPTORS_TX; i++) {
|
||
struct eqos_desc *tx_desc = eqos_get_desc(eqos, i, false);
|
||
arch_clean_cache_range((addr_t)tx_desc, sizeof(struct eqos_desc));
|
||
}
|
||
|
||
for (i = 0; i < EQOS_DESCRIPTORS_RX; i++) {
|
||
struct eqos_desc *rx_desc = eqos_get_desc(eqos, i, true);
|
||
rx_desc->des0 = (paddr_t)eqos->rx_dma_buf +
|
||
(i * EQOS_MAX_PACKET_SIZE);
|
||
DMB;
|
||
rx_desc->des3 = EQOS_DESC3_OWN | EQOS_DESC3_IOC | EQOS_DESC3_BUF1V;
|
||
arch_clean_cache_range((addr_t)rx_desc, sizeof(struct eqos_desc));
|
||
arch_invalidate_cache_range((addr_t)((char *)eqos->rx_dma_buf +
|
||
(i * EQOS_MAX_PACKET_SIZE)), EQOS_MAX_PACKET_SIZE);
|
||
}
|
||
|
||
//enable rx/tx
|
||
mac_start(cfg->base, true);
|
||
|
||
eqos->started = true;
|
||
|
||
eth_dev->eqos = eqos;
|
||
|
||
return ret;
|
||
}
|
||
|
||
static inline void eqos_rx_lock(struct eqos_priv *eqos)
|
||
{
|
||
EQOS_RX_LOCK(eqos);
|
||
}
|
||
|
||
static inline void eqos_rx_unlock(struct eqos_priv *eqos)
|
||
{
|
||
EQOS_RX_UNLOCK(eqos);
|
||
}
|
||
|
||
static inline void eqos_tx_lock(struct eqos_priv *eqos)
|
||
{
|
||
EQOS_TX_LOCK(eqos);
|
||
}
|
||
|
||
static inline void eqos_tx_unlock(struct eqos_priv *eqos)
|
||
{
|
||
EQOS_TX_UNLOCK(eqos);
|
||
}
|
||
|
||
static err_t dwc_eth_txavail(struct net_driver_s *dev, struct pbuf *p)
|
||
{
|
||
int i, ret = 0;
|
||
struct eqos_desc *tx_desc;
|
||
uint8_t *tx_buf;
|
||
uint32_t offset = 0;
|
||
struct pbuf *q = NULL;
|
||
dwc_eth_dev_t *eth_dev = (dwc_eth_dev_t *)dev->d_private;
|
||
dwc_eth_config_t *cfg = eth_dev->config;
|
||
struct eqos_priv *eqos = eth_dev->eqos;
|
||
|
||
if (p->tot_len > (cfg->mtu + ETH_FRAME_HEADER_LEN)) {
|
||
ssdk_printf(SSDK_INFO, "eth tx len > MTU, %d\n", p->tot_len);
|
||
return -1;
|
||
}
|
||
|
||
eqos_tx_lock(eqos);
|
||
|
||
tx_desc = eqos_get_desc(eqos, eqos->tx_desc_idx, false);
|
||
|
||
for (i = 0; i < 100000; i++) {
|
||
arch_invalidate_cache_range((addr_t)tx_desc, sizeof(struct eqos_desc));
|
||
|
||
if ((tx_desc->des3 & EQOS_DESC3_OWN) == 0) {
|
||
goto start_tx;
|
||
}
|
||
}
|
||
|
||
ssdk_printf(SSDK_INFO, "%s: Previous tX on desc %d timeout\n", __func__,
|
||
eqos->tx_desc_idx);
|
||
ret = -1;
|
||
goto tx_out;
|
||
|
||
start_tx:
|
||
tx_buf = (uint8_t *)eqos->tx_dma_buf +
|
||
eqos->tx_desc_idx * EQOS_MAX_PACKET_SIZE;
|
||
//copy data to pbuf
|
||
for (q = p; q != NULL; q = q->next) {
|
||
memcpy((uint8_t *)(tx_buf + offset),
|
||
(uint8_t *)((uint8_t *)q->payload), q->len);
|
||
offset += q->len;
|
||
}
|
||
|
||
arch_clean_cache_range((addr_t)tx_buf, p->tot_len);
|
||
|
||
eqos->tx_desc_idx ++;
|
||
eqos->tx_desc_idx %= EQOS_DESCRIPTORS_TX;
|
||
tx_desc->des0 = (paddr_t)tx_buf;
|
||
tx_desc->des1 = 0;
|
||
tx_desc->des2 = p->tot_len;
|
||
|
||
/*
|
||
* Make sure that if HW sees the _OWN write below, it will see all the
|
||
* writes to the rest of the descriptor too.
|
||
*/
|
||
DMB;
|
||
tx_desc->des3 = EQOS_DESC3_OWN | EQOS_DESC3_FD | EQOS_DESC3_LD | (p->tot_len);
|
||
|
||
arch_clean_cache_range((addr_t)tx_desc, sizeof(struct eqos_desc));
|
||
|
||
dwmac_set_tx_tail_ptr(cfg->base,
|
||
(uint32_t)eqos_get_desc(eqos, EQOS_DESCRIPTORS_TX, false),
|
||
0);
|
||
|
||
tx_out:
|
||
eqos_tx_unlock(eqos);
|
||
|
||
return ret;
|
||
}
|
||
|
||
static uint32_t dwc_eth_getmac(struct net_driver_s *dev, void *buf, uint32_t max_len)
|
||
{
|
||
dwc_eth_dev_t *eth_dev = (dwc_eth_dev_t *)dev->d_private;
|
||
uint8_t *mac_addr = eth_dev->config->mac_addr;
|
||
|
||
if (mac_addr == NULL) {
|
||
mac_addr = dwmac_get_default_mac_addr();
|
||
}
|
||
|
||
memcpy(buf, (void *)mac_addr, max_len);
|
||
|
||
return max_len;
|
||
}
|
||
|
||
static uint32_t dwc_eth_getmtu(struct net_driver_s *dev)
|
||
{
|
||
dwc_eth_dev_t *eth_dev = (dwc_eth_dev_t *)dev->d_private;
|
||
|
||
return eth_dev->config->mtu;
|
||
}
|
||
|
||
static int dwc_eth_rx_poll(struct net_driver_s *dev)
|
||
{
|
||
struct eqos_desc *rx_desc;
|
||
uint32_t length = 0;
|
||
uint8_t *recbuf;
|
||
struct pbuf *rx_pbuf = NULL;
|
||
dwc_eth_dev_t *eth_dev = (dwc_eth_dev_t *)dev->d_private;
|
||
dwc_eth_config_t *cfg = eth_dev->config;
|
||
struct eqos_priv *eqos = eth_dev->eqos;
|
||
|
||
eqos_rx_lock(eqos);
|
||
|
||
rx_desc = eqos_get_desc(eqos, eqos->rx_desc_idx, true);
|
||
|
||
arch_invalidate_cache_range((addr_t)rx_desc, sizeof(struct eqos_desc));
|
||
|
||
if (rx_desc->des3 & EQOS_DESC3_OWN) {
|
||
eqos_rx_unlock(eqos);
|
||
return -1;
|
||
}
|
||
|
||
/*
|
||
* Make sure reading des3 before dma buffer.
|
||
* Avoid speculative reading of buffer before des3 own bit cleared.
|
||
*/
|
||
DMB;
|
||
|
||
recbuf = (uint8_t *)eqos->rx_dma_buf + (eqos->rx_desc_idx *
|
||
EQOS_MAX_PACKET_SIZE);
|
||
length = rx_desc->des3 & 0x7FFF;
|
||
|
||
arch_invalidate_cache_range((addr_t)((char *)recbuf), length);
|
||
|
||
if (length <= (cfg->mtu + ETH_FRAME_OVERHEAD_LEN)) {
|
||
rx_pbuf = pbuf_alloc(PBUF_RAW, length, PBUF_POOL);
|
||
|
||
if (rx_pbuf) {
|
||
uint32_t offset = 0;
|
||
struct pbuf *q = NULL;
|
||
|
||
for (q = rx_pbuf; q != NULL; q = q->next) {
|
||
memcpy( (uint8_t *)((uint8_t *)q->payload ), ((uint8_t *)recbuf + offset),
|
||
q->len);
|
||
offset += q->len;
|
||
}
|
||
}
|
||
}
|
||
else {
|
||
ssdk_printf(SSDK_INFO, "len > 1500! desc3 = 0x%x\n", rx_desc->des3);
|
||
}
|
||
|
||
//free packet
|
||
rx_desc->des0 = (paddr_t)recbuf;
|
||
rx_desc->des1 = 0;
|
||
rx_desc->des2 = 0;
|
||
/*
|
||
* Make sure DMA observes new des3 value after observing
|
||
* new des0 & des1 & des2 value under any condition, especially
|
||
* when des3 located in a different cache line and the cache line
|
||
* is automatically evicted due to corresponding cache set full.
|
||
*/
|
||
DMB;
|
||
rx_desc->des3 = EQOS_DESC3_OWN | EQOS_DESC3_IOC | EQOS_DESC3_BUF1V;
|
||
arch_clean_cache_range((addr_t)rx_desc, sizeof(struct eqos_desc));
|
||
|
||
dwmac_set_rx_tail_ptr(cfg->base,
|
||
(uint32_t)eqos_get_desc(eqos, EQOS_DESCRIPTORS_RX, true),
|
||
0);
|
||
eqos->rx_desc_idx++;
|
||
eqos->rx_desc_idx %= EQOS_DESCRIPTORS_RX;
|
||
|
||
eqos_rx_unlock(eqos);
|
||
|
||
if (rx_pbuf)
|
||
netdev_input(dev, rx_pbuf);
|
||
|
||
|
||
|
||
// if(0x40 == length)
|
||
// {
|
||
// udp_length ++;
|
||
// }
|
||
|
||
// if(50 == length) //20250113 <20><><EFBFBD><EFBFBD>
|
||
// {
|
||
// udp_length = udp_length + length;
|
||
// }
|
||
|
||
// if(50 == length) //20250113 <20><><EFBFBD><EFBFBD>
|
||
// {
|
||
// udp_length = length;
|
||
// }
|
||
|
||
|
||
|
||
// else
|
||
// {
|
||
// }
|
||
|
||
return length;
|
||
}
|
||
|
||
static inline void __dwc_eth_enable_dma_rx_int(dwc_eth_dev_t *dwc_eth)
|
||
{
|
||
for (int chn = 0; chn < ETH_MAX_DMA_CHANNEL; chn++) {
|
||
dwmac_enable_dma_rx_int(dwc_eth->config->base, chn, true);
|
||
}
|
||
}
|
||
|
||
__UNUSED static inline void __dwc_eth_disable_dma_rx_int(dwc_eth_dev_t *dwc_eth)
|
||
{
|
||
for (int chn = 0; chn < ETH_MAX_DMA_CHANNEL; chn++) {
|
||
dwmac_enable_dma_rx_int(dwc_eth->config->base, chn, false);
|
||
}
|
||
}
|
||
|
||
void dwc_eth_enable_dma_rx_int(struct net_driver_s *dev, dma_rx_int_cb_t callback)
|
||
{
|
||
ASSERT(dev);
|
||
dwc_eth_dev_t *dwc_eth = dev->d_private;
|
||
|
||
dwc_eth->rx_cb = callback;
|
||
__dwc_eth_enable_dma_rx_int(dwc_eth);
|
||
}
|
||
|
||
/**
|
||
* @brief try to read rx packet and send the packet to lwip stack.
|
||
* Should be called in thread context.
|
||
* If enable DMA rx interrupt, i.e. poll is false, this function
|
||
* should be called after DMA rx interrupt triggered. DMA rx
|
||
* interrupt will be disabled in ISR, and re-enabled in this
|
||
* function after reading out all rx packets, this is helpful to
|
||
* avoid too much rx interrupt.
|
||
*
|
||
* @param [in] dev eth device
|
||
* @param [in] poll true for polling read, false for interrupt read
|
||
* @return int total length of rx packets or -1 if rx nothing
|
||
*/
|
||
int dwc_eth_rx(struct net_driver_s *dev, bool poll)
|
||
{
|
||
ASSERT(dev);
|
||
|
||
int rx_pkt_len = 0;
|
||
int len;
|
||
|
||
do {
|
||
len = dwc_eth_rx_poll(dev);
|
||
if (len != -1)
|
||
rx_pkt_len += len;
|
||
}
|
||
while (len != -1);
|
||
|
||
if (!poll) {
|
||
dwc_eth_dev_t *dwc_eth = dev->d_private;
|
||
/* After reading all packets out, re-enable eth dma rx
|
||
* interrupt which had been disabled in rx int service
|
||
* routine. This strategy can avoid too much eth interrupt.
|
||
*/
|
||
__dwc_eth_enable_dma_rx_int(dwc_eth);
|
||
}
|
||
|
||
return rx_pkt_len ? rx_pkt_len : -1;
|
||
}
|
||
|
||
static int dwc_eth_irq_handler(uint32_t irq, void *arg)
|
||
{
|
||
struct net_driver_s *dev = arg;
|
||
dwc_eth_dev_t *dwc_eth = dev->d_private;
|
||
uint32_t reg_base = dwc_eth->config->base;
|
||
uint32_t status = dwmac_get_dma_int_status(reg_base);
|
||
uint32_t sub_sts, detail;
|
||
uint8_t chn = (irq == ENET1_SBD_INTR_NUM) ? 1 : 2;
|
||
|
||
// ssdk_printf(SSDK_DEBUG, "%s int status 0x%x\n", __func__, status);
|
||
if (!status) {
|
||
return -1;
|
||
}
|
||
|
||
if (status & (1 << 17)) {
|
||
/* MAC interrupt */
|
||
sub_sts = dwmac_get_mac_int_status(reg_base);
|
||
ssdk_printf(SSDK_EMERG, "%s %d mac int status 0x%x\n", __func__, irq, sub_sts);
|
||
if (sub_sts & (1 << 0)) {
|
||
/* RGMII/SMII PHY link status changed. Read of MAC_PHYIF_Control_Status
|
||
* register will clear the interrupt status.
|
||
*/
|
||
detail = dwmac_get_mac_phyif_control_status(reg_base);
|
||
if (detail & (1 << 19))
|
||
ssdk_printf(SSDK_EMERG, "ETH%d phy link up\n", chn);
|
||
else
|
||
ssdk_printf(SSDK_EMERG, "ETH%d phy link down\n", chn);
|
||
}
|
||
if (sub_sts & (3 << 1)) {
|
||
/* TBI/RTBI/SGMII PHY link status changed, or TBI/RTBI/SGMII PHY
|
||
* auto-negotiation is completed. Read of MAC_AN_Status register
|
||
* will clear the interrupt status.
|
||
*/
|
||
detail = dwmac_get_mac_an_status(reg_base);
|
||
ssdk_printf(SSDK_EMERG, "ETH%d mac an status 0x%x\n",
|
||
chn, detail);
|
||
}
|
||
if (sub_sts & (1 << 4)) {
|
||
/* A magic packet or Wake-on-LAN packet is received.
|
||
* Read of MAC_PMT_Control_Status register will clear
|
||
* the interrupt status.
|
||
*/
|
||
detail = dwmac_get_mac_pmt_control_status(reg_base);
|
||
ssdk_printf(SSDK_EMERG, "ETH%d mac pmt ctrl status 0x%x\n",
|
||
chn, detail);
|
||
}
|
||
if (sub_sts & (1 << 5)) {
|
||
/* LPI state entry or exit in the MAC Transmitter or Receiver.
|
||
* Read of MAC_LPI_Control_Status register will clear the
|
||
* interrupt status.
|
||
*/
|
||
detail = dwmac_get_mac_lpi_control_status(reg_base);
|
||
ssdk_printf(SSDK_EMERG, "ETH%d mac lpi ctrl status 0x%x\n",
|
||
chn, detail);
|
||
}
|
||
}
|
||
|
||
if (status & (1 << 16)) {
|
||
/* MTL interrupt */
|
||
sub_sts = dwmac_get_mtl_int_status(reg_base);
|
||
if (sub_sts & 0xFFFFFF00) {
|
||
ssdk_printf(SSDK_EMERG, "ETH%d mtl int status 0x%x\n",
|
||
chn, sub_sts);
|
||
ASSERT(0);
|
||
}
|
||
for (int i = 0; i < ETH_MAX_DMA_CHANNEL; i++) {
|
||
if (sub_sts & (1 << i)) {
|
||
detail = dwmac_get_mtl_q_int_status(reg_base, i);
|
||
dwmac_clr_mtl_q_int_status(reg_base, i, detail);
|
||
if (detail & (1 << 16))
|
||
ssdk_printf(SSDK_WARNING, "ETH%d mtl queue %d rx overflow\n",
|
||
chn, i);
|
||
if (detail & (1 << 1))
|
||
ssdk_printf(SSDK_WARNING, "ETH%d mtl queue %d ABS updated\n",
|
||
chn, i);
|
||
if (detail & (1 << 1))
|
||
ssdk_printf(SSDK_WARNING, "ETH%d mtl queue %d tx underflow\n",
|
||
chn, i);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (status & 0x7F) {
|
||
/* DMA interrupt */
|
||
for (int chn = 0; chn < ETH_MAX_DMA_CHANNEL; chn++) {
|
||
sub_sts = dwmac_get_dma_chn_status(reg_base, chn);
|
||
dwmac_clear_dma_chn_int_status(reg_base, chn);
|
||
if (sub_sts & BIT(6)) {
|
||
/* DMA rx interrupt */
|
||
// ssdk_printf(SSDK_DEBUG, "%s: chn %d rx int\n", __func__, chn);
|
||
dwmac_enable_dma_rx_int(reg_base, chn, false);
|
||
if (dwc_eth->rx_cb)
|
||
dwc_eth->rx_cb(dev);
|
||
}
|
||
if (sub_sts & BIT(0)) {
|
||
/* DMA tx interrupt */
|
||
// ssdk_printf(SSDK_DEBUG, "%s: chn %d tx int\n", __func__, chn);
|
||
/* Nothing to do for now. */
|
||
}
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int dwc_eqos_mdio_read(phy_bus_t *bus, uint32_t phyaddr,
|
||
uint32_t devaddr, uint32_t reg)
|
||
{
|
||
dwc_eth_dev_t *dwc_eth_dev = containerof(bus, dwc_eth_dev_t, phy_bus);
|
||
uint32_t reg_base = dwc_eth_dev->config->base;
|
||
|
||
return mac_mdio_read(reg_base, phyaddr, devaddr, reg);
|
||
}
|
||
|
||
static int dwc_eqos_mdio_write(phy_bus_t *bus, uint32_t phyaddr,
|
||
uint32_t devaddr, uint32_t reg, uint32_t data)
|
||
{
|
||
dwc_eth_dev_t *dwc_eth_dev = containerof(bus, dwc_eth_dev_t, phy_bus);
|
||
uint32_t reg_base = dwc_eth_dev->config->base;
|
||
|
||
return mac_mdio_write(reg_base, phyaddr, devaddr, reg, data);
|
||
}
|
||
|
||
static phy_bus_ops_t g_dwc_eqos_mdio_ops = {
|
||
.mdio_read = dwc_eqos_mdio_read,
|
||
.mdio_write = dwc_eqos_mdio_write
|
||
};
|
||
|
||
static void dwc_eth_phy_bus_init(phy_bus_t *bus)
|
||
{
|
||
list_initialize(&bus->phy_dev_list);
|
||
bus->ref_cnt = 0;
|
||
bus->ops = &g_dwc_eqos_mdio_ops;
|
||
}
|
||
|
||
static dwc_eth_dev_t *alloc_dwc_eth_device(void)
|
||
{
|
||
#if !CONFIG_ETH_USE_HEAP
|
||
for (int i = 0; i < MAX_ETH_CNT; i++) {
|
||
if (!g_eqos_data[i].used)
|
||
{
|
||
g_eqos_data[i].used = true;
|
||
return &g_eqos_data[i].dev;
|
||
}
|
||
}
|
||
|
||
return NULL;
|
||
#else
|
||
return (dwc_eth_dev_t *)malloc(sizeof(dwc_eth_dev_t));
|
||
#endif
|
||
}
|
||
|
||
int dwc_eth_probe(struct net_driver_s *dev, dwc_eth_config_t *cfg)
|
||
{
|
||
ASSERT(dev && cfg);
|
||
|
||
dev->d_init = dwc_eth_init;
|
||
dev->d_txavail = dwc_eth_txavail;
|
||
dev->d_getmac = dwc_eth_getmac;
|
||
dev->d_getmtu = dwc_eth_getmtu;
|
||
|
||
dwc_eth_dev_t *dw_eth_dev = alloc_dwc_eth_device();
|
||
ASSERT(dw_eth_dev);
|
||
dw_eth_dev->init = false;
|
||
dw_eth_dev->config = cfg;
|
||
dev->d_private = dw_eth_dev;
|
||
|
||
dev->d_ipaddr.addr = (cfg->ip[3] << 24) | (cfg->ip[2] << 16) |
|
||
(cfg->ip[1] << 8) | cfg->ip[0];
|
||
dev->d_netmask.addr = (cfg->mask[3] << 24) | (cfg->mask[2] << 16) |
|
||
(cfg->mask[1] << 8) | cfg->mask[0];
|
||
dev->d_gateway.addr = (1 << 24) | (cfg->ip[2] << 16) |
|
||
(cfg->ip[1] << 8) | cfg->ip[0];
|
||
|
||
dwc_eth_phy_bus_init(&dw_eth_dev->phy_bus);
|
||
|
||
for (int i = 0; cfg->phy[i].phy_addr != ~0; i++) {
|
||
phy_dev_register(&dw_eth_dev->phy_bus, &cfg->phy[i]);
|
||
}
|
||
|
||
External_wdt_refresh();
|
||
int ret = netdev_register(dev, NET_LL_ETHERNET);
|
||
External_wdt_refresh();
|
||
irq_attach(cfg->irq_num, dwc_eth_irq_handler, dev);
|
||
irq_enable(cfg->irq_num);
|
||
|
||
return ret;
|
||
}
|