mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
214 lines
7.6 KiB
C
214 lines
7.6 KiB
C
/*
|
|
* 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
|
|
#define DEBUG_VERBOSE 0
|
|
#include "debug.h"
|
|
|
|
/* 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 rounded result of x / y
|
|
* @param x Number to divide
|
|
* @param y @p x should be divided by this
|
|
* @return x/y, rounded to the nearest integer, using "round half up" rule
|
|
* @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_PTPTSCR_TSSARFE;
|
|
}
|
|
DEBUG("[periph_ptp] Initialized with PTPSAR = %" PRIu32 ", PTPSSIR = %" PRIu32 "\n",
|
|
ptptsar, ptpssir);
|
|
}
|
|
|
|
void ptp_clock_adjust_speed(int32_t correction)
|
|
{
|
|
uint64_t offset = ptptsar;
|
|
offset *= correction;
|
|
offset >>= 32;
|
|
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 ".%09" 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);
|
|
if (IS_ACTIVE(DEBUG_VERBOSE)) {
|
|
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) */
|