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:
parent
7d1edd51f4
commit
ea3752db77
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
102
cpu/stm32/periph/eth_common.c
Normal file
102
cpu/stm32/periph/eth_common.c
Normal 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
218
cpu/stm32/periph/ptp.c
Normal 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(Ð->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) */
|
||||
Loading…
x
Reference in New Issue
Block a user