From e2d5b8f29cf57fd69db250e303aa1dd0faddd62c Mon Sep 17 00:00:00 2001 From: "Daniel P. Carvalho" Date: Sun, 20 Sep 2026 20:46:33 -0300 Subject: [PATCH] arch/arm/stm32h7: add the PTP hardware clock of the Ethernet MAC. The MAC has a system time that is the base of the PTP hardware timestamps, but the driver never started it, and STM32_ETH_PTP only printed a warning. Add the registers of the timestamp unit and start the system time with the fine update method and the digital rollover, so that the nanoseconds count up to 10^9. The increment is 2 * 10^9 / HCLK ns, and the addend makes the update rate half of HCLK, which leaves room to trim the frequency in both directions. The time starts at zero right after the MAC reset, not with the MAC configuration, so it does not depend on the PHY having a link. The reset clears it, so it starts again each time the interface goes up. With STM32_ETH_PTP_GPIO, start the pulse-per-second output as a pulse train with a period of one second and a width of half of it, at the whole seconds of the system time. The fixed frequency mode of the MAC gives a pulse too short to be seen, so the flexible mode is used. The interrupt of the timestamp unit is not enabled in the MAC: it is set each time the target time of the PPS output is reached and is cleared by reading MACTSSR, which the interrupt handler does not do, so it would stay pending and keep the handler running until the network stops. Register /dev/ptp0 when CONFIG_PTP_CLOCK is set, so that a PTP daemon can read and set the system time and correct its frequency and its phase. The frequency is corrected by changing the addend, by up to 50% each way. A step of the time is added or subtracted with the update register; with the digital rollover a subtraction is programmed with the negated seconds and with 10^9 minus the nanoseconds. The pulse train counts by itself, so it does not follow a step of the system time and would be displaced by the same amount: after the time is set or stepped, it is started again at the next whole second. With HCLK at 200 MHz the system time advanced 10.0018 s while a host clock advanced 10.002 s, and the pulse train was seen on the pin. Reading, setting, steps of +0.5 s and -1.25 s and a change of 100 ppm were done through /dev/ptp0 on hardware, and the edges of the PPS output stayed at the whole seconds of the system time. Signed-off-by: Daniel P. Carvalho Assisted-by: Claude:claude-sonnet-5 --- .../arm/src/stm32h7/hardware/stm32_ethernet.h | 98 ++++ arch/arm/src/stm32h7/stm32_ethernet.c | 536 +++++++++++++++++- 2 files changed, 631 insertions(+), 3 deletions(-) diff --git a/arch/arm/src/stm32h7/hardware/stm32_ethernet.h b/arch/arm/src/stm32h7/hardware/stm32_ethernet.h index 5765047abe4..c8b04b4c475 100644 --- a/arch/arm/src/stm32h7/hardware/stm32_ethernet.h +++ b/arch/arm/src/stm32h7/hardware/stm32_ethernet.h @@ -66,6 +66,22 @@ #define STM32_ETH_MACA0HR_OFFSET 0x0300 /* Address 0 high register */ #define STM32_ETH_MACA0LR_OFFSET 0x0304 /* Address 0 low register */ +/* MAC Timestamp Registers */ + +#define STM32_ETH_MACTSCR_OFFSET 0x0B00 /* Ethernet MAC timestamp control register */ +#define STM32_ETH_MACSSIR_OFFSET 0x0B04 /* Ethernet MAC sub-second increment register */ +#define STM32_ETH_MACSTSR_OFFSET 0x0B08 /* Ethernet MAC system time seconds register */ +#define STM32_ETH_MACSTNR_OFFSET 0x0B0C /* Ethernet MAC system time nanoseconds register */ +#define STM32_ETH_MACSTSUR_OFFSET 0x0B10 /* Ethernet MAC system time seconds update register */ +#define STM32_ETH_MACSTNUR_OFFSET 0x0B14 /* Ethernet MAC system time nanoseconds update register */ +#define STM32_ETH_MACTSAR_OFFSET 0x0B18 /* Ethernet MAC timestamp addend register */ +#define STM32_ETH_MACTSSR_OFFSET 0x0B20 /* Ethernet MAC timestamp status register */ +#define STM32_ETH_MACPPSCR_OFFSET 0x0B70 /* Ethernet MAC PPS control register */ +#define STM32_ETH_MACPPSTTSR_OFFSET 0x0B80 /* Ethernet MAC PPS target time seconds register */ +#define STM32_ETH_MACPPSTTNR_OFFSET 0x0B84 /* Ethernet MAC PPS target time nanoseconds register */ +#define STM32_ETH_MACPPSIR_OFFSET 0x0B88 /* Ethernet MAC PPS interval register */ +#define STM32_ETH_MACPPSWR_OFFSET 0x0B8C /* Ethernet MAC PPS width register */ + /* MTL Registers */ #define STM32_ETH_MTLOMR_OFFSET 0x0C00 /* Operating mode Register */ @@ -121,6 +137,22 @@ #define STM32_ETH_MACA0LR (STM32_EMAC_BASE+STM32_ETH_MACA0LR_OFFSET) #define STM32_ETH_MACA0HR (STM32_EMAC_BASE+STM32_ETH_MACA0HR_OFFSET) +/* MAC Timestamp Registers */ + +#define STM32_ETH_MACTSCR (STM32_EMAC_BASE+STM32_ETH_MACTSCR_OFFSET) +#define STM32_ETH_MACSSIR (STM32_EMAC_BASE+STM32_ETH_MACSSIR_OFFSET) +#define STM32_ETH_MACSTSR (STM32_EMAC_BASE+STM32_ETH_MACSTSR_OFFSET) +#define STM32_ETH_MACSTNR (STM32_EMAC_BASE+STM32_ETH_MACSTNR_OFFSET) +#define STM32_ETH_MACSTSUR (STM32_EMAC_BASE+STM32_ETH_MACSTSUR_OFFSET) +#define STM32_ETH_MACSTNUR (STM32_EMAC_BASE+STM32_ETH_MACSTNUR_OFFSET) +#define STM32_ETH_MACTSAR (STM32_EMAC_BASE+STM32_ETH_MACTSAR_OFFSET) +#define STM32_ETH_MACTSSR (STM32_EMAC_BASE+STM32_ETH_MACTSSR_OFFSET) +#define STM32_ETH_MACPPSCR (STM32_EMAC_BASE+STM32_ETH_MACPPSCR_OFFSET) +#define STM32_ETH_MACPPSTTSR (STM32_EMAC_BASE+STM32_ETH_MACPPSTTSR_OFFSET) +#define STM32_ETH_MACPPSTTNR (STM32_EMAC_BASE+STM32_ETH_MACPPSTTNR_OFFSET) +#define STM32_ETH_MACPPSIR (STM32_EMAC_BASE+STM32_ETH_MACPPSIR_OFFSET) +#define STM32_ETH_MACPPSWR (STM32_EMAC_BASE+STM32_ETH_MACPPSWR_OFFSET) + /* MTL Registers */ #define STM32_ETH_MTLOMR (STM32_EMAC_BASE+STM32_ETH_MTLOMR_OFFSET) @@ -314,6 +346,72 @@ #define STM32_ETH_MACAxHR_SA (1 << 30) /* Bit 30: Source Address */ #define STM32_ETH_MACAxHR_AE (1 << 31) /* Bit 31: Address Enable */ +/* Ethernet MAC timestamp control register */ + +#define ETH_MACTSCR_TSENA (1 << 0) /* Bit 0: Enable timestamp */ +#define ETH_MACTSCR_TSCFUPDT (1 << 1) /* Bit 1: Fine or coarse timestamp update */ +#define ETH_MACTSCR_TSINIT (1 << 2) /* Bit 2: Initialize timestamp */ +#define ETH_MACTSCR_TSUPDT (1 << 3) /* Bit 3: Update timestamp */ +#define ETH_MACTSCR_TSADDREG (1 << 5) /* Bit 5: Update addend register */ +#define ETH_MACTSCR_TSENALL (1 << 8) /* Bit 8: Enable timestamp for all packets */ +#define ETH_MACTSCR_TSCTRLSSR (1 << 9) /* Bit 9: Timestamp digital or binary rollover control */ +#define ETH_MACTSCR_TSVER2ENA (1 << 10) /* Bit 10: Enable PTP packet processing for version 2 format */ +#define ETH_MACTSCR_TSIPENA (1 << 11) /* Bit 11: Enable processing of PTP over Ethernet packets */ +#define ETH_MACTSCR_TSIPV6ENA (1 << 12) /* Bit 12: Enable processing of PTP packets sent over IPv6-UDP */ +#define ETH_MACTSCR_TSIPV4ENA (1 << 13) /* Bit 13: Enable processing of PTP packets sent over IPv4-UDP */ +#define ETH_MACTSCR_TSEVNTENA (1 << 14) /* Bit 14: Enable timestamp snapshot for event messages */ +#define ETH_MACTSCR_TSMSTRENA (1 << 15) /* Bit 15: Enable snapshot for messages relevant to master */ +#define ETH_MACTSCR_SNAPTYPSEL_SHIFT (16) /* Bits 16-17: Select PTP packets for taking snapshots */ +#define ETH_MACTSCR_SNAPTYPSEL_MASK (0x3 << ETH_MACTSCR_SNAPTYPSEL_SHIFT) +#define ETH_MACTSCR_TSENMACADDR (1 << 18) /* Bit 18: Enable MAC address for PTP packet filtering */ +#define ETH_MACTSCR_CSC (1 << 19) /* Bit 19: Checksum correction for PTP over UDP/IPv4 (one-step) */ +#define ETH_MACTSCR_TXTSSTSM (1 << 24) /* Bit 24: Transmit timestamp status mode */ + +/* Ethernet MAC sub-second increment register */ + +#define ETH_MACSSIR_SNSINC_SHIFT (8) /* Bits 8-15: Sub-nanosecond increment value */ +#define ETH_MACSSIR_SNSINC_MASK (0xFF << ETH_MACSSIR_SNSINC_SHIFT) +#define ETH_MACSSIR_SSINC_SHIFT (16) /* Bits 16-23: Sub-second increment value */ +#define ETH_MACSSIR_SSINC_MASK (0xFF << ETH_MACSSIR_SSINC_SHIFT) + +/* Ethernet MAC system time nanoseconds register */ + +#define ETH_MACSTNR_TSSS_SHIFT (0) /* Bits 0-30: Timestamp sub-seconds */ +#define ETH_MACSTNR_TSSS_MASK (0x7FFFFFFF << ETH_MACSTNR_TSSS_SHIFT) + +/* Ethernet MAC system time nanoseconds update register */ + +#define ETH_MACSTNUR_TSSS_SHIFT (0) /* Bits 0-30: Timestamp sub-seconds */ +#define ETH_MACSTNUR_TSSS_MASK (0x7FFFFFFF << ETH_MACSTNUR_TSSS_SHIFT) +#define ETH_MACSTNUR_ADDSUB (1 << 31) /* Bit 31: Add or subtract time */ + +/* Ethernet MAC timestamp status register */ + +#define ETH_MACTSSR_TSSOVF (1 << 0) /* Bit 0: Timestamp seconds overflow */ +#define ETH_MACTSSR_TSTARGT0 (1 << 1) /* Bit 1: Timestamp target time reached */ +#define ETH_MACTSSR_AUXTSTRIG (1 << 2) /* Bit 2: Auxiliary timestamp trigger snapshot */ +#define ETH_MACTSSR_TSTRGTERR0 (1 << 3) /* Bit 3: Timestamp target time error */ +#define ETH_MACTSSR_TXTSSIS (1 << 15) /* Bit 15: Tx timestamp status interrupt status */ +#define ETH_MACTSSR_ATSSTN_SHIFT (16) /* Bits 16-19: Auxiliary timestamp snapshot trigger identifier */ +#define ETH_MACTSSR_ATSSTN_MASK (0xF << ETH_MACTSSR_ATSSTN_SHIFT) +#define ETH_MACTSSR_ATSSTM (1 << 24) /* Bit 24: Auxiliary timestamp snapshot trigger missed */ +#define ETH_MACTSSR_ATSNS_SHIFT (25) /* Bits 25-29: Number of auxiliary timestamp snapshots */ +#define ETH_MACTSSR_ATSNS_MASK (0x1F << ETH_MACTSSR_ATSNS_SHIFT) + +/* Ethernet MAC PPS control register */ + +#define ETH_MACPPSCR_PPSCTRL_SHIFT (0) /* Bits 0-3: PPS output frequency control or command */ +#define ETH_MACPPSCR_PPSCTRL_MASK (0xF << ETH_MACPPSCR_PPSCTRL_SHIFT) +# define ETH_MACPPSCR_PPSCMD_START_TRAIN (2 << ETH_MACPPSCR_PPSCTRL_SHIFT) +# define ETH_MACPPSCR_PPSCMD_STOP_NOW (5 << ETH_MACPPSCR_PPSCTRL_SHIFT) +#define ETH_MACPPSCR_PPSEN0 (1 << 4) /* Bit 4: Flexible PPS output mode enable */ + +/* Ethernet MAC PPS target time nanoseconds register */ + +#define ETH_MACPPSTTNR_TTSL0_SHIFT (0) /* Bits 0-30: Target time low */ +#define ETH_MACPPSTTNR_TTSL0_MASK (0x7FFFFFFF << ETH_MACPPSTTNR_TTSL0_SHIFT) +#define ETH_MACPPSTTNR_TRGTBUSY0 (1 << 31) /* Bit 31: PPS target time register busy */ + /* Ethernet MTL registers */ #define ETH_MTLOMR_DTXSTS (1 << 1) /* Bit 1: Drop Transmit Status */ diff --git a/arch/arm/src/stm32h7/stm32_ethernet.c b/arch/arm/src/stm32h7/stm32_ethernet.c index d6e5e36a776..388ca220666 100644 --- a/arch/arm/src/stm32h7/stm32_ethernet.c +++ b/arch/arm/src/stm32h7/stm32_ethernet.c @@ -51,6 +51,7 @@ #include #include #include +#include #if defined(CONFIG_NET_PKT) # include @@ -253,7 +254,47 @@ #endif #ifdef CONFIG_STM32_ETH_PTP -# warning "CONFIG_STM32_ETH_PTP is not yet supported" +/* The timestamp unit is clocked from HCLK. The system time advances by + * STM32_PTP_SSINC nanoseconds each time the 32-bit accumulator of the + * addend register overflows, so its rate is HCLK * addend / 2^32. Aim at + * an update rate of half of HCLK: the nominal addend is then close to 2^31 + * and the frequency can be trimmed by +/- 50 %. + */ + +# define STM32_PTP_CLOCK STM32_HCLK_FREQUENCY +# define STM32_PTP_SSINC ((2 * NSEC_PER_SEC + STM32_PTP_CLOCK / 2) / \ + STM32_PTP_CLOCK) +# define STM32_PTP_ADDEND ((uint32_t)((((uint64_t)1 << 32) * \ + NSEC_PER_SEC) / \ + ((uint64_t)STM32_PTP_SSINC * \ + STM32_PTP_CLOCK))) + +/* Time to wait for the unit to take an update of the addend or of the + * system time, in microseconds. + */ + +# define STM32_PTP_UPDATE_USTIMEOUT (1000) + +/* The addend can be trimmed by up to 50% each way from its nominal value, + * in parts per billion. + */ + +# define STM32_PTP_MAX_ADJ (500000000) + +# ifdef CONFIG_STM32_ETH_PTP_GPIO + +/* The pulse-per-second output is a pulse train with a period of one + * second and a width of half of it, that starts at a whole second of the + * system time. The first pulse is at least STM32_PTP_PPS_MARGIN_NS ahead, + * so the target time is loaded before it. The interval and the width are + * in increments of the system time, minus one. + */ + +# define STM32_PTP_PPS_MARGIN_NS (100000000) +# define STM32_PTP_PPS_INTERVAL (NSEC_PER_SEC / STM32_PTP_SSINC - 1) +# define STM32_PTP_PPS_WIDTH ((NSEC_PER_SEC / 2) / \ + STM32_PTP_SSINC - 1) +# endif #endif #undef CONFIG_STM32_ETH_HWCHECKSUM @@ -722,6 +763,10 @@ struct stm32_ethmac_s sq_queue_t freeb; /* The free buffer list */ struct mdio_bus_s *mdio; + +#if defined(CONFIG_STM32_ETH_PTP) && defined(CONFIG_PTP_CLOCK) + struct ptp_lowerhalf_s ptp_lower; /* PTP hardware clock lower half */ +#endif }; /**************************************************************************** @@ -860,6 +905,9 @@ static inline void stm32_selectrmii(void); #endif static inline void stm32_ethgpioconfig(struct stm32_ethmac_s *priv); static void stm32_ethreset(struct stm32_ethmac_s *priv); +#ifdef CONFIG_STM32_ETH_PTP +static int stm32_eth_ptp_init(void); +#endif static int stm32_macconfig(struct stm32_ethmac_s *priv); static void stm32_macaddress(struct stm32_ethmac_s *priv); static int stm32_macenable(struct stm32_ethmac_s *priv); @@ -3802,6 +3850,460 @@ static inline void stm32_ethgpioconfig(struct stm32_ethmac_s *priv) #endif } +#ifdef CONFIG_STM32_ETH_PTP +/**************************************************************************** + * Name: stm32_eth_ptp_wait + * + * Description: + * Wait for the timestamp unit to clear a bit that is set while it takes + * an update of the addend, of the system time or of the PPS target time. + * + * Input Parameters: + * reg - The register that holds the bit + * bit - The bit to wait for + * + * Returned Value: + * OK on success, -ETIMEDOUT if the bit was not cleared in time. + * + ****************************************************************************/ + +static int stm32_eth_ptp_wait(uint32_t reg, uint32_t bit) +{ + int timeout; + + for (timeout = 0; timeout < STM32_PTP_UPDATE_USTIMEOUT; timeout++) + { + if ((stm32_getreg(reg) & bit) == 0) + { + return OK; + } + + up_udelay(1); + } + + return -ETIMEDOUT; +} + +#if defined(CONFIG_STM32_ETH_PTP_GPIO) || defined(CONFIG_PTP_CLOCK) +/**************************************************************************** + * Name: stm32_eth_ptp_read + * + * Description: + * Read the system time. The seconds and the nanoseconds are in two + * registers, so the seconds are read again to detect a rollover between + * the reads. + * + * Input Parameters: + * sec - The location to store the seconds + * nsec - The location to store the nanoseconds + * + ****************************************************************************/ + +static void stm32_eth_ptp_read(uint32_t *sec, uint32_t *nsec) +{ + uint32_t sec1; + uint32_t sec2; + uint32_t ns; + + sec1 = stm32_getreg(STM32_ETH_MACSTSR); + ns = stm32_getreg(STM32_ETH_MACSTNR); + sec2 = stm32_getreg(STM32_ETH_MACSTSR); + + if (sec1 != sec2) + { + ns = stm32_getreg(STM32_ETH_MACSTNR); + } + + *sec = sec2; + *nsec = ns & ETH_MACSTNR_TSSS_MASK; +} +#endif + +/**************************************************************************** + * Name: stm32_eth_ptp_setaddend + * + * Description: + * Load the addend, that sets the rate of the system time. + * + * Input Parameters: + * addend - The value to load + * + * Returned Value: + * OK on success, -ETIMEDOUT if the unit did not take it in time. + * + ****************************************************************************/ + +static int stm32_eth_ptp_setaddend(uint32_t addend) +{ + stm32_putreg(addend, STM32_ETH_MACTSAR); + stm32_putreg(stm32_getreg(STM32_ETH_MACTSCR) | ETH_MACTSCR_TSADDREG, + STM32_ETH_MACTSCR); + return stm32_eth_ptp_wait(STM32_ETH_MACTSCR, ETH_MACTSCR_TSADDREG); +} + +#ifdef CONFIG_STM32_ETH_PTP_GPIO +/**************************************************************************** + * Name: stm32_eth_ptp_pps_start + * + * Description: + * Start the pulse-per-second output, a pulse train with a period of one + * second and a duty cycle of 50%, aligned to the system time. The fixed + * frequency mode of the MAC gives a pulse too short to be seen, so the + * flexible mode is used. + * + * Returned Value: + * OK on success, a negated errno value on failure. + * + ****************************************************************************/ + +static int stm32_eth_ptp_pps_start(void) +{ + uint32_t sec; + uint32_t nsec; + int ret; + + /* The first pulse is at the next whole second, or at the one after it + * if that is too close to be loaded in time. + */ + + stm32_eth_ptp_read(&sec, &nsec); + sec += (nsec < NSEC_PER_SEC - STM32_PTP_PPS_MARGIN_NS) ? 1 : 2; + + stm32_putreg(sec, STM32_ETH_MACPPSTTSR); + stm32_putreg(0, STM32_ETH_MACPPSTTNR); + + ret = stm32_eth_ptp_wait(STM32_ETH_MACPPSTTNR, ETH_MACPPSTTNR_TRGTBUSY0); + if (ret < 0) + { + nerr("ERROR: Timed out loading the PPS target time\n"); + return ret; + } + + stm32_putreg(STM32_PTP_PPS_INTERVAL, STM32_ETH_MACPPSIR); + stm32_putreg(STM32_PTP_PPS_WIDTH, STM32_ETH_MACPPSWR); + stm32_putreg(ETH_MACPPSCR_PPSEN0 | ETH_MACPPSCR_PPSCMD_START_TRAIN, + STM32_ETH_MACPPSCR); + return OK; +} + +#ifdef CONFIG_PTP_CLOCK +/**************************************************************************** + * Name: stm32_eth_ptp_pps_restart + * + * Description: + * Align the pulse-per-second output to the whole seconds of the system + * time again. The pulse train counts by itself, and does not follow a + * step of the system time, so after a step the pulses would be displaced + * by the same amount. + * + * Returned Value: + * OK on success, a negated errno value on failure. + * + ****************************************************************************/ + +static int stm32_eth_ptp_pps_restart(void) +{ + int ret; + + stm32_putreg(ETH_MACPPSCR_PPSEN0 | ETH_MACPPSCR_PPSCMD_STOP_NOW, + STM32_ETH_MACPPSCR); + ret = stm32_eth_ptp_wait(STM32_ETH_MACPPSCR, ETH_MACPPSCR_PPSCTRL_MASK); + if (ret < 0) + { + nerr("ERROR: Timed out stopping the PPS output\n"); + return ret; + } + + return stm32_eth_ptp_pps_start(); +} +#endif /* CONFIG_PTP_CLOCK */ +#endif /* CONFIG_STM32_ETH_PTP_GPIO */ + +/**************************************************************************** + * Name: stm32_eth_ptp_init + * + * Description: + * Start the system time of the timestamp unit of the MAC, at zero and at + * its nominal rate. The unit needs to be enabled before the addend and + * the system time can be loaded. It does not depend on the link, but it + * needs the MAC to be out of reset, and a reset of the MAC clears it. + * + * Returned Value: + * OK on success, a negated errno value on failure. + * + ****************************************************************************/ + +static int stm32_eth_ptp_init(void) +{ + uint32_t tscr; + int ret; + + /* Enable the unit, with the fine update method (the addend sets the + * rate) and a digital rollover (the nanoseconds count from 0 to + * 999999999). + */ + + tscr = ETH_MACTSCR_TSENA | ETH_MACTSCR_TSCFUPDT | ETH_MACTSCR_TSCTRLSSR; + stm32_putreg(tscr, STM32_ETH_MACTSCR); + + /* Set the increment of the system time */ + + stm32_putreg(STM32_PTP_SSINC << ETH_MACSSIR_SSINC_SHIFT, + STM32_ETH_MACSSIR); + + /* Load the addend for the nominal rate */ + + ret = stm32_eth_ptp_setaddend(STM32_PTP_ADDEND); + if (ret < 0) + { + nerr("ERROR: Timed out loading the PTP addend\n"); + return ret; + } + + /* Initialize the system time to zero */ + + stm32_putreg(0, STM32_ETH_MACSTSUR); + stm32_putreg(0, STM32_ETH_MACSTNUR); + stm32_putreg(tscr | ETH_MACTSCR_TSINIT, STM32_ETH_MACTSCR); + ret = stm32_eth_ptp_wait(STM32_ETH_MACTSCR, ETH_MACTSCR_TSINIT); + if (ret < 0) + { + nerr("ERROR: Timed out initializing the PTP system time\n"); + return ret; + } + +#ifdef CONFIG_STM32_ETH_PTP_GPIO + ret = stm32_eth_ptp_pps_start(); + if (ret < 0) + { + return ret; + } +#endif + + ninfo("PTP: system time started, SSINC=%u addend=%" PRIu32 "\n", + (unsigned int)STM32_PTP_SSINC, (uint32_t)STM32_PTP_ADDEND); + return OK; +} + +#ifdef CONFIG_PTP_CLOCK +/**************************************************************************** + * Name: stm32_ptp_adjfine + * + * Description: + * Adjust the frequency of the system time. + * + * Input Parameters: + * lower - The PTP clock lower half (unused) + * ppb - The offset from the nominal frequency, in parts per billion. + * Positive makes the system time run faster. + * + * Returned Value: + * OK on success, a negated errno value on failure. + * + ****************************************************************************/ + +static int stm32_ptp_adjfine(struct ptp_lowerhalf_s *lower, long ppb) +{ + irqstate_t flags; + int64_t addend; + int ret; + + addend = STM32_PTP_ADDEND; + addend += addend * ppb / NSEC_PER_SEC; + + if (addend <= 0 || addend > UINT32_MAX) + { + nerr("ERROR: PTP adjustment out of range: %ld ppb\n", ppb); + return -EINVAL; + } + + flags = enter_critical_section(); + ret = stm32_eth_ptp_setaddend((uint32_t)addend); + leave_critical_section(flags); + + return ret; +} + +/**************************************************************************** + * Name: stm32_ptp_adjtime + * + * Description: + * Step the system time by a signed amount, without changing its rate. + * + * Input Parameters: + * lower - The PTP clock lower half (unused) + * delta - The amount to add, in nanoseconds + * + * Returned Value: + * OK on success, a negated errno value on failure. + * + ****************************************************************************/ + +static int stm32_ptp_adjtime(struct ptp_lowerhalf_s *lower, int64_t delta) +{ + irqstate_t flags; + uint64_t abs_ns; + uint64_t sec; + uint32_t nsec; + uint32_t nsreg; + int ret; + + abs_ns = delta < 0 ? (uint64_t)0 - (uint64_t)delta : (uint64_t)delta; + sec = abs_ns / NSEC_PER_SEC; + nsec = abs_ns % NSEC_PER_SEC; + + if (sec > UINT32_MAX) + { + return -EINVAL; + } + + if (delta < 0) + { + /* To subtract, the seconds register holds the negated seconds and + * the nanoseconds register 10^9 minus the nanoseconds. + */ + + sec = (uint32_t)(0 - (uint32_t)sec); + nsreg = ETH_MACSTNUR_ADDSUB | (NSEC_PER_SEC - nsec); + } + else + { + nsreg = nsec; + } + + flags = enter_critical_section(); + stm32_putreg((uint32_t)sec, STM32_ETH_MACSTSUR); + stm32_putreg(nsreg, STM32_ETH_MACSTNUR); + stm32_putreg(stm32_getreg(STM32_ETH_MACTSCR) | ETH_MACTSCR_TSUPDT, + STM32_ETH_MACTSCR); + ret = stm32_eth_ptp_wait(STM32_ETH_MACTSCR, ETH_MACTSCR_TSUPDT); + if (ret < 0) + { + nerr("ERROR: Timed out stepping the PTP system time\n"); + } +#ifdef CONFIG_STM32_ETH_PTP_GPIO + else + { + ret = stm32_eth_ptp_pps_restart(); + } +#endif + + leave_critical_section(flags); + return ret; +} + +/**************************************************************************** + * Name: stm32_ptp_adjphase + * + * Description: + * Step the system time by a signed amount, in nanoseconds. + * + ****************************************************************************/ + +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 system time. The system clock timestamps are not supported. + * + ****************************************************************************/ + +static int stm32_ptp_gettime(struct ptp_lowerhalf_s *lower, + struct timespec *ts, + struct ptp_system_timestamp *sts) +{ + uint32_t sec; + uint32_t nsec; + + stm32_eth_ptp_read(&sec, &nsec); + + ts->tv_sec = sec; + ts->tv_nsec = nsec; + return OK; +} + +/**************************************************************************** + * Name: stm32_ptp_settime + * + * Description: + * Set the system time. + * + * Input Parameters: + * lower - The PTP clock lower half (unused) + * ts - The time to set. The seconds must fit in 32 bits. + * + * Returned Value: + * OK on success, a negated errno value on failure. + * + ****************************************************************************/ + +static int stm32_ptp_settime(struct ptp_lowerhalf_s *lower, + const struct timespec *ts) +{ + irqstate_t flags; + int ret; + + if (ts->tv_sec < 0 || ts->tv_sec > UINT32_MAX || + ts->tv_nsec < 0 || ts->tv_nsec >= NSEC_PER_SEC) + { + return -EINVAL; + } + + flags = enter_critical_section(); + stm32_putreg((uint32_t)ts->tv_sec, STM32_ETH_MACSTSUR); + stm32_putreg((uint32_t)ts->tv_nsec, STM32_ETH_MACSTNUR); + stm32_putreg(stm32_getreg(STM32_ETH_MACTSCR) | ETH_MACTSCR_TSINIT, + STM32_ETH_MACTSCR); + ret = stm32_eth_ptp_wait(STM32_ETH_MACTSCR, ETH_MACTSCR_TSINIT); + if (ret < 0) + { + nerr("ERROR: Timed out setting the PTP system time\n"); + } +#ifdef CONFIG_STM32_ETH_PTP_GPIO + else + { + ret = stm32_eth_ptp_pps_restart(); + } +#endif + + leave_critical_section(flags); + return ret; +} + +/**************************************************************************** + * Name: stm32_ptp_getres + * + * Description: + * Get the resolution of the system time, that is its increment. + * + ****************************************************************************/ + +static int stm32_ptp_getres(struct ptp_lowerhalf_s *lower, + struct timespec *res) +{ + res->tv_sec = 0; + res->tv_nsec = STM32_PTP_SSINC; + 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 /* CONFIG_PTP_CLOCK */ +#endif /* CONFIG_STM32_ETH_PTP */ + /**************************************************************************** * Function: stm32_ethreset * @@ -4087,9 +4589,13 @@ static int stm32_macenable(struct stm32_ethmac_s *priv) regval |= ETH_DMACRXCR_SR; stm32_putreg(regval, STM32_ETH_DMACRXCR); - /* Enable Ethernet DMA interrupts */ + /* Enable Ethernet MAC interrupts, except the one of the timestamp unit. + * It is set each time the target time of the PPS output is reached and + * is cleared by reading MACTSSR, which the interrupt handler does not, + * so it would stay pending and keep the handler running. + */ - stm32_putreg(ETH_MACIER_ALLINTS, STM32_ETH_MACIER); + stm32_putreg(ETH_MACIER_ALLINTS & ~ETH_MACIER_TSIE, STM32_ETH_MACIER); /* Ethernet DMA supports two classes of interrupts: Normal interrupt * summary (NIS) and Abnormal interrupt summary (AIS) with a variety @@ -4154,6 +4660,19 @@ static int stm32_ethconfig(struct stm32_ethmac_s *priv) ninfo("Reset the Ethernet block\n"); stm32_ethreset(priv); +#ifdef CONFIG_STM32_ETH_PTP + /* Start the system time of the timestamp unit. It does not depend on the + * link, so it is started before the PHY, which fails to initialize when + * there is no link. + */ + + ret = stm32_eth_ptp_init(); + if (ret < 0) + { + return ret; + } +#endif + /* Initialize TX Descriptors list */ stm32_txdescinit(priv, @@ -4315,6 +4834,17 @@ static inline 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 hardware clock, /dev/ptp0 */ + + priv->ptp_lower.ops = &g_stm32_ptp_ops; + if (ptp_clock_register(&priv->ptp_lower, STM32_PTP_MAX_ADJ, intf) < 0) + { + nerr("ERROR: Failed to register the PTP clock\n"); + } +#endif + return ret; }