diff --git a/cpu/sam0_common/Makefile b/cpu/sam0_common/Makefile index e09377cd1e..3824d9e978 100644 --- a/cpu/sam0_common/Makefile +++ b/cpu/sam0_common/Makefile @@ -1,3 +1,7 @@ DIRS = periph +ifneq (, $(filter sam0_eth, $(USEMODULE))) + DIRS += sam0_eth +endif + include $(RIOTBASE)/Makefile.base diff --git a/cpu/sam0_common/Makefile.dep b/cpu/sam0_common/Makefile.dep index 183c361c72..276bd6531c 100644 --- a/cpu/sam0_common/Makefile.dep +++ b/cpu/sam0_common/Makefile.dep @@ -12,4 +12,11 @@ USEMODULE += pm_layered # include sam0 common periph drivers USEMODULE += sam0_common_periph +ifneq (,$(filter sam0_eth,$(USEMODULE))) + USEMODULE += netdev_eth + USEMODULE += netopt + USEMODULE += xtimer + USEMODULE += iolist + FEATURES_REQUIRED += periph_eth +endif include $(RIOTCPU)/cortexm_common/Makefile.dep diff --git a/cpu/sam0_common/Makefile.include b/cpu/sam0_common/Makefile.include index 8a15929c2b..01803bc058 100644 --- a/cpu/sam0_common/Makefile.include +++ b/cpu/sam0_common/Makefile.include @@ -31,5 +31,9 @@ LINKER_SCRIPT ?= cortexm.ld INCLUDES += -I$(RIOTCPU)/sam0_common/include +ifneq (,$(filter sam0_eth,$(USEMODULE))) + INCLUDES += -I$(RIOTCPU)/sam0_common/sam0_eth/ +endif + PSEUDOMODULES += periph_rtc PSEUDOMODULES += periph_rtt diff --git a/cpu/sam0_common/include/periph_cpu_common.h b/cpu/sam0_common/include/periph_cpu_common.h index d6310e6123..2f3203515b 100644 --- a/cpu/sam0_common/include/periph_cpu_common.h +++ b/cpu/sam0_common/include/periph_cpu_common.h @@ -141,6 +141,7 @@ typedef enum { GPIO_MUX_F = 0x5, /**< select peripheral function F */ GPIO_MUX_G = 0x6, /**< select peripheral function G */ GPIO_MUX_H = 0x7, /**< select peripheral function H */ + GPIO_MUX_L = 0xb, } gpio_mux_t; #endif @@ -767,6 +768,48 @@ typedef struct { uint32_t muxpos; /**< ADC channel pin multiplexer value */ } adc_conf_chan_t; +/** + * @name Ethernet peripheral parameters + * @{ + */ +#ifndef ETH_RX_BUFFER_COUNT +#define ETH_RX_BUFFER_COUNT (4) +#endif + +#ifndef ETH_TX_BUFFER_COUNT +#define ETH_TX_BUFFER_COUNT (4) +#endif + +#ifndef ETH_RX_BUFFER_SIZE +#define ETH_RX_BUFFER_SIZE (1536) +#endif + +#ifndef ETH_TX_BUFFER_SIZE +#define ETH_TX_BUFFER_SIZE (1536) +#endif +/** @} */ + +/** + * @brief Ethernet parameters struct + */ +#if defined(GMAC_INST_NUM) || defined(DOXYGEN) +typedef struct { + Gmac *dev; /**< ptr to the device registers */ + gpio_t refclk; /**< REFCLK gpio */ + gpio_t txen; /**< TXEN gpio */ + gpio_t txd0; /**< TXD0 gpio */ + gpio_t txd1; /**< TXD1 gpio */ + gpio_t crsdv; /**< CRSDV gpio */ + gpio_t rxd0; /**< RXD0 gpio */ + gpio_t rxd1; /**< RXD1 gpio */ + gpio_t rxer; /**< RXER gpio */ + gpio_t mdc; /**< MII interface, clock gpio */ + gpio_t mdio; /**< MII interface, data gpio */ + gpio_t rst_pin; /**< PHY reset gpio */ + gpio_t int_pin; /**< PHY interrupt gpio */ +} sam0_common_gmac_config_t; +#endif + /** * @brief USB peripheral parameters */ diff --git a/cpu/sam0_common/periph/eth.c b/cpu/sam0_common/periph/eth.c new file mode 100644 index 0000000000..46cd5dd607 --- /dev/null +++ b/cpu/sam0_common/periph/eth.c @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2020 Mesotic SAS + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup cpu_sam0_common + * @{ + * + * @file + * @brief Low-level Ethernet driver implementation + * + * @author Dylan Laduranty + * + * @} + */ + +#include "iolist.h" +#include "net/eui48.h" +#include "net/ethernet.h" +#include "net/netdev/eth.h" + +#include "periph/gpio.h" + +#include "sam0_eth_netdev.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" +#include "log.h" + +#include + +/* Internal helpers */ +#define PHY_READ_OP 0x02 +#define PHY_WRITE_OP 0x01 + +/* Internal RX/TX descriptors */ +#define DESC_RX_ADDR_OWNSHP 1 +#define DESC_RX_ADDR_WRAP 2 +#define DESC_RX_ADDR_ADDR_MASK 0xFFFFFFFC + +#define DESC_RX_STATUS_FRAME_LEN_MASK 0x1FFF + +#define DESC_RX_STATUS_FCS_MASK (1ul << 13) +#define DESC_RX_STATUS_STA_FRAME (1ul << 14) +#define DESC_RX_STATUS_END_FRAME (1ul << 15) +#define DESC_RX_STATUS_CFI (1ul << 16) +#define DESC_RX_STATUS_VLAN_PRIO (1ul << 17) || (1ul << 18) || (1ul << 19) +#define DESC_RX_STATUS_PRIO_TAG (1ul << 20) +#define DESC_RX_STATUS_VLAN_TAG (1ul << 21) +#define DESC_RX_STATUS_TYPE_ID (1ul << 22) || (1ul << 23) +#define DESC_RX_STATUS_CHKSUM (1ul << 24) +#define DESC_RX_STATUS_SPEC_ADDR (1ul << 25) || (1ul << 26) +#define DESC_RX_STATUS_SPEC_TAG (1ul << 27) +#define DESC_RX_STATUS_UNIHASH (1ul << 29) +#define DESC_RX_STATUS_MULTIHASH (1ul << 30) +#define DESC_RX_STATUS_BROADCAST (1ul << 31) + +#define DESC_TX_STATUS_LEN_MASK 0x3FFF +#define DESC_TX_STATUS_LAST_BUF (1ul << 15) +#define DESC_TX_STATUS_CRC (1ul << 16) +#define DESC_TX_STATUS_CHKSUM (1ul << 20) || (1ul << 21) || (1ul << 22) +#define DESC_TX_STATUS_LATE_COL (1ul << 26) +#define DESC_TX_STATUS_TX_ERROR (1ul << 27) +#define DESC_TX_STATUS_RETRY (1ul << 29) +#define DESC_TX_STATUS_WRAP (1ul << 30) +#define DESC_TX_STATUS_USED (1ul << 31) + +struct eth_buf_desc { + uint32_t address; + uint32_t status; +}; + +/* GMAC buffer descriptors */ +#define GMAC_DESC_ALIGNMENT 8 +#define GMAC_BUF_ALIGNMENT 32 +static struct eth_buf_desc rx_desc[ETH_RX_BUFFER_COUNT] __attribute__((aligned(GMAC_DESC_ALIGNMENT))); +static struct eth_buf_desc tx_desc[ETH_TX_BUFFER_COUNT] __attribute__((aligned(GMAC_DESC_ALIGNMENT))); + +static struct eth_buf_desc *rx_curr; +static struct eth_buf_desc *tx_curr; + +/* Declare our own indexes to point to a RX/TX buffer descriptor. + GMAC IP have its own indexes on its side */ +static uint8_t tx_idx; +static uint8_t rx_idx; + +static uint8_t rx_buf[ETH_RX_BUFFER_COUNT][ETH_RX_BUFFER_SIZE] __attribute__((aligned(GMAC_BUF_ALIGNMENT))); +static uint8_t tx_buf[ETH_TX_BUFFER_COUNT][ETH_TX_BUFFER_SIZE] __attribute__((aligned(GMAC_BUF_ALIGNMENT))); + +static void _init_desc_buf(void) +{ + int i; + /* Initialize RX buffer descriptors */ + for (i=0; i < ETH_RX_BUFFER_COUNT; i++) { + rx_desc[i].address = ((uint32_t) (rx_buf[i]) & DESC_RX_ADDR_ADDR_MASK); + } + /* Set WRAP flag to indicate last buffer */ + rx_desc[i-1].address |= DESC_RX_ADDR_WRAP; + rx_curr = &rx_desc[0]; + /* Initialize TX buffer descriptors */ + for (i=0; i < ETH_TX_BUFFER_COUNT; i++) { + tx_desc[i].address = (uint32_t) tx_buf[i]; + } + /* Set WRAP flag to indicate last buffer */ + tx_desc[i-1].status |= DESC_TX_STATUS_WRAP; + tx_curr = &tx_desc[0]; + /* Setup buffers index */ + rx_idx = 0; + tx_idx = 0; + /* Store RX buffer descriptor list */ + GMAC->RBQB.reg = (uint32_t) rx_desc; + /* Store TX buffer descriptor list */ + GMAC->TBQB.reg = (uint32_t) tx_desc; +} + +int sam0_read_phy(uint8_t phy, uint8_t addr) +{ + GMAC->MAN.reg = GMAC_MAN_REGA(addr) | GMAC_MAN_PHYA(phy) + | GMAC_MAN_CLTTO | GMAC_MAN_WTN(0x2) + | GMAC_MAN_OP(PHY_READ_OP); + + /* Wait for operation completion */ + while (!GMAC->NSR.bit.IDLE) {} + /* return content of shift register */ + return (GMAC->MAN.reg & GMAC_MAN_DATA_Msk); +} + +void sam0_write_phy(uint8_t phy, uint8_t addr, uint16_t data) +{ + GMAC->MAN.reg = GMAC_MAN_REGA(addr) | GMAC_MAN_PHYA(phy) + | GMAC_MAN_WTN(0x2) | GMAC_MAN_OP(PHY_WRITE_OP) + | GMAC_MAN_CLTTO | GMAC_MAN_DATA(data); + + /* Wait for operation completion */ + while (!GMAC->NSR.bit.IDLE) {} +} + +void sam0_eth_set_mac(const eui48_t *mac) +{ + GMAC->Sa[0].SAT.reg = ((mac->uint8[5] << 8) | mac->uint8[4]); + GMAC->Sa[0].SAB.reg = ((mac->uint8[3] << 24) | (mac->uint8[2] << 16) + | (mac->uint8[1] << 8) | mac->uint8[0]); +} + +void sam0_eth_get_mac(eui48_t *out) +{ + out->uint8[5] = (GMAC->Sa[0].SAT.reg >> 8); + out->uint8[4] = (GMAC->Sa[0].SAT.reg); + out->uint8[3] = (GMAC->Sa[0].SAB.reg >> 24); + out->uint8[2] = (GMAC->Sa[0].SAB.reg >> 16); + out->uint8[1] = (GMAC->Sa[0].SAB.reg >> 8); + out->uint8[0] = (GMAC->Sa[0].SAB.reg); +} + +int sam0_eth_send(const struct iolist *iolist) +{ + unsigned len = iolist_size(iolist); + unsigned tx_len = 0; + tx_curr = &tx_desc[tx_idx]; + + /* load packet data into TX buffer */ + for (const iolist_t *iol = iolist; iol; iol = iol->iol_next) { + if (tx_len + iol->iol_len > ETHERNET_MAX_LEN) { + return -EBUSY; + } + if (iol->iol_len) { + memcpy ((uint32_t*)(tx_curr->address + tx_len), iol->iol_base, iol->iol_len); + tx_len += iol->iol_len; + } + } + if (len == tx_len) { + /* Clear and set the frame size */ + tx_curr->status &= ~DESC_TX_STATUS_LEN_MASK; + tx_curr->status |= (len & DESC_TX_STATUS_LEN_MASK); + /* Indicate this is the last buffer and the frame is ready */ + tx_curr->status |= DESC_TX_STATUS_LAST_BUF | DESC_TX_STATUS_USED; + /* Prepare next buffer index */ + tx_idx = (tx_idx < ETH_TX_BUFFER_COUNT-1) ? tx_idx+1 : 0; + __DSB(); + tx_curr->status &= ~DESC_TX_STATUS_USED; + /* Start transmission */ + GMAC->NCR.reg |= GMAC_NCR_TSTART; + /* Set the next buffer */ + tx_curr = &tx_desc[tx_idx]; + } + else { + DEBUG("Mismatch TX len, abort send\n"); + } + return len; +} + +static int _try_receive(char* data, int max_len, int block) +{ + (void)block; + unsigned rxlen = 0; + uint16_t idx = rx_idx; + /* Ensure we are at the beginning of the new frame */ + while (!(rx_curr->address & DESC_RX_ADDR_OWNSHP) && (rx_curr->status & DESC_RX_STATUS_STA_FRAME)) {} + + for (unsigned cpt=0; cpt < ETH_RX_BUFFER_COUNT; cpt++) { + /* Get the length of the received frame */ + unsigned len = (rx_curr->status & DESC_RX_STATUS_FRAME_LEN_MASK); + + /* Only copy data if the stack requested it, otherwise return the length + of the next frame if available */ + if (max_len) { + /* If buffer available, copy data into it */ + if (data) { + memcpy(&data[rxlen], rx_buf[idx], len); + } + /* Tell the GMAC IP that we don't need this frame anymore */ + rx_curr->address &= ~DESC_RX_ADDR_OWNSHP; + } + + rxlen += len; + + if (rx_curr->status & DESC_RX_STATUS_END_FRAME) { + /* We reach the end of frame, leave the loop */ + break; + } + /* Prepare next buffer */ + idx = (idx + 1) % ETH_RX_BUFFER_COUNT; + rx_curr = &rx_desc[idx]; + + } + /* restore the previous index if packets were not released */ + if (!max_len) { + rx_curr = &rx_desc[rx_idx]; + } + /* Point to the next buffer as GMAC IP will likely used it + to store the next frame */ + else { + rx_idx = (idx+1) % ETH_RX_BUFFER_COUNT; + rx_curr = &rx_desc[rx_idx]; + } + + return rxlen; +} + +int sam0_eth_receive_blocking(char *data, unsigned max_len) +{ + return _try_receive(data, max_len, 1); +} + +static void _enable_clock(void) +{ + /* Enable GMAC clocks */ + MCLK->AHBMASK.reg |= MCLK_AHBMASK_GMAC; + MCLK->APBCMASK.reg |= MCLK_APBCMASK_GMAC; +} + +int sam0_eth_init(void) +{ + /* Enable clocks */ + _enable_clock(); + /* Initialize GPIOs */ + gpio_init_mux(sam_gmac_config[0].refclk, GPIO_MUX_L); + gpio_init_mux(sam_gmac_config[0].txen, GPIO_MUX_L); + gpio_init_mux(sam_gmac_config[0].txd0, GPIO_MUX_L); + gpio_init_mux(sam_gmac_config[0].txd1, GPIO_MUX_L); + gpio_init_mux(sam_gmac_config[0].crsdv, GPIO_MUX_L); + gpio_init_mux(sam_gmac_config[0].rxd0, GPIO_MUX_L); + gpio_init_mux(sam_gmac_config[0].rxd1, GPIO_MUX_L); + gpio_init_mux(sam_gmac_config[0].rxer, GPIO_MUX_L); + gpio_init_mux(sam_gmac_config[0].mdc, GPIO_MUX_L); + gpio_init_mux(sam_gmac_config[0].mdio, GPIO_MUX_L); + /* PHY reset */ + gpio_init(sam_gmac_config[0].rst_pin, GPIO_OUT); + gpio_clear(sam_gmac_config[0].rst_pin); + + /* reset buffers */ + memset(rx_buf, 0, sizeof(rx_buf)); + memset(tx_buf, 0, sizeof(tx_buf)); + memset(rx_desc, 0, sizeof(rx_desc)); + memset(tx_desc, 0, sizeof(tx_desc)); + + /* Enable PHY */ + gpio_set(sam_gmac_config[0].rst_pin); + + /* Initialize buffers descriptor */ + _init_desc_buf(); + /* Disable RX and TX */ + GMAC->NCR.reg &= ~(GMAC_NCR_RXEN | GMAC_NCR_TXEN); + /* Enable Port Management */ + GMAC->NCR.reg |= GMAC_NCR_MPE; + /* TODO: Implements MII, default to RMII */ + GMAC->UR.reg = 0; + /* disable all interrupts */ + GMAC->IDR.reg = 0xFFFFFFFF; + /* clear flags */ + GMAC->RSR.reg = GMAC_RSR_HNO | GMAC_RSR_RXOVR | GMAC_RSR_REC | GMAC_RSR_BNA; + /* Enable needed interrupts */ + GMAC->IER.reg = GMAC_IER_RCOMP; + + /* Set TxBase-100-FD by default */ + /* TODO: implement auto negotiation */ + GMAC->NCFGR.reg |= (GMAC_NCFGR_SPD | GMAC_NCFGR_FD | GMAC_NCFGR_MTIHEN | + GMAC_NCFGR_RXCOEN | GMAC_NCFGR_MAXFS | GMAC_NCFGR_CAF | + GMAC_NCFGR_LFERD | GMAC_NCFGR_RFCS | GMAC_NCFGR_CLK(3)); + + /* Enable all multicast addresses */ + GMAC->HRB.reg = 0xffffffff; + GMAC->HRT.reg = 0xffffffff; + + /* Set DMA receive buffer size to 1536 bytes */ + GMAC->DCFGR.reg |= GMAC_DCFGR_DRBS(0x18); + /* Enable PHY */ + gpio_set(sam_gmac_config[0].rst_pin); + /* Enable IRQ */ + NVIC_EnableIRQ(GMAC_IRQn); + /* Enable both receiver and transmitter */ + GMAC->NCR.reg |= GMAC_NCR_TXEN | GMAC_NCR_RXEN; + + return 0; +} diff --git a/cpu/sam0_common/sam0_eth/Makefile b/cpu/sam0_common/sam0_eth/Makefile new file mode 100644 index 0000000000..0b76ba4d73 --- /dev/null +++ b/cpu/sam0_common/sam0_eth/Makefile @@ -0,0 +1,3 @@ +MODULE=sam0_eth + +include $(RIOTBASE)/Makefile.base diff --git a/cpu/sam0_common/sam0_eth/eth-netdev.c b/cpu/sam0_common/sam0_eth/eth-netdev.c new file mode 100644 index 0000000000..6592478f33 --- /dev/null +++ b/cpu/sam0_common/sam0_eth/eth-netdev.c @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2020 Mesotic SAS + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup cpu_sam0_common + * @{ + * + * @file + * @brief Low-level Ethernet driver implementation + * + * @author Dylan Laduranty + * + * @} + */ +#include + +#include "iolist.h" +#include "net/gnrc/netif/ethernet.h" +#include "net/gnrc.h" +#include "net/ethernet.h" +#include "net/netdev/eth.h" +#include "net/eui_provider.h" + +#include "periph/gpio.h" + +#include "sam0_eth_netdev.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" +#include "log.h" + +/* Internal helpers */ +extern int sam0_eth_init(void); +extern int sam0_eth_send(const struct iolist *iolist); +extern int sam0_eth_receive_blocking(char *data, unsigned max_len); +extern void sam0_eth_set_mac(const eui48_t *mac); +extern void sam0_eth_get_mac(char *out); + +/* SAM0 CPUs only have one GMAC IP, so it is safe to +statically defines one in this file */ +static sam0_eth_netdev_t _sam0_eth_dev; + +static int _sam0_eth_init(netdev_t *netdev) +{ + sam0_eth_init(); + eui48_t hwaddr; + netdev_eui48_get(netdev, &hwaddr); + sam0_eth_set_mac(&hwaddr); + return 0; +} + +static void _sam0_eth_isr(netdev_t *netdev) +{ + netdev->event_callback(netdev, NETDEV_EVENT_RX_COMPLETE); + return; +} + +static int _sam0_eth_recv(netdev_t *netdev, void *buf, size_t len, void *info) +{ + (void)info; + (void)netdev; + unsigned ret = sam0_eth_receive_blocking((char *)buf, len); + return ret; +} + +static int _sam0_eth_send(netdev_t *netdev, const iolist_t *iolist) +{ + int ret; + + netdev->event_callback(netdev, NETDEV_EVENT_TX_STARTED); + ret = sam0_eth_send(iolist); + if (ret == -EOVERFLOW) { + /* TODO: use a specific netdev callback here ? */ + return -EOVERFLOW; + } + netdev->event_callback(netdev, NETDEV_EVENT_TX_COMPLETE); + return ret; +} + +static int _sam0_eth_get(netdev_t *netdev, netopt_t opt, void *val, size_t max_len) +{ + int res = -1; + + switch (opt) { + case NETOPT_ADDRESS: + assert(max_len >= ETHERNET_ADDR_LEN); + sam0_eth_get_mac((char *)val); + res = ETHERNET_ADDR_LEN; + break; + default: + res = netdev_eth_get(netdev, opt, val, max_len); + break; + } + + return res; +} + +static int _sam0_eth_set(netdev_t *netdev, netopt_t opt, const void *val, size_t max_len) +{ + int res = -1; + + switch (opt) { + case NETOPT_ADDRESS: + assert(max_len >= ETHERNET_ADDR_LEN); + sam0_eth_set_mac((eui48_t *)val); + res = ETHERNET_ADDR_LEN; + break; + default: + res = netdev_eth_set(netdev, opt, val, max_len); + break; + } + + return res; +} + +static const netdev_driver_t _sam0_eth_driver = +{ + .send = _sam0_eth_send, + .recv = _sam0_eth_recv, + .init = _sam0_eth_init, + .isr = _sam0_eth_isr, + .get = _sam0_eth_get, + .set = _sam0_eth_set, +}; + +void sam0_eth_setup(netdev_t* netdev) +{ + + DEBUG_PUTS("[sam0_eth]: initializing SAM0 Ethernet MAC (GMAC) device"); + + _sam0_eth_dev.netdev = netdev; + /* set the netdev driver */ + netdev->driver = &_sam0_eth_driver; + /* Register SAM0 Ethernet to netdev */ + netdev_register(netdev, NETDEV_SAM0_ETH, 0); +} + +/* TODO: rework the whole isr management... */ +void isr_gmac(void) +{ + uint32_t isr; + uint32_t tsr; + uint32_t rsr; + + isr = GMAC->ISR.reg; + tsr = GMAC->TSR.reg; + rsr = GMAC->RSR.reg; + + if (rsr == GMAC_RSR_REC) { + netdev_trigger_event_isr(_sam0_eth_dev.netdev); + } + + GMAC->TSR.reg = tsr; + GMAC->RSR.reg = rsr; + GMAC->ISR.reg = isr; + + cortexm_isr_end(); +} diff --git a/cpu/sam0_common/sam0_eth/sam0_eth_netdev.h b/cpu/sam0_common/sam0_eth/sam0_eth_netdev.h new file mode 100644 index 0000000000..54dd7c75dd --- /dev/null +++ b/cpu/sam0_common/sam0_eth/sam0_eth_netdev.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2020 Dylan Laduranty + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup cpu_sam0_common_eth sam0 Ethernet peripheral + * @ingroup cpu_sam0_common + * @{ + * + * @file + * @brief Netdev interface for the SAM0 Ethernet GMAC peripheral + * + * @author Dylan Laduranty + */ + +#ifndef SAM0_ETH_NETDEV_H +#define SAM0_ETH_NETDEV_H + +#include + +#include "net/ethernet.h" +#include "net/netdev.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Reference to the netdev device driver struct + */ +extern const netdev_driver_t sam0_eth_driver; + +/** + * @brief Device descriptor for SAM0-ETH devices + */ +typedef struct { + netdev_t* netdev; /**< netdev parent struct */ +} sam0_eth_netdev_t; + +/** + * @brief Setup SAM0 Ethernet peripheral + * + * @param[in] dev Pointer to the SAM0 Ethernet netdev struct + * + */ + +void sam0_eth_netdev_setup(sam0_eth_netdev_t* dev); + +#ifdef __cplusplus +} +#endif + +#endif /* SAM0_ETH_NETDEV_H */ +/** @} */