diff --git a/cpu/stm32/Makefile.dep b/cpu/stm32/Makefile.dep index 398d6b212b..63b3ec9af9 100644 --- a/cpu/stm32/Makefile.dep +++ b/cpu/stm32/Makefile.dep @@ -32,4 +32,8 @@ ifneq (,$(filter periph_can,$(FEATURES_USED))) FEATURES_REQUIRED += periph_gpio_irq endif +ifneq (,$(filter periph_eth periph_ptp,$(USEMODULE))) + USEMODULE += periph_eth_common +endif + include $(RIOTCPU)/cortexm_common/Makefile.dep diff --git a/cpu/stm32/include/periph_cpu.h b/cpu/stm32/include/periph_cpu.h index 94ae88d428..e7c9808e8b 100644 --- a/cpu/stm32/include/periph_cpu.h +++ b/cpu/stm32/include/periph_cpu.h @@ -1154,6 +1154,23 @@ typedef struct eth_dma_desc { #define TX_DESC_STAT_OWN (BIT31) /**< If set, descriptor is owned by DMA, otherwise by CPU */ /** @} */ +#ifdef MODULE_PERIPH_ETH_COMMON +/** + * @brief Perform ETH initialization common to periph_stm32_eth and + * periph_ptp_clock + */ +void stm32_eth_common_init(void); +#endif /* MODULE_PERIPH_ETH_COMMON */ + +/** + * @name PTP clock configuration + * @{ + */ +#define HAVE_PTP_CLOCK_READ 1 /**< Native implementation available */ +#define HAVE_PTP_CLOCK_SET 1 /**< Native implementation available */ +#define HAVE_PTP_TIMER_SET_ABSOLUTE 1 /**< Native implementation available */ +/** @} */ + #ifdef __cplusplus } #endif diff --git a/cpu/stm32/periph/eth.c b/cpu/stm32/periph/eth.c index 7e8ef14790..71d8d557f3 100644 --- a/cpu/stm32/periph/eth.c +++ b/cpu/stm32/periph/eth.c @@ -104,7 +104,7 @@ static edma_desc_t *tx_curr; static char rx_buffer[ETH_RX_DESCRIPTOR_COUNT][ETH_RX_BUFFER_SIZE]; /* Netdev used in RIOT's API to upper layer */ -netdev_t *_netdev; +netdev_t *stm32_eth_netdev; #if IS_USED(MODULE_STM32_ETH_LINK_UP) /* Used for checking the link status */ @@ -403,37 +403,19 @@ static void _setup_phy(void) static int stm32_eth_init(netdev_t *netdev) { + (void)netdev; #if IS_USED(MODULE_STM32_ETH_LINK_UP) _link_status_timer.callback = _timer_cb; _link_status_timer.arg = netdev; xtimer_set(&_link_status_timer, STM32_ETH_LINK_UP_TIMEOUT_US); #endif - /* enable APB2 clock */ - RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN; - /* select RMII if necessary */ - if (eth_config.mode == RMII) { - SYSCFG->PMC |= SYSCFG_PMC_MII_RMII_SEL; + /* The PTP clock is initialized prior to the netdevs and will have already + * initialized the common stuff, if used.*/ + if (!IS_USED(MODULE_PERIPH_INIT_PTP)) { + stm32_eth_common_init(); } - /* 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; @@ -465,7 +447,6 @@ static int stm32_eth_init(netdev_t *netdev) _init_buffer(); - NVIC_EnableIRQ(ETH_IRQn); ETH->DMAIER |= ETH_DMAIER_NISE | ETH_DMAIER_TIE | ETH_DMAIER_RIE; /* enable transmitter and receiver */ @@ -597,7 +578,7 @@ static void handle_lost_rx_irqs(void) * risk a stack overflow if we would send an * NETDEV_EVENT_RX_COMPLETE */ - netdev_trigger_event_isr(_netdev); + netdev_trigger_event_isr(stm32_eth_netdev); break; } iter = iter->desc_next; @@ -690,27 +671,6 @@ static void stm32_eth_isr(netdev_t *netdev) netdev->event_callback(netdev, NETDEV_EVENT_RX_COMPLETE); } -void isr_eth(void) -{ - unsigned tmp = ETH->DMASR; - - if ((tmp & ETH_DMASR_TS)) { - ETH->DMASR = ETH_DMASR_NIS | ETH_DMASR_TS; - DEBUG("isr_eth: TX completed\n"); - mutex_unlock(&stm32_eth_tx_completed); - } - - if ((tmp & ETH_DMASR_RS)) { - ETH->DMASR = ETH_DMASR_NIS | ETH_DMASR_RS; - DEBUG("isr_eth: RX completed\n"); - if (_netdev) { - netdev_trigger_event_isr(_netdev); - } - } - - cortexm_isr_end(); -} - static const netdev_driver_t netdev_driver_stm32f4eth = { .send = stm32_eth_send, .recv = stm32_eth_recv, @@ -722,6 +682,6 @@ static const netdev_driver_t netdev_driver_stm32f4eth = { void stm32_eth_netdev_setup(netdev_t *netdev) { - _netdev = netdev; + stm32_eth_netdev = netdev; netdev->driver = &netdev_driver_stm32f4eth; } diff --git a/cpu/stm32/periph/eth_common.c b/cpu/stm32/periph/eth_common.c new file mode 100644 index 0000000000..e9c2262fbd --- /dev/null +++ b/cpu/stm32/periph/eth_common.c @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2016 TriaGnoSys GmbH + * 2020 Otto-von-Guericke-Universität Magdeburg + * + * 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_stm32 + * @{ + * + * @file + * @brief Common code for the ETH and PTP driver + * + * @author Víctor Ariño + * @author Marian Buschsieweke + * + * @} + */ +#include + +#include "mutex.h" +#include "net/netdev/eth.h" +#include "periph/gpio.h" +#include "periph/ptp.h" +#include "periph_conf.h" +#include "periph_cpu.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +void stm32_eth_common_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) {} + + if (IS_USED(MODULE_PERIPH_ETH) || IS_USED(MODULE_PERIPH_PTP_TIMER)) { + NVIC_EnableIRQ(ETH_IRQn); + } +} + +#if IS_USED(MODULE_STM32_ETH) || IS_USED(MODULE_PERIPH_PTP_TIMER) +void isr_eth(void) +{ + DEBUG("[periph_eth_common] isr_eth()\n"); + + if (IS_USED(MODULE_PERIPH_PTP_TIMER)) { + if (ETH->MACSR & ETH_MACSR_TSTS) { + ptp_timer_cb(); + /* clear interrupt by reading PTPTSSR */ + (void)ETH->PTPTSSR; + } + } + + if (IS_USED(MODULE_STM32_ETH)) { + extern netdev_t *stm32_eth_netdev; + extern mutex_t stm32_eth_tx_completed; + unsigned tmp = ETH->DMASR; + + if ((tmp & ETH_DMASR_TS)) { + ETH->DMASR = ETH_DMASR_NIS | ETH_DMASR_TS; + DEBUG("isr_eth: TX completed\n"); + mutex_unlock(&stm32_eth_tx_completed); + } + + if ((tmp & ETH_DMASR_RS)) { + ETH->DMASR = ETH_DMASR_NIS | ETH_DMASR_RS; + DEBUG("isr_eth: RX completed\n"); + if (stm32_eth_netdev) { + netdev_trigger_event_isr(stm32_eth_netdev); + } + } + } + + cortexm_isr_end(); +} +#endif diff --git a/cpu/stm32/periph/ptp.c b/cpu/stm32/periph/ptp.c new file mode 100644 index 0000000000..d1b76aa29d --- /dev/null +++ b/cpu/stm32/periph/ptp.c @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg + * + * 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_stm32 + * @{ + * + * @file + * @brief PTP clock and timer implementation + * + * @author Marian Buschsieweke + * + * @} + */ +#include +#include + +#include "assert.h" +#include "atomic_utils.h" +#include "bit.h" +#include "macros/units.h" +#include "periph/ptp.h" +#include "periph_conf.h" +#include "periph_cpu.h" +#include "timex.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +/* Workaround for typos in vendor files; drop when fixed upstream */ +#ifndef ETH_PTPTSCR_TSSSR +#define ETH_PTPTSCR_TSSSR ETH_PTPTSSR_TSSSR +#endif +#ifndef ETH_PTPTSCR_TSSARFE +#define ETH_PTPTSCR_TSSARFE ETH_PTPTSSR_TSSARFE +#endif + +/* PTPSSIR is the number of nanoseconds to add onto the sub-second register + * (the one counting the nanoseconds part of the timestamp with the + * configuration we chose here). It is therefore the resolution of the clock + * in nanoseconds. (Note that the accuracy is expected to be way worse than + * the resolution.) + */ +#ifndef STM32_PTPSSIR +#if CLOCK_CORECLOCK > MHZ(200) +/* Go for 10 ns resolution on CPU clocked higher than 200 MHz */ +#define STM32_PTPSSIR (10LLU) +#elif CLOCK_CORECLOCK > MHZ(100) +/* Go for 20 ns resolution on CPU clocked higher than 100 MHz */ +#define STM32_PTPSSIR (20LLU) +#else +/* Go for 50 ns resolution on CPU all other CPUs */ +#define STM32_PTPSSIR (50LLU) +#endif /* CLOCK_CORECLOCK */ +#endif /* !STM32_PTPSSIR */ + +/** + * @brief Return the result of x / y, scientifically rounded + * @param x Number to divide + * @param y @p x should be divided by this + * @return x/y, scientifically rounded + * @pre Both @p x and @p y are compile time constant integers and the + * expressions are evaluated without side-effects + */ +#define ROUNDED_DIV(x, y) (((x) + ((y) / 2)) / (y)) + +static const uint32_t ptpssir = STM32_PTPSSIR; +static const uint32_t ptptsar = ROUNDED_DIV(NS_PER_SEC * (1ULL << 32), CLOCK_AHB * STM32_PTPSSIR); + +void ptp_init(void) +{ + /* The PTP clock is initialized during periph_init(), while stm32_eth is + * initialized during auto_init(). As auto_init() depends on periph_init(), + * we can be sure that the PTP clock is always the first to use the + * Ethernet MAC. The Ethernet driver will skip the common initialization + * part when the PTP clock is used. */ + stm32_eth_common_init(); + + /* In the following, the steps described in "Programming steps for + * system time generation initialization" on page 1805 in RM0410 Rev4 + * are done */ + + /* Mask the time stamp trigger interrupt */ + ETH->MACIMR |= ETH_MACIMR_TSTIM; + /* Set TSE bit in time stamp register to enable time stamping */ + ETH->PTPTSCR |= ETH_PTPTSCR_TSE; + /* Use decimal mode (subsecond register counts nanoseconds, not in + * 2^(-31) seconds) */ + ETH->PTPTSCR |= ETH_PTPTSCR_TSSSR; + /* Set subsecond increment register. This will be added onto the subsecond + * register whenever a 32 bit accumulator register overflows*/ + ETH->PTPSSIR = ptpssir; + ptp_clock_adjust_speed(0); + /* Wait new PTPSAR value becomes active */ + while (ETH->PTPTSCR & ETH_PTPTSCR_TSARU) { } + /* Enable fine grained correction now */ + ETH->PTPTSCR |= ETH_PTPTSCR_TSFCU; + + static const ptp_timestamp_t initial_time = { + .seconds = 0, + .nanoseconds = 0 + }; + ptp_clock_set(&initial_time); + if (IS_USED(MODULE_PERIPH_ETH)) { + /* enable timestamping of all received frames */ + ETH->PTPTSCR |= ETH_PTPTSSR_TSSARFE; + } + DEBUG("[periph_ptp] Initialized with PTPSAR = %" PRIu32 ", PTPSSIR = %" PRIu32 "\n", + ptptsar, ptpssir); +} + +void ptp_clock_adjust_speed(int16_t correction) +{ + uint64_t offset = ptptsar; + offset *= correction; + offset >>= 16; + uint32_t adjusted_ptptsar = ptptsar + (uint32_t)offset; + /* Value to add onto the 32 bit accumulator register (which causes the + * value in ETH->PTPSSIR to be added onto the subsection register on + * overflow) */ + ETH->PTPTSAR = adjusted_ptptsar; + /* Wait for pending clock speed adjustments to complete */ + while (ETH->PTPTSCR & ETH_PTPTSCR_TSARU) { } + /* Load new PTPTSAR value to hardware */ + ETH->PTPTSCR |= ETH_PTPTSCR_TSARU; + DEBUG("[periph_ptp] Using PTPSAR = %" PRIu32 ", PTPSSIR = %" PRIu32 "\n", + adjusted_ptptsar, ptpssir); +} + +void ptp_clock_adjust(int64_t offset) +{ + unsigned state = irq_disable(); + ptp_timestamp_t ts; + uint64_t abs_offset = (offset < 0) ? -offset : offset; + ptp_ns2ts(&ts, abs_offset); + ETH->PTPTSHUR = ts.seconds; + ETH->PTPTSLUR = (offset < 0) ? (1UL << 31) | ts.nanoseconds : ts.nanoseconds; + while (ETH->PTPTSCR & (ETH_PTPTSCR_TSSTU | ETH_PTPTSCR_TSSTI)) { + /* wait until new time value can be set */ + } + ETH->PTPTSCR |= ETH_PTPTSCR_TSSTU; + irq_restore(state); + DEBUG("[periph_ptp] Updated time by %c%" PRIu32 ".%09" PRIu32 "\n", + (offset < 0) ? '-' : '+', (uint32_t)ts.seconds, ts.nanoseconds); +} + +void ptp_clock_set(const ptp_timestamp_t *time) +{ + assert(time && time->nanoseconds < NS_PER_SEC); + unsigned state = irq_disable(); + /* First, set the timestamp update registers */ + ETH->PTPTSHUR = time->seconds; + ETH->PTPTSLUR = time->nanoseconds; + /* From the data sheet (regarding setting TSSTI): + * > Both the TSSTU and TSSTI bits must be read as zero before you can set + * > this bit. + */ + while (ETH->PTPTSCR & (ETH_PTPTSCR_TSSTU | ETH_PTPTSCR_TSSTI)) { + /* wait until new time value can be set */ + } + /* Now, ask the peripheral to atomically set the clock from the update + * registers */ + ETH->PTPTSCR |= ETH_PTPTSCR_TSSTI; + irq_restore(state); +} + +void ptp_clock_read(ptp_timestamp_t *timestamp) +{ + unsigned irq_state = irq_disable(); + /* Read first high register, then low, then again high. If the value in + * high register changed between the reads, we start again to prevent + * corrupted timestamps being passed to the user. */ + do { + timestamp->seconds = ETH->PTPTSHR; + timestamp->nanoseconds = ETH->PTPTSLR; + } while (timestamp->seconds != ETH->PTPTSHR); + + /* TODO: Most significant bit of ETH->PTPTSLR is the sign bit of the time + * stamp. Because the seconds register is unsigned, an overflow is not + * expected before year 2106. It is not clear from the data sheet, how the + * time stamp is to be interpreted when the negative bit is set. For now, + * we just ignore this potential source of problems. */ + irq_restore(irq_state); +} + +#if IS_USED(MODULE_PERIPH_PTP_TIMER) +void ptp_timer_clear(void) +{ + const atomic_bit_u32_t tsite = atomic_bit_u32(Ð->PTPTSCR, ETH_PTPTSCR_TSITE_Pos); + atomic_clear_bit_u32(tsite); +} + +void ptp_timer_set_absolute(const ptp_timestamp_t *target) +{ + assert(target); + DEBUG("[periph_ptp] Set timer: %" PRIu32 ".%" PRIu32 "\n", + (uint32_t)target->seconds, target->nanoseconds); + unsigned state = irq_disable(); + /* Mask PTP timer IRQ first, so that an interrupt is not triggered + * too early. (The target time is not set atomically.) */ + ETH->MACIMR |= ETH_MACIMR_TSTIM; + /* Set target time */ + ETH->PTPTTHR = target->seconds; + ETH->PTPTTLR = target->nanoseconds; + /* Enable PTP timer IRQ */ + ETH->PTPTSCR |= ETH_PTPTSCR_TSITE; + /* Unmask the time stamp trigger interrupt */ + ETH->MACIMR &= ~ETH_MACIMR_TSTIM; + irq_restore(state); + DEBUG("PTPTSCR: 0x%08x, MACIMR: 0x%08x, MACSR: 0x%08x\n", + (unsigned)ETH->PTPTSCR, (unsigned)ETH->MACIMR, (unsigned)ETH->MACSR); +} +#endif /* IS_USED(MODULE_PTP_TIMER) */