[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <aJtXNrndlngzeSm8@lizhi-Precision-Tower-5810>
Date: Tue, 12 Aug 2025 11:01:10 -0400
From: Frank Li <Frank.li@....com>
To: Wei Fang <wei.fang@....com>
Cc: robh@...nel.org, krzk+dt@...nel.org, conor+dt@...nel.org,
richardcochran@...il.com, claudiu.manoil@....com,
vladimir.oltean@....com, xiaoning.wang@....com,
andrew+netdev@...n.ch, davem@...emloft.net, edumazet@...gle.com,
kuba@...nel.org, pabeni@...hat.com, vadim.fedorenko@...ux.dev,
shawnguo@...nel.org, s.hauer@...gutronix.de, festevam@...il.com,
fushi.peng@....com, devicetree@...r.kernel.org,
netdev@...r.kernel.org, linux-kernel@...r.kernel.org,
imx@...ts.linux.dev, kernel@...gutronix.de
Subject: Re: [PATCH v3 net-next 04/15] ptp: netc: add NETC V4 Timer PTP
driver support
On Tue, Aug 12, 2025 at 05:46:23PM +0800, Wei Fang wrote:
> NETC V4 Timer provides current time with nanosecond resolution, precise
> periodic pulse, pulse on timeout (alarm), and time capture on external
> pulse support. And it supports time synchronization as required for
> IEEE 1588 and IEEE 802.1AS-2020.
>
> Inside NETC, ENETC can capture the timestamp of the sent/received packet
> through the PHC provided by the Timer and record it on the Tx/Rx BD. And
> through the relevant PHC interfaces provided by the driver, the enetc V4
> driver can support PTP time synchronization.
>
> In addition, NETC V4 Timer is similar to the QorIQ 1588 timer, but it is
> not exactly the same. The current ptp-qoriq driver is not compatible with
> NETC V4 Timer, most of the code cannot be reused, see below reasons.
>
> 1. The architecture of ptp-qoriq driver makes the register offset fixed,
> however, the offsets of all the high registers and low registers of V4
> are swapped, and V4 also adds some new registers. so extending ptp-qoriq
> to make it compatible with V4 Timer is tantamount to completely rewriting
> ptp-qoriq driver.
>
> 2. The usage of some functions is somewhat different from QorIQ timer,
> such as the setting of TCLK_PERIOD and TMR_ADD, the logic of configuring
> PPS, etc., so making the driver compatible with V4 Timer will undoubtedly
> increase the complexity of the code and reduce readability.
>
> 3. QorIQ is an expired brand. It is difficult for us to verify whether
> it works stably on the QorIQ platforms if we refactor the driver, and
> this will make maintenance difficult, so refactoring the driver obviously
> does not bring any benefits.
>
> Therefore, add this new driver for NETC V4 Timer. Note that the missing
> features like PEROUT, PPS and EXTTS will be added in subsequent patches.
>
> Signed-off-by: Wei Fang <wei.fang@....com>
>
> ---
> v2 changes:
> 1. Rename netc_timer_get_source_clk() to
> netc_timer_get_reference_clk_source() and refactor it
> 2. Remove the scaled_ppm check in netc_timer_adjfine()
> 3. Add a comment in netc_timer_cur_time_read()
> 4. Add linux/bitfield.h to fix the build errors
> v3 changes:
> 1. Refactor netc_timer_adjtime() and remove netc_timer_cnt_read()
> 2. Remove the check of dma_set_mask_and_coherent()
> 3. Use devm_kzalloc() and pci_ioremap_bar()
> 4. Move alarm related logic including irq handler to the next patch
> 5. Improve the commit message
> 6. Refactor netc_timer_get_reference_clk_source() and remove
> clk_prepare_enable()
> 7. Use FIELD_PREP() helper
> 8. Rename PTP_1588_CLOCK_NETC to PTP_NETC_V4_TIMER and improve the
> help text.
> 9. Refine netc_timer_adjtime(), change tmr_off to s64 type as we
> confirmed TMR_OFF is a signed register.
> ---
> drivers/ptp/Kconfig | 11 +
> drivers/ptp/Makefile | 1 +
> drivers/ptp/ptp_netc.c | 438 ++++++++++++++++++++++++++++++++
> include/linux/fsl/netc_global.h | 12 +-
> 4 files changed, 461 insertions(+), 1 deletion(-)
> create mode 100644 drivers/ptp/ptp_netc.c
>
> diff --git a/drivers/ptp/Kconfig b/drivers/ptp/Kconfig
> index 204278eb215e..0ac31a20096c 100644
> --- a/drivers/ptp/Kconfig
> +++ b/drivers/ptp/Kconfig
> @@ -252,4 +252,15 @@ config PTP_S390
> driver provides the raw clock value without the delta to
> userspace. That way userspace programs like chrony could steer
> the kernel clock.
> +
> +config PTP_NETC_V4_TIMER
> + bool "NXP NETC V4 Timer PTP Driver"
> + depends on PTP_1588_CLOCK=y
> + depends on PCI_MSI
> + help
> + This driver adds support for using the NXP NETC V4 Timer as a PTP
> + clock, the clock is used by ENETC V4 or NETC V4 Switch for PTP time
> + synchronization. It also supports periodic output signal (e.g. PPS)
> + and external trigger timestamping.
> +
> endmenu
> diff --git a/drivers/ptp/Makefile b/drivers/ptp/Makefile
> index 25f846fe48c9..8985d723d29c 100644
> --- a/drivers/ptp/Makefile
> +++ b/drivers/ptp/Makefile
> @@ -23,3 +23,4 @@ obj-$(CONFIG_PTP_1588_CLOCK_VMW) += ptp_vmw.o
> obj-$(CONFIG_PTP_1588_CLOCK_OCP) += ptp_ocp.o
> obj-$(CONFIG_PTP_DFL_TOD) += ptp_dfl_tod.o
> obj-$(CONFIG_PTP_S390) += ptp_s390.o
> +obj-$(CONFIG_PTP_NETC_V4_TIMER) += ptp_netc.o
> diff --git a/drivers/ptp/ptp_netc.c b/drivers/ptp/ptp_netc.c
> new file mode 100644
> index 000000000000..cbe2a64d1ced
> --- /dev/null
> +++ b/drivers/ptp/ptp_netc.c
> @@ -0,0 +1,438 @@
> +// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
> +/*
> + * NXP NETC V4 Timer driver
> + * Copyright 2025 NXP
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/clk.h>
> +#include <linux/fsl/netc_global.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_platform.h>
> +#include <linux/ptp_clock_kernel.h>
> +
> +#define NETC_TMR_PCI_VENDOR 0x1131
> +#define NETC_TMR_PCI_DEVID 0xee02
Nit: Like this only use once constant, needn't macro, espcial DEVID.
> +
> +#define NETC_TMR_CTRL 0x0080
> +#define TMR_CTRL_CK_SEL GENMASK(1, 0)
> +#define TMR_CTRL_TE BIT(2)
> +#define TMR_COMP_MODE BIT(15)
> +#define TMR_CTRL_TCLK_PERIOD GENMASK(25, 16)
> +
> +#define NETC_TMR_CNT_L 0x0098
> +#define NETC_TMR_CNT_H 0x009c
> +#define NETC_TMR_ADD 0x00a0
> +#define NETC_TMR_PRSC 0x00a8
> +#define NETC_TMR_OFF_L 0x00b0
> +#define NETC_TMR_OFF_H 0x00b4
> +
> +#define NETC_TMR_FIPER_CTRL 0x00dc
> +#define FIPER_CTRL_DIS(i) (BIT(7) << (i) * 8)
> +#define FIPER_CTRL_PG(i) (BIT(6) << (i) * 8)
> +
> +#define NETC_TMR_CUR_TIME_L 0x00f0
> +#define NETC_TMR_CUR_TIME_H 0x00f4
> +
> +#define NETC_TMR_REGS_BAR 0
> +
> +#define NETC_TMR_FIPER_NUM 3
> +#define NETC_TMR_DEFAULT_PRSC 2
> +
> +/* 1588 timer reference clock source select */
> +#define NETC_TMR_CCM_TIMER1 0 /* enet_timer1_clk_root, from CCM */
> +#define NETC_TMR_SYSTEM_CLK 1 /* enet_clk_root/2, from CCM */
> +#define NETC_TMR_EXT_OSC 2 /* tmr_1588_clk, from IO pins */
> +
> +#define NETC_TMR_SYSCLK_333M 333333333U
> +
> +struct netc_timer {
> + void __iomem *base;
> + struct pci_dev *pdev;
> + spinlock_t lock; /* Prevent concurrent access to registers */
> +
> + struct ptp_clock *clock;
> + struct ptp_clock_info caps;
> + int phc_index;
> + u32 clk_select;
> + u32 clk_freq;
> + u32 oclk_prsc;
> + /* High 32-bit is integer part, low 32-bit is fractional part */
> + u64 period;
> +};
> +
> +#define netc_timer_rd(p, o) netc_read((p)->base + (o))
> +#define netc_timer_wr(p, o, v) netc_write((p)->base + (o), v)
> +#define ptp_to_netc_timer(ptp) container_of((ptp), struct netc_timer, caps)
> +
> +static const char *const timer_clk_src[] = {
> + "ccm_timer",
> + "ext_1588"
> +};
> +
> +static void netc_timer_cnt_write(struct netc_timer *priv, u64 ns)
> +{
> + u32 tmr_cnt_h = upper_32_bits(ns);
> + u32 tmr_cnt_l = lower_32_bits(ns);
> +
> + /* Writes to the TMR_CNT_L register copies the written value
> + * into the shadow TMR_CNT_L register. Writes to the TMR_CNT_H
> + * register copies the values written into the shadow TMR_CNT_H
> + * register. Contents of the shadow registers are copied into
> + * the TMR_CNT_L and TMR_CNT_H registers following a write into
> + * the TMR_CNT_H register. So the user must writes to TMR_CNT_L
> + * register first.
> + */
Is all other register the same? like OFF_L, OFF_H?
And read have similar behavor?
> + netc_timer_wr(priv, NETC_TMR_CNT_L, tmr_cnt_l);
> + netc_timer_wr(priv, NETC_TMR_CNT_H, tmr_cnt_h);
> +}
> +
> +static u64 netc_timer_offset_read(struct netc_timer *priv)
> +{
> + u32 tmr_off_l, tmr_off_h;
> + u64 offset;
> +
> + tmr_off_l = netc_timer_rd(priv, NETC_TMR_OFF_L);
> + tmr_off_h = netc_timer_rd(priv, NETC_TMR_OFF_H);
> + offset = (((u64)tmr_off_h) << 32) | tmr_off_l;
> +
> + return offset;
> +}
> +
> +static void netc_timer_offset_write(struct netc_timer *priv, u64 offset)
> +{
> + u32 tmr_off_h = upper_32_bits(offset);
> + u32 tmr_off_l = lower_32_bits(offset);
> +
> + netc_timer_wr(priv, NETC_TMR_OFF_L, tmr_off_l);
> + netc_timer_wr(priv, NETC_TMR_OFF_H, tmr_off_h);
> +}
> +
> +static u64 netc_timer_cur_time_read(struct netc_timer *priv)
> +{
> + u32 time_h, time_l;
> + u64 ns;
> +
> + /* The user should read NETC_TMR_CUR_TIME_L first to
> + * get correct current time.
> + */
> + time_l = netc_timer_rd(priv, NETC_TMR_CUR_TIME_L);
> + time_h = netc_timer_rd(priv, NETC_TMR_CUR_TIME_H);
> + ns = (u64)time_h << 32 | time_l;
> +
> + return ns;
> +}
> +
> +static void netc_timer_adjust_period(struct netc_timer *priv, u64 period)
> +{
> + u32 fractional_period = lower_32_bits(period);
> + u32 integral_period = upper_32_bits(period);
> + u32 tmr_ctrl, old_tmr_ctrl;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&priv->lock, flags);
> +
> + old_tmr_ctrl = netc_timer_rd(priv, NETC_TMR_CTRL);
> + tmr_ctrl = u32_replace_bits(old_tmr_ctrl, integral_period,
> + TMR_CTRL_TCLK_PERIOD);
> + if (tmr_ctrl != old_tmr_ctrl)
> + netc_timer_wr(priv, NETC_TMR_CTRL, tmr_ctrl);
> +
> + netc_timer_wr(priv, NETC_TMR_ADD, fractional_period);
> +
> + spin_unlock_irqrestore(&priv->lock, flags);
> +}
> +
> +static int netc_timer_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
> +{
> + struct netc_timer *priv = ptp_to_netc_timer(ptp);
> + u64 new_period;
> +
> + new_period = adjust_by_scaled_ppm(priv->period, scaled_ppm);
> + netc_timer_adjust_period(priv, new_period);
> +
> + return 0;
> +}
> +
> +static int netc_timer_adjtime(struct ptp_clock_info *ptp, s64 delta)
> +{
> + struct netc_timer *priv = ptp_to_netc_timer(ptp);
> + unsigned long flags;
> + s64 tmr_off;
> +
> + spin_lock_irqsave(&priv->lock, flags);
> +
> + /* Adjusting TMROFF instead of TMR_CNT is that the timer
> + * counter keeps increasing during reading and writing
> + * TMR_CNT, which will cause latency.
> + */
> + tmr_off = netc_timer_offset_read(priv);
> + tmr_off += delta;
> + netc_timer_offset_write(priv, tmr_off);
> +
> + spin_unlock_irqrestore(&priv->lock, flags);
> +
> + return 0;
> +}
> +
> +static int netc_timer_gettimex64(struct ptp_clock_info *ptp,
> + struct timespec64 *ts,
> + struct ptp_system_timestamp *sts)
> +{
> + struct netc_timer *priv = ptp_to_netc_timer(ptp);
> + unsigned long flags;
> + u64 ns;
> +
> + spin_lock_irqsave(&priv->lock, flags);
> +
> + ptp_read_system_prets(sts);
> + ns = netc_timer_cur_time_read(priv);
> + ptp_read_system_postts(sts);
> +
> + spin_unlock_irqrestore(&priv->lock, flags);
> +
> + *ts = ns_to_timespec64(ns);
> +
> + return 0;
> +}
> +
> +static int netc_timer_settime64(struct ptp_clock_info *ptp,
> + const struct timespec64 *ts)
> +{
> + struct netc_timer *priv = ptp_to_netc_timer(ptp);
> + u64 ns = timespec64_to_ns(ts);
> + unsigned long flags;
> +
> + spin_lock_irqsave(&priv->lock, flags);
> + netc_timer_offset_write(priv, 0);
> + netc_timer_cnt_write(priv, ns);
> + spin_unlock_irqrestore(&priv->lock, flags);
> +
> + return 0;
> +}
> +
> +int netc_timer_get_phc_index(struct pci_dev *timer_pdev)
> +{
> + struct netc_timer *priv;
> +
> + if (!timer_pdev)
> + return -ENODEV;
> +
> + priv = pci_get_drvdata(timer_pdev);
> + if (!priv)
> + return -EINVAL;
> +
> + return priv->phc_index;
> +}
> +EXPORT_SYMBOL_GPL(netc_timer_get_phc_index);
> +
> +static const struct ptp_clock_info netc_timer_ptp_caps = {
> + .owner = THIS_MODULE,
> + .name = "NETC Timer PTP clock",
> + .max_adj = 500000000,
> + .n_pins = 0,
> + .adjfine = netc_timer_adjfine,
> + .adjtime = netc_timer_adjtime,
> + .gettimex64 = netc_timer_gettimex64,
> + .settime64 = netc_timer_settime64,
> +};
> +
> +static void netc_timer_init(struct netc_timer *priv)
> +{
> + u32 fractional_period = lower_32_bits(priv->period);
> + u32 integral_period = upper_32_bits(priv->period);
> + u32 tmr_ctrl, fiper_ctrl;
> + struct timespec64 now;
> + u64 ns;
> + int i;
> +
> + /* Software must enable timer first and the clock selected must be
> + * active, otherwise, the registers which are in the timer clock
> + * domain are not accessible.
> + */
> + tmr_ctrl = FIELD_PREP(TMR_CTRL_CK_SEL, priv->clk_select) |
> + TMR_CTRL_TE;
> + netc_timer_wr(priv, NETC_TMR_CTRL, tmr_ctrl);
> + netc_timer_wr(priv, NETC_TMR_PRSC, priv->oclk_prsc);
> +
> + /* Disable FIPER by default */
> + fiper_ctrl = netc_timer_rd(priv, NETC_TMR_FIPER_CTRL);
> + for (i = 0; i < NETC_TMR_FIPER_NUM; i++) {
> + fiper_ctrl |= FIPER_CTRL_DIS(i);
> + fiper_ctrl &= ~FIPER_CTRL_PG(i);
> + }
> + netc_timer_wr(priv, NETC_TMR_FIPER_CTRL, fiper_ctrl);
> +
> + ktime_get_real_ts64(&now);
> + ns = timespec64_to_ns(&now);
> + netc_timer_cnt_write(priv, ns);
> +
> + /* Allow atomic writes to TCLK_PERIOD and TMR_ADD, An update to
> + * TCLK_PERIOD does not take effect until TMR_ADD is written.
> + */
> + tmr_ctrl |= FIELD_PREP(TMR_CTRL_TCLK_PERIOD, integral_period) |
> + TMR_COMP_MODE;
> + netc_timer_wr(priv, NETC_TMR_CTRL, tmr_ctrl);
> + netc_timer_wr(priv, NETC_TMR_ADD, fractional_period);
> +}
> +
> +static int netc_timer_pci_probe(struct pci_dev *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct netc_timer *priv;
> + int err;
> +
> + pcie_flr(pdev);
> + err = pci_enable_device_mem(pdev);
> + if (err)
> + return dev_err_probe(dev, err, "Failed to enable device\n");
> +
> + dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
> + err = pci_request_mem_regions(pdev, KBUILD_MODNAME);
> + if (err) {
> + dev_err(dev, "pci_request_regions() failed, err:%pe\n",
> + ERR_PTR(err));
> + goto disable_dev;
> + }
> +
> + pci_set_master(pdev);
> + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv) {
> + err = -ENOMEM;
> + goto release_mem_regions;
> + }
move devm_kzalloc() before pci_enable_device_mem() to reduce a goto
> +
> + priv->pdev = pdev;
> + priv->base = pci_ioremap_bar(pdev, NETC_TMR_REGS_BAR);
> + if (!priv->base) {
> + err = -ENOMEM;
> + goto release_mem_regions;
> + }
> +
> + pci_set_drvdata(pdev, priv);
> +
> + return 0;
> +
> +release_mem_regions:
> + pci_release_mem_regions(pdev);
> +disable_dev:
> + pci_disable_device(pdev);
> +
> + return err;
> +}
> +
> +static void netc_timer_pci_remove(struct pci_dev *pdev)
> +{
> + struct netc_timer *priv = pci_get_drvdata(pdev);
> +
> + iounmap(priv->base);
> + pci_release_mem_regions(pdev);
> + pci_disable_device(pdev);
> +}
> +
> +static int netc_timer_get_reference_clk_source(struct netc_timer *priv)
> +{
> + struct device *dev = &priv->pdev->dev;
> + struct clk *clk;
> + int i;
> +
> + /* Select NETC system clock as the reference clock by default */
> + priv->clk_select = NETC_TMR_SYSTEM_CLK;
> + priv->clk_freq = NETC_TMR_SYSCLK_333M;
> +
> + /* Update the clock source of the reference clock if the clock
> + * is specified in DT node.
> + */
> + for (i = 0; i < ARRAY_SIZE(timer_clk_src); i++) {
> + clk = devm_clk_get_optional_enabled(dev, timer_clk_src[i]);
> + if (IS_ERR(clk))
> + return PTR_ERR(clk);
> +
> + if (clk) {
> + priv->clk_freq = clk_get_rate(clk);
> + priv->clk_select = i ? NETC_TMR_EXT_OSC :
> + NETC_TMR_CCM_TIMER1;
> + break;
> + }
> + }
> +
> + /* The period is a 64-bit number, the high 32-bit is the integer
> + * part of the period, the low 32-bit is the fractional part of
> + * the period. In order to get the desired 32-bit fixed-point
> + * format, multiply the numerator of the fraction by 2^32.
> + */
> + priv->period = div_u64(NSEC_PER_SEC << 32, priv->clk_freq);
> +
> + return 0;
> +}
> +
> +static int netc_timer_parse_dt(struct netc_timer *priv)
> +{
> + return netc_timer_get_reference_clk_source(priv);
> +}
> +
> +static int netc_timer_probe(struct pci_dev *pdev,
> + const struct pci_device_id *id)
> +{
> + struct device *dev = &pdev->dev;
> + struct netc_timer *priv;
> + int err;
> +
> + err = netc_timer_pci_probe(pdev);
> + if (err)
> + return err;
> +
> + priv = pci_get_drvdata(pdev);
> + err = netc_timer_parse_dt(priv);
> + if (err) {
> + if (err != -EPROBE_DEFER)
> + dev_err(dev, "Failed to parse DT node\n");
> + goto timer_pci_remove;
> + }
move netc_timer_parse_dt() before netc_timer_pci_probe()
you can use return dev_err_probe(), which already handle -EPROBE_DEFER
case.
> +
> + priv->caps = netc_timer_ptp_caps;
> + priv->oclk_prsc = NETC_TMR_DEFAULT_PRSC;
> + spin_lock_init(&priv->lock);
> +
> + netc_timer_init(priv);
> + priv->clock = ptp_clock_register(&priv->caps, dev);
> + if (IS_ERR(priv->clock)) {
> + err = PTR_ERR(priv->clock);
> + goto timer_pci_remove;
> + }
> +
> + priv->phc_index = ptp_clock_index(priv->clock);
> +
> + return 0;
> +
> +timer_pci_remove:
> + netc_timer_pci_remove(pdev);
> +
> + return err;
> +}
> +
> +static void netc_timer_remove(struct pci_dev *pdev)
> +{
use devm_add_action_or_reset() can simpify your error handle.
Frank
> + struct netc_timer *priv = pci_get_drvdata(pdev);
> +
> + ptp_clock_unregister(priv->clock);
> + netc_timer_pci_remove(pdev);
> +}
> +
> +static const struct pci_device_id netc_timer_id_table[] = {
> + { PCI_DEVICE(NETC_TMR_PCI_VENDOR, NETC_TMR_PCI_DEVID) },
> + { }
> +};
> +MODULE_DEVICE_TABLE(pci, netc_timer_id_table);
> +
> +static struct pci_driver netc_timer_driver = {
> + .name = KBUILD_MODNAME,
> + .id_table = netc_timer_id_table,
> + .probe = netc_timer_probe,
> + .remove = netc_timer_remove,
> +};
> +module_pci_driver(netc_timer_driver);
> +
> +MODULE_DESCRIPTION("NXP NETC Timer PTP Driver");
> +MODULE_LICENSE("Dual BSD/GPL");
> diff --git a/include/linux/fsl/netc_global.h b/include/linux/fsl/netc_global.h
> index fdecca8c90f0..17c19c8d3f93 100644
> --- a/include/linux/fsl/netc_global.h
> +++ b/include/linux/fsl/netc_global.h
> @@ -1,10 +1,11 @@
> /* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */
> -/* Copyright 2024 NXP
> +/* Copyright 2024-2025 NXP
> */
> #ifndef __NETC_GLOBAL_H
> #define __NETC_GLOBAL_H
>
> #include <linux/io.h>
> +#include <linux/pci.h>
>
> static inline u32 netc_read(void __iomem *reg)
> {
> @@ -16,4 +17,13 @@ static inline void netc_write(void __iomem *reg, u32 val)
> iowrite32(val, reg);
> }
>
> +#if IS_ENABLED(CONFIG_PTP_NETC_V4_TIMER)
> +int netc_timer_get_phc_index(struct pci_dev *timer_pdev);
> +#else
> +static inline int netc_timer_get_phc_index(struct pci_dev *timer_pdev)
> +{
> + return -ENODEV;
> +}
> +#endif
> +
> #endif
> --
> 2.34.1
>
Powered by blists - more mailing lists