2020-05-03 14:35:01 +02:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2016 TriaGnoSys GmbH
|
2020-07-24 17:43:01 +02:00
|
|
|
* 2020 Otto-von-Guericke-Universität Magdeburg
|
2020-05-03 14:35:01 +02:00
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
2020-05-03 17:17:54 +02:00
|
|
|
* @ingroup cpu_stm32
|
2020-05-03 14:35:01 +02:00
|
|
|
* @{
|
|
|
|
*
|
|
|
|
* @file
|
|
|
|
* @brief Low-level ETH driver implementation
|
|
|
|
*
|
|
|
|
* @author Víctor Ariño <victor.arino@triagnosys.com>
|
2020-07-24 17:43:01 +02:00
|
|
|
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
2020-05-03 14:35:01 +02:00
|
|
|
*
|
|
|
|
* @}
|
|
|
|
*/
|
2020-07-24 17:43:01 +02:00
|
|
|
#include <errno.h>
|
2020-05-03 14:35:01 +02:00
|
|
|
#include <string.h>
|
|
|
|
|
2020-07-24 11:37:23 +02:00
|
|
|
#include "bitarithm.h"
|
2020-05-03 14:35:01 +02:00
|
|
|
#include "mutex.h"
|
|
|
|
#include "luid.h"
|
|
|
|
|
|
|
|
#include "iolist.h"
|
|
|
|
#include "net/ethernet.h"
|
|
|
|
|
|
|
|
#include "periph/gpio.h"
|
|
|
|
|
|
|
|
#define ENABLE_DEBUG (0)
|
|
|
|
#include "debug.h"
|
|
|
|
|
|
|
|
/* Set the value of the divider with the clock configured */
|
|
|
|
#if !defined(CLOCK_CORECLOCK) || CLOCK_CORECLOCK < (20000000U)
|
|
|
|
#error This peripheral requires a CORECLOCK of at least 20MHz
|
|
|
|
#elif CLOCK_CORECLOCK < (35000000U)
|
|
|
|
#define CLOCK_RANGE ETH_MACMIIAR_CR_Div16
|
|
|
|
#elif CLOCK_CORECLOCK < (60000000U)
|
|
|
|
#define CLOCK_RANGE ETH_MACMIIAR_CR_Div26
|
|
|
|
#elif CLOCK_CORECLOCK < (100000000U)
|
|
|
|
#define CLOCK_RANGE ETH_MACMIIAR_CR_Div42
|
|
|
|
#elif CLOCK_CORECLOCK < (150000000U)
|
|
|
|
#define CLOCK_RANGE ETH_MACMIIAR_CR_Div62
|
|
|
|
#else /* CLOCK_CORECLOCK < (20000000U) */
|
|
|
|
#define CLOCK_RANGE ETH_MACMIIAR_CR_Div102
|
|
|
|
#endif /* CLOCK_CORECLOCK < (20000000U) */
|
|
|
|
|
2020-07-24 17:43:01 +02:00
|
|
|
#define MIN(a, b) (((a) <= (b)) ? (a) : (b))
|
|
|
|
|
2020-05-03 14:35:01 +02:00
|
|
|
/* Descriptors */
|
|
|
|
static edma_desc_t rx_desc[ETH_RX_BUFFER_COUNT];
|
|
|
|
static edma_desc_t tx_desc[ETH_TX_BUFFER_COUNT];
|
|
|
|
static edma_desc_t *rx_curr;
|
|
|
|
static edma_desc_t *tx_curr;
|
|
|
|
|
|
|
|
/* Buffers */
|
|
|
|
static char rx_buffer[ETH_RX_BUFFER_COUNT][ETH_RX_BUFFER_SIZE];
|
|
|
|
static char tx_buffer[ETH_TX_BUFFER_COUNT][ETH_TX_BUFFER_SIZE];
|
|
|
|
|
|
|
|
/** Read or write a phy register, to write the register ETH_MACMIIAR_MW is to
|
|
|
|
* be passed as the higher nibble of the value */
|
|
|
|
static unsigned _rw_phy(unsigned addr, unsigned reg, unsigned value)
|
|
|
|
{
|
|
|
|
unsigned tmp;
|
|
|
|
|
|
|
|
while (ETH->MACMIIAR & ETH_MACMIIAR_MB) {}
|
|
|
|
DEBUG("stm32_eth: rw_phy %x (%x): %x\n", addr, reg, value);
|
|
|
|
|
|
|
|
tmp = (ETH->MACMIIAR & ETH_MACMIIAR_CR) | ETH_MACMIIAR_MB;
|
|
|
|
tmp |= (((addr & 0x1f) << 11) | ((reg & 0x1f) << 6));
|
|
|
|
tmp |= (value >> 16);
|
|
|
|
|
|
|
|
ETH->MACMIIDR = (value & 0xffff);
|
|
|
|
ETH->MACMIIAR = tmp;
|
|
|
|
while (ETH->MACMIIAR & ETH_MACMIIAR_MB) {}
|
|
|
|
|
|
|
|
DEBUG("stm32_eth: %lx\n", ETH->MACMIIDR);
|
|
|
|
return (ETH->MACMIIDR & 0x0000ffff);
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t stm32_eth_phy_read(uint16_t addr, uint8_t reg)
|
|
|
|
{
|
|
|
|
return _rw_phy(addr, reg, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t stm32_eth_phy_write(uint16_t addr, uint8_t reg, uint16_t value)
|
|
|
|
{
|
|
|
|
_rw_phy(addr, reg, (value & 0xffff) | (ETH_MACMIIAR_MW << 16));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void stm32_eth_get_mac(char *out)
|
|
|
|
{
|
|
|
|
unsigned t;
|
|
|
|
|
|
|
|
t = ETH->MACA0HR;
|
|
|
|
out[5] = (t >> 8);
|
|
|
|
out[4] = (t & 0xff);
|
|
|
|
|
|
|
|
t = ETH->MACA0LR;
|
|
|
|
out[3] = (t >> 24);
|
|
|
|
out[2] = (t >> 16);
|
|
|
|
out[1] = (t >> 8);
|
|
|
|
out[0] = (t & 0xff);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Set the mac address. The peripheral supports up to 4 MACs but only one is
|
|
|
|
* implemented */
|
|
|
|
void stm32_eth_set_mac(const char *mac)
|
|
|
|
{
|
|
|
|
ETH->MACA0HR &= 0xffff0000;
|
|
|
|
ETH->MACA0HR |= ((mac[5] << 8) | mac[4]);
|
|
|
|
ETH->MACA0LR = ((mac[3] << 24) | (mac[2] << 16) | (mac[1] << 8) | mac[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Initialization of the DMA descriptors to be used */
|
|
|
|
static void _init_buffer(void)
|
|
|
|
{
|
2020-07-24 17:43:01 +02:00
|
|
|
size_t i;
|
2020-05-03 14:35:01 +02:00
|
|
|
for (i = 0; i < ETH_RX_BUFFER_COUNT; i++) {
|
2020-07-24 11:37:23 +02:00
|
|
|
rx_desc[i].status = RX_DESC_STAT_OWN;
|
|
|
|
rx_desc[i].control = RX_DESC_CTRL_RCH | (ETH_RX_BUFFER_SIZE & 0x0fff);
|
2020-05-03 14:35:01 +02:00
|
|
|
rx_desc[i].buffer_addr = &rx_buffer[i][0];
|
|
|
|
if((i+1) < ETH_RX_BUFFER_COUNT) {
|
|
|
|
rx_desc[i].desc_next = &rx_desc[i + 1];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
rx_desc[i - 1].desc_next = &rx_desc[0];
|
|
|
|
|
|
|
|
for (i = 0; i < ETH_TX_BUFFER_COUNT; i++) {
|
2020-07-24 11:37:23 +02:00
|
|
|
tx_desc[i].status = TX_DESC_STAT_TCH | TX_DESC_STAT_CIC;
|
2020-05-03 14:35:01 +02:00
|
|
|
tx_desc[i].buffer_addr = &tx_buffer[i][0];
|
|
|
|
if ((i + 1) < ETH_RX_BUFFER_COUNT) {
|
|
|
|
tx_desc[i].desc_next = &tx_desc[i + 1];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
tx_desc[i - 1].desc_next = &tx_desc[0];
|
|
|
|
|
|
|
|
rx_curr = &rx_desc[0];
|
|
|
|
tx_curr = &tx_desc[0];
|
|
|
|
|
2020-07-24 11:37:23 +02:00
|
|
|
ETH->DMARDLAR = (uintptr_t)rx_curr;
|
|
|
|
ETH->DMATDLAR = (uintptr_t)tx_curr;
|
2020-05-03 14:35:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
int stm32_eth_init(void)
|
|
|
|
{
|
|
|
|
/* enable APB2 clock */
|
|
|
|
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
|
|
|
|
|
|
|
|
/* select RMII if necessary */
|
|
|
|
if (eth_config.mode == RMII) {
|
|
|
|
SYSCFG->PMC |= SYSCFG_PMC_MII_RMII_SEL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* initialize GPIO */
|
|
|
|
for (int i = 0; i < (int) eth_config.mode; i++) {
|
|
|
|
gpio_init(eth_config.pins[i], GPIO_OUT);
|
|
|
|
gpio_init_af(eth_config.pins[i], GPIO_AF11);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* enable all clocks */
|
|
|
|
RCC->AHB1ENR |= (RCC_AHB1ENR_ETHMACEN | RCC_AHB1ENR_ETHMACTXEN |
|
|
|
|
RCC_AHB1ENR_ETHMACRXEN | RCC_AHB1ENR_ETHMACPTPEN);
|
|
|
|
|
|
|
|
/* reset the peripheral */
|
|
|
|
RCC->AHB1RSTR |= RCC_AHB1RSTR_ETHMACRST;
|
|
|
|
RCC->AHB1RSTR &= ~RCC_AHB1RSTR_ETHMACRST;
|
|
|
|
|
|
|
|
/* software reset */
|
|
|
|
ETH->DMABMR |= ETH_DMABMR_SR;
|
|
|
|
while (ETH->DMABMR & ETH_DMABMR_SR) {}
|
|
|
|
|
|
|
|
/* set the clock divider */
|
|
|
|
while (ETH->MACMIIAR & ETH_MACMIIAR_MB) {}
|
|
|
|
ETH->MACMIIAR = CLOCK_RANGE;
|
|
|
|
|
|
|
|
/* configure the PHY (standard for all PHY's) */
|
|
|
|
/* if there's no PHY, this has no effect */
|
|
|
|
stm32_eth_phy_write(eth_config.phy_addr, PHY_BMCR, BMCR_RESET);
|
|
|
|
|
|
|
|
/* speed from conf */
|
|
|
|
ETH->MACCR |= (ETH_MACCR_ROD | ETH_MACCR_IPCO | ETH_MACCR_APCS |
|
|
|
|
((eth_config.speed & 0x0100) << 3) |
|
|
|
|
((eth_config.speed & 0x2000) << 1));
|
|
|
|
|
|
|
|
/* pass all */
|
|
|
|
//ETH->MACFFR |= ETH_MACFFR_RA;
|
|
|
|
/* pass on perfect filter match and pass all multicast address matches */
|
|
|
|
ETH->MACFFR |= ETH_MACFFR_PAM;
|
|
|
|
|
|
|
|
/* store forward */
|
|
|
|
ETH->DMAOMR |= (ETH_DMAOMR_RSF | ETH_DMAOMR_TSF | ETH_DMAOMR_OSF);
|
|
|
|
|
|
|
|
/* configure DMA */
|
|
|
|
ETH->DMABMR = (ETH_DMABMR_DA | ETH_DMABMR_AAB | ETH_DMABMR_FB |
|
|
|
|
ETH_DMABMR_RDP_32Beat | ETH_DMABMR_PBL_32Beat | ETH_DMABMR_EDE);
|
|
|
|
|
|
|
|
if(eth_config.mac[0] != 0) {
|
|
|
|
stm32_eth_set_mac(eth_config.mac);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
eui48_t hwaddr;
|
|
|
|
luid_get_eui48(&hwaddr);
|
|
|
|
stm32_eth_set_mac((const char *)hwaddr.uint8);
|
|
|
|
}
|
|
|
|
|
|
|
|
_init_buffer();
|
|
|
|
|
|
|
|
NVIC_EnableIRQ(ETH_IRQn);
|
|
|
|
ETH->DMAIER |= ETH_DMAIER_NISE | ETH_DMAIER_TIE | ETH_DMAIER_RIE;
|
|
|
|
|
2020-07-24 11:44:08 +02:00
|
|
|
/* enable transmitter and receiver */
|
|
|
|
ETH->MACCR |= ETH_MACCR_TE | ETH_MACCR_RE;
|
|
|
|
/* flush transmit FIFO */
|
2020-05-03 14:35:01 +02:00
|
|
|
ETH->DMAOMR |= ETH_DMAOMR_FTF;
|
2020-07-24 11:44:08 +02:00
|
|
|
/* wait for FIFO flushing to complete */
|
|
|
|
while (ETH->DMAOMR & ETH_DMAOMR_FTF) { }
|
2020-05-03 14:35:01 +02:00
|
|
|
|
2020-07-24 11:44:08 +02:00
|
|
|
/* enable DMA TX and RX */
|
|
|
|
ETH->DMAOMR |= ETH_DMAOMR_ST | ETH_DMAOMR_SR;
|
2020-05-03 14:35:01 +02:00
|
|
|
|
|
|
|
/* configure speed, do it at the end so the PHY had time to
|
|
|
|
* reset */
|
|
|
|
stm32_eth_phy_write(eth_config.phy_addr, PHY_BMCR, eth_config.speed);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int stm32_eth_send(const struct iolist *iolist)
|
|
|
|
{
|
|
|
|
unsigned len = iolist_size(iolist);
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
/* safety check */
|
|
|
|
if (len > ETH_TX_BUFFER_SIZE) {
|
|
|
|
DEBUG("stm32_eth: Error iolist_size > ETH_TX_BUFFER_SIZE\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* block until there's an available descriptor */
|
2020-07-24 11:37:23 +02:00
|
|
|
while (tx_curr->status & TX_DESC_STAT_OWN) {
|
2020-05-03 14:35:01 +02:00
|
|
|
DEBUG("stm32_eth: not avail\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* clear status field */
|
|
|
|
tx_curr->status &= 0x0fffffff;
|
|
|
|
|
|
|
|
dma_acquire(eth_config.dma);
|
|
|
|
for (; iolist; iolist = iolist->iol_next) {
|
2020-07-24 11:37:23 +02:00
|
|
|
ret += dma_transfer(
|
|
|
|
eth_config.dma, eth_config.dma_chan,
|
|
|
|
iolist->iol_base, tx_curr->buffer_addr + ret, iolist->iol_len,
|
|
|
|
DMA_MEM_TO_MEM, DMA_INC_BOTH_ADDR);
|
2020-05-03 14:35:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
dma_release(eth_config.dma);
|
|
|
|
if (ret < 0) {
|
|
|
|
DEBUG("stm32_eth: Failure in dma_transfer\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
tx_curr->control = (len & 0x1fff);
|
|
|
|
|
|
|
|
/* set flags for first and last frames */
|
2020-07-24 11:37:23 +02:00
|
|
|
tx_curr->status |= TX_DESC_STAT_FS;
|
|
|
|
tx_curr->status |= TX_DESC_STAT_LS | TX_DESC_STAT_IC;
|
2020-05-03 14:35:01 +02:00
|
|
|
|
|
|
|
/* give the descriptors to the DMA */
|
2020-07-24 11:37:23 +02:00
|
|
|
tx_curr->status |= TX_DESC_STAT_OWN;
|
2020-05-03 14:35:01 +02:00
|
|
|
tx_curr = tx_curr->desc_next;
|
|
|
|
|
|
|
|
/* start tx */
|
|
|
|
ETH->DMATPDR = 0;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2020-07-24 17:43:01 +02:00
|
|
|
static ssize_t get_rx_frame_size(void)
|
2020-05-03 14:35:01 +02:00
|
|
|
{
|
2020-07-24 17:43:01 +02:00
|
|
|
edma_desc_t *i = rx_curr;
|
|
|
|
uint32_t status;
|
|
|
|
while (1) {
|
|
|
|
/* Wait until DMA gave up control over descriptor */
|
|
|
|
while ((status = i->status) & RX_DESC_STAT_OWN) { }
|
|
|
|
DEBUG("stm32_eth: get_rx_frame_size(): FS=%c, LS=%c, DE=%c, FL=%lu\n",
|
|
|
|
(status & RX_DESC_STAT_FS) ? '1' : '0',
|
|
|
|
(status & RX_DESC_STAT_LS) ? '1' : '0',
|
|
|
|
(status & RX_DESC_STAT_DE) ? '1' : '0',
|
|
|
|
((status >> 16) & 0x3fff) - ETHERNET_FCS_LEN);
|
|
|
|
if (status & RX_DESC_STAT_LS) {
|
|
|
|
break;
|
2020-05-03 14:35:01 +02:00
|
|
|
}
|
2020-07-24 17:43:01 +02:00
|
|
|
i = i->desc_next;
|
2020-05-03 14:35:01 +02:00
|
|
|
}
|
|
|
|
|
2020-07-24 17:43:01 +02:00
|
|
|
if (status & RX_DESC_STAT_DE) {
|
|
|
|
return -1;
|
2020-05-03 14:35:01 +02:00
|
|
|
}
|
|
|
|
|
2020-07-24 17:43:01 +02:00
|
|
|
/* bits 16-29 contain the frame length including 4 B frame check sequence */
|
|
|
|
return ((status >> 16) & 0x3fff) - ETHERNET_FCS_LEN;
|
2020-05-03 14:35:01 +02:00
|
|
|
}
|
|
|
|
|
2020-07-24 17:43:01 +02:00
|
|
|
static void drop_frame_and_update_rx_curr(void)
|
2020-05-03 14:35:01 +02:00
|
|
|
{
|
2020-07-24 17:43:01 +02:00
|
|
|
while (1) {
|
|
|
|
uint32_t old_status = rx_curr->status;
|
|
|
|
/* hand over old descriptor to DMA */
|
|
|
|
rx_curr->status = RX_DESC_STAT_OWN;
|
|
|
|
rx_curr = rx_curr->desc_next;
|
|
|
|
if (old_status & RX_DESC_STAT_LS) {
|
|
|
|
/* reached last DMA descriptor of frame ==> done */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2020-05-03 14:35:01 +02:00
|
|
|
}
|
|
|
|
|
2020-07-24 17:43:01 +02:00
|
|
|
int stm32_eth_receive(void *buf, size_t max_len)
|
2020-05-03 14:35:01 +02:00
|
|
|
{
|
2020-07-24 17:43:01 +02:00
|
|
|
char *data = buf;
|
|
|
|
/* Determine the size of received frame. The frame might span multiple
|
|
|
|
* DMA buffers */
|
|
|
|
ssize_t size = get_rx_frame_size();
|
|
|
|
|
|
|
|
if (size == -1) {
|
|
|
|
DEBUG("stm32_eth: Received frame was too large for DMA buffer(s)\n");
|
|
|
|
drop_frame_and_update_rx_curr();
|
|
|
|
return -EOVERFLOW;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!buf) {
|
|
|
|
if (max_len) {
|
|
|
|
DEBUG("stm32_eth: Dropping frame as requested by upper layer\n");
|
|
|
|
drop_frame_and_update_rx_curr();
|
|
|
|
}
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (max_len < (size_t)size) {
|
|
|
|
DEBUG("stm32_eth: Buffer provided by upper layer is too small\n");
|
|
|
|
drop_frame_and_update_rx_curr();
|
|
|
|
return -ENOBUFS;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t remain = size;
|
|
|
|
while (remain) {
|
|
|
|
size_t chunk = MIN(remain, ETH_RX_BUFFER_SIZE);
|
|
|
|
memcpy(data, rx_curr->buffer_addr, chunk);
|
|
|
|
data += chunk;
|
|
|
|
remain -= chunk;
|
|
|
|
/* Hand over descriptor to DMA */
|
|
|
|
rx_curr->status = RX_DESC_STAT_OWN;
|
|
|
|
rx_curr = rx_curr->desc_next;
|
|
|
|
}
|
|
|
|
|
|
|
|
return size;
|
2020-05-03 14:35:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
int stm32_eth_get_rx_status_owned(void)
|
|
|
|
{
|
2020-07-24 11:37:23 +02:00
|
|
|
return (!(rx_curr->status & RX_DESC_STAT_OWN));
|
2020-05-03 14:35:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void stm32_eth_isr_eth_wkup(void)
|
|
|
|
{
|
|
|
|
cortexm_isr_end();
|
|
|
|
}
|