This is an automated email from the ASF dual-hosted git repository. acassis pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit 3589fceab3edc81228a829ea32cd01bcf38a2196 Author: Daniel P. Carvalho <[email protected]> AuthorDate: Thu Sep 17 17:33:32 2026 -0300 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 <[email protected]> --- arch/arm/src/common/stm32/stm32_eth_m3m4_v1.c | 261 ++++++++++++++++++++++++-- 1 file changed, 249 insertions(+), 12 deletions(-) diff --git a/arch/arm/src/common/stm32/stm32_eth_m3m4_v1.c b/arch/arm/src/common/stm32/stm32_eth_m3m4_v1.c index 90fc38586da..56947eb3a2a 100644 --- a/arch/arm/src/common/stm32/stm32_eth_m3m4_v1.c +++ b/arch/arm/src/common/stm32/stm32_eth_m3m4_v1.c @@ -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 */
