summaryrefslogtreecommitdiff
path: root/drivers/ptp
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/ptp')
-rw-r--r--drivers/ptp/Kconfig13
-rw-r--r--drivers/ptp/Makefile5
-rw-r--r--drivers/ptp/ptp_chardev.c62
-rw-r--r--drivers/ptp/ptp_clock.c150
-rw-r--r--drivers/ptp/ptp_clockmatrix.c2
-rw-r--r--drivers/ptp/ptp_netc.c1043
-rw-r--r--drivers/ptp/ptp_ocp.c9
-rw-r--r--drivers/ptp/ptp_private.h8
-rw-r--r--drivers/ptp/ptp_qoriq.c24
-rw-r--r--drivers/ptp/ptp_qoriq_debugfs.c101
-rw-r--r--drivers/ptp/ptp_sysfs.c2
-rw-r--r--drivers/ptp/ptp_vclock.c7
12 files changed, 1300 insertions, 126 deletions
diff --git a/drivers/ptp/Kconfig b/drivers/ptp/Kconfig
index 204278eb215e..5f8ea34d11d6 100644
--- a/drivers/ptp/Kconfig
+++ b/drivers/ptp/Kconfig
@@ -67,7 +67,7 @@ config PTP_1588_CLOCK_QORIQ
packets using the SO_TIMESTAMPING API.
To compile this driver as a module, choose M here: the module
- will be called ptp-qoriq.
+ will be called ptp_qoriq.
comment "Enable PHYLIB and NETWORK_PHY_TIMESTAMPING to see the additional clocks."
depends on PHYLIB=n || NETWORK_PHY_TIMESTAMPING=n
@@ -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
+ tristate "NXP NETC V4 Timer PTP Driver"
+ depends on PTP_1588_CLOCK
+ 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..bdc47e284f14 100644
--- a/drivers/ptp/Makefile
+++ b/drivers/ptp/Makefile
@@ -12,9 +12,7 @@ obj-$(CONFIG_PTP_1588_CLOCK_INES) += ptp_ines.o
obj-$(CONFIG_PTP_1588_CLOCK_PCH) += ptp_pch.o
obj-$(CONFIG_PTP_1588_CLOCK_KVM) += ptp_kvm.o
obj-$(CONFIG_PTP_1588_CLOCK_VMCLOCK) += ptp_vmclock.o
-obj-$(CONFIG_PTP_1588_CLOCK_QORIQ) += ptp-qoriq.o
-ptp-qoriq-y += ptp_qoriq.o
-ptp-qoriq-$(CONFIG_DEBUG_FS) += ptp_qoriq_debugfs.o
+obj-$(CONFIG_PTP_1588_CLOCK_QORIQ) += ptp_qoriq.o
obj-$(CONFIG_PTP_1588_CLOCK_IDTCM) += ptp_clockmatrix.o
obj-$(CONFIG_PTP_1588_CLOCK_FC3W) += ptp_fc3.o
obj-$(CONFIG_PTP_1588_CLOCK_IDT82P33) += ptp_idt82p33.o
@@ -23,3 +21,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_chardev.c b/drivers/ptp/ptp_chardev.c
index 4ca5a464a46a..8106eb617c8c 100644
--- a/drivers/ptp/ptp_chardev.c
+++ b/drivers/ptp/ptp_chardev.c
@@ -47,6 +47,26 @@ static int ptp_disable_pinfunc(struct ptp_clock_info *ops,
return err;
}
+void ptp_disable_all_events(struct ptp_clock *ptp)
+{
+ struct ptp_clock_info *info = ptp->info;
+ unsigned int i;
+
+ mutex_lock(&ptp->pincfg_mux);
+ /* Disable any pins that may raise EXTTS events */
+ for (i = 0; i < info->n_pins; i++)
+ if (info->pin_config[i].func == PTP_PF_EXTTS)
+ ptp_disable_pinfunc(info, info->pin_config[i].func,
+ info->pin_config[i].chan);
+
+ /* Disable the PPS event if the driver has PPS support */
+ if (info->pps) {
+ struct ptp_clock_request req = { .type = PTP_CLK_REQ_PPS };
+ info->enable(info, &req, 0);
+ }
+ mutex_unlock(&ptp->pincfg_mux);
+}
+
int ptp_set_pinfunc(struct ptp_clock *ptp, unsigned int pin,
enum ptp_pin_function func, unsigned int chan)
{
@@ -91,12 +111,18 @@ int ptp_set_pinfunc(struct ptp_clock *ptp, unsigned int pin,
return -EOPNOTSUPP;
}
- /* Disable whatever function was previously assigned. */
+ /* Disable whichever pin was previously assigned to this function and
+ * channel.
+ */
if (pin1) {
ptp_disable_pinfunc(info, func, chan);
pin1->func = PTP_PF_NONE;
pin1->chan = 0;
}
+
+ /* Disable whatever function was previously assigned to the requested
+ * pin.
+ */
ptp_disable_pinfunc(info, pin2->func, pin2->chan);
pin2->func = func;
pin2->chan = chan;
@@ -285,17 +311,21 @@ static long ptp_enable_pps(struct ptp_clock *ptp, bool enable)
return ops->enable(ops, &req, enable);
}
-static long ptp_sys_offset_precise(struct ptp_clock *ptp, void __user *arg)
+typedef int (*ptp_crosststamp_fn)(struct ptp_clock_info *,
+ struct system_device_crosststamp *);
+
+static long ptp_sys_offset_precise(struct ptp_clock *ptp, void __user *arg,
+ ptp_crosststamp_fn crosststamp_fn)
{
struct ptp_sys_offset_precise precise_offset;
struct system_device_crosststamp xtstamp;
struct timespec64 ts;
int err;
- if (!ptp->info->getcrosststamp)
+ if (!crosststamp_fn)
return -EOPNOTSUPP;
- err = ptp->info->getcrosststamp(ptp->info, &xtstamp);
+ err = crosststamp_fn(ptp->info, &xtstamp);
if (err)
return err;
@@ -313,12 +343,17 @@ static long ptp_sys_offset_precise(struct ptp_clock *ptp, void __user *arg)
return copy_to_user(arg, &precise_offset, sizeof(precise_offset)) ? -EFAULT : 0;
}
-static long ptp_sys_offset_extended(struct ptp_clock *ptp, void __user *arg)
+typedef int (*ptp_gettimex_fn)(struct ptp_clock_info *,
+ struct timespec64 *,
+ struct ptp_system_timestamp *);
+
+static long ptp_sys_offset_extended(struct ptp_clock *ptp, void __user *arg,
+ ptp_gettimex_fn gettimex_fn)
{
struct ptp_sys_offset_extended *extoff __free(kfree) = NULL;
struct ptp_system_timestamp sts;
- if (!ptp->info->gettimex64)
+ if (!gettimex_fn)
return -EOPNOTSUPP;
extoff = memdup_user(arg, sizeof(*extoff));
@@ -346,7 +381,7 @@ static long ptp_sys_offset_extended(struct ptp_clock *ptp, void __user *arg)
struct timespec64 ts;
int err;
- err = ptp->info->gettimex64(ptp->info, &ts, &sts);
+ err = gettimex_fn(ptp->info, &ts, &sts);
if (err)
return err;
@@ -497,11 +532,13 @@ long ptp_ioctl(struct posix_clock_context *pccontext, unsigned int cmd,
case PTP_SYS_OFFSET_PRECISE:
case PTP_SYS_OFFSET_PRECISE2:
- return ptp_sys_offset_precise(ptp, argptr);
+ return ptp_sys_offset_precise(ptp, argptr,
+ ptp->info->getcrosststamp);
case PTP_SYS_OFFSET_EXTENDED:
case PTP_SYS_OFFSET_EXTENDED2:
- return ptp_sys_offset_extended(ptp, argptr);
+ return ptp_sys_offset_extended(ptp, argptr,
+ ptp->info->gettimex64);
case PTP_SYS_OFFSET:
case PTP_SYS_OFFSET2:
@@ -523,6 +560,13 @@ long ptp_ioctl(struct posix_clock_context *pccontext, unsigned int cmd,
case PTP_MASK_EN_SINGLE:
return ptp_mask_en_single(pccontext->private_clkdata, argptr);
+ case PTP_SYS_OFFSET_PRECISE_CYCLES:
+ return ptp_sys_offset_precise(ptp, argptr,
+ ptp->info->getcrosscycles);
+
+ case PTP_SYS_OFFSET_EXTENDED_CYCLES:
+ return ptp_sys_offset_extended(ptp, argptr,
+ ptp->info->getcyclesx64);
default:
return -ENOTTY;
}
diff --git a/drivers/ptp/ptp_clock.c b/drivers/ptp/ptp_clock.c
index 1cc06b7cb17e..ef020599b771 100644
--- a/drivers/ptp/ptp_clock.c
+++ b/drivers/ptp/ptp_clock.c
@@ -11,6 +11,7 @@
#include <linux/module.h>
#include <linux/posix-clock.h>
#include <linux/pps_kernel.h>
+#include <linux/property.h>
#include <linux/slab.h>
#include <linux/syscalls.h>
#include <linux/uaccess.h>
@@ -100,6 +101,9 @@ static int ptp_clock_settime(struct posix_clock *pc, const struct timespec64 *tp
return -EBUSY;
}
+ if (!timespec64_valid_settod(tp))
+ return -EINVAL;
+
return ptp->info->settime64(ptp->info, tp);
}
@@ -130,7 +134,7 @@ static int ptp_clock_adjtime(struct posix_clock *pc, struct __kernel_timex *tx)
ops = ptp->info;
if (tx->modes & ADJ_SETOFFSET) {
- struct timespec64 ts;
+ struct timespec64 ts, ts2;
ktime_t kt;
s64 delta;
@@ -143,6 +147,14 @@ static int ptp_clock_adjtime(struct posix_clock *pc, struct __kernel_timex *tx)
if ((unsigned long) ts.tv_nsec >= NSEC_PER_SEC)
return -EINVAL;
+ /* Make sure the offset is valid */
+ err = ptp_clock_gettime(pc, &ts2);
+ if (err)
+ return err;
+ ts2 = timespec64_add(ts2, ts);
+ if (!timespec64_valid_settod(&ts2))
+ return -EINVAL;
+
kt = timespec64_to_ktime(ts);
delta = ktime_to_ns(kt);
err = ops->adjtime(ops, delta);
@@ -236,6 +248,69 @@ static void ptp_aux_kworker(struct kthread_work *work)
kthread_queue_delayed_work(ptp->kworker, &ptp->aux_work, delay);
}
+static ssize_t ptp_n_perout_loopback_read(struct file *filep,
+ char __user *buffer,
+ size_t count, loff_t *pos)
+{
+ struct ptp_clock *ptp = filep->private_data;
+ char buf[12] = {};
+
+ snprintf(buf, sizeof(buf), "%d\n", ptp->info->n_per_lp);
+
+ return simple_read_from_buffer(buffer, count, pos, buf, strlen(buf));
+}
+
+static const struct file_operations ptp_n_perout_loopback_fops = {
+ .owner = THIS_MODULE,
+ .open = simple_open,
+ .read = ptp_n_perout_loopback_read,
+};
+
+static ssize_t ptp_perout_loopback_write(struct file *filep,
+ const char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct ptp_clock *ptp = filep->private_data;
+ struct ptp_clock_info *ops = ptp->info;
+ unsigned int index, enable;
+ int len, cnt, err;
+ char buf[32] = {};
+
+ if (*ppos || !count)
+ return -EINVAL;
+
+ if (count >= sizeof(buf))
+ return -ENOSPC;
+
+ len = simple_write_to_buffer(buf, sizeof(buf) - 1,
+ ppos, buffer, count);
+ if (len < 0)
+ return len;
+
+ buf[len] = '\0';
+ cnt = sscanf(buf, "%u %u", &index, &enable);
+ if (cnt != 2)
+ return -EINVAL;
+
+ if (index >= ops->n_per_lp)
+ return -EINVAL;
+
+ if (enable != 0 && enable != 1)
+ return -EINVAL;
+
+ err = ops->perout_loopback(ops, index, enable);
+ if (err)
+ return err;
+
+ return count;
+}
+
+static const struct file_operations ptp_perout_loopback_ops = {
+ .owner = THIS_MODULE,
+ .open = simple_open,
+ .write = ptp_perout_loopback_write,
+};
+
/* public interface */
struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info,
@@ -377,6 +452,12 @@ struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info,
/* Debugfs initialization */
snprintf(debugfsname, sizeof(debugfsname), "ptp%d", ptp->index);
ptp->debugfs_root = debugfs_create_dir(debugfsname, NULL);
+ if (info->n_per_lp > 0 && info->perout_loopback) {
+ debugfs_create_file("n_perout_loopback", 0400, ptp->debugfs_root,
+ ptp, &ptp_n_perout_loopback_fops);
+ debugfs_create_file("perout_loopback", 0200, ptp->debugfs_root,
+ ptp, &ptp_perout_loopback_ops);
+ }
return ptp;
@@ -417,9 +498,21 @@ int ptp_clock_unregister(struct ptp_clock *ptp)
device_for_each_child(&ptp->dev, NULL, unregister_vclock);
}
+ /* Get the device to stop posix_clock_unregister() doing the last put
+ * and freeing the structure(s)
+ */
+ get_device(&ptp->dev);
+
+ /* Wake up any userspace waiting for an event. */
ptp->defunct = 1;
wake_up_interruptible(&ptp->tsev_wq);
+ /* Tear down the POSIX clock, which removes the user interface. */
+ posix_clock_unregister(&ptp->clock);
+
+ /* Disable all sources of event generation. */
+ ptp_disable_all_events(ptp);
+
if (ptp->kworker) {
kthread_cancel_delayed_work_sync(&ptp->aux_work);
kthread_destroy_worker(ptp->kworker);
@@ -429,7 +522,8 @@ int ptp_clock_unregister(struct ptp_clock *ptp)
if (ptp->pps_source)
pps_unregister_source(ptp->pps_source);
- posix_clock_unregister(&ptp->clock);
+ /* The final put, normally here, will invoke ptp_clock_release(). */
+ put_device(&ptp->dev);
return 0;
}
@@ -477,6 +571,58 @@ int ptp_clock_index(struct ptp_clock *ptp)
}
EXPORT_SYMBOL(ptp_clock_index);
+static int ptp_clock_of_node_match(struct device *dev, const void *data)
+{
+ const struct device_node *parent_np = data;
+
+ return (dev->parent && dev_of_node(dev->parent) == parent_np);
+}
+
+int ptp_clock_index_by_of_node(struct device_node *np)
+{
+ struct ptp_clock *ptp;
+ struct device *dev;
+ int phc_index;
+
+ dev = class_find_device(&ptp_class, NULL, np,
+ ptp_clock_of_node_match);
+ if (!dev)
+ return -1;
+
+ ptp = dev_get_drvdata(dev);
+ phc_index = ptp_clock_index(ptp);
+ put_device(dev);
+
+ return phc_index;
+}
+EXPORT_SYMBOL_GPL(ptp_clock_index_by_of_node);
+
+static int ptp_clock_dev_match(struct device *dev, const void *data)
+{
+ const struct device *parent = data;
+
+ return dev->parent == parent;
+}
+
+int ptp_clock_index_by_dev(struct device *parent)
+{
+ struct ptp_clock *ptp;
+ struct device *dev;
+ int phc_index;
+
+ dev = class_find_device(&ptp_class, NULL, parent,
+ ptp_clock_dev_match);
+ if (!dev)
+ return -1;
+
+ ptp = dev_get_drvdata(dev);
+ phc_index = ptp_clock_index(ptp);
+ put_device(dev);
+
+ return phc_index;
+}
+EXPORT_SYMBOL_GPL(ptp_clock_index_by_dev);
+
int ptp_find_pin(struct ptp_clock *ptp,
enum ptp_pin_function func, unsigned int chan)
{
diff --git a/drivers/ptp/ptp_clockmatrix.c b/drivers/ptp/ptp_clockmatrix.c
index b8d4df8c6da2..59cd6bbb33f3 100644
--- a/drivers/ptp/ptp_clockmatrix.c
+++ b/drivers/ptp/ptp_clockmatrix.c
@@ -1161,7 +1161,7 @@ static int set_pll_output_mask(struct idtcm *idtcm, u16 addr, u8 val)
SET_U16_MSB(idtcm->channel[3].output_mask, val);
break;
default:
- err = -EFAULT; /* Bad address */;
+ err = -EFAULT; /* Bad address */
break;
}
diff --git a/drivers/ptp/ptp_netc.c b/drivers/ptp/ptp_netc.c
new file mode 100644
index 000000000000..94e952ee6990
--- /dev/null
+++ b/drivers/ptp/ptp_netc.c
@@ -0,0 +1,1043 @@
+// 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/pci.h>
+#include <linux/ptp_clock_kernel.h>
+
+#define NETC_TMR_PCI_VENDOR_NXP 0x1131
+
+#define NETC_TMR_CTRL 0x0080
+#define TMR_CTRL_CK_SEL GENMASK(1, 0)
+#define TMR_CTRL_TE BIT(2)
+#define TMR_ETEP(i) BIT(8 + (i))
+#define TMR_COMP_MODE BIT(15)
+#define TMR_CTRL_TCLK_PERIOD GENMASK(25, 16)
+#define TMR_CTRL_PPL(i) BIT(27 - (i))
+#define TMR_CTRL_FS BIT(28)
+
+#define NETC_TMR_TEVENT 0x0084
+#define TMR_TEVNET_PPEN(i) BIT(7 - (i))
+#define TMR_TEVENT_PPEN_ALL GENMASK(7, 5)
+#define TMR_TEVENT_ALMEN(i) BIT(16 + (i))
+#define TMR_TEVENT_ETS_THREN(i) BIT(20 + (i))
+#define TMR_TEVENT_ETSEN(i) BIT(24 + (i))
+#define TMR_TEVENT_ETS_OVEN(i) BIT(28 + (i))
+#define TMR_TEVENT_ETS(i) (TMR_TEVENT_ETS_THREN(i) | \
+ TMR_TEVENT_ETSEN(i) | \
+ TMR_TEVENT_ETS_OVEN(i))
+
+#define NETC_TMR_TEMASK 0x0088
+#define NETC_TMR_STAT 0x0094
+#define TMR_STAT_ETS_VLD(i) BIT(24 + (i))
+
+#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_ECTRL 0x00ac
+#define NETC_TMR_OFF_L 0x00b0
+#define NETC_TMR_OFF_H 0x00b4
+
+/* i = 0, 1, i indicates the index of TMR_ALARM */
+#define NETC_TMR_ALARM_L(i) (0x00b8 + (i) * 8)
+#define NETC_TMR_ALARM_H(i) (0x00bc + (i) * 8)
+
+/* i = 0, 1, 2. i indicates the index of TMR_FIPER. */
+#define NETC_TMR_FIPER(i) (0x00d0 + (i) * 4)
+
+#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 FIPER_CTRL_FS_ALARM(i) (BIT(5) << (i) * 8)
+#define FIPER_CTRL_PW(i) (GENMASK(4, 0) << (i) * 8)
+#define FIPER_CTRL_SET_PW(i, v) (((v) & GENMASK(4, 0)) << 8 * (i))
+
+/* i = 0, 1, i indicates the index of TMR_ETTS */
+#define NETC_TMR_ETTS_L(i) (0x00e0 + (i) * 8)
+#define NETC_TMR_ETTS_H(i) (0x00e4 + (i) * 8)
+#define NETC_TMR_CUR_TIME_L 0x00f0
+#define NETC_TMR_CUR_TIME_H 0x00f4
+
+#define NETC_TMR_REGS_BAR 0
+#define NETC_GLOBAL_OFFSET 0x10000
+#define NETC_GLOBAL_IPBRR0 0xbf8
+#define IPBRR0_IP_REV GENMASK(15, 0)
+#define NETC_REV_4_1 0x0401
+
+#define NETC_TMR_FIPER_NUM 3
+#define NETC_TMR_INVALID_CHANNEL NETC_TMR_FIPER_NUM
+#define NETC_TMR_DEFAULT_PRSC 2
+#define NETC_TMR_DEFAULT_ALARM GENMASK_ULL(63, 0)
+#define NETC_TMR_DEFAULT_FIPER GENMASK(31, 0)
+#define NETC_TMR_FIPER_MAX_PW GENMASK(4, 0)
+#define NETC_TMR_ALARM_NUM 2
+#define NETC_TMR_DEFAULT_ETTF_THR 7
+
+/* 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
+
+enum netc_pp_type {
+ NETC_PP_PPS = 1,
+ NETC_PP_PEROUT,
+};
+
+struct netc_pp {
+ enum netc_pp_type type;
+ bool enabled;
+ int alarm_id;
+ u32 period; /* pulse period, ns */
+ u64 stime; /* start time, ns */
+};
+
+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;
+ u32 clk_select;
+ u32 clk_freq;
+ u32 oclk_prsc;
+ /* High 32-bit is integer part, low 32-bit is fractional part */
+ u64 period;
+
+ int irq;
+ char irq_name[24];
+ int revision;
+ u32 tmr_emask;
+ u8 pps_channel;
+ u8 fs_alarm_num;
+ u8 fs_alarm_bitmap;
+ struct netc_pp pp[NETC_TMR_FIPER_NUM]; /* periodic pulse */
+};
+
+#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",
+ "ext"
+};
+
+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. Other H/L registers should have the same
+ * behavior.
+ */
+ 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_alarm_write(struct netc_timer *priv,
+ u64 alarm, int index)
+{
+ u32 alarm_h = upper_32_bits(alarm);
+ u32 alarm_l = lower_32_bits(alarm);
+
+ netc_timer_wr(priv, NETC_TMR_ALARM_L(index), alarm_l);
+ netc_timer_wr(priv, NETC_TMR_ALARM_H(index), alarm_h);
+}
+
+static u32 netc_timer_get_integral_period(struct netc_timer *priv)
+{
+ u32 tmr_ctrl, integral_period;
+
+ tmr_ctrl = netc_timer_rd(priv, NETC_TMR_CTRL);
+ integral_period = FIELD_GET(TMR_CTRL_TCLK_PERIOD, tmr_ctrl);
+
+ return integral_period;
+}
+
+static u32 netc_timer_calculate_fiper_pw(struct netc_timer *priv,
+ u32 fiper)
+{
+ u64 divisor, pulse_width;
+
+ /* Set the FIPER pulse width to half FIPER interval by default.
+ * pulse_width = (fiper / 2) / TMR_GCLK_period,
+ * TMR_GCLK_period = NSEC_PER_SEC / TMR_GCLK_freq,
+ * TMR_GCLK_freq = (clk_freq / oclk_prsc) Hz,
+ * so pulse_width = fiper * clk_freq / (2 * NSEC_PER_SEC * oclk_prsc).
+ */
+ divisor = mul_u32_u32(2 * NSEC_PER_SEC, priv->oclk_prsc);
+ pulse_width = div64_u64(mul_u32_u32(fiper, priv->clk_freq), divisor);
+
+ /* The FIPER_PW field only has 5 bits, need to update oclk_prsc */
+ if (pulse_width > NETC_TMR_FIPER_MAX_PW)
+ pulse_width = NETC_TMR_FIPER_MAX_PW;
+
+ return pulse_width;
+}
+
+static void netc_timer_set_pps_alarm(struct netc_timer *priv, int channel,
+ u32 integral_period)
+{
+ struct netc_pp *pp = &priv->pp[channel];
+ u64 alarm;
+
+ /* Get the alarm value */
+ alarm = netc_timer_cur_time_read(priv) + NSEC_PER_MSEC;
+ alarm = roundup_u64(alarm, NSEC_PER_SEC);
+ alarm = roundup_u64(alarm, integral_period);
+
+ netc_timer_alarm_write(priv, alarm, pp->alarm_id);
+}
+
+static void netc_timer_set_perout_alarm(struct netc_timer *priv, int channel,
+ u32 integral_period)
+{
+ u64 cur_time = netc_timer_cur_time_read(priv);
+ struct netc_pp *pp = &priv->pp[channel];
+ u64 alarm, delta, min_time;
+ u32 period = pp->period;
+ u64 stime = pp->stime;
+
+ min_time = cur_time + NSEC_PER_MSEC + period;
+ if (stime < min_time) {
+ delta = min_time - stime;
+ stime += roundup_u64(delta, period);
+ }
+
+ alarm = roundup_u64(stime - period, integral_period);
+ netc_timer_alarm_write(priv, alarm, pp->alarm_id);
+}
+
+static int netc_timer_get_alarm_id(struct netc_timer *priv)
+{
+ int i;
+
+ for (i = 0; i < priv->fs_alarm_num; i++) {
+ if (!(priv->fs_alarm_bitmap & BIT(i))) {
+ priv->fs_alarm_bitmap |= BIT(i);
+ break;
+ }
+ }
+
+ return i;
+}
+
+static u64 netc_timer_get_gclk_period(struct netc_timer *priv)
+{
+ /* TMR_GCLK_freq = (clk_freq / oclk_prsc) Hz.
+ * TMR_GCLK_period = NSEC_PER_SEC / TMR_GCLK_freq.
+ * TMR_GCLK_period = (NSEC_PER_SEC * oclk_prsc) / clk_freq
+ */
+
+ return div_u64(mul_u32_u32(NSEC_PER_SEC, priv->oclk_prsc),
+ priv->clk_freq);
+}
+
+static void netc_timer_enable_periodic_pulse(struct netc_timer *priv,
+ u8 channel)
+{
+ u32 fiper_pw, fiper, fiper_ctrl, integral_period;
+ struct netc_pp *pp = &priv->pp[channel];
+ int alarm_id = pp->alarm_id;
+
+ integral_period = netc_timer_get_integral_period(priv);
+ /* Set to desired FIPER interval in ns - TCLK_PERIOD */
+ fiper = pp->period - integral_period;
+ fiper_pw = netc_timer_calculate_fiper_pw(priv, fiper);
+
+ fiper_ctrl = netc_timer_rd(priv, NETC_TMR_FIPER_CTRL);
+ fiper_ctrl &= ~(FIPER_CTRL_DIS(channel) | FIPER_CTRL_PW(channel) |
+ FIPER_CTRL_FS_ALARM(channel));
+ fiper_ctrl |= FIPER_CTRL_SET_PW(channel, fiper_pw);
+ fiper_ctrl |= alarm_id ? FIPER_CTRL_FS_ALARM(channel) : 0;
+
+ priv->tmr_emask |= TMR_TEVENT_ALMEN(alarm_id);
+
+ if (pp->type == NETC_PP_PPS) {
+ priv->tmr_emask |= TMR_TEVNET_PPEN(channel);
+ netc_timer_set_pps_alarm(priv, channel, integral_period);
+ } else {
+ netc_timer_set_perout_alarm(priv, channel, integral_period);
+ }
+
+ netc_timer_wr(priv, NETC_TMR_TEMASK, priv->tmr_emask);
+ netc_timer_wr(priv, NETC_TMR_FIPER(channel), fiper);
+ netc_timer_wr(priv, NETC_TMR_FIPER_CTRL, fiper_ctrl);
+}
+
+static void netc_timer_disable_periodic_pulse(struct netc_timer *priv,
+ u8 channel)
+{
+ struct netc_pp *pp = &priv->pp[channel];
+ int alarm_id = pp->alarm_id;
+ u32 fiper_ctrl;
+
+ if (!pp->enabled)
+ return;
+
+ priv->tmr_emask &= ~(TMR_TEVNET_PPEN(channel) |
+ TMR_TEVENT_ALMEN(alarm_id));
+
+ fiper_ctrl = netc_timer_rd(priv, NETC_TMR_FIPER_CTRL);
+ fiper_ctrl |= FIPER_CTRL_DIS(channel);
+
+ netc_timer_alarm_write(priv, NETC_TMR_DEFAULT_ALARM, alarm_id);
+ netc_timer_wr(priv, NETC_TMR_TEMASK, priv->tmr_emask);
+ netc_timer_wr(priv, NETC_TMR_FIPER(channel), NETC_TMR_DEFAULT_FIPER);
+ netc_timer_wr(priv, NETC_TMR_FIPER_CTRL, fiper_ctrl);
+}
+
+static u8 netc_timer_select_pps_channel(struct netc_timer *priv)
+{
+ int i;
+
+ for (i = 0; i < NETC_TMR_FIPER_NUM; i++) {
+ if (!priv->pp[i].enabled)
+ return i;
+ }
+
+ return NETC_TMR_INVALID_CHANNEL;
+}
+
+/* Note that users should not use this API to output PPS signal on
+ * external pins, because PTP_CLK_REQ_PPS trigger internal PPS event
+ * for input into kernel PPS subsystem. See:
+ * https://lore.kernel.org/r/20201117213826.18235-1-a.fatoum@pengutronix.de
+ */
+static int netc_timer_enable_pps(struct netc_timer *priv,
+ struct ptp_clock_request *rq, int on)
+{
+ struct device *dev = &priv->pdev->dev;
+ unsigned long flags;
+ struct netc_pp *pp;
+ int err = 0;
+
+ spin_lock_irqsave(&priv->lock, flags);
+
+ if (on) {
+ int alarm_id;
+ u8 channel;
+
+ if (priv->pps_channel < NETC_TMR_FIPER_NUM) {
+ channel = priv->pps_channel;
+ } else {
+ channel = netc_timer_select_pps_channel(priv);
+ if (channel == NETC_TMR_INVALID_CHANNEL) {
+ dev_err(dev, "No available FIPERs\n");
+ err = -EBUSY;
+ goto unlock_spinlock;
+ }
+ }
+
+ pp = &priv->pp[channel];
+ if (pp->enabled)
+ goto unlock_spinlock;
+
+ alarm_id = netc_timer_get_alarm_id(priv);
+ if (alarm_id == priv->fs_alarm_num) {
+ dev_err(dev, "No available ALARMs\n");
+ err = -EBUSY;
+ goto unlock_spinlock;
+ }
+
+ pp->enabled = true;
+ pp->type = NETC_PP_PPS;
+ pp->alarm_id = alarm_id;
+ pp->period = NSEC_PER_SEC;
+ priv->pps_channel = channel;
+
+ netc_timer_enable_periodic_pulse(priv, channel);
+ } else {
+ /* pps_channel is invalid if PPS is not enabled, so no
+ * processing is needed.
+ */
+ if (priv->pps_channel >= NETC_TMR_FIPER_NUM)
+ goto unlock_spinlock;
+
+ netc_timer_disable_periodic_pulse(priv, priv->pps_channel);
+ pp = &priv->pp[priv->pps_channel];
+ priv->fs_alarm_bitmap &= ~BIT(pp->alarm_id);
+ memset(pp, 0, sizeof(*pp));
+ priv->pps_channel = NETC_TMR_INVALID_CHANNEL;
+ }
+
+unlock_spinlock:
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return err;
+}
+
+static int net_timer_enable_perout(struct netc_timer *priv,
+ struct ptp_clock_request *rq, int on)
+{
+ struct device *dev = &priv->pdev->dev;
+ u32 channel = rq->perout.index;
+ unsigned long flags;
+ struct netc_pp *pp;
+ int err = 0;
+
+ spin_lock_irqsave(&priv->lock, flags);
+
+ pp = &priv->pp[channel];
+ if (pp->type == NETC_PP_PPS) {
+ dev_err(dev, "FIPER%u is being used for PPS\n", channel);
+ err = -EBUSY;
+ goto unlock_spinlock;
+ }
+
+ if (on) {
+ u64 period_ns, gclk_period, max_period, min_period;
+ struct timespec64 period, stime;
+ u32 integral_period;
+ int alarm_id;
+
+ period.tv_sec = rq->perout.period.sec;
+ period.tv_nsec = rq->perout.period.nsec;
+ period_ns = timespec64_to_ns(&period);
+
+ integral_period = netc_timer_get_integral_period(priv);
+ max_period = (u64)NETC_TMR_DEFAULT_FIPER + integral_period;
+ gclk_period = netc_timer_get_gclk_period(priv);
+ min_period = gclk_period * 4 + integral_period;
+ if (period_ns > max_period || period_ns < min_period) {
+ dev_err(dev, "The period range is %llu ~ %llu\n",
+ min_period, max_period);
+ err = -EINVAL;
+ goto unlock_spinlock;
+ }
+
+ if (pp->enabled) {
+ alarm_id = pp->alarm_id;
+ } else {
+ alarm_id = netc_timer_get_alarm_id(priv);
+ if (alarm_id == priv->fs_alarm_num) {
+ dev_err(dev, "No available ALARMs\n");
+ err = -EBUSY;
+ goto unlock_spinlock;
+ }
+
+ pp->type = NETC_PP_PEROUT;
+ pp->enabled = true;
+ pp->alarm_id = alarm_id;
+ }
+
+ stime.tv_sec = rq->perout.start.sec;
+ stime.tv_nsec = rq->perout.start.nsec;
+ pp->stime = timespec64_to_ns(&stime);
+ pp->period = period_ns;
+
+ netc_timer_enable_periodic_pulse(priv, channel);
+ } else {
+ netc_timer_disable_periodic_pulse(priv, channel);
+ priv->fs_alarm_bitmap &= ~BIT(pp->alarm_id);
+ memset(pp, 0, sizeof(*pp));
+ }
+
+unlock_spinlock:
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return err;
+}
+
+static void netc_timer_handle_etts_event(struct netc_timer *priv, int index,
+ bool update_event)
+{
+ struct ptp_clock_event event;
+ u32 etts_l = 0, etts_h = 0;
+
+ while (netc_timer_rd(priv, NETC_TMR_STAT) & TMR_STAT_ETS_VLD(index)) {
+ etts_l = netc_timer_rd(priv, NETC_TMR_ETTS_L(index));
+ etts_h = netc_timer_rd(priv, NETC_TMR_ETTS_H(index));
+ }
+
+ /* Invalid time stamp */
+ if (!etts_l && !etts_h)
+ return;
+
+ if (update_event) {
+ event.type = PTP_CLOCK_EXTTS;
+ event.index = index;
+ event.timestamp = (u64)etts_h << 32;
+ event.timestamp |= etts_l;
+ ptp_clock_event(priv->clock, &event);
+ }
+}
+
+static int netc_timer_enable_extts(struct netc_timer *priv,
+ struct ptp_clock_request *rq, int on)
+{
+ int index = rq->extts.index;
+ unsigned long flags;
+ u32 tmr_ctrl;
+
+ /* Reject requests to enable time stamping on both edges */
+ if ((rq->extts.flags & PTP_EXTTS_EDGES) == PTP_EXTTS_EDGES)
+ return -EOPNOTSUPP;
+
+ spin_lock_irqsave(&priv->lock, flags);
+
+ netc_timer_handle_etts_event(priv, rq->extts.index, false);
+ if (on) {
+ tmr_ctrl = netc_timer_rd(priv, NETC_TMR_CTRL);
+ if (rq->extts.flags & PTP_FALLING_EDGE)
+ tmr_ctrl |= TMR_ETEP(index);
+ else
+ tmr_ctrl &= ~TMR_ETEP(index);
+
+ netc_timer_wr(priv, NETC_TMR_CTRL, tmr_ctrl);
+ priv->tmr_emask |= TMR_TEVENT_ETS(index);
+ } else {
+ priv->tmr_emask &= ~TMR_TEVENT_ETS(index);
+ }
+
+ netc_timer_wr(priv, NETC_TMR_TEMASK, priv->tmr_emask);
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return 0;
+}
+
+static void netc_timer_disable_fiper(struct netc_timer *priv)
+{
+ u32 fiper_ctrl = netc_timer_rd(priv, NETC_TMR_FIPER_CTRL);
+ int i;
+
+ for (i = 0; i < NETC_TMR_FIPER_NUM; i++) {
+ if (!priv->pp[i].enabled)
+ continue;
+
+ fiper_ctrl |= FIPER_CTRL_DIS(i);
+ netc_timer_wr(priv, NETC_TMR_FIPER(i), NETC_TMR_DEFAULT_FIPER);
+ }
+
+ netc_timer_wr(priv, NETC_TMR_FIPER_CTRL, fiper_ctrl);
+}
+
+static void netc_timer_enable_fiper(struct netc_timer *priv)
+{
+ u32 integral_period = netc_timer_get_integral_period(priv);
+ u32 fiper_ctrl = netc_timer_rd(priv, NETC_TMR_FIPER_CTRL);
+ int i;
+
+ for (i = 0; i < NETC_TMR_FIPER_NUM; i++) {
+ struct netc_pp *pp = &priv->pp[i];
+ u32 fiper;
+
+ if (!pp->enabled)
+ continue;
+
+ fiper_ctrl &= ~FIPER_CTRL_DIS(i);
+
+ if (pp->type == NETC_PP_PPS)
+ netc_timer_set_pps_alarm(priv, i, integral_period);
+ else if (pp->type == NETC_PP_PEROUT)
+ netc_timer_set_perout_alarm(priv, i, integral_period);
+
+ fiper = pp->period - integral_period;
+ netc_timer_wr(priv, NETC_TMR_FIPER(i), fiper);
+ }
+
+ netc_timer_wr(priv, NETC_TMR_FIPER_CTRL, fiper_ctrl);
+}
+
+static int netc_timer_enable(struct ptp_clock_info *ptp,
+ struct ptp_clock_request *rq, int on)
+{
+ struct netc_timer *priv = ptp_to_netc_timer(ptp);
+
+ switch (rq->type) {
+ case PTP_CLK_REQ_PPS:
+ return netc_timer_enable_pps(priv, rq, on);
+ case PTP_CLK_REQ_PEROUT:
+ return net_timer_enable_perout(priv, rq, on);
+ case PTP_CLK_REQ_EXTTS:
+ return netc_timer_enable_extts(priv, rq, on);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int netc_timer_perout_loopback(struct ptp_clock_info *ptp,
+ unsigned int index, int on)
+{
+ struct netc_timer *priv = ptp_to_netc_timer(ptp);
+ unsigned long flags;
+ u32 tmr_ctrl;
+
+ spin_lock_irqsave(&priv->lock, flags);
+
+ tmr_ctrl = netc_timer_rd(priv, NETC_TMR_CTRL);
+ if (on)
+ tmr_ctrl |= TMR_CTRL_PPL(index);
+ else
+ tmr_ctrl &= ~TMR_CTRL_PPL(index);
+
+ netc_timer_wr(priv, NETC_TMR_CTRL, tmr_ctrl);
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return 0;
+}
+
+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_disable_fiper(priv);
+ netc_timer_wr(priv, NETC_TMR_CTRL, tmr_ctrl);
+ netc_timer_enable_fiper(priv);
+ }
+
+ 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);
+
+ netc_timer_disable_fiper(priv);
+
+ /* 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);
+
+ netc_timer_enable_fiper(priv);
+
+ 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_disable_fiper(priv);
+ netc_timer_offset_write(priv, 0);
+ netc_timer_cnt_write(priv, ns);
+ netc_timer_enable_fiper(priv);
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return 0;
+}
+
+static const struct ptp_clock_info netc_timer_ptp_caps = {
+ .owner = THIS_MODULE,
+ .name = "NETC Timer PTP clock",
+ .max_adj = 500000000,
+ .n_pins = 0,
+ .n_alarm = 2,
+ .pps = 1,
+ .n_per_out = 3,
+ .n_ext_ts = 2,
+ .n_per_lp = 2,
+ .supported_extts_flags = PTP_RISING_EDGE | PTP_FALLING_EDGE |
+ PTP_STRICT_FLAGS,
+ .adjfine = netc_timer_adjfine,
+ .adjtime = netc_timer_adjtime,
+ .gettimex64 = netc_timer_gettimex64,
+ .settime64 = netc_timer_settime64,
+ .enable = netc_timer_enable,
+ .perout_loopback = netc_timer_perout_loopback,
+};
+
+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 | TMR_CTRL_FS;
+ 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);
+ netc_timer_wr(priv, NETC_TMR_ECTRL, NETC_TMR_DEFAULT_ETTF_THR);
+
+ 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;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ 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->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 dev_err_probe(dev, PTR_ERR(clk),
+ "Failed to enable clock\n");
+
+ 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((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 irqreturn_t netc_timer_isr(int irq, void *data)
+{
+ struct netc_timer *priv = data;
+ struct ptp_clock_event event;
+ u32 tmr_event;
+
+ spin_lock(&priv->lock);
+
+ tmr_event = netc_timer_rd(priv, NETC_TMR_TEVENT);
+ tmr_event &= priv->tmr_emask;
+ /* Clear interrupts status */
+ netc_timer_wr(priv, NETC_TMR_TEVENT, tmr_event);
+
+ if (tmr_event & TMR_TEVENT_ALMEN(0))
+ netc_timer_alarm_write(priv, NETC_TMR_DEFAULT_ALARM, 0);
+
+ if (tmr_event & TMR_TEVENT_ALMEN(1))
+ netc_timer_alarm_write(priv, NETC_TMR_DEFAULT_ALARM, 1);
+
+ if (tmr_event & TMR_TEVENT_PPEN_ALL) {
+ event.type = PTP_CLOCK_PPS;
+ ptp_clock_event(priv->clock, &event);
+ }
+
+ if (tmr_event & TMR_TEVENT_ETS(0))
+ netc_timer_handle_etts_event(priv, 0, true);
+
+ if (tmr_event & TMR_TEVENT_ETS(1))
+ netc_timer_handle_etts_event(priv, 1, true);
+
+ spin_unlock(&priv->lock);
+
+ return IRQ_HANDLED;
+}
+
+static int netc_timer_init_msix_irq(struct netc_timer *priv)
+{
+ struct pci_dev *pdev = priv->pdev;
+ int err, n;
+
+ n = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSIX);
+ if (n != 1) {
+ err = (n < 0) ? n : -EPERM;
+ dev_err(&pdev->dev, "pci_alloc_irq_vectors() failed\n");
+ return err;
+ }
+
+ priv->irq = pci_irq_vector(pdev, 0);
+ err = request_irq(priv->irq, netc_timer_isr, 0, priv->irq_name, priv);
+ if (err) {
+ dev_err(&pdev->dev, "request_irq() failed\n");
+ pci_free_irq_vectors(pdev);
+
+ return err;
+ }
+
+ return 0;
+}
+
+static void netc_timer_free_msix_irq(struct netc_timer *priv)
+{
+ struct pci_dev *pdev = priv->pdev;
+
+ disable_irq(priv->irq);
+ free_irq(priv->irq, priv);
+ pci_free_irq_vectors(pdev);
+}
+
+static int netc_timer_get_global_ip_rev(struct netc_timer *priv)
+{
+ u32 val;
+
+ val = netc_timer_rd(priv, NETC_GLOBAL_OFFSET + NETC_GLOBAL_IPBRR0);
+
+ return val & IPBRR0_IP_REV;
+}
+
+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);
+ priv->revision = netc_timer_get_global_ip_rev(priv);
+ if (priv->revision == NETC_REV_4_1)
+ priv->fs_alarm_num = 1;
+ else
+ priv->fs_alarm_num = NETC_TMR_ALARM_NUM;
+
+ err = netc_timer_parse_dt(priv);
+ if (err)
+ goto timer_pci_remove;
+
+ priv->caps = netc_timer_ptp_caps;
+ priv->oclk_prsc = NETC_TMR_DEFAULT_PRSC;
+ priv->pps_channel = NETC_TMR_INVALID_CHANNEL;
+ spin_lock_init(&priv->lock);
+ snprintf(priv->irq_name, sizeof(priv->irq_name), "ptp-netc %s",
+ pci_name(pdev));
+
+ err = netc_timer_init_msix_irq(priv);
+ if (err)
+ goto timer_pci_remove;
+
+ netc_timer_init(priv);
+ priv->clock = ptp_clock_register(&priv->caps, dev);
+ if (IS_ERR(priv->clock)) {
+ err = PTR_ERR(priv->clock);
+ goto free_msix_irq;
+ }
+
+ return 0;
+
+free_msix_irq:
+ netc_timer_free_msix_irq(priv);
+timer_pci_remove:
+ netc_timer_pci_remove(pdev);
+
+ return err;
+}
+
+static void netc_timer_remove(struct pci_dev *pdev)
+{
+ struct netc_timer *priv = pci_get_drvdata(pdev);
+
+ netc_timer_wr(priv, NETC_TMR_TEMASK, 0);
+ netc_timer_wr(priv, NETC_TMR_CTRL, 0);
+ ptp_clock_unregister(priv->clock);
+ netc_timer_free_msix_irq(priv);
+ netc_timer_pci_remove(pdev);
+}
+
+static const struct pci_device_id netc_timer_id_table[] = {
+ { PCI_DEVICE(NETC_TMR_PCI_VENDOR_NXP, 0xee02) },
+ { }
+};
+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/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c
index d39073dc4072..794ec6e71990 100644
--- a/drivers/ptp/ptp_ocp.c
+++ b/drivers/ptp/ptp_ocp.c
@@ -1485,6 +1485,8 @@ static const struct ptp_clock_info ptp_ocp_clock_info = {
.pps = true,
.n_ext_ts = 6,
.n_per_out = 5,
+ .supported_extts_flags = PTP_STRICT_FLAGS | PTP_RISING_EDGE,
+ .supported_perout_flags = PTP_PEROUT_DUTY_CYCLE | PTP_PEROUT_PHASE,
};
static void
@@ -2095,10 +2097,6 @@ ptp_ocp_signal_from_perout(struct ptp_ocp *bp, int gen,
{
struct ptp_ocp_signal s = { };
- if (req->flags & ~(PTP_PEROUT_DUTY_CYCLE |
- PTP_PEROUT_PHASE))
- return -EOPNOTSUPP;
-
s.polarity = bp->signal[gen].polarity;
s.period = ktime_set(req->period.sec, req->period.nsec);
if (!s.period)
@@ -4557,8 +4555,7 @@ ptp_ocp_detach(struct ptp_ocp *bp)
ptp_ocp_debugfs_remove_device(bp);
ptp_ocp_detach_sysfs(bp);
ptp_ocp_attr_group_del(bp);
- if (timer_pending(&bp->watchdog))
- timer_delete_sync(&bp->watchdog);
+ timer_delete_sync(&bp->watchdog);
if (bp->ts0)
ptp_ocp_unregister_ext(bp->ts0);
if (bp->ts1)
diff --git a/drivers/ptp/ptp_private.h b/drivers/ptp/ptp_private.h
index a6aad743c282..db4039d642b4 100644
--- a/drivers/ptp/ptp_private.h
+++ b/drivers/ptp/ptp_private.h
@@ -22,8 +22,14 @@
#define PTP_MAX_TIMESTAMPS 128
#define PTP_BUF_TIMESTAMPS 30
#define PTP_DEFAULT_MAX_VCLOCKS 20
+#define PTP_MAX_VCLOCKS_LIMIT (KMALLOC_MAX_SIZE/(sizeof(int)))
#define PTP_MAX_CHANNELS 2048
+enum {
+ PTP_LOCK_PHYSICAL = 0,
+ PTP_LOCK_VIRTUAL,
+};
+
struct timestamp_event_queue {
struct ptp_extts_event buf[PTP_MAX_TIMESTAMPS];
int head;
@@ -136,6 +142,8 @@ extern const struct class ptp_class;
* see ptp_chardev.c
*/
+void ptp_disable_all_events(struct ptp_clock *ptp);
+
/* caller must hold pincfg_mux */
int ptp_set_pinfunc(struct ptp_clock *ptp, unsigned int pin,
enum ptp_pin_function func, unsigned int chan);
diff --git a/drivers/ptp/ptp_qoriq.c b/drivers/ptp/ptp_qoriq.c
index 4d488c1f1941..8da995e36aeb 100644
--- a/drivers/ptp/ptp_qoriq.c
+++ b/drivers/ptp/ptp_qoriq.c
@@ -465,6 +465,25 @@ static int ptp_qoriq_auto_config(struct ptp_qoriq *ptp_qoriq,
return 0;
}
+static int ptp_qoriq_perout_loopback(struct ptp_clock_info *ptp,
+ unsigned int index, int on)
+{
+ struct ptp_qoriq *ptp_qoriq = container_of(ptp, struct ptp_qoriq, caps);
+ struct ptp_qoriq_registers *regs = &ptp_qoriq->regs;
+ u32 loopback_bit = index ? PP2L : PP1L;
+ u32 tmr_ctrl;
+
+ tmr_ctrl = ptp_qoriq->read(&regs->ctrl_regs->tmr_ctrl);
+ if (on)
+ tmr_ctrl |= loopback_bit;
+ else
+ tmr_ctrl &= ~loopback_bit;
+
+ ptp_qoriq->write(&regs->ctrl_regs->tmr_ctrl, tmr_ctrl);
+
+ return 0;
+}
+
int ptp_qoriq_init(struct ptp_qoriq *ptp_qoriq, void __iomem *base,
const struct ptp_clock_info *caps)
{
@@ -479,6 +498,8 @@ int ptp_qoriq_init(struct ptp_qoriq *ptp_qoriq, void __iomem *base,
ptp_qoriq->base = base;
ptp_qoriq->caps = *caps;
+ ptp_qoriq->caps.n_per_lp = 2;
+ ptp_qoriq->caps.perout_loopback = ptp_qoriq_perout_loopback;
if (of_property_read_u32(node, "fsl,cksel", &ptp_qoriq->cksel))
ptp_qoriq->cksel = DEFAULT_CKSEL;
@@ -568,7 +589,7 @@ int ptp_qoriq_init(struct ptp_qoriq *ptp_qoriq, void __iomem *base,
return PTR_ERR(ptp_qoriq->clock);
ptp_qoriq->phc_index = ptp_clock_index(ptp_qoriq->clock);
- ptp_qoriq_create_debugfs(ptp_qoriq);
+
return 0;
}
EXPORT_SYMBOL_GPL(ptp_qoriq_init);
@@ -580,7 +601,6 @@ void ptp_qoriq_free(struct ptp_qoriq *ptp_qoriq)
ptp_qoriq->write(&regs->ctrl_regs->tmr_temask, 0);
ptp_qoriq->write(&regs->ctrl_regs->tmr_ctrl, 0);
- ptp_qoriq_remove_debugfs(ptp_qoriq);
ptp_clock_unregister(ptp_qoriq->clock);
iounmap(ptp_qoriq->base);
free_irq(ptp_qoriq->irq, ptp_qoriq);
diff --git a/drivers/ptp/ptp_qoriq_debugfs.c b/drivers/ptp/ptp_qoriq_debugfs.c
deleted file mode 100644
index e8dddcedf288..000000000000
--- a/drivers/ptp/ptp_qoriq_debugfs.c
+++ /dev/null
@@ -1,101 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/* Copyright 2019 NXP
- */
-#include <linux/device.h>
-#include <linux/debugfs.h>
-#include <linux/fsl/ptp_qoriq.h>
-
-static int ptp_qoriq_fiper1_lpbk_get(void *data, u64 *val)
-{
- struct ptp_qoriq *ptp_qoriq = data;
- struct ptp_qoriq_registers *regs = &ptp_qoriq->regs;
- u32 ctrl;
-
- ctrl = ptp_qoriq->read(&regs->ctrl_regs->tmr_ctrl);
- *val = ctrl & PP1L ? 1 : 0;
-
- return 0;
-}
-
-static int ptp_qoriq_fiper1_lpbk_set(void *data, u64 val)
-{
- struct ptp_qoriq *ptp_qoriq = data;
- struct ptp_qoriq_registers *regs = &ptp_qoriq->regs;
- u32 ctrl;
-
- ctrl = ptp_qoriq->read(&regs->ctrl_regs->tmr_ctrl);
- if (val == 0)
- ctrl &= ~PP1L;
- else
- ctrl |= PP1L;
-
- ptp_qoriq->write(&regs->ctrl_regs->tmr_ctrl, ctrl);
- return 0;
-}
-
-DEFINE_DEBUGFS_ATTRIBUTE(ptp_qoriq_fiper1_fops, ptp_qoriq_fiper1_lpbk_get,
- ptp_qoriq_fiper1_lpbk_set, "%llu\n");
-
-static int ptp_qoriq_fiper2_lpbk_get(void *data, u64 *val)
-{
- struct ptp_qoriq *ptp_qoriq = data;
- struct ptp_qoriq_registers *regs = &ptp_qoriq->regs;
- u32 ctrl;
-
- ctrl = ptp_qoriq->read(&regs->ctrl_regs->tmr_ctrl);
- *val = ctrl & PP2L ? 1 : 0;
-
- return 0;
-}
-
-static int ptp_qoriq_fiper2_lpbk_set(void *data, u64 val)
-{
- struct ptp_qoriq *ptp_qoriq = data;
- struct ptp_qoriq_registers *regs = &ptp_qoriq->regs;
- u32 ctrl;
-
- ctrl = ptp_qoriq->read(&regs->ctrl_regs->tmr_ctrl);
- if (val == 0)
- ctrl &= ~PP2L;
- else
- ctrl |= PP2L;
-
- ptp_qoriq->write(&regs->ctrl_regs->tmr_ctrl, ctrl);
- return 0;
-}
-
-DEFINE_DEBUGFS_ATTRIBUTE(ptp_qoriq_fiper2_fops, ptp_qoriq_fiper2_lpbk_get,
- ptp_qoriq_fiper2_lpbk_set, "%llu\n");
-
-void ptp_qoriq_create_debugfs(struct ptp_qoriq *ptp_qoriq)
-{
- struct dentry *root;
-
- root = debugfs_create_dir(dev_name(ptp_qoriq->dev), NULL);
- if (IS_ERR(root))
- return;
- if (!root)
- goto err_root;
-
- ptp_qoriq->debugfs_root = root;
-
- if (!debugfs_create_file_unsafe("fiper1-loopback", 0600, root,
- ptp_qoriq, &ptp_qoriq_fiper1_fops))
- goto err_node;
- if (!debugfs_create_file_unsafe("fiper2-loopback", 0600, root,
- ptp_qoriq, &ptp_qoriq_fiper2_fops))
- goto err_node;
- return;
-
-err_node:
- debugfs_remove_recursive(root);
- ptp_qoriq->debugfs_root = NULL;
-err_root:
- dev_err(ptp_qoriq->dev, "failed to initialize debugfs\n");
-}
-
-void ptp_qoriq_remove_debugfs(struct ptp_qoriq *ptp_qoriq)
-{
- debugfs_remove_recursive(ptp_qoriq->debugfs_root);
- ptp_qoriq->debugfs_root = NULL;
-}
diff --git a/drivers/ptp/ptp_sysfs.c b/drivers/ptp/ptp_sysfs.c
index 6b1b8f57cd95..200eaf500696 100644
--- a/drivers/ptp/ptp_sysfs.c
+++ b/drivers/ptp/ptp_sysfs.c
@@ -284,7 +284,7 @@ static ssize_t max_vclocks_store(struct device *dev,
size_t size;
u32 max;
- if (kstrtou32(buf, 0, &max) || max == 0)
+ if (kstrtou32(buf, 0, &max) || max == 0 || max > PTP_MAX_VCLOCKS_LIMIT)
return -EINVAL;
if (max == ptp->max_vclocks)
diff --git a/drivers/ptp/ptp_vclock.c b/drivers/ptp/ptp_vclock.c
index 2fdeedd60e21..64c950456517 100644
--- a/drivers/ptp/ptp_vclock.c
+++ b/drivers/ptp/ptp_vclock.c
@@ -154,6 +154,11 @@ static long ptp_vclock_refresh(struct ptp_clock_info *ptp)
return PTP_VCLOCK_REFRESH_INTERVAL;
}
+static void ptp_vclock_set_subclass(struct ptp_clock *ptp)
+{
+ lockdep_set_subclass(&ptp->clock.rwsem, PTP_LOCK_VIRTUAL);
+}
+
static const struct ptp_clock_info ptp_vclock_info = {
.owner = THIS_MODULE,
.name = "ptp virtual clock",
@@ -213,6 +218,8 @@ struct ptp_vclock *ptp_vclock_register(struct ptp_clock *pclock)
return NULL;
}
+ ptp_vclock_set_subclass(vclock->clock);
+
timecounter_init(&vclock->tc, &vclock->cc, 0);
ptp_schedule_worker(vclock->clock, PTP_VCLOCK_REFRESH_INTERVAL);