arch/arm/stm32: implement PTP hardware clock driver (/dev/ptp0)

Implement lower-half PTP hardware clock operations (struct ptp_lowerhalf_s
and struct ptp_ops_s) in the STM32 Ethernet driver and register it with the
generic PTP clock framework (drivers/timers/ptp_clock.c) to expose /dev/ptp0.

Supported operations:
- adjfine: adjust PTP clock frequency in parts per billion (ppb)
- adjphase: adjust PTP clock phase via hardware TSSTU
- adjtime: shift PTP clock time by signed delta in nanoseconds
- gettime: atomic double-read of hardware timestamp registers
- settime: initialize hardware timestamp counter via TSSTI
- getres: return 1 ns clock resolution

Also fix a sign bug in stm32_eth_ptp_adjust() where uint64_t addend
promoted negative ppb adjustments to unsigned, corrupting frequency trim
for crystals running faster than nominal.

Follow-up to #20148 per review recommendation to use the standard POSIX
/dev/ptp0 character driver instead of custom socket ioctls.

Assisted-by: Gemini:gemini-3.8-pro
Signed-off-by: Daniel P. Carvalho <danieloak@gmail.com>
This commit is contained in:
Daniel P. Carvalho 2026-09-17 17:33:32 -03:00 • committed by Alan C. Assis
parent c21d96eb68
commit 3589fceab3

View file

@ -31,6 +31,7 @@
#include <stdint.h>
#include <stdbool.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <nuttx/debug.h>
@ -54,6 +55,10 @@
# include <nuttx/net/pkt.h>
#endif
#if defined(CONFIG_PTP_CLOCK) || defined(CONFIG_STM32_ETH_PTP)
# include <nuttx/timers/ptp_clock.h>
#endif
#include "arm_internal.h"
#include "chip.h"
#include "stm32_gpio.h"
@ -630,6 +635,9 @@ struct stm32_ethmac_s
uint32_t rxtimelow; /* Received packet timestamp subsecond */
uint32_t rxtimehigh; /* Received packet timestamp seconds */
#endif
#if defined(CONFIG_STM32_ETH_PTP) && defined(CONFIG_PTP_CLOCK)
struct ptp_lowerhalf_s ptp_lower; /* PTP hardware clock lower half */
#endif
};
/****************************************************************************
@ -772,7 +780,10 @@ static int stm32_ethconfig(struct stm32_ethmac_s *priv);
/* PTP initialization and access */
#ifdef CONFIG_STM32_ETH_PTP
static int stm32_eth_ptp_adjust(long ppb);
static int stm32_ptp_adjfine(struct ptp_lowerhalf_s *lower, long ppb);
#ifdef CONFIG_PTP_CLOCK
static int stm32_ptp_adjtime(struct ptp_lowerhalf_s *lower, int64_t delta);
#endif
static void stm32_eth_ptp_init(uint64_t timestamp);
#ifdef CONFIG_STM32_ETH_PTP_RTC_HIRES
static uint64_t stm32_eth_ptp_gettime(void);
@ -3567,15 +3578,19 @@ static inline void stm32_ethgpioconfig(struct stm32_ethmac_s *priv)
#ifdef CONFIG_STM32_ETH_PTP
/****************************************************************************
* Function: stm32_eth_ptp_adjust
* Name: stm32_ptp_adjfine
*
* Description:
* Adjust PTP timer run rate.
* Adjust the PTP clock frequency in parts per billion (ppb).
*
* Input Parameters:
* ppb - Adjustment in parts per billion (nanoseconds per second).
* Zero is default rate, positive value makes clock run faster
* and negative value slower.
* lower - Pointer to the PTP clock lower-half instance (unused; this
* implementation also serves as the PTP timer's own internal
* rate setter, called with lower == NULL from
* stm32_eth_ptp_init() and up_rtc_adjtime()).
* ppb - Adjustment in parts per billion (nanoseconds per second).
* Zero is default rate, positive value makes clock run faster
* and negative value slower.
*
* Returned Value:
* OK on success, negated errno on failure.
@ -3585,12 +3600,14 @@ static inline void stm32_ethgpioconfig(struct stm32_ethmac_s *priv)
*
****************************************************************************/
static int stm32_eth_ptp_adjust(long ppb)
static int stm32_ptp_adjfine(struct ptp_lowerhalf_s *lower, long ppb)
{
uint32_t regval;
uint64_t addend;
int64_t addend;
uint32_t increment;
UNUSED(lower);
/* Compute addend value to achieve nominal timer rate.
* Increment is set by stm32_eth_ptp_init() and remains constants after
* that.
@ -3608,9 +3625,9 @@ static int stm32_eth_ptp_adjust(long ppb)
/* Check for overflows */
if (addend == 0 || (uint32_t)addend != addend)
if (addend <= 0 || addend > UINT32_MAX)
{
nerr("PTP adjustment out of range: ppb=%ld, addend=%lld\n",
nerr("PTP adjustment out of range: ppb=%ld, addend=%" PRId64 "\n",
ppb, addend);
return -EINVAL;
}
@ -3632,6 +3649,72 @@ static int stm32_eth_ptp_adjust(long ppb)
return OK;
}
#ifdef CONFIG_PTP_CLOCK
/****************************************************************************
* Name: stm32_ptp_adjtime
*
* Description:
* Nudge the PTP hardware counter's phase by a signed delta, in
* nanoseconds, via the System Time Update (TSSTU) mechanism. Unlike
* stm32_eth_ptp_init(), this does not reset the rate (addend) that
* stm32_ptp_adjfine() may already have applied.
*
* Input Parameters:
* lower - Pointer to the PTP clock lower-half instance (unused; this
* implementation also serves as the PTP timer's own internal
* phase setter, called with lower == NULL from
* stm32_ptp_adjphase()).
* delta - Amount to add to (positive) or subtract from (negative) the
* current counter value, in nanoseconds.
*
* Returned Value:
* OK on success, negated errno on failure.
*
****************************************************************************/
static int stm32_ptp_adjtime(struct ptp_lowerhalf_s *lower, int64_t delta)
{
uint32_t regval;
uint32_t sec;
uint32_t subsec;
uint32_t abs_nsec;
uint64_t abs_ns;
bool negative;
UNUSED(lower);
negative = (delta < 0);
abs_ns = llabs(delta);
sec = abs_ns / NSEC_PER_SEC;
abs_nsec = abs_ns % NSEC_PER_SEC;
/* Convert the nanosecond remainder to the same 32-bit binary fraction
* of a second used by ptp_to_timespec()/stm32_eth_ptp_init(), then
* halve it to fit the 31-bit TSUSS field (mirrors the >>1 done in
* stm32_eth_ptp_init()).
*/
subsec = (((uint64_t)abs_nsec << 32) / NSEC_PER_SEC) >> 1;
subsec &= ETH_PTPTSLR_MASK;
stm32_putreg(sec, STM32_ETH_PTPTSHUR);
stm32_putreg(subsec | (negative ? ETH_PTPTSLU_TSUPNS : 0),
STM32_ETH_PTPTSLUR);
regval = stm32_getreg(STM32_ETH_PTPTSCR);
stm32_putreg(regval | ETH_PTPTSCR_TSSTU, STM32_ETH_PTPTSCR);
up_udelay(1);
if (stm32_getreg(STM32_ETH_PTPTSCR) & ETH_PTPTSCR_TSSTU)
{
nerr("PTP phase update failed\n");
return -EBUSY;
}
return OK;
}
#endif /* CONFIG_PTP_CLOCK */
/****************************************************************************
* Function: stm32_eth_ptp_init
*
@ -3678,7 +3761,7 @@ static void stm32_eth_ptp_init(uint64_t timestamp)
/* Update addend value to default rate */
stm32_eth_ptp_adjust(0);
stm32_ptp_adjfine(NULL, 0);
/* Enable fine update mode */
@ -3709,6 +3792,148 @@ static void stm32_eth_ptp_init(uint64_t timestamp)
#endif
}
#if defined(CONFIG_STM32_ETH_PTP) && defined(CONFIG_PTP_CLOCK)
/****************************************************************************
* Name: stm32_ptp_adjphase
*
* Description:
* Adjust the PTP clock phase by a signed offset in nanoseconds.
*
* Input Parameters:
* lower - Pointer to the PTP clock lower-half instance
* phase - Phase adjustment in nanoseconds
*
* Returned Value:
* OK on success, negated errno on failure.
*
****************************************************************************/
static int stm32_ptp_adjphase(struct ptp_lowerhalf_s *lower, int32_t phase)
{
return stm32_ptp_adjtime(lower, phase);
}
/****************************************************************************
* Name: stm32_ptp_gettime
*
* Description:
* Read the current time from the PTP hardware clock.
*
* Input Parameters:
* lower - Pointer to the PTP clock lower-half instance
* ts - Location to store the current PTP time
* sts - System timestamp pair (unused, can be NULL)
*
* Returned Value:
* OK on success, negated errno on failure.
*
****************************************************************************/
static int stm32_ptp_gettime(struct ptp_lowerhalf_s *lower,
struct timespec *ts,
struct ptp_system_timestamp *sts)
{
uint32_t high1;
uint32_t low;
uint32_t high2;
uint32_t subsec;
high1 = getreg32(STM32_ETH_PTPTSHR);
low = getreg32(STM32_ETH_PTPTSLR);
high2 = getreg32(STM32_ETH_PTPTSHR);
if (high1 != high2)
{
low = getreg32(STM32_ETH_PTPTSLR);
}
ts->tv_sec = high2;
subsec = (low & ETH_PTPTSLR_MASK) << 1;
ts->tv_nsec = ((uint64_t)subsec * NSEC_PER_SEC) >> 32;
return OK;
}
/****************************************************************************
* Name: stm32_ptp_settime
*
* Description:
* Set the current time on the PTP hardware clock.
*
* Input Parameters:
* lower - Pointer to the PTP clock lower-half instance
* ts - Time value to set
*
* Returned Value:
* OK on success, negated errno on failure.
*
****************************************************************************/
static int stm32_ptp_settime(struct ptp_lowerhalf_s *lower,
const struct timespec *ts)
{
uint32_t regval;
uint32_t subsec;
if (ts->tv_nsec < 0 || ts->tv_nsec >= NSEC_PER_SEC)
{
return -EINVAL;
}
subsec = (((uint64_t)ts->tv_nsec << 32) / NSEC_PER_SEC) >> 1;
subsec &= ETH_PTPTSLR_MASK;
stm32_putreg((uint32_t)ts->tv_sec, STM32_ETH_PTPTSHUR);
stm32_putreg(subsec, STM32_ETH_PTPTSLUR);
regval = stm32_getreg(STM32_ETH_PTPTSCR);
stm32_putreg(regval | ETH_PTPTSCR_TSSTI, STM32_ETH_PTPTSCR);
up_udelay(1);
if (stm32_getreg(STM32_ETH_PTPTSCR) & ETH_PTPTSCR_TSSTI)
{
nerr("PTP timestamp update failed\n");
return -EBUSY;
}
return OK;
}
/****************************************************************************
* Name: stm32_ptp_getres
*
* Description:
* Get the resolution of the PTP hardware clock (1 ns).
*
* Input Parameters:
* lower - Pointer to the PTP clock lower-half instance
* res - Location to store the resolution
*
* Returned Value:
* OK on success.
*
****************************************************************************/
static int stm32_ptp_getres(struct ptp_lowerhalf_s *lower,
struct timespec *res)
{
res->tv_sec = 0;
res->tv_nsec = 1;
return OK;
}
static const struct ptp_ops_s g_stm32_ptp_ops =
{
stm32_ptp_adjfine, /* adjfine */
stm32_ptp_adjphase, /* adjphase */
stm32_ptp_adjtime, /* adjtime */
stm32_ptp_gettime, /* gettime */
NULL, /* getcrosststamp */
stm32_ptp_settime, /* settime */
stm32_ptp_getres, /* getres */
};
#endif
#ifdef CONFIG_STM32_ETH_PTP_RTC_HIRES
/****************************************************************************
* Name: stm32_eth_ptp_gettime
@ -4242,6 +4467,18 @@ int stm32_ethinitialize(int intf)
/* Register the device with the OS so that socket IOCTLs can be performed */
netdev_register(&priv->dev, NET_LL_ETHERNET);
#if defined(CONFIG_STM32_ETH_PTP) && defined(CONFIG_PTP_CLOCK)
/* Register the PTP clock character driver (/dev/ptp0) */
priv->ptp_lower.ops = &g_stm32_ptp_ops;
ret = ptp_clock_register(&priv->ptp_lower, 500000000, intf);
if (ret < 0)
{
nerr("ERROR: Failed to register PTP clock: %d\n", ret);
}
#endif
return OK;
}
@ -4416,7 +4653,7 @@ int up_rtc_settime(const struct timespec *tp)
int up_rtc_adjtime(long ppb)
{
return stm32_eth_ptp_adjust(ppb);
return stm32_ptp_adjfine(NULL, ppb);
}
#endif /* CONFIG_STM32_ETH_PTP_RTC_HIRES */