blob: 9d6682217650d74b033ec1cf7854ca89e2481b7e [file] [log] [blame]
/*
* 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 = &eth_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 = &eth_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 = &eth_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 = &eth_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 = &eth_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)&eth_cfg->pclken,
#else
(clock_control_subsys_t)&eth_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);