drivers: ethernet: support for Nuvoton numaker series
Add Nuvoton numaker series EMAC controller feature. Signed-off-by: cyliang tw <cyliang@nuvoton.com>
This commit is contained in:
parent
97fab83716
commit
8ba8c188a0
|
@ -32,4 +32,21 @@
|
|||
<PJ11MFP_CAN0_RXD>;
|
||||
};
|
||||
};
|
||||
|
||||
/* EMAC multi-function pins for MDIO, TX, REFCLK, RX pins */
|
||||
emac_default: emac_default {
|
||||
group0 {
|
||||
pinmux = <PE8MFP_EMAC0_RMII_MDC>,
|
||||
<PE9MFP_EMAC0_RMII_MDIO>,
|
||||
<PE10MFP_EMAC0_RMII_TXD0>,
|
||||
<PE11MFP_EMAC0_RMII_TXD1>,
|
||||
<PE12MFP_EMAC0_RMII_TXEN>,
|
||||
<PC8MFP_EMAC0_RMII_REFCLK>,
|
||||
<PC7MFP_EMAC0_RMII_RXD0>,
|
||||
<PC6MFP_EMAC0_RMII_RXD1>,
|
||||
<PA7MFP_EMAC0_RMII_CRSDV>,
|
||||
<PA6MFP_EMAC0_RMII_RXERR>,
|
||||
<PB6MFP_EMAC0_PPS>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -117,3 +117,9 @@
|
|||
pinctrl-names = "default";
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
&emac {
|
||||
pinctrl-0 = <&emac_default>;
|
||||
pinctrl-names = "default";
|
||||
status = "okay";
|
||||
};
|
||||
|
|
|
@ -42,6 +42,7 @@ if(CONFIG_ETH_NXP_S32_NETC)
|
|||
endif()
|
||||
|
||||
zephyr_library_sources_ifdef(CONFIG_ETH_NXP_S32_GMAC eth_nxp_s32_gmac.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_ETH_NUMAKER eth_numaker.c)
|
||||
|
||||
if(CONFIG_ETH_NATIVE_POSIX)
|
||||
set(native_posix_source_files eth_native_posix.c eth_native_posix_adapt.c)
|
||||
|
|
|
@ -61,6 +61,7 @@ source "drivers/ethernet/Kconfig.nxp_s32_gmac"
|
|||
source "drivers/ethernet/Kconfig.smsc91x"
|
||||
source "drivers/ethernet/Kconfig.ivshmem"
|
||||
source "drivers/ethernet/Kconfig.adin2111"
|
||||
source "drivers/ethernet/Kconfig.numaker"
|
||||
|
||||
source "drivers/ethernet/phy/Kconfig"
|
||||
|
||||
|
|
14
drivers/ethernet/Kconfig.numaker
Normal file
14
drivers/ethernet/Kconfig.numaker
Normal file
|
@ -0,0 +1,14 @@
|
|||
# NUMAKER Ethernet Driver configuration options
|
||||
|
||||
# Copyright (c) 2023 Nuvoton Technology Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
config ETH_NUMAKER
|
||||
bool "Nuvoton NUMAKER MCU Ethernet driver"
|
||||
default y
|
||||
select HAS_NUMAKER_ETH
|
||||
depends on DT_HAS_NUVOTON_NUMAKER_ETHERNET_ENABLED
|
||||
help
|
||||
This option enables the Ethernet driver for Nuvoton NuMaker family of
|
||||
processors.
|
||||
Say y if you wish to enable NuMaker ETH.
|
790
drivers/ethernet/eth_numaker.c
Normal file
790
drivers/ethernet/eth_numaker.c
Normal file
|
@ -0,0 +1,790 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Nuvoton Technology Corporation.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#define DT_DRV_COMPAT nuvoton_numaker_ethernet
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/drivers/reset.h>
|
||||
#include <zephyr/drivers/clock_control.h>
|
||||
#include <zephyr/drivers/clock_control/clock_control_numaker.h>
|
||||
#include <zephyr/drivers/pinctrl.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/net/ethernet.h>
|
||||
#include "eth_numaker_priv.h"
|
||||
#include "ethernet/eth_stats.h"
|
||||
#include <soc.h>
|
||||
#include <NuMicro.h>
|
||||
#include <synopGMAC_network_interface.h>
|
||||
|
||||
#ifdef CONFIG_SOC_M467
|
||||
#include <m460_eth.h>
|
||||
#endif
|
||||
|
||||
LOG_MODULE_REGISTER(eth_numaker, CONFIG_ETHERNET_LOG_LEVEL);
|
||||
|
||||
/* Device EMAC Interface port */
|
||||
#define NUMAKER_GMAC_INTF 0
|
||||
/* 2KB Data Flash at 0xFF800 */
|
||||
#define NUMAKER_DATA_FLASH (0xFF800U)
|
||||
#define NUMAKER_MASK_32 (0xFFFFFFFFU)
|
||||
#define NUMAKER_MII_CONFIG (ADVERTISE_CSMA | ADVERTISE_10HALF | ADVERTISE_10FULL | \
|
||||
ADVERTISE_100HALF | ADVERTISE_100FULL)
|
||||
#define NUMAKER_MII_LINKED (BMSR_ANEGCOMPLETE | BMSR_LSTATUS)
|
||||
|
||||
extern synopGMACdevice GMACdev[GMAC_CNT];
|
||||
extern struct sk_buff tx_buf[GMAC_CNT][TRANSMIT_DESC_SIZE];
|
||||
extern struct sk_buff rx_buf[GMAC_CNT][RECEIVE_DESC_SIZE];
|
||||
|
||||
static uint32_t eth_phy_addr;
|
||||
|
||||
/* Device config */
|
||||
struct eth_numaker_config {
|
||||
uint32_t gmac_base;
|
||||
const struct reset_dt_spec reset;
|
||||
uint32_t phy_addr;
|
||||
uint32_t clk_modidx;
|
||||
uint32_t clk_src;
|
||||
uint32_t clk_div;
|
||||
const struct device *clk_dev;
|
||||
const struct pinctrl_dev_config *pincfg;
|
||||
};
|
||||
|
||||
/* Driver context/data */
|
||||
struct eth_numaker_data {
|
||||
synopGMACdevice *gmacdev;
|
||||
struct net_if *iface;
|
||||
uint8_t mac_addr[NU_HWADDR_SIZE];
|
||||
struct k_mutex tx_frame_buf_mutex;
|
||||
struct k_spinlock rx_frame_buf_lock;
|
||||
};
|
||||
|
||||
/* Delay execution for given amount of ticks for SDK-HAL */
|
||||
void plat_delay(uint32_t delay)
|
||||
{
|
||||
uint32_t us_cnt = k_ticks_to_us_floor32((uint64_t)delay);
|
||||
|
||||
k_busy_wait(us_cnt);
|
||||
}
|
||||
|
||||
static void mdio_write(synopGMACdevice *gmacdev, uint32_t addr, uint32_t reg, int data)
|
||||
{
|
||||
synopGMAC_write_phy_reg((u32 *)gmacdev->MacBase, addr, reg, data);
|
||||
}
|
||||
|
||||
static int mdio_read(synopGMACdevice *gmacdev, uint32_t addr, uint32_t reg)
|
||||
{
|
||||
uint16_t data;
|
||||
|
||||
synopGMAC_read_phy_reg((u32 *)gmacdev->MacBase, addr, reg, &data);
|
||||
return data;
|
||||
}
|
||||
|
||||
static int numaker_eth_link_ok(synopGMACdevice *gmacdev)
|
||||
{
|
||||
/* first, a dummy read to latch */
|
||||
mdio_read(gmacdev, eth_phy_addr, MII_BMSR);
|
||||
if (mdio_read(gmacdev, eth_phy_addr, MII_BMSR) & BMSR_LSTATUS) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int reset_phy(synopGMACdevice *gmacdev)
|
||||
{
|
||||
uint16_t reg;
|
||||
uint32_t delay_us;
|
||||
bool ret;
|
||||
|
||||
mdio_write(gmacdev, eth_phy_addr, MII_BMCR, BMCR_RESET);
|
||||
|
||||
delay_us = 200000U;
|
||||
ret = WAIT_FOR(!(mdio_read(gmacdev, eth_phy_addr, MII_BMCR) & BMCR_RESET),
|
||||
delay_us, k_msleep(1));
|
||||
if (ret == false) {
|
||||
LOG_DBG("Reset phy failed");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
LOG_INF("PHY ID 1:0x%x", mdio_read(gmacdev, eth_phy_addr, MII_PHYSID1));
|
||||
LOG_INF("PHY ID 2:0x%x", mdio_read(gmacdev, eth_phy_addr, MII_PHYSID2));
|
||||
delay_us = 3000000U;
|
||||
ret = WAIT_FOR(numaker_eth_link_ok(gmacdev), delay_us, k_msleep(1));
|
||||
if (ret) {
|
||||
gmacdev->LinkState = LINKUP;
|
||||
LOG_DBG("Link Up");
|
||||
} else {
|
||||
gmacdev->LinkState = LINKDOWN;
|
||||
LOG_DBG("Link Down");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
mdio_write(gmacdev, eth_phy_addr, MII_ADVERTISE, NUMAKER_MII_CONFIG);
|
||||
reg = mdio_read(gmacdev, eth_phy_addr, MII_BMCR);
|
||||
mdio_write(gmacdev, eth_phy_addr, MII_BMCR, reg | BMCR_ANRESTART);
|
||||
delay_us = 3000000U;
|
||||
ret = WAIT_FOR((mdio_read(gmacdev, eth_phy_addr, MII_BMSR) &
|
||||
NUMAKER_MII_LINKED) == NUMAKER_MII_LINKED,
|
||||
delay_us, k_msleep(1));
|
||||
if (ret == false) {
|
||||
LOG_DBG("AN failed. Set to 100 FULL");
|
||||
synopGMAC_set_full_duplex(gmacdev);
|
||||
synopGMAC_set_mode(NUMAKER_GMAC_INTF, 1); /* Set mode 1: 100Mbps; 2: 10Mbps */
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
reg = mdio_read(gmacdev, eth_phy_addr, MII_LPA);
|
||||
if (reg & ADVERTISE_100FULL) {
|
||||
LOG_DBG("100 full");
|
||||
gmacdev->DuplexMode = FULLDUPLEX;
|
||||
gmacdev->Speed = SPEED100;
|
||||
synopGMAC_set_full_duplex(gmacdev);
|
||||
synopGMAC_set_mode(NUMAKER_GMAC_INTF, 1); /* Set mode 1: 100Mbps; 2: 10Mbps */
|
||||
} else if (reg & ADVERTISE_100HALF) {
|
||||
LOG_DBG("100 half");
|
||||
gmacdev->DuplexMode = HALFDUPLEX;
|
||||
gmacdev->Speed = SPEED100;
|
||||
synopGMAC_set_half_duplex(gmacdev);
|
||||
synopGMAC_set_mode(NUMAKER_GMAC_INTF, 1); /* Set mode 1: 100Mbps; 2: 10Mbps */
|
||||
} else if (reg & ADVERTISE_10FULL) {
|
||||
LOG_DBG("10 full");
|
||||
gmacdev->DuplexMode = FULLDUPLEX;
|
||||
gmacdev->Speed = SPEED10;
|
||||
synopGMAC_set_full_duplex(gmacdev);
|
||||
synopGMAC_set_mode(NUMAKER_GMAC_INTF, 2); /* Set mode 1: 100Mbps; 2: 10Mbps */
|
||||
} else {
|
||||
LOG_DBG("10 half");
|
||||
gmacdev->DuplexMode = HALFDUPLEX;
|
||||
gmacdev->Speed = SPEED10;
|
||||
synopGMAC_set_half_duplex(gmacdev);
|
||||
synopGMAC_set_mode(NUMAKER_GMAC_INTF, 2); /* Set mode 1: 100Mbps; 2: 10Mbps */
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void m_numaker_read_mac_addr(char *mac)
|
||||
{
|
||||
uint32_t uid1;
|
||||
/* Fetch word 0 of data flash */
|
||||
uint32_t word0 = *(uint32_t *)(NUMAKER_DATA_FLASH + 0x04U);
|
||||
/*
|
||||
* Fetch word 1 of data flash
|
||||
* we only want bottom 16 bits of word1 (MAC bits 32-47)
|
||||
* and bit 9 forced to 1, bit 8 forced to 0
|
||||
* Locally administered MAC, reduced conflicts
|
||||
* http://en.wikipedia.org/wiki/MAC_address
|
||||
*/
|
||||
uint32_t word1 = *(uint32_t *)NUMAKER_DATA_FLASH;
|
||||
|
||||
/* Not burn any mac address at the beginning of data flash */
|
||||
if (word0 == NUMAKER_MASK_32) {
|
||||
/* Generate a semi-unique MAC address from the UUID */
|
||||
SYS_UnlockReg();
|
||||
/* Enable FMC ISP function */
|
||||
FMC_Open();
|
||||
uid1 = FMC_ReadUID(1);
|
||||
word1 = (uid1 & 0x003FFFFF) | ((uid1 & 0x030000) << 6) >> 8;
|
||||
word0 = ((FMC_ReadUID(0) >> 4) << 20) | ((uid1 & 0xFF) << 12) |
|
||||
(FMC_ReadUID(2) & 0xFFF);
|
||||
/* Disable FMC ISP function */
|
||||
FMC_Close();
|
||||
/* Lock protected registers */
|
||||
SYS_LockReg();
|
||||
}
|
||||
|
||||
word1 |= 0x00000200;
|
||||
word1 &= 0x0000FEFF;
|
||||
|
||||
mac[0] = (word1 & 0x0000ff00) >> 8;
|
||||
mac[1] = (word1 & 0x000000ff);
|
||||
mac[2] = (word0 & 0xff000000) >> 24;
|
||||
mac[3] = (word0 & 0x00ff0000) >> 16;
|
||||
mac[4] = (word0 & 0x0000ff00) >> 8;
|
||||
mac[5] = (word0 & 0x000000ff);
|
||||
|
||||
LOG_INF("mac address %02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3],
|
||||
mac[4], mac[5]);
|
||||
}
|
||||
|
||||
static void m_numaker_gmacdev_enable(synopGMACdevice *gmacdev)
|
||||
{
|
||||
|
||||
synopGMAC_clear_interrupt(gmacdev);
|
||||
|
||||
/* Enable INT & TX/RX */
|
||||
synopGMAC_enable_interrupt(gmacdev, DmaIntEnable);
|
||||
synopGMAC_enable_dma_rx(gmacdev);
|
||||
synopGMAC_enable_dma_tx(gmacdev);
|
||||
|
||||
synopGMAC_tx_enable(gmacdev);
|
||||
synopGMAC_rx_enable(gmacdev);
|
||||
}
|
||||
|
||||
static int m_numaker_gmacdev_init(synopGMACdevice *gmacdev, uint8_t *mac_addr, uint32_t gmac_base)
|
||||
{
|
||||
int status;
|
||||
int i;
|
||||
uint32_t offload_needed = 0;
|
||||
struct sk_buff *skb;
|
||||
|
||||
LOG_DBG("");
|
||||
|
||||
/*Attach the device to MAC struct This will configure all the required base
|
||||
* addresses such as Mac base, configuration base, phy base address(out of 32
|
||||
* possible phys )
|
||||
*/
|
||||
synopGMAC_attach(gmacdev, gmac_base + MACBASE, gmac_base + DMABASE, DEFAULT_PHY_BASE);
|
||||
synopGMAC_disable_interrupt_all(gmacdev);
|
||||
|
||||
/* Reset MAC */
|
||||
synopGMAC_reset(gmacdev);
|
||||
gmacdev->Intf = NUMAKER_GMAC_INTF;
|
||||
synopGMAC_read_version(gmacdev);
|
||||
|
||||
/* Check for Phy initialization */
|
||||
synopGMAC_set_mdc_clk_div(gmacdev, GmiiCsrClk5);
|
||||
gmacdev->ClockDivMdc = synopGMAC_get_mdc_clk_div(gmacdev);
|
||||
|
||||
/* Reset PHY */
|
||||
status = reset_phy(gmacdev);
|
||||
|
||||
/* Set up the tx and rx descriptor queue/ring */
|
||||
synopGMAC_setup_tx_desc_queue(gmacdev, TRANSMIT_DESC_SIZE, RINGMODE);
|
||||
synopGMAC_init_tx_desc_base(gmacdev);
|
||||
|
||||
synopGMAC_setup_rx_desc_queue(gmacdev, RECEIVE_DESC_SIZE, RINGMODE);
|
||||
synopGMAC_init_rx_desc_base(gmacdev);
|
||||
|
||||
/* Initialize the dma interface */
|
||||
synopGMAC_dma_bus_mode_init(gmacdev,
|
||||
DmaBurstLength32 | DmaDescriptorSkip0 | DmaDescriptor8Words);
|
||||
synopGMAC_dma_control_init(gmacdev,
|
||||
DmaStoreAndForward | DmaTxSecondFrame | DmaRxThreshCtrl128);
|
||||
|
||||
/* Initialize the mac interface */
|
||||
synopGMAC_mac_init(gmacdev);
|
||||
synopGMAC_promisc_enable(gmacdev);
|
||||
|
||||
/* This enables the pause control in Full duplex mode of operation */
|
||||
synopGMAC_pause_control(gmacdev);
|
||||
|
||||
#if defined(NU_USING_HW_CHECKSUM)
|
||||
/*IPC Checksum offloading is enabled for this driver. Should only be used if
|
||||
* Full Ip checksumm offload engine is configured in the hardware
|
||||
*/
|
||||
offload_needed = 1;
|
||||
|
||||
/* Enable the offload engine in the receive path */
|
||||
synopGMAC_enable_rx_chksum_offload(gmacdev);
|
||||
|
||||
/* Default configuration, DMA drops the packets if error in encapsulated ethernet payload */
|
||||
synopGMAC_rx_tcpip_chksum_drop_enable(gmacdev);
|
||||
#endif
|
||||
|
||||
for (i = 0; i < RECEIVE_DESC_SIZE; i++) {
|
||||
skb = &rx_buf[NUMAKER_GMAC_INTF][i];
|
||||
synopGMAC_set_rx_qptr(gmacdev, (u32)((u64)(skb->data) & NUMAKER_MASK_32),
|
||||
sizeof(skb->data), (u32)((u64)skb & NUMAKER_MASK_32));
|
||||
}
|
||||
|
||||
for (i = 0; i < TRANSMIT_DESC_SIZE; i++) {
|
||||
skb = &tx_buf[NUMAKER_GMAC_INTF][i];
|
||||
synopGMAC_set_tx_qptr(gmacdev, (u32)((u64)(skb->data) & NUMAKER_MASK_32),
|
||||
sizeof(skb->data), (u32)((u64)skb & NUMAKER_MASK_32),
|
||||
offload_needed, 0);
|
||||
}
|
||||
|
||||
synopGMAC_set_mac_address(NUMAKER_GMAC_INTF, mac_addr);
|
||||
synopGMAC_clear_interrupt(gmacdev);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static int m_numaker_gmacdev_get_rx_buf(synopGMACdevice *gmacdev, uint16_t *len, uint8_t **buf)
|
||||
{
|
||||
DmaDesc *rxdesc = gmacdev->RxBusyDesc;
|
||||
|
||||
LOG_DBG("start");
|
||||
if (synopGMAC_is_desc_owned_by_dma(rxdesc)) {
|
||||
return -EIO;
|
||||
}
|
||||
if (synopGMAC_is_desc_empty(rxdesc)) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
*len = synop_handle_received_data(NUMAKER_GMAC_INTF, buf);
|
||||
if (*len <= 0) {
|
||||
synopGMAC_enable_interrupt(gmacdev, DmaIntEnable);
|
||||
return -ENOSPC; /* No available RX frame */
|
||||
}
|
||||
|
||||
/* length of payload should be <= 1514 */
|
||||
if (*len > (NU_ETH_MAX_FLEN - 4)) {
|
||||
LOG_DBG("unexpected long packet length=%d, buf=0x%x", *len, (uint32_t)*buf);
|
||||
*len = 0; /* Skip this unexpected long packet */
|
||||
}
|
||||
|
||||
LOG_DBG("end");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void m_numaker_gmacdev_rx_next(synopGMACdevice *gmacdev)
|
||||
{
|
||||
LOG_DBG("RX Next");
|
||||
/* Already did in synop_handle_received_data
|
||||
* No-op at this stage
|
||||
* DmaDesc * rxdesc = (gmacdev->RxBusyDesc - 1);
|
||||
* rxdesc->status = DescOwnByDma;
|
||||
*/
|
||||
}
|
||||
|
||||
static void m_numaker_gmacdev_trigger_rx(synopGMACdevice *gmacdev)
|
||||
{
|
||||
LOG_DBG("start");
|
||||
|
||||
/* Enable the interrupt */
|
||||
synopGMAC_enable_interrupt(gmacdev, DmaIntEnable);
|
||||
|
||||
/* Trigger RX DMA */
|
||||
synopGMAC_enable_dma_rx(gmacdev);
|
||||
synopGMAC_resume_dma_rx(gmacdev);
|
||||
LOG_DBG("resume RX DMA");
|
||||
LOG_DBG("end");
|
||||
}
|
||||
|
||||
static void m_numaker_gmacdev_packet_rx(const struct device *dev)
|
||||
{
|
||||
struct eth_numaker_data *data = dev->data;
|
||||
synopGMACdevice *gmacdev = data->gmacdev;
|
||||
uint8_t *buffer;
|
||||
uint16_t len;
|
||||
struct net_pkt *pkt;
|
||||
k_spinlock_key_t key;
|
||||
int res;
|
||||
|
||||
/* Get exclusive access, use spin-lock instead of mutex in ISR */
|
||||
key = k_spin_lock(&data->rx_frame_buf_lock);
|
||||
|
||||
/* Two approach: 1. recv all RX packets in one time.
|
||||
* 2. recv one RX and set pending interrupt for rx-next.
|
||||
*/
|
||||
while (1) {
|
||||
/* get received frame */
|
||||
if (m_numaker_gmacdev_get_rx_buf(gmacdev, &len, &buffer) != 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (len == 0) {
|
||||
LOG_WRN("No available RX frame");
|
||||
break;
|
||||
}
|
||||
/* Allocate a memory buffer chain from buffer pool
|
||||
* Using root iface. It will be updated in net_recv_data()
|
||||
*/
|
||||
pkt = net_pkt_rx_alloc_with_buffer(data->iface, len, AF_UNSPEC, 0, K_NO_WAIT);
|
||||
if (!pkt) {
|
||||
LOG_ERR("pkt alloc frame-len=%d failed", len);
|
||||
goto next;
|
||||
}
|
||||
|
||||
LOG_DBG("length=%d, pkt=0x%x", len, (uint32_t)pkt);
|
||||
/* deliver RX packet to upper layer, pack as one net_pkt */
|
||||
if (net_pkt_write(pkt, buffer, len)) {
|
||||
LOG_ERR("Unable to write RX frame into the pkt");
|
||||
net_pkt_unref(pkt);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (pkt != NULL) {
|
||||
res = net_recv_data(data->iface, pkt);
|
||||
if (res < 0) {
|
||||
LOG_ERR("net_recv_data: %d", res);
|
||||
net_pkt_unref(pkt);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
next:
|
||||
m_numaker_gmacdev_rx_next(gmacdev);
|
||||
}
|
||||
m_numaker_gmacdev_trigger_rx(gmacdev);
|
||||
|
||||
error:
|
||||
k_spin_unlock(&data->rx_frame_buf_lock, key);
|
||||
}
|
||||
|
||||
static uint8_t *m_numaker_gmacdev_get_tx_buf(synopGMACdevice *gmacdev)
|
||||
{
|
||||
DmaDesc *txdesc = gmacdev->TxNextDesc;
|
||||
|
||||
if (!synopGMAC_is_desc_empty(txdesc)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (synopGMAC_is_desc_owned_by_dma(txdesc)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return (uint8_t *)(txdesc->buffer1);
|
||||
}
|
||||
|
||||
static void m_numaker_gmacdev_trigger_tx(synopGMACdevice *gmacdev, uint16_t length)
|
||||
{
|
||||
DmaDesc *txdesc = gmacdev->TxNextDesc;
|
||||
uint32_t txnext = gmacdev->TxNext;
|
||||
bool offload_needed = IS_ENABLED(NU_USING_HW_CHECKSUM);
|
||||
|
||||
/* busy tx descriptor is incremented by one as it will be handed over to DMA */
|
||||
(gmacdev->BusyTxDesc)++;
|
||||
|
||||
txdesc->length |= ((length << DescSize1Shift) & DescSize1Mask);
|
||||
txdesc->status |= (DescTxFirst | DescTxLast | DescTxIntEnable);
|
||||
if (offload_needed) {
|
||||
/*
|
||||
* Make sure that the OS you are running supports the IP and TCP checksum
|
||||
* offloading, before calling any of the functions given below.
|
||||
*/
|
||||
synopGMAC_tx_checksum_offload_tcp_pseudo(gmacdev, txdesc);
|
||||
} else {
|
||||
synopGMAC_tx_checksum_offload_bypass(gmacdev, txdesc);
|
||||
}
|
||||
__DSB();
|
||||
txdesc->status |= DescOwnByDma;
|
||||
|
||||
gmacdev->TxNext = synopGMAC_is_last_tx_desc(gmacdev, txdesc) ? 0 : txnext + 1;
|
||||
gmacdev->TxNextDesc =
|
||||
synopGMAC_is_last_tx_desc(gmacdev, txdesc) ? gmacdev->TxDesc : (txdesc + 1);
|
||||
|
||||
/* Enable the interrupt */
|
||||
synopGMAC_enable_interrupt(gmacdev, DmaIntEnable);
|
||||
/* Trigger TX DMA */
|
||||
synopGMAC_resume_dma_tx(gmacdev);
|
||||
}
|
||||
|
||||
static int numaker_eth_tx(const struct device *dev, struct net_pkt *pkt)
|
||||
{
|
||||
struct eth_numaker_data *data = dev->data;
|
||||
synopGMACdevice *gmacdev = data->gmacdev;
|
||||
uint16_t total_len = net_pkt_get_len(pkt);
|
||||
uint8_t *buffer;
|
||||
|
||||
/* Get exclusive access */
|
||||
k_mutex_lock(&data->tx_frame_buf_mutex, K_FOREVER);
|
||||
if (total_len > NET_ETH_MAX_FRAME_SIZE) {
|
||||
/* NuMaker SDK reserve 2048 for tx_buf */
|
||||
LOG_ERR("TX packet length [%d] over max [%d]", total_len, NET_ETH_MAX_FRAME_SIZE);
|
||||
goto error;
|
||||
}
|
||||
|
||||
buffer = m_numaker_gmacdev_get_tx_buf(gmacdev);
|
||||
LOG_DBG("buffer=0x%x", (uint32_t)buffer);
|
||||
if (buffer == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (net_pkt_read(pkt, buffer, total_len)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Prepare transmit descriptors to give to DMA */
|
||||
m_numaker_gmacdev_trigger_tx(gmacdev, total_len);
|
||||
|
||||
k_mutex_unlock(&data->tx_frame_buf_mutex);
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
LOG_ERR("Writing pkt to TX descriptor failed");
|
||||
k_mutex_unlock(&data->tx_frame_buf_mutex);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static void numaker_eth_if_init(struct net_if *iface)
|
||||
{
|
||||
const struct device *dev = net_if_get_device(iface);
|
||||
struct eth_numaker_data *data = dev->data;
|
||||
|
||||
synopGMACdevice *gmacdev = data->gmacdev;
|
||||
|
||||
LOG_DBG("eth_if_init");
|
||||
|
||||
/* Read mac address */
|
||||
m_numaker_read_mac_addr(data->mac_addr);
|
||||
|
||||
net_if_set_link_addr(iface, data->mac_addr, sizeof(data->mac_addr), NET_LINK_ETHERNET);
|
||||
data->iface = iface;
|
||||
ethernet_init(iface);
|
||||
|
||||
/* Enable GMAC device INT & TX/RX */
|
||||
m_numaker_gmacdev_enable(gmacdev);
|
||||
}
|
||||
|
||||
static int numaker_eth_set_config(const struct device *dev, enum ethernet_config_type type,
|
||||
const struct ethernet_config *config)
|
||||
{
|
||||
struct eth_numaker_data *data = dev->data;
|
||||
|
||||
switch (type) {
|
||||
case ETHERNET_CONFIG_TYPE_MAC_ADDRESS:
|
||||
memcpy(data->mac_addr, config->mac_address.addr, sizeof(data->mac_addr));
|
||||
synopGMAC_set_mac_address(NUMAKER_GMAC_INTF, data->mac_addr);
|
||||
net_if_set_link_addr(data->iface, data->mac_addr, sizeof(data->mac_addr),
|
||||
NET_LINK_ETHERNET);
|
||||
LOG_DBG("%s MAC set to %02x:%02x:%02x:%02x:%02x:%02x", dev->name, data->mac_addr[0],
|
||||
data->mac_addr[1], data->mac_addr[2], data->mac_addr[3], data->mac_addr[4],
|
||||
data->mac_addr[5]);
|
||||
return 0;
|
||||
default:
|
||||
return -ENOTSUP;
|
||||
}
|
||||
}
|
||||
|
||||
static enum ethernet_hw_caps numaker_eth_get_cap(const struct device *dev)
|
||||
{
|
||||
ARG_UNUSED(dev);
|
||||
#if defined(NU_USING_HW_CHECKSUM)
|
||||
return ETHERNET_LINK_10BASE_T | ETHERNET_LINK_100BASE_T | ETHERNET_HW_RX_CHKSUM_OFFLOAD;
|
||||
#else
|
||||
return ETHERNET_LINK_10BASE_T | ETHERNET_LINK_100BASE_T;
|
||||
#endif
|
||||
}
|
||||
|
||||
static const struct ethernet_api eth_numaker_driver_api = {
|
||||
.iface_api.init = numaker_eth_if_init,
|
||||
.get_capabilities = numaker_eth_get_cap,
|
||||
.set_config = numaker_eth_set_config,
|
||||
.send = numaker_eth_tx,
|
||||
};
|
||||
|
||||
/* EMAC IRQ Handler */
|
||||
static void eth_numaker_isr(const struct device *dev)
|
||||
{
|
||||
struct eth_numaker_data *data = dev->data;
|
||||
synopGMACdevice *gmacdev = data->gmacdev;
|
||||
uint32_t interrupt;
|
||||
uint32_t dma_status_reg;
|
||||
uint32_t mac_status_reg;
|
||||
int status;
|
||||
uint32_t dma_ie = DmaIntEnable;
|
||||
|
||||
uint32_t volatile reg;
|
||||
|
||||
/* Check GMAC interrupt */
|
||||
mac_status_reg = synopGMACReadReg((u32 *)gmacdev->MacBase, GmacInterruptStatus);
|
||||
if (mac_status_reg & GmacTSIntSts) {
|
||||
gmacdev->synopGMACNetStats.ts_int = 1;
|
||||
status = synopGMACReadReg((u32 *)gmacdev->MacBase, GmacTSStatus);
|
||||
if (!(status & BIT(1))) {
|
||||
LOG_WRN("TS alarm flag not set??");
|
||||
} else {
|
||||
LOG_DBG("TS alarm");
|
||||
}
|
||||
}
|
||||
|
||||
if (mac_status_reg & GmacLPIIntSts) {
|
||||
LOG_DBG("LPI");
|
||||
}
|
||||
|
||||
if (mac_status_reg & GmacRgmiiIntSts) {
|
||||
reg = synopGMACReadReg((u32 *)gmacdev->MacBase, GmacRgmiiCtrlSts);
|
||||
}
|
||||
|
||||
synopGMACWriteReg((u32 *)gmacdev->MacBase, GmacInterruptStatus, mac_status_reg);
|
||||
/* Read the Dma interrupt status to know whether the interrupt got generated by
|
||||
* our device or not
|
||||
*/
|
||||
dma_status_reg = synopGMACReadReg((u32 *)gmacdev->DmaBase, DmaStatus);
|
||||
LOG_DBG("i %08x %08x", mac_status_reg, dma_status_reg);
|
||||
|
||||
if (dma_status_reg == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
synopGMAC_disable_interrupt_all(gmacdev);
|
||||
LOG_DBG("Dma Status Reg: 0x%08x", dma_status_reg);
|
||||
if (dma_status_reg & GmacPmtIntr) {
|
||||
LOG_DBG("Interrupt due to PMT module");
|
||||
synopGMAC_powerup_mac(gmacdev);
|
||||
}
|
||||
|
||||
if (dma_status_reg & GmacLineIntfIntr) {
|
||||
LOG_DBG("Interrupt due to GMAC LINE module");
|
||||
}
|
||||
|
||||
/* Now lets handle the DMA interrupts */
|
||||
interrupt = synopGMAC_get_interrupt_type(gmacdev);
|
||||
LOG_DBG("Interrupts to be handled: 0x%08x", interrupt);
|
||||
if (interrupt & synopGMACDmaError) {
|
||||
LOG_DBG("Fatal Bus Error Interrupt Seen");
|
||||
synopGMAC_disable_dma_tx(gmacdev);
|
||||
synopGMAC_disable_dma_rx(gmacdev);
|
||||
|
||||
synopGMAC_take_desc_ownership_tx(gmacdev);
|
||||
synopGMAC_take_desc_ownership_rx(gmacdev);
|
||||
|
||||
synopGMAC_init_tx_rx_desc_queue(gmacdev);
|
||||
|
||||
synopGMAC_reset(gmacdev); /* reset the DMA engine and the GMAC ip */
|
||||
synopGMAC_set_mac_address(NUMAKER_GMAC_INTF, data->mac_addr);
|
||||
synopGMAC_dma_bus_mode_init(gmacdev, DmaFixedBurstEnable | DmaBurstLength8 |
|
||||
DmaDescriptorSkip0);
|
||||
synopGMAC_dma_control_init(gmacdev, DmaStoreAndForward);
|
||||
synopGMAC_init_rx_desc_base(gmacdev);
|
||||
synopGMAC_init_tx_desc_base(gmacdev);
|
||||
synopGMAC_mac_init(gmacdev);
|
||||
synopGMAC_enable_dma_rx(gmacdev);
|
||||
synopGMAC_enable_dma_tx(gmacdev);
|
||||
}
|
||||
|
||||
if (interrupt & synopGMACDmaRxNormal) {
|
||||
LOG_DBG("Rx Normal");
|
||||
/* disable RX interrupt */
|
||||
dma_ie &= ~DmaIntRxNormMask;
|
||||
/* to handle received data */
|
||||
m_numaker_gmacdev_packet_rx(dev);
|
||||
}
|
||||
|
||||
if (interrupt & synopGMACDmaRxAbnormal) {
|
||||
LOG_ERR("Abnormal Rx Interrupt Seen");
|
||||
/* If Mac is not in powerdown */
|
||||
if (gmacdev->GMAC_Power_down == 0) {
|
||||
gmacdev->synopGMACNetStats.rx_over_errors++;
|
||||
dma_ie &= ~DmaIntRxAbnMask;
|
||||
/* To handle GBPS with 12 descriptors. */
|
||||
synopGMAC_resume_dma_rx(gmacdev);
|
||||
}
|
||||
}
|
||||
|
||||
/* Receiver gone in to stopped state */
|
||||
if (interrupt & synopGMACDmaRxStopped) {
|
||||
LOG_ERR("Receiver stopped seeing Rx interrupts");
|
||||
if (gmacdev->GMAC_Power_down == 0) {
|
||||
gmacdev->synopGMACNetStats.rx_over_errors++;
|
||||
synopGMAC_enable_dma_rx(gmacdev);
|
||||
}
|
||||
}
|
||||
|
||||
if (interrupt & synopGMACDmaTxNormal) {
|
||||
LOG_DBG("Finished Normal Transmission");
|
||||
synop_handle_transmit_over(0);
|
||||
/* No-op at this stage for TX INT */
|
||||
}
|
||||
|
||||
if (interrupt & synopGMACDmaTxAbnormal) {
|
||||
LOG_ERR("Abnormal Tx Interrupt Seen");
|
||||
if (gmacdev->GMAC_Power_down == 0) {
|
||||
synop_handle_transmit_over(0);
|
||||
/* No-op at this stage for TX INT */
|
||||
}
|
||||
}
|
||||
|
||||
if (interrupt & synopGMACDmaTxStopped) {
|
||||
LOG_ERR("Transmitter stopped sending the packets");
|
||||
if (gmacdev->GMAC_Power_down == 0) {
|
||||
synopGMAC_disable_dma_tx(gmacdev);
|
||||
synopGMAC_take_desc_ownership_tx(gmacdev);
|
||||
synopGMAC_enable_dma_tx(gmacdev);
|
||||
LOG_ERR("Transmission Resumed");
|
||||
}
|
||||
}
|
||||
|
||||
/* Enable the interrupt before returning from ISR*/
|
||||
synopGMAC_enable_interrupt(gmacdev, dma_ie);
|
||||
}
|
||||
|
||||
/* Declare pin-ctrl __pinctrl_dev_config__device_dts_ord_xx before
|
||||
* PINCTRL_DT_INST_DEV_CONFIG_GET()
|
||||
*/
|
||||
PINCTRL_DT_INST_DEFINE(0);
|
||||
|
||||
static int eth_numaker_init(const struct device *dev)
|
||||
{
|
||||
const struct eth_numaker_config *cfg = dev->config;
|
||||
struct eth_numaker_data *data = dev->data;
|
||||
synopGMACdevice *gmacdev;
|
||||
|
||||
/* Init MAC Address based on UUID*/
|
||||
uint8_t mac_addr[NU_HWADDR_SIZE];
|
||||
int ret = 0;
|
||||
struct numaker_scc_subsys scc_subsys;
|
||||
|
||||
gmacdev = &GMACdev[NUMAKER_GMAC_INTF];
|
||||
data->gmacdev = gmacdev;
|
||||
|
||||
k_mutex_init(&data->tx_frame_buf_mutex);
|
||||
|
||||
eth_phy_addr = cfg->phy_addr;
|
||||
|
||||
/* CLK controller */
|
||||
memset(&scc_subsys, 0x00, sizeof(scc_subsys));
|
||||
scc_subsys.subsys_id = NUMAKER_SCC_SUBSYS_ID_PCC;
|
||||
scc_subsys.pcc.clk_modidx = cfg->clk_modidx;
|
||||
scc_subsys.pcc.clk_src = cfg->clk_src;
|
||||
scc_subsys.pcc.clk_div = cfg->clk_div;
|
||||
|
||||
/* Equivalent to CLK_EnableModuleClock() */
|
||||
ret = clock_control_on(cfg->clk_dev, (clock_control_subsys_t)&scc_subsys);
|
||||
if (ret != 0) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* For EMAC, not need CLK_SetModuleClock()
|
||||
* Validate this module's reset object
|
||||
*/
|
||||
if (!device_is_ready(cfg->reset.dev)) {
|
||||
LOG_ERR("reset controller not ready");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
SYS_UnlockReg();
|
||||
|
||||
irq_disable(DT_INST_IRQN(0));
|
||||
ret = pinctrl_apply_state(cfg->pincfg, PINCTRL_STATE_DEFAULT);
|
||||
if (ret != 0) {
|
||||
LOG_ERR("Failed to apply pinctrl state");
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Reset EMAC to default state, same as BSP's SYS_ResetModule(id_rst) */
|
||||
reset_line_toggle_dt(&cfg->reset);
|
||||
|
||||
/* Read mac address */
|
||||
m_numaker_read_mac_addr(mac_addr);
|
||||
|
||||
/* Configure GMAC device */
|
||||
ret = m_numaker_gmacdev_init(gmacdev, mac_addr, cfg->gmac_base);
|
||||
if (ret != 0) {
|
||||
LOG_ERR("GMAC failed to initialize");
|
||||
goto done;
|
||||
}
|
||||
|
||||
IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), eth_numaker_isr,
|
||||
DEVICE_DT_INST_GET(0), 0);
|
||||
|
||||
irq_enable(DT_INST_IRQN(0));
|
||||
|
||||
done:
|
||||
SYS_LockReg();
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct eth_numaker_data eth_numaker_data_inst;
|
||||
|
||||
/* Set config based on DTS */
|
||||
static struct eth_numaker_config eth_numaker_cfg_inst = {
|
||||
.gmac_base = (uint32_t)DT_INST_REG_ADDR(0),
|
||||
.reset = RESET_DT_SPEC_INST_GET(0),
|
||||
.phy_addr = DT_INST_PROP(0, phy_addr),
|
||||
.clk_modidx = DT_INST_CLOCKS_CELL(0, clock_module_index),
|
||||
.clk_src = DT_INST_CLOCKS_CELL(0, clock_source),
|
||||
.clk_div = DT_INST_CLOCKS_CELL(0, clock_divider),
|
||||
.clk_dev = DEVICE_DT_GET(DT_PARENT(DT_INST_CLOCKS_CTLR(0))),
|
||||
.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0),
|
||||
.reset = RESET_DT_SPEC_INST_GET(0),
|
||||
};
|
||||
|
||||
ETH_NET_DEVICE_DT_INST_DEFINE(0, eth_numaker_init, NULL, ð_numaker_data_inst,
|
||||
ð_numaker_cfg_inst, CONFIG_ETH_INIT_PRIORITY,
|
||||
ð_numaker_driver_api, NET_ETH_MTU);
|
18
drivers/ethernet/eth_numaker_priv.h
Normal file
18
drivers/ethernet/eth_numaker_priv.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Nuvoton Technology Corporation.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef ZEPHYR_DRIVERS_ETHERNET_ETH_NUMAKER_PRIV_H_
|
||||
#define ZEPHYR_DRIVERS_ETHERNET_ETH_NUMAKER_PRIV_H_
|
||||
|
||||
#include <zephyr/types.h>
|
||||
|
||||
#define NU_ETH_MAX_FLEN (1518)
|
||||
|
||||
#define NU_HWADDR_SIZE (6)
|
||||
|
||||
#define NU_ETH_MTU_SIZE 1500
|
||||
|
||||
#endif /* ZEPHYR_DRIVERS_ETHERNET_ETH_NUMAKER_PRIV_H_ */
|
|
@ -492,6 +492,16 @@
|
|||
sample-point = <875>;
|
||||
sample-point-data = <875>;
|
||||
};
|
||||
|
||||
emac: ethernet@40012000 {
|
||||
compatible = "nuvoton,numaker-ethernet";
|
||||
reg = <0x40012000 0x105C>;
|
||||
interrupts = <66 0>;
|
||||
resets = <&rst NUMAKER_EMAC0_RST>;
|
||||
phy-addr = <1>;
|
||||
clocks = <&pcc NUMAKER_EMAC0_MODULE 0 0>;
|
||||
status = "disabled";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
|
30
dts/bindings/ethernet/nuvoton,numaker-ethernet.yaml
Normal file
30
dts/bindings/ethernet/nuvoton,numaker-ethernet.yaml
Normal file
|
@ -0,0 +1,30 @@
|
|||
# Copyright (c) 2023 Nuvoton Technology Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
description: Nuvoton, NuMaker Ethernet controller
|
||||
|
||||
compatible: "nuvoton,numaker-ethernet"
|
||||
|
||||
include:
|
||||
- ethernet-controller.yaml
|
||||
- ethernet,fixed-link.yaml
|
||||
- reset-device.yaml
|
||||
- pinctrl-device.yaml
|
||||
|
||||
properties:
|
||||
reg:
|
||||
required: true
|
||||
|
||||
interrupts:
|
||||
required: true
|
||||
|
||||
resets:
|
||||
required: true
|
||||
|
||||
clocks:
|
||||
required: true
|
||||
|
||||
phy-addr:
|
||||
type: int
|
||||
description: Address of the phy controller
|
||||
required: true
|
Loading…
Reference in a new issue