mirror of
https://github.com/apache/nuttx.git
synced 2026-09-29 10:34:23 +00:00
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:
parent
c21d96eb68
commit
3589fceab3
1 changed files with 249 additions and 12 deletions
|
|
@ -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 */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue