You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
685 lines
14 KiB
685 lines
14 KiB
/* |
|
* Copyright (c) 2017-2018 ARM Limited |
|
* Copyright (c) 2018 Linaro Limited |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT smsc_lan9220 |
|
|
|
/* SMSC911x/SMSC9220 driver. Partly based on mbedOS driver. */ |
|
|
|
#define LOG_MODULE_NAME eth_smsc911x |
|
#define LOG_LEVEL CONFIG_ETHERNET_LOG_LEVEL |
|
|
|
#include <zephyr/logging/log.h> |
|
LOG_MODULE_REGISTER(LOG_MODULE_NAME); |
|
|
|
#include <soc.h> |
|
#include <zephyr/device.h> |
|
#include <errno.h> |
|
#include <zephyr/init.h> |
|
#include <zephyr/kernel.h> |
|
#include <zephyr/sys/__assert.h> |
|
#include <zephyr/net/net_core.h> |
|
#include <zephyr/net/net_pkt.h> |
|
#include <stdbool.h> |
|
#include <stdio.h> |
|
#include <string.h> |
|
#include <zephyr/sys/sys_io.h> |
|
#include <zephyr/net/ethernet.h> |
|
#include <zephyr/irq.h> |
|
#include "ethernet/eth_stats.h" |
|
|
|
#ifdef CONFIG_SHARED_IRQ |
|
#include <zephyr/shared_irq.h> |
|
#endif |
|
|
|
#include "eth_smsc911x_priv.h" |
|
|
|
#define RESET_TIMEOUT 10 |
|
#define PHY_RESET_TIMEOUT K_MSEC(100) |
|
#define REG_WRITE_TIMEOUT 50 |
|
|
|
/* Controller has only one PHY with address 1 */ |
|
#define PHY_ADDR 1 |
|
|
|
struct eth_context { |
|
struct net_if *iface; |
|
uint8_t mac[6]; |
|
#if defined(CONFIG_NET_STATISTICS_ETHERNET) |
|
struct net_stats_eth stats; |
|
#endif |
|
}; |
|
|
|
/* SMSC911x helper functions */ |
|
|
|
static int smsc_mac_regread(uint8_t reg, uint32_t *val) |
|
{ |
|
uint32_t cmd = MAC_CSR_CMD_BUSY | MAC_CSR_CMD_READ | reg; |
|
|
|
SMSC9220->MAC_CSR_CMD = cmd; |
|
|
|
while ((SMSC9220->MAC_CSR_CMD & MAC_CSR_CMD_BUSY) != 0) { |
|
} |
|
|
|
*val = SMSC9220->MAC_CSR_DATA; |
|
|
|
return 0; |
|
} |
|
|
|
static int smsc_mac_regwrite(uint8_t reg, uint32_t val) |
|
{ |
|
uint32_t cmd = MAC_CSR_CMD_BUSY | MAC_CSR_CMD_WRITE | reg; |
|
|
|
SMSC9220->MAC_CSR_DATA = val; |
|
|
|
SMSC9220->MAC_CSR_CMD = cmd; |
|
|
|
while ((SMSC9220->MAC_CSR_CMD & MAC_CSR_CMD_BUSY) != 0) { |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
int smsc_phy_regread(uint8_t regoffset, uint32_t *data) |
|
{ |
|
uint32_t val = 0U; |
|
uint32_t phycmd = 0U; |
|
unsigned int time_out = REG_WRITE_TIMEOUT; |
|
|
|
if (smsc_mac_regread(SMSC9220_MAC_MII_ACC, &val) < 0) { |
|
return -1; |
|
} |
|
|
|
if (val & MAC_MII_ACC_MIIBZY) { |
|
*data = 0U; |
|
return -EBUSY; |
|
} |
|
|
|
phycmd = 0U; |
|
phycmd |= PHY_ADDR << 11; |
|
phycmd |= (regoffset & 0x1F) << 6; |
|
phycmd |= MAC_MII_ACC_READ; |
|
phycmd |= MAC_MII_ACC_MIIBZY; /* Operation start */ |
|
|
|
if (smsc_mac_regwrite(SMSC9220_MAC_MII_ACC, phycmd)) { |
|
return -1; |
|
} |
|
|
|
val = 0U; |
|
do { |
|
k_sleep(K_MSEC(1)); |
|
time_out--; |
|
if (smsc_mac_regread(SMSC9220_MAC_MII_ACC, &val)) { |
|
return -1; |
|
} |
|
} while (time_out != 0U && (val & MAC_MII_ACC_MIIBZY)); |
|
|
|
if (time_out == 0U) { |
|
return -ETIMEDOUT; |
|
} |
|
|
|
if (smsc_mac_regread(SMSC9220_MAC_MII_DATA, data) < 0) { |
|
return -1; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
int smsc_phy_regwrite(uint8_t regoffset, uint32_t data) |
|
{ |
|
uint32_t val = 0U; |
|
uint32_t phycmd = 0U; |
|
unsigned int time_out = REG_WRITE_TIMEOUT; |
|
|
|
if (smsc_mac_regread(SMSC9220_MAC_MII_ACC, &val) < 0) { |
|
return -1; |
|
} |
|
|
|
if (val & MAC_MII_ACC_MIIBZY) { |
|
return -EBUSY; |
|
} |
|
|
|
if (smsc_mac_regwrite(SMSC9220_MAC_MII_DATA, data & 0xFFFF) < 0) { |
|
return -1; |
|
} |
|
|
|
phycmd |= PHY_ADDR << 11; |
|
phycmd |= (regoffset & 0x1F) << 6; |
|
phycmd |= MAC_MII_ACC_WRITE; |
|
phycmd |= MAC_MII_ACC_MIIBZY; /* Operation start */ |
|
|
|
if (smsc_mac_regwrite(SMSC9220_MAC_MII_ACC, phycmd) < 0) { |
|
return -1; |
|
} |
|
|
|
do { |
|
k_sleep(K_MSEC(1)); |
|
time_out--; |
|
if (smsc_mac_regread(SMSC9220_MAC_MII_ACC, &phycmd)) { |
|
return -1; |
|
} |
|
} while (time_out != 0U && (phycmd & MAC_MII_ACC_MIIBZY)); |
|
|
|
if (time_out == 0U) { |
|
return -ETIMEDOUT; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int smsc_read_mac_address(uint8_t *mac) |
|
{ |
|
uint32_t tmp; |
|
int res; |
|
|
|
res = smsc_mac_regread(SMSC9220_MAC_ADDRL, &tmp); |
|
if (res < 0) { |
|
return res; |
|
} |
|
|
|
mac[0] = (uint8_t)(tmp >> 0); |
|
mac[1] = (uint8_t)(tmp >> 8); |
|
mac[2] = (uint8_t)(tmp >> 16); |
|
mac[3] = (uint8_t)(tmp >> 24); |
|
|
|
res = smsc_mac_regread(SMSC9220_MAC_ADDRH, &tmp); |
|
if (res < 0) { |
|
return res; |
|
} |
|
|
|
mac[4] = (uint8_t)(tmp >> 0); |
|
mac[5] = (uint8_t)(tmp >> 8); |
|
|
|
return 0; |
|
} |
|
|
|
static int smsc_check_id(void) |
|
{ |
|
uint32_t id = SMSC9220->ID_REV; |
|
|
|
/* If bottom and top halves of the word are the same, |
|
* the hardware is (likely) not present. |
|
*/ |
|
if (((id >> 16) & 0xFFFF) == (id & 0xFFFF)) { |
|
return -1; |
|
} |
|
|
|
switch (((id >> 16) & 0xFFFF)) { |
|
case 0x9220: /* SMSC9220 on MPS2 */ |
|
case 0x0118: /* SMS9118 as emulated by QEMU */ |
|
break; |
|
|
|
default: |
|
return -1; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int smsc_soft_reset(void) |
|
{ |
|
unsigned int time_out = RESET_TIMEOUT; |
|
|
|
SMSC9220->HW_CFG |= HW_CFG_SRST; |
|
|
|
do { |
|
k_sleep(K_MSEC(1)); |
|
time_out--; |
|
} while (time_out != 0U && (SMSC9220->HW_CFG & HW_CFG_SRST)); |
|
|
|
if (time_out == 0U) { |
|
return -1; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
void smsc_set_txfifo(unsigned int val) |
|
{ |
|
/* 2kb minimum, 14kb maximum */ |
|
if (val >= 2U && val <= 14U) { |
|
SMSC9220->HW_CFG = val << 16; |
|
} |
|
} |
|
|
|
void smsc_init_irqs(void) |
|
{ |
|
SMSC9220->INT_EN = 0; |
|
/* Clear all interrupts */ |
|
SMSC9220->INT_STS = 0xFFFFFFFF; |
|
/* Polarity config which works with QEMU */ |
|
/* IRQ deassertion at 220 usecs and master IRQ enable */ |
|
SMSC9220->IRQ_CFG = 0x22000111; |
|
} |
|
|
|
static int smsc_check_phy(void) |
|
{ |
|
uint32_t phyid1, phyid2; |
|
|
|
if (smsc_phy_regread(SMSC9220_PHY_ID1, &phyid1)) { |
|
return -1; |
|
} |
|
|
|
if (smsc_phy_regread(SMSC9220_PHY_ID2, &phyid2)) { |
|
return -1; |
|
} |
|
|
|
return ((phyid1 == 0xFFFF && phyid2 == 0xFFFF) || |
|
(phyid1 == 0x0 && phyid2 == 0x0)); |
|
} |
|
|
|
int smsc_reset_phy(void) |
|
{ |
|
uint32_t val; |
|
|
|
if (smsc_phy_regread(SMSC9220_PHY_BCONTROL, &val)) { |
|
return -1; |
|
} |
|
|
|
val |= 1 << 15; |
|
|
|
if (smsc_phy_regwrite(SMSC9220_PHY_BCONTROL, val)) { |
|
return -1; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
/** |
|
* Advertise all speeds and pause capabilities |
|
*/ |
|
void smsc_advertise_caps(void) |
|
{ |
|
uint32_t aneg_adv = 0U; |
|
|
|
smsc_phy_regread(SMSC9220_PHY_ANEG_ADV, &aneg_adv); |
|
aneg_adv |= 0xDE0; |
|
|
|
smsc_phy_regwrite(SMSC9220_PHY_ANEG_ADV, aneg_adv); |
|
smsc_phy_regread(SMSC9220_PHY_ANEG_ADV, &aneg_adv); |
|
} |
|
|
|
void smsc_establish_link(void) |
|
{ |
|
uint32_t bcr = 0U; |
|
uint32_t hw_cfg = 0U; |
|
|
|
smsc_phy_regread(SMSC9220_PHY_BCONTROL, &bcr); |
|
bcr |= (1 << 12) | (1 << 9); |
|
smsc_phy_regwrite(SMSC9220_PHY_BCONTROL, bcr); |
|
smsc_phy_regread(SMSC9220_PHY_BCONTROL, &bcr); |
|
|
|
hw_cfg = SMSC9220->HW_CFG; |
|
hw_cfg &= 0xF0000; |
|
hw_cfg |= (1 << 20); |
|
SMSC9220->HW_CFG = hw_cfg; |
|
} |
|
|
|
static inline void smsc_enable_xmit(void) |
|
{ |
|
SMSC9220->TX_CFG = 0x2 /*TX_CFG_TX_ON*/; |
|
} |
|
|
|
void smsc_enable_mac_xmit(void) |
|
{ |
|
uint32_t mac_cr = 0U; |
|
|
|
smsc_mac_regread(SMSC9220_MAC_CR, &mac_cr); |
|
|
|
mac_cr |= (1 << 3); /* xmit enable */ |
|
mac_cr |= (1 << 28); /* Heartbeat disable */ |
|
|
|
smsc_mac_regwrite(SMSC9220_MAC_CR, mac_cr); |
|
} |
|
|
|
void smsc_enable_mac_recv(void) |
|
{ |
|
uint32_t mac_cr = 0U; |
|
|
|
smsc_mac_regread(SMSC9220_MAC_CR, &mac_cr); |
|
mac_cr |= (1 << 2); /* Recv enable */ |
|
smsc_mac_regwrite(SMSC9220_MAC_CR, mac_cr); |
|
} |
|
|
|
int smsc_init(void) |
|
{ |
|
unsigned int phyreset = 0U; |
|
|
|
if (smsc_check_id() < 0) { |
|
return -1; |
|
} |
|
|
|
if (smsc_soft_reset() < 0) { |
|
return -1; |
|
} |
|
|
|
smsc_set_txfifo(5); |
|
|
|
/* Sets automatic flow control thresholds, and backpressure */ |
|
/* threshold to defaults specified. */ |
|
SMSC9220->AFC_CFG = 0x006E3740; |
|
|
|
/* May need to initialize EEPROM/read MAC from it on real HW. */ |
|
|
|
/* Configure GPIOs as LED outputs. */ |
|
SMSC9220->GPIO_CFG = 0x70070000; |
|
|
|
smsc_init_irqs(); |
|
|
|
/* Configure MAC addresses here if needed. */ |
|
|
|
if (smsc_check_phy() < 0) { |
|
return -1; |
|
} |
|
|
|
if (smsc_reset_phy() < 0) { |
|
return -1; |
|
} |
|
|
|
k_sleep(PHY_RESET_TIMEOUT); |
|
/* Checking whether phy reset completed successfully.*/ |
|
if (smsc_phy_regread(SMSC9220_PHY_BCONTROL, &phyreset)) { |
|
return 1; |
|
} |
|
|
|
if (phyreset & (1 << 15)) { |
|
return 1; |
|
} |
|
|
|
smsc_advertise_caps(); |
|
/* bit [12] of BCONTROL seems self-clearing. */ |
|
/* Although it's not so in the manual. */ |
|
smsc_establish_link(); |
|
|
|
/* Interrupt threshold */ |
|
SMSC9220->FIFO_INT = 0xFF000000; |
|
|
|
smsc_enable_mac_xmit(); |
|
smsc_enable_xmit(); |
|
SMSC9220->RX_CFG = 0; |
|
smsc_enable_mac_recv(); |
|
|
|
/* Rx status FIFO level irq threshold */ |
|
SMSC9220->FIFO_INT &= ~(0xFF); /* Clear 2 bottom nibbles */ |
|
|
|
/* This sleep is compulsory otherwise txmit/receive will fail. */ |
|
k_sleep(K_MSEC(2000)); |
|
|
|
return 0; |
|
} |
|
|
|
/* Driver functions */ |
|
|
|
static enum ethernet_hw_caps eth_smsc911x_get_capabilities(const struct device *dev) |
|
{ |
|
ARG_UNUSED(dev); |
|
|
|
return ETHERNET_LINK_10BASE | ETHERNET_LINK_100BASE; |
|
} |
|
|
|
#if defined(CONFIG_NET_STATISTICS_ETHERNET) |
|
static struct net_stats_eth *get_stats(const struct device *dev) |
|
{ |
|
struct eth_context *context = dev->data; |
|
|
|
return &context->stats; |
|
} |
|
#endif |
|
|
|
static void eth_initialize(struct net_if *iface) |
|
{ |
|
const struct device *dev = net_if_get_device(iface); |
|
struct eth_context *context = dev->data; |
|
|
|
LOG_DBG("eth_initialize"); |
|
|
|
smsc_read_mac_address(context->mac); |
|
|
|
SMSC9220->INT_EN |= BIT(SMSC9220_INTERRUPT_RXSTATUS_FIFO_LEVEL); |
|
|
|
net_if_set_link_addr(iface, context->mac, sizeof(context->mac), |
|
NET_LINK_ETHERNET); |
|
|
|
context->iface = iface; |
|
|
|
ethernet_init(iface); |
|
} |
|
|
|
static int smsc_write_tx_fifo(const uint8_t *buf, uint32_t len, bool is_last) |
|
{ |
|
uint32_t *buf32; |
|
|
|
__ASSERT_NO_MSG(((uintptr_t)buf & 3) == 0); |
|
|
|
if (is_last) { |
|
/* Last fragment may be not full */ |
|
len = (len + 3) & ~3; |
|
} |
|
|
|
if ((len & 3) != 0U || len == 0U) { |
|
LOG_ERR("Chunk size not aligned: %u", len); |
|
return -1; |
|
} |
|
|
|
buf32 = (uint32_t *)buf; |
|
len /= 4U; |
|
do { |
|
SMSC9220->TX_DATA_PORT = *buf32++; |
|
} while (--len); |
|
|
|
return 0; |
|
} |
|
|
|
static int eth_tx(const struct device *dev, struct net_pkt *pkt) |
|
{ |
|
uint16_t total_len = net_pkt_get_len(pkt); |
|
static uint8_t tx_buf[NET_ETH_MAX_FRAME_SIZE] __aligned(4); |
|
uint32_t txcmd_a, txcmd_b; |
|
uint32_t tx_stat; |
|
int res; |
|
|
|
txcmd_a = (1/*is_first_segment*/ << 13) | (1/*is_last_segment*/ << 12) |
|
| total_len; |
|
/* Use len as a tag */ |
|
txcmd_b = total_len << 16 | total_len; |
|
SMSC9220->TX_DATA_PORT = txcmd_a; |
|
SMSC9220->TX_DATA_PORT = txcmd_b; |
|
|
|
if (net_pkt_read(pkt, tx_buf, total_len)) { |
|
goto error; |
|
} |
|
|
|
res = smsc_write_tx_fifo(tx_buf, total_len, true); |
|
if (res < 0) { |
|
goto error; |
|
} |
|
|
|
tx_stat = SMSC9220->TX_STAT_PORT; |
|
LOG_DBG("TX_STAT: %x", tx_stat); |
|
|
|
return 0; |
|
|
|
error: |
|
LOG_ERR("Writing pkt to FIFO failed"); |
|
return -1; |
|
} |
|
|
|
static const struct ethernet_api api_funcs = { |
|
.iface_api.init = eth_initialize, |
|
|
|
.get_capabilities = eth_smsc911x_get_capabilities, |
|
.send = eth_tx, |
|
#if defined(CONFIG_NET_STATISTICS_ETHERNET) |
|
.get_stats = get_stats, |
|
#endif |
|
}; |
|
|
|
static void smsc_discard_pkt(void) |
|
{ |
|
/* TODO: */ |
|
/* Datasheet p.43: */ |
|
/* When performing a fast-forward, there must be at least 4 DWORDs |
|
* of data in the RX data FIFO for the packet being discarded. For |
|
* less than 4 DWORDs do not use RX_FFWD. In this case data must be |
|
* read from the RX data FIFO and discarded using standard PIO read |
|
* operations. |
|
*/ |
|
SMSC9220->RX_DP_CTRL = RX_DP_CTRL_RX_FFWD; |
|
} |
|
|
|
static inline void smsc_wait_discard_pkt(void) |
|
{ |
|
while ((SMSC9220->RX_DP_CTRL & RX_DP_CTRL_RX_FFWD) != 0) { |
|
} |
|
} |
|
|
|
static int smsc_read_rx_fifo(struct net_pkt *pkt, uint32_t len) |
|
{ |
|
uint32_t buf32; |
|
|
|
__ASSERT_NO_MSG((len & 3) == 0U && len >= 4U); |
|
|
|
len /= 4U; |
|
|
|
do { |
|
buf32 = SMSC9220->RX_DATA_PORT; |
|
|
|
if (net_pkt_write(pkt, &buf32, sizeof(uint32_t))) { |
|
return -1; |
|
} |
|
} while (--len); |
|
|
|
return 0; |
|
} |
|
|
|
static struct net_pkt *smsc_recv_pkt(const struct device *dev, |
|
uint32_t pkt_size) |
|
{ |
|
struct eth_context *context = dev->data; |
|
struct net_pkt *pkt; |
|
uint32_t rem_size; |
|
|
|
/* Round up to next DWORD size */ |
|
rem_size = (pkt_size + 3) & ~3; |
|
/* Don't account for FCS when filling net pkt */ |
|
rem_size -= 4U; |
|
|
|
pkt = net_pkt_rx_alloc_with_buffer(context->iface, rem_size, |
|
AF_UNSPEC, 0, K_NO_WAIT); |
|
if (!pkt) { |
|
LOG_ERR("Failed to obtain RX buffer"); |
|
smsc_discard_pkt(); |
|
return NULL; |
|
} |
|
|
|
if (smsc_read_rx_fifo(pkt, rem_size) < 0) { |
|
smsc_discard_pkt(); |
|
net_pkt_unref(pkt); |
|
return NULL; |
|
} |
|
|
|
/* Discard FCS */ |
|
{ |
|
uint32_t __unused dummy = SMSC9220->RX_DATA_PORT; |
|
} |
|
|
|
/* Adjust len of the last buf down for DWORD alignment */ |
|
if (pkt_size & 3) { |
|
net_pkt_update_length(pkt, net_pkt_get_len(pkt) - |
|
(4 - (pkt_size & 3))); |
|
} |
|
|
|
return pkt; |
|
} |
|
|
|
static void eth_smsc911x_isr(const struct device *dev) |
|
{ |
|
uint32_t int_status = SMSC9220->INT_STS; |
|
struct eth_context *context = dev->data; |
|
|
|
LOG_DBG("%s: INT_STS=%x INT_EN=%x", __func__, |
|
int_status, SMSC9220->INT_EN); |
|
|
|
if (int_status & BIT(SMSC9220_INTERRUPT_RXSTATUS_FIFO_LEVEL)) { |
|
struct net_pkt *pkt; |
|
uint32_t pkt_size, val; |
|
uint32_t rx_stat; |
|
|
|
val = SMSC9220->RX_FIFO_INF; |
|
uint32_t pkt_pending = BFIELD(val, RX_FIFO_INF_RXSUSED); |
|
|
|
LOG_DBG("in RX FIFO: pkts: %u, bytes: %u", |
|
pkt_pending, |
|
BFIELD(val, RX_FIFO_INF_RXDUSED)); |
|
|
|
/* Ack rxstatus_fifo_level only when no packets pending. The |
|
* idea is to serve 1 packet per interrupt (e.g. to allow |
|
* higher priority interrupts to fire) by keeping interrupt |
|
* pending for as long as there're packets in FIFO. And when |
|
* there's none, finally acknowledge it. |
|
*/ |
|
if (pkt_pending == 0U) { |
|
goto done; |
|
} |
|
|
|
int_status &= ~BIT(SMSC9220_INTERRUPT_RXSTATUS_FIFO_LEVEL); |
|
|
|
/* Make sure that any previously started discard op is |
|
* finished. |
|
*/ |
|
smsc_wait_discard_pkt(); |
|
|
|
rx_stat = SMSC9220->RX_STAT_PORT; |
|
pkt_size = BFIELD(rx_stat, RX_STAT_PORT_PKT_LEN); |
|
LOG_DBG("pkt sz: %u", pkt_size); |
|
|
|
pkt = smsc_recv_pkt(dev, pkt_size); |
|
|
|
LOG_DBG("out RX FIFO: pkts: %u, bytes: %u", |
|
SMSC9220_BFIELD(RX_FIFO_INF, RXSUSED), |
|
SMSC9220_BFIELD(RX_FIFO_INF, RXDUSED)); |
|
|
|
if (pkt != NULL) { |
|
int res = net_recv_data(context->iface, pkt); |
|
|
|
if (res < 0) { |
|
LOG_ERR("net_recv_data: %d", res); |
|
net_pkt_unref(pkt); |
|
} |
|
} |
|
} |
|
|
|
done: |
|
/* Ack pending interrupts */ |
|
SMSC9220->INT_STS = int_status; |
|
|
|
} |
|
|
|
/* Bindings to the platform */ |
|
|
|
int eth_init(const struct device *dev) |
|
{ |
|
IRQ_CONNECT(DT_INST_IRQN(0), |
|
DT_INST_IRQ(0, priority), |
|
eth_smsc911x_isr, DEVICE_DT_INST_GET(0), 0); |
|
|
|
int ret = smsc_init(); |
|
|
|
if (ret != 0) { |
|
LOG_ERR("smsc911x failed to initialize"); |
|
return -ENODEV; |
|
} |
|
|
|
irq_enable(DT_INST_IRQN(0)); |
|
|
|
return ret; |
|
} |
|
|
|
static struct eth_context eth_0_context; |
|
|
|
ETH_NET_DEVICE_DT_INST_DEFINE(0, |
|
eth_init, NULL, ð_0_context, |
|
NULL /*ð_config_0*/, CONFIG_ETH_INIT_PRIORITY, &api_funcs, |
|
NET_ETH_MTU /*MTU*/);
|
|
|