1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

cpu/sam0: add initial Ethernet support

This commit is contained in:
dylad 2020-12-13 21:41:35 +01:00
parent 7cbaa3f890
commit a1f5b6182d
8 changed files with 601 additions and 0 deletions

View File

@ -1,3 +1,7 @@
DIRS = periph
ifneq (, $(filter sam0_eth, $(USEMODULE)))
DIRS += sam0_eth
endif
include $(RIOTBASE)/Makefile.base

View File

@ -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

View File

@ -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

View File

@ -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
*/

View File

@ -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 <dylan.laduranty@mesotic.com>
*
* @}
*/
#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 <string.h>
/* 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;
}

View File

@ -0,0 +1,3 @@
MODULE=sam0_eth
include $(RIOTBASE)/Makefile.base

View File

@ -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 <dylan.laduranty@mesotic.com>
*
* @}
*/
#include <string.h>
#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();
}

View File

@ -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 <dylanladuranty@gmail.com>
*/
#ifndef SAM0_ETH_NETDEV_H
#define SAM0_ETH_NETDEV_H
#include <stdbool.h>
#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 */
/** @} */