1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-12-25 06:23:53 +01:00

cpu/stm32: Added PTP clock implementation

This commit is contained in:
Marian Buschsieweke 2020-07-15 17:18:19 +02:00
parent 7d1edd51f4
commit ea3752db77
No known key found for this signature in database
GPG Key ID: 61F64C6599B1539F
5 changed files with 349 additions and 48 deletions

View File

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

View File

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

View File

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

View File

@ -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 <victor.arino@triagnosys.com>
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
*
* @}
*/
#include <string.h>
#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

218
cpu/stm32/periph/ptp.c Normal file
View File

@ -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 <marian.buschsieweke@ovgu.de>
*
* @}
*/
#include <inttypes.h>
#include <string.h>
#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(&ETH->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) */