| /* |
| * Copyright (c) 2017 Erwin Rol <erwin@erwinrol.com> |
| * Copyright (c) 2020 Alexander Kozhinov <ak.alexander.kozhinov@gmail.com> |
| * Copyright (c) 2021 Carbon Robotics |
| * Copyright (c) 2025 STMicroelectronics |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/ptp_clock.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/net/ethernet.h> |
| #include <soc.h> |
| |
| #include <stdbool.h> |
| |
| #include "eth_stm32_hal_priv.h" |
| |
| LOG_MODULE_REGISTER(eth_stm32_hal_ptp, CONFIG_ETHERNET_LOG_LEVEL); |
| |
| /* Naming of the ETH PTP Config Status changes depending on the stm32 serie */ |
| #if defined(CONFIG_SOC_SERIES_STM32F4X) |
| #define ETH_STM32_PTP_CONFIGURED HAL_ETH_PTP_CONFIGURATED |
| #define ETH_STM32_PTP_NOT_CONFIGURED HAL_ETH_PTP_NOT_CONFIGURATED |
| #else |
| #define ETH_STM32_PTP_CONFIGURED HAL_ETH_PTP_CONFIGURED |
| #define ETH_STM32_PTP_NOT_CONFIGURED HAL_ETH_PTP_NOT_CONFIGURED |
| #endif /* stm32F7x or sm32F4x */ |
| |
| bool eth_stm32_is_ptp_pkt(struct net_if *iface, struct net_pkt *pkt) |
| { |
| if (ntohs(NET_ETH_HDR(pkt)->type) != NET_ETH_PTYPE_PTP) { |
| return false; |
| } |
| |
| net_pkt_set_priority(pkt, NET_PRIORITY_CA); |
| |
| return true; |
| } |
| |
| void HAL_ETH_TxPtpCallback(uint32_t *buff, ETH_TimeStampTypeDef *timestamp) |
| { |
| struct eth_stm32_tx_context *ctx = (struct eth_stm32_tx_context *)buff; |
| |
| ctx->pkt->timestamp.second = timestamp->TimeStampHigh; |
| ctx->pkt->timestamp.nanosecond = timestamp->TimeStampLow; |
| |
| net_if_add_tx_timestamp(ctx->pkt); |
| } |
| |
| const struct device *eth_stm32_get_ptp_clock(const struct device *dev) |
| { |
| struct eth_stm32_hal_dev_data *dev_data = dev->data; |
| |
| return dev_data->ptp_clock; |
| } |
| |
| struct ptp_context { |
| struct eth_stm32_hal_dev_data *eth_dev_data; |
| }; |
| |
| static struct ptp_context ptp_stm32_0_context; |
| |
| static int ptp_clock_stm32_set(const struct device *dev, |
| struct net_ptp_time *tm) |
| { |
| struct ptp_context *ptp_context = dev->data; |
| struct eth_stm32_hal_dev_data *eth_dev_data = ptp_context->eth_dev_data; |
| ETH_HandleTypeDef *heth = ð_dev_data->heth; |
| unsigned int key; |
| |
| key = irq_lock(); |
| |
| #if DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_ethernet) |
| heth->Instance->MACSTSUR = tm->second; |
| heth->Instance->MACSTNUR = tm->nanosecond; |
| heth->Instance->MACTSCR |= ETH_MACTSCR_TSINIT; |
| while (heth->Instance->MACTSCR & ETH_MACTSCR_TSINIT_Msk) { |
| /* spin lock */ |
| } |
| #else |
| heth->Instance->PTPTSHUR = tm->second; |
| heth->Instance->PTPTSLUR = tm->nanosecond; |
| heth->Instance->PTPTSCR |= ETH_PTPTSCR_TSSTI; |
| while (heth->Instance->PTPTSCR & ETH_PTPTSCR_TSSTI_Msk) { |
| /* spin lock */ |
| } |
| #endif /* DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_ethernet) */ |
| |
| irq_unlock(key); |
| |
| return 0; |
| } |
| |
| static int ptp_clock_stm32_get(const struct device *dev, |
| struct net_ptp_time *tm) |
| { |
| struct ptp_context *ptp_context = dev->data; |
| struct eth_stm32_hal_dev_data *eth_dev_data = ptp_context->eth_dev_data; |
| ETH_HandleTypeDef *heth = ð_dev_data->heth; |
| unsigned int key; |
| uint32_t second_2; |
| |
| key = irq_lock(); |
| |
| #if DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_ethernet) |
| tm->second = heth->Instance->MACSTSR; |
| tm->nanosecond = heth->Instance->MACSTNR; |
| second_2 = heth->Instance->MACSTSR; |
| #else |
| tm->second = heth->Instance->PTPTSHR; |
| tm->nanosecond = heth->Instance->PTPTSLR; |
| second_2 = heth->Instance->PTPTSHR; |
| #endif /* DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_ethernet) */ |
| |
| irq_unlock(key); |
| |
| if (tm->second != second_2 && tm->nanosecond < NSEC_PER_SEC / 2) { |
| /* Second rollover has happened during first measurement: second register |
| * was read before second boundary and nanosecond register was read after. |
| * We will use second_2 as a new second value. |
| */ |
| tm->second = second_2; |
| } |
| |
| return 0; |
| } |
| |
| static int ptp_clock_stm32_adjust(const struct device *dev, int increment) |
| { |
| struct ptp_context *ptp_context = dev->data; |
| struct eth_stm32_hal_dev_data *eth_dev_data = ptp_context->eth_dev_data; |
| ETH_HandleTypeDef *heth = ð_dev_data->heth; |
| int key, ret; |
| |
| if ((increment <= (int32_t)(-NSEC_PER_SEC)) || |
| (increment >= (int32_t)NSEC_PER_SEC)) { |
| ret = -EINVAL; |
| } else { |
| key = irq_lock(); |
| |
| #if DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_ethernet) |
| heth->Instance->MACSTSUR = 0; |
| if (increment >= 0) { |
| heth->Instance->MACSTNUR = increment; |
| } else { |
| heth->Instance->MACSTNUR = ETH_MACSTNUR_ADDSUB | (NSEC_PER_SEC + increment); |
| } |
| heth->Instance->MACTSCR |= ETH_MACTSCR_TSUPDT; |
| while (heth->Instance->MACTSCR & ETH_MACTSCR_TSUPDT_Msk) { |
| /* spin lock */ |
| } |
| #else |
| heth->Instance->PTPTSHUR = 0; |
| if (increment >= 0) { |
| heth->Instance->PTPTSLUR = increment; |
| } else { |
| heth->Instance->PTPTSLUR = ETH_PTPTSLUR_TSUPNS | (-increment); |
| } |
| heth->Instance->PTPTSCR |= ETH_PTPTSCR_TSSTU; |
| while (heth->Instance->PTPTSCR & ETH_PTPTSCR_TSSTU_Msk) { |
| /* spin lock */ |
| } |
| #endif /* DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_ethernet) */ |
| |
| ret = 0; |
| irq_unlock(key); |
| } |
| |
| return ret; |
| } |
| |
| static int ptp_clock_stm32_rate_adjust(const struct device *dev, double ratio) |
| { |
| struct ptp_context *ptp_context = dev->data; |
| struct eth_stm32_hal_dev_data *eth_dev_data = ptp_context->eth_dev_data; |
| ETH_HandleTypeDef *heth = ð_dev_data->heth; |
| int key, ret; |
| uint32_t addend_val; |
| |
| key = irq_lock(); |
| |
| /* Limit possible ratio */ |
| if (ratio * 100 < CONFIG_ETH_STM32_HAL_PTP_CLOCK_ADJ_MIN_PCT || |
| ratio * 100 > CONFIG_ETH_STM32_HAL_PTP_CLOCK_ADJ_MAX_PCT) { |
| ret = -EINVAL; |
| goto error; |
| } |
| |
| /* Update addend register */ |
| addend_val = UINT32_MAX * (double)eth_dev_data->clk_ratio * ratio; |
| |
| #if DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_ethernet) |
| heth->Instance->MACTSAR = addend_val; |
| heth->Instance->MACTSCR |= ETH_MACTSCR_TSADDREG; |
| while (heth->Instance->MACTSCR & ETH_MACTSCR_TSADDREG_Msk) { |
| /* spin lock */ |
| } |
| #else |
| heth->Instance->PTPTSAR = addend_val; |
| heth->Instance->PTPTSCR |= ETH_PTPTSCR_TSARU; |
| while (heth->Instance->PTPTSCR & ETH_PTPTSCR_TSARU_Msk) { |
| /* spin lock */ |
| } |
| #endif /* DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_ethernet) */ |
| |
| ret = 0; |
| |
| error: |
| irq_unlock(key); |
| |
| return ret; |
| } |
| |
| static DEVICE_API(ptp_clock, api) = { |
| .set = ptp_clock_stm32_set, |
| .get = ptp_clock_stm32_get, |
| .adjust = ptp_clock_stm32_adjust, |
| .rate_adjust = ptp_clock_stm32_rate_adjust, |
| }; |
| |
| static int ptp_stm32_init(const struct device *port) |
| { |
| const struct device *const dev = DEVICE_DT_GET(DT_NODELABEL(mac)); |
| struct eth_stm32_hal_dev_data *eth_dev_data = dev->data; |
| const struct eth_stm32_hal_dev_cfg *eth_cfg = dev->config; |
| struct ptp_context *ptp_context = port->data; |
| ETH_HandleTypeDef *heth = ð_dev_data->heth; |
| int ret; |
| uint32_t ptp_hclk_rate; |
| uint32_t ss_incr_ns; |
| uint32_t addend_val; |
| |
| eth_dev_data->ptp_clock = port; |
| ptp_context->eth_dev_data = eth_dev_data; |
| |
| /* Mask the Timestamp Trigger interrupt */ |
| #if DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_ethernet) |
| heth->Instance->MACIER &= ~(ETH_MACIER_TSIE); |
| #else |
| heth->Instance->MACIMR &= ~(ETH_MACIMR_TSTIM); |
| #endif /* DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_ethernet) */ |
| |
| /* Enable timestamping */ |
| #if DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_ethernet) |
| heth->Instance->MACTSCR |= ETH_MACTSCR_TSENA; |
| #else |
| heth->Instance->PTPTSCR |= ETH_PTPTSCR_TSE; |
| #endif /* DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_ethernet) */ |
| |
| /* Query ethernet clock rate */ |
| ret = clock_control_get_rate(DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE), |
| #if DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_ethernet) |
| (clock_control_subsys_t)ð_cfg->pclken, |
| #else |
| (clock_control_subsys_t)ð_cfg->pclken_ptp, |
| #endif /* DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_ethernet) */ |
| &ptp_hclk_rate); |
| if (ret) { |
| LOG_ERR("Failed to query ethernet clock"); |
| return -EIO; |
| } |
| |
| /* Program the subsecond increment register based on the PTP clock freq */ |
| if (NSEC_PER_SEC % CONFIG_ETH_STM32_HAL_PTP_CLOCK_SRC_HZ != 0) { |
| LOG_ERR("PTP clock period must be an integer nanosecond value"); |
| return -EINVAL; |
| } |
| ss_incr_ns = NSEC_PER_SEC / CONFIG_ETH_STM32_HAL_PTP_CLOCK_SRC_HZ; |
| if (ss_incr_ns > UINT8_MAX) { |
| LOG_ERR("PTP clock period is more than %d nanoseconds", UINT8_MAX); |
| return -EINVAL; |
| } |
| #if DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_ethernet) |
| heth->Instance->MACSSIR = ss_incr_ns << ETH_MACMACSSIR_SSINC_Pos; |
| #else |
| heth->Instance->PTPSSIR = ss_incr_ns; |
| #endif /* DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_ethernet) */ |
| |
| /* Program timestamp addend register */ |
| eth_dev_data->clk_ratio = |
| ((double)CONFIG_ETH_STM32_HAL_PTP_CLOCK_SRC_HZ) / ((double)ptp_hclk_rate); |
| /* |
| * clk_ratio is a ratio between desired PTP clock frequency and HCLK rate. |
| * Because HCLK is defined by a physical oscillator, it might drift due |
| * to manufacturing tolerances and environmental effects (e.g. temperature). |
| * It gets adjusted by calling ptp_clock_stm32_rate_adjust(). |
| */ |
| addend_val = |
| UINT32_MAX * eth_dev_data->clk_ratio; |
| #if DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_ethernet) |
| heth->Instance->MACTSAR = addend_val; |
| heth->Instance->MACTSCR |= ETH_MACTSCR_TSADDREG; |
| while (heth->Instance->MACTSCR & ETH_MACTSCR_TSADDREG_Msk) { |
| k_yield(); |
| } |
| #else |
| heth->Instance->PTPTSAR = addend_val; |
| heth->Instance->PTPTSCR |= ETH_PTPTSCR_TSARU; |
| while (heth->Instance->PTPTSCR & ETH_PTPTSCR_TSARU_Msk) { |
| k_yield(); |
| } |
| #endif /* DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_ethernet) */ |
| |
| /* Enable fine timestamp correction method */ |
| #if DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_ethernet) |
| heth->Instance->MACTSCR |= ETH_MACTSCR_TSCFUPDT; |
| #else |
| heth->Instance->PTPTSCR |= ETH_PTPTSCR_TSFCU; |
| #endif /* DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_ethernet) */ |
| |
| /* Enable nanosecond rollover into a new second */ |
| #if DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_ethernet) |
| heth->Instance->MACTSCR |= ETH_MACTSCR_TSCTRLSSR; |
| #else |
| heth->Instance->PTPTSCR |= ETH_PTPTSCR_TSSSR; |
| #endif /* DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_ethernet) */ |
| |
| /* Initialize timestamp */ |
| #if DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_ethernet) |
| heth->Instance->MACSTSUR = 0; |
| heth->Instance->MACSTNUR = 0; |
| heth->Instance->MACTSCR |= ETH_MACTSCR_TSINIT; |
| while (heth->Instance->MACTSCR & ETH_MACTSCR_TSINIT_Msk) { |
| k_yield(); |
| } |
| #else |
| heth->Instance->PTPTSHUR = 0; |
| heth->Instance->PTPTSLUR = 0; |
| heth->Instance->PTPTSCR |= ETH_PTPTSCR_TSSTI; |
| while (heth->Instance->PTPTSCR & ETH_PTPTSCR_TSSTI_Msk) { |
| k_yield(); |
| } |
| #endif /* DT_HAS_COMPAT_STATUS_OKAY(st_stm32h7_ethernet) */ |
| |
| /* Set PTP Configuration done */ |
| heth->IsPtpConfigured = ETH_STM32_PTP_CONFIGURED; |
| |
| return 0; |
| } |
| |
| DEVICE_DEFINE(stm32_ptp_clock_0, PTP_CLOCK_NAME, ptp_stm32_init, |
| NULL, &ptp_stm32_0_context, NULL, POST_KERNEL, |
| CONFIG_ETH_STM32_HAL_PTP_CLOCK_INIT_PRIO, &api); |