From 4352b8aee98005853aa63f57d6377282de17a33f Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 23 Aug 2026 13:28:57 -0700 Subject: Merge tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux Pull I3C updates from Alexandre Belloni: "A new driver this cycle, for the AMD AXI I3C controller. There are also new features such as IBI wakeup support, SETAASA device discovery and ACPI support for the the DAA methods, meaning we can now communicate with the SPD devices on DDR5 modules. New driver: - AMD AXI I3C master controller Subsystem: - I3C Common Command Code (CCC) handling improvements, especially around GET CCCs - SETAASA device discovery support - ACPI support for all existing DAA methods like SETDASA, SETNEWDA as well as I2C devices on I3C bus - IBI-based wakeup support Drivers: - dw: SETAASA support - mipi-i3c-hci: advertise IBI wakeup capability, AMD_PT I3C controller support, PIO queue management support for HCI v1.2 - renesas: improve suspend to RAM support, add runtime PM support" * tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux: (70 commits) i3c: dw: reduce do_daa time if there's no client i3c: mipi-i3c-hci: Add support for AMD_PT I3C controller i3c: mipi-i3c-hci: Add PIO queue management support for HCI v1.2 i3c: mipi-i3c-hci: Fix missing STAT_IBI_STATUS_THLD in PIO mode i3c: dw: rename "pclk" to "apb" to match dt-binding i3c: dw: make struct dw_i3c_cmd smaller i3c: dw: use COMMAND_PORT_TRANSFER_ARG instead of hardcoding i3c: renesas: Don't register devices when ENTDAA times out i3c: master: dw-i3c-master: fix OD timing for first broadcast i3c: mipi-i3c-hci: Advertise IBI wakeup capability i3c: mipi-i3c-hci: Factor out i3c_hci_sysdev() i3c: mipi-i3c-hci-pci: Propagate I3C wakeup requirements to PCI i3c: master: Reject IBI requests from non-IBI-capable devices i3c: master: Add helper to query bus wakeup requirements i3c: master: Report wakeup events for IBIs i3c: master: Support IBI-based wakeup capability i3c: master: Fix potential UAF in i3c_device_match() i3c: master: Fix potential UAF in i3c_device_uevent() i3c: Make dev->desc locking assumptions explicit i3c: master: Fix use-after-free of master->this ... --- drivers/ptp/Kconfig | 267 +++ drivers/ptp/Makefile | 24 + drivers/ptp/ptp_chardev.c | 646 +++++ drivers/ptp/ptp_clock.c | 723 ++++++ drivers/ptp/ptp_clockmatrix.c | 2467 +++++++++++++++++++ drivers/ptp/ptp_clockmatrix.h | 142 ++ drivers/ptp/ptp_dfl_tod.c | 332 +++ drivers/ptp/ptp_dte.c | 335 +++ drivers/ptp/ptp_fc3.c | 1010 ++++++++ drivers/ptp/ptp_fc3.h | 45 + drivers/ptp/ptp_idt82p33.c | 1458 ++++++++++++ drivers/ptp/ptp_idt82p33.h | 114 + drivers/ptp/ptp_ines.c | 814 +++++++ drivers/ptp/ptp_kvm_arm.c | 32 + drivers/ptp/ptp_kvm_common.c | 159 ++ drivers/ptp/ptp_kvm_x86.c | 129 + drivers/ptp/ptp_mock.c | 175 ++ drivers/ptp/ptp_netc.c | 1056 ++++++++ drivers/ptp/ptp_ocp.c | 5282 +++++++++++++++++++++++++++++++++++++++++ drivers/ptp/ptp_pch.c | 545 +++++ drivers/ptp/ptp_private.h | 175 ++ drivers/ptp/ptp_qoriq.c | 700 ++++++ drivers/ptp/ptp_s390.c | 133 ++ drivers/ptp/ptp_sysfs.c | 481 ++++ drivers/ptp/ptp_vclock.c | 306 +++ drivers/ptp/ptp_vmclock.c | 797 +++++++ drivers/ptp/ptp_vmw.c | 134 ++ 27 files changed, 18481 insertions(+) create mode 100644 drivers/ptp/Kconfig create mode 100644 drivers/ptp/Makefile create mode 100644 drivers/ptp/ptp_chardev.c create mode 100644 drivers/ptp/ptp_clock.c create mode 100644 drivers/ptp/ptp_clockmatrix.c create mode 100644 drivers/ptp/ptp_clockmatrix.h create mode 100644 drivers/ptp/ptp_dfl_tod.c create mode 100644 drivers/ptp/ptp_dte.c create mode 100644 drivers/ptp/ptp_fc3.c create mode 100644 drivers/ptp/ptp_fc3.h create mode 100644 drivers/ptp/ptp_idt82p33.c create mode 100644 drivers/ptp/ptp_idt82p33.h create mode 100644 drivers/ptp/ptp_ines.c create mode 100644 drivers/ptp/ptp_kvm_arm.c create mode 100644 drivers/ptp/ptp_kvm_common.c create mode 100644 drivers/ptp/ptp_kvm_x86.c create mode 100644 drivers/ptp/ptp_mock.c create mode 100644 drivers/ptp/ptp_netc.c create mode 100644 drivers/ptp/ptp_ocp.c create mode 100644 drivers/ptp/ptp_pch.c create mode 100644 drivers/ptp/ptp_private.h create mode 100644 drivers/ptp/ptp_qoriq.c create mode 100644 drivers/ptp/ptp_s390.c create mode 100644 drivers/ptp/ptp_sysfs.c create mode 100644 drivers/ptp/ptp_vclock.c create mode 100644 drivers/ptp/ptp_vmclock.c create mode 100644 drivers/ptp/ptp_vmw.c (limited to 'drivers/ptp') diff --git a/drivers/ptp/Kconfig b/drivers/ptp/Kconfig new file mode 100644 index 000000000..feb50f8cc --- /dev/null +++ b/drivers/ptp/Kconfig @@ -0,0 +1,267 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# PTP clock support configuration +# + +menu "PTP clock support" + +config PTP_1588_CLOCK + tristate "PTP clock support" + depends on NET && POSIX_TIMERS + default ETHERNET + select PPS + select NET_PTP_CLASSIFY + help + The IEEE 1588 standard defines a method to precisely + synchronize distributed clocks over Ethernet networks. The + standard defines a Precision Time Protocol (PTP), which can + be used to achieve synchronization within a few dozen + microseconds. In addition, with the help of special hardware + time stamping units, it can be possible to achieve + synchronization to within a few hundred nanoseconds. + + This driver adds support for PTP clocks as character + devices. If you want to use a PTP clock, then you should + also enable at least one clock driver as well. + + To compile this driver as a module, choose M here: the module + will be called ptp. + +config PTP_1588_CLOCK_OPTIONAL + tristate + default y if PTP_1588_CLOCK=n + default PTP_1588_CLOCK + help + Drivers that can optionally use the PTP_1588_CLOCK framework + should depend on this symbol to prevent them from being built + into vmlinux while the PTP support itself is in a loadable + module. + If PTP support is disabled, this dependency will still be + met, and drivers refer to dummy helpers. + +config PTP_1588_CLOCK_DTE + tristate "Broadcom DTE as PTP clock" + depends on PTP_1588_CLOCK + depends on NET && HAS_IOMEM + depends on ARCH_BCM_MOBILE || (ARCH_BCM_IPROC && !(ARCH_BCM_NSP || ARCH_BCM_5301X)) || COMPILE_TEST + default y if ARCH_BCM_MOBILE || ARCH_BCM_IPROC + help + This driver adds support for using the Digital timing engine + (DTE) in the Broadcom SoC's as a PTP clock. + + The clock can be used in both wired and wireless networks + for PTP purposes. + + To compile this driver as a module, choose M here: the module + will be called ptp_dte. + +config PTP_1588_CLOCK_QORIQ + tristate "Freescale QorIQ 1588 timer as PTP clock" + depends on GIANFAR || FSL_DPAA_ETH || FSL_DPAA2_ETH || FSL_ENETC || FSL_ENETC_VF || COMPILE_TEST + depends on PTP_1588_CLOCK + default y if GIANFAR || FSL_DPAA_ETH || FSL_DPAA2_ETH || FSL_ENETC || FSL_ENETC_VF + help + This driver adds support for using the Freescale QorIQ 1588 + timer as a PTP clock. This clock is only useful if your PTP + programs are getting hardware time stamps on the PTP Ethernet + packets using the SO_TIMESTAMPING API. + + To compile this driver as a module, choose M here: the module + 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 + +config DP83640_PHY + tristate "Driver for the National Semiconductor DP83640 PHYTER" + depends on NETWORK_PHY_TIMESTAMPING + depends on PHYLIB + depends on PTP_1588_CLOCK + select CRC32 + select PHY_PACKAGE + help + Supports the DP83640 PHYTER with IEEE 1588 features. + + This driver adds support for using the DP83640 as a PTP + clock. This clock is only useful if your PTP programs are + getting hardware time stamps on the PTP Ethernet packets + using the SO_TIMESTAMPING API. + + In order for this to work, your MAC driver must also + implement the skb_tx_timestamp() function. + +config PTP_1588_CLOCK_INES + tristate "ZHAW InES PTP time stamping IP core" + depends on NETWORK_PHY_TIMESTAMPING + depends on HAS_IOMEM + depends on PHYLIB + depends on PTP_1588_CLOCK + help + This driver adds support for using the ZHAW InES 1588 IP + core. This clock is only useful if the MII bus of your MAC + is wired up to the core. + +config PTP_1588_CLOCK_PCH + tristate "Intel PCH EG20T as PTP clock" + depends on MIPS_GENERIC || X86_32 || COMPILE_TEST + depends on HAS_IOMEM && PCI + depends on NET + depends on PTP_1588_CLOCK + help + This driver adds support for using the PCH EG20T as a PTP + clock. The hardware supports time stamping of PTP packets + when using the end-to-end delay (E2E) mechanism. The peer + delay mechanism (P2P) is not supported. + + This clock is only useful if your PTP programs are getting + hardware time stamps on the PTP Ethernet packets using the + SO_TIMESTAMPING API. + + To compile this driver as a module, choose M here: the module + will be called ptp_pch. + +config PTP_1588_CLOCK_KVM + tristate "KVM virtual PTP clock" + depends on PTP_1588_CLOCK + depends on (KVM_GUEST && X86) || (HAVE_ARM_SMCCC_DISCOVERY && ARM_ARCH_TIMER) + default y + help + This driver adds support for using kvm infrastructure as a PTP + clock. This clock is only useful if you are using KVM guests. + + To compile this driver as a module, choose M here: the module + will be called ptp_kvm. + +config PTP_1588_CLOCK_VMCLOCK + tristate "Virtual machine PTP clock" + depends on X86_TSC || ARM_ARCH_TIMER + depends on PTP_1588_CLOCK && ARCH_SUPPORTS_INT128 + default PTP_1588_CLOCK_KVM + help + This driver adds support for using a virtual precision clock + advertised by the hypervisor. This clock is only useful in virtual + machines where such a device is present. + + Unlike the KVM virtual PTP clock, the VMCLOCK device offers support + for reliable timekeeping even across live migration. So this driver + is enabled by default whenever the KVM PTP clock is. + + To compile this driver as a module, choose M here: the module + will be called ptp_vmclock. + +config PTP_1588_CLOCK_IDT82P33 + tristate "IDT 82P33xxx PTP clock" + depends on PTP_1588_CLOCK && I2C + default n + help + This driver adds support for using the IDT 82P33xxx as a PTP + clock. This clock is only useful if your time stamping MAC + is connected to the IDT chip. + + To compile this driver as a module, choose M here: the module + will be called ptp_idt82p33. + +config PTP_1588_CLOCK_IDTCM + tristate "IDT CLOCKMATRIX as PTP clock" + depends on PTP_1588_CLOCK && I2C + default n + help + This driver adds support for using IDT CLOCKMATRIX(TM) as a PTP + clock. This clock is only useful if your time stamping MAC + is connected to the IDT chip. + + To compile this driver as a module, choose M here: the module + will be called ptp_clockmatrix. + +config PTP_1588_CLOCK_FC3W + tristate "RENESAS FemtoClock3 Wireless as PTP clock" + depends on PTP_1588_CLOCK && I2C + default n + help + This driver adds support for using Renesas FemtoClock3 Wireless + as a PTP clock. This clock is only useful if your time stamping + MAC is connected to the RENESAS chip. + + To compile this driver as a module, choose M here: the module + will be called ptp_fc3. + +config PTP_1588_CLOCK_MOCK + tristate "Mock-up PTP clock" + depends on PTP_1588_CLOCK + help + This driver offers a set of PTP clock manipulation operations over + the system monotonic time. It can be used by virtual network device + drivers to emulate PTP capabilities. + + To compile this driver as a module, choose M here: the module + will be called ptp_mock. + +config PTP_1588_CLOCK_VMW + tristate "VMware virtual PTP clock" + depends on ACPI && HYPERVISOR_GUEST && X86 + depends on PTP_1588_CLOCK + help + This driver adds support for using VMware virtual precision + clock device as a PTP clock. This is only useful in virtual + machines running on VMware virtual infrastructure. + + To compile this driver as a module, choose M here: the module + will be called ptp_vmw. + +config PTP_1588_CLOCK_OCP + tristate "OpenCompute TimeCard as PTP clock" + depends on PTP_1588_CLOCK + depends on HAS_IOMEM && PCI + depends on I2C && MTD + depends on SERIAL_8250 + depends on !S390 + depends on COMMON_CLK + select NET_DEVLINK + select CRC16 + select DPLL + help + This driver adds support for an OpenCompute time card. + + The OpenCompute time card is an atomic clock along with + a GPS receiver that provides a Grandmaster clock source + for a PTP enabled network. + + More information is available at http://www.timingcard.com/ + +config PTP_DFL_TOD + tristate "FPGA DFL ToD Driver" + depends on FPGA_DFL + depends on PTP_1588_CLOCK + help + The DFL (Device Feature List) device driver for the Intel ToD + (Time-of-Day) device in FPGA card. The ToD IP within the FPGA + is exposed as PTP Hardware Clock (PHC) device to the Linux PTP + stack to synchronize the system clock to its ToD information + using phc2sys utility of the Linux PTP stack. + + To compile this driver as a module, choose M here: the module + will be called ptp_dfl_tod. + +config PTP_S390 + tristate "S390 PTP driver" + depends on PTP_1588_CLOCK + depends on S390 + help + This driver adds support for S390 time steering via the PtP + interface. This works by adding a in-kernel clock delta value, + which is always added to time values used in the kernel. The PtP + 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 new file mode 100644 index 000000000..bdc47e284 --- /dev/null +++ b/drivers/ptp/Makefile @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for PTP 1588 clock support. +# + +ptp-y := ptp_clock.o ptp_chardev.o ptp_sysfs.o ptp_vclock.o +ptp_kvm-$(CONFIG_X86) := ptp_kvm_x86.o ptp_kvm_common.o +ptp_kvm-$(CONFIG_HAVE_ARM_SMCCC) := ptp_kvm_arm.o ptp_kvm_common.o +obj-$(CONFIG_PTP_1588_CLOCK) += ptp.o +obj-$(CONFIG_PTP_1588_CLOCK_DTE) += ptp_dte.o +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 +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 +obj-$(CONFIG_PTP_1588_CLOCK_MOCK) += ptp_mock.o +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 new file mode 100644 index 000000000..dc23cd708 --- /dev/null +++ b/drivers/ptp/ptp_chardev.c @@ -0,0 +1,646 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PTP 1588 clock support - character device implementation. + * + * Copyright (C) 2010 OMICRON electronics GmbH + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ptp_private.h" + +static int ptp_disable_pinfunc(struct ptp_clock_info *ops, + enum ptp_pin_function func, unsigned int chan) +{ + struct ptp_clock_request rq; + int err = 0; + + memset(&rq, 0, sizeof(rq)); + + switch (func) { + case PTP_PF_NONE: + break; + case PTP_PF_EXTTS: + rq.type = PTP_CLK_REQ_EXTTS; + rq.extts.index = chan; + err = ops->enable(ops, &rq, 0); + break; + case PTP_PF_PEROUT: + rq.type = PTP_CLK_REQ_PEROUT; + rq.perout.index = chan; + err = ops->enable(ops, &rq, 0); + break; + case PTP_PF_PHYSYNC: + break; + default: + return -EINVAL; + } + + 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) +{ + struct ptp_clock_info *info = ptp->info; + struct ptp_pin_desc *pin1 = NULL, *pin2 = &info->pin_config[pin]; + unsigned int i; + + /* Check to see if any other pin previously had this function. */ + for (i = 0; i < info->n_pins; i++) { + if (info->pin_config[i].func == func && + info->pin_config[i].chan == chan) { + pin1 = &info->pin_config[i]; + break; + } + } + if (pin1 && i == pin) + return 0; + + /* Check the desired function and channel. */ + switch (func) { + case PTP_PF_NONE: + break; + case PTP_PF_EXTTS: + if (chan >= info->n_ext_ts) + return -EINVAL; + break; + case PTP_PF_PEROUT: + if (chan >= info->n_per_out) + return -EINVAL; + break; + case PTP_PF_PHYSYNC: + if (chan != 0) + return -EINVAL; + break; + default: + return -EINVAL; + } + + if (info->verify(info, pin, func, chan)) { + pr_err("driver cannot use function %u and channel %u on pin %u\n", + func, chan, pin); + return -EOPNOTSUPP; + } + + /* 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; + + return 0; +} + +int ptp_open(struct posix_clock_context *pccontext, fmode_t fmode) +{ + struct ptp_clock *ptp = container_of(pccontext->clk, struct ptp_clock, clock); + struct timestamp_event_queue *queue; + char debugfsname[32]; + + queue = kzalloc(sizeof(*queue), GFP_KERNEL); + if (!queue) + return -EINVAL; + queue->mask = bitmap_alloc(PTP_MAX_CHANNELS, GFP_KERNEL); + if (!queue->mask) { + kfree(queue); + return -EINVAL; + } + bitmap_set(queue->mask, 0, PTP_MAX_CHANNELS); + spin_lock_init(&queue->lock); + scoped_guard(spinlock_irq, &ptp->tsevqs_lock) + list_add_tail(&queue->qlist, &ptp->tsevqs); + pccontext->private_clkdata = queue; + + /* Debugfs contents */ + sprintf(debugfsname, "0x%p", queue); + queue->debugfs_instance = + debugfs_create_dir(debugfsname, ptp->debugfs_root); + queue->dfs_bitmap.array = (u32 *)queue->mask; + queue->dfs_bitmap.n_elements = + DIV_ROUND_UP(PTP_MAX_CHANNELS, BITS_PER_BYTE * sizeof(u32)); + debugfs_create_u32_array("mask", 0444, queue->debugfs_instance, + &queue->dfs_bitmap); + + return 0; +} + +int ptp_release(struct posix_clock_context *pccontext) +{ + struct timestamp_event_queue *queue = pccontext->private_clkdata; + struct ptp_clock *ptp = + container_of(pccontext->clk, struct ptp_clock, clock); + + debugfs_remove(queue->debugfs_instance); + pccontext->private_clkdata = NULL; + scoped_guard(spinlock_irq, &ptp->tsevqs_lock) + list_del(&queue->qlist); + bitmap_free(queue->mask); + kfree(queue); + return 0; +} + +static long ptp_clock_getcaps(struct ptp_clock *ptp, void __user *arg) +{ + struct ptp_clock_caps caps = { + .max_adj = ptp->info->max_adj, + .n_alarm = ptp->info->n_alarm, + .n_ext_ts = ptp->info->n_ext_ts, + .n_per_out = ptp->info->n_per_out, + .pps = ptp->info->pps, + .n_pins = ptp->info->n_pins, + .cross_timestamping = ptp->info->getcrosststamp != NULL, + .adjust_phase = ptp->info->adjphase != NULL && + ptp->info->getmaxphase != NULL, + }; + + if (caps.adjust_phase) + caps.max_phase_adj = ptp->info->getmaxphase(ptp->info); + + return copy_to_user(arg, &caps, sizeof(caps)) ? -EFAULT : 0; +} + +static long ptp_extts_request(struct ptp_clock *ptp, unsigned int cmd, void __user *arg) +{ + struct ptp_clock_request req = { .type = PTP_CLK_REQ_EXTTS }; + struct ptp_clock_info *ops = ptp->info; + unsigned int supported_extts_flags; + + if (copy_from_user(&req.extts, arg, sizeof(req.extts))) + return -EFAULT; + + if (cmd == PTP_EXTTS_REQUEST2) { + /* Tell the drivers to check the flags carefully. */ + req.extts.flags |= PTP_STRICT_FLAGS; + /* Make sure no reserved bit is set. */ + if ((req.extts.flags & ~PTP_EXTTS_VALID_FLAGS) || + req.extts.rsv[0] || req.extts.rsv[1]) + return -EINVAL; + + /* Ensure one of the rising/falling edge bits is set. */ + if ((req.extts.flags & PTP_ENABLE_FEATURE) && + (req.extts.flags & PTP_EXTTS_EDGES) == 0) + return -EINVAL; + } else { + req.extts.flags &= PTP_EXTTS_V1_VALID_FLAGS; + memset(req.extts.rsv, 0, sizeof(req.extts.rsv)); + } + + if (req.extts.index >= ops->n_ext_ts) + return -EINVAL; + + supported_extts_flags = ptp->info->supported_extts_flags; + /* The PTP_ENABLE_FEATURE flag is always supported. */ + supported_extts_flags |= PTP_ENABLE_FEATURE; + /* If the driver does not support strictly checking flags, the + * PTP_RISING_EDGE and PTP_FALLING_EDGE flags are merely hints + * which are not enforced. + */ + if (!(supported_extts_flags & PTP_STRICT_FLAGS)) + supported_extts_flags |= PTP_EXTTS_EDGES; + /* Reject unsupported flags */ + if (req.extts.flags & ~supported_extts_flags) + return -EOPNOTSUPP; + + scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &ptp->pincfg_mux) + return ops->enable(ops, &req, req.extts.flags & PTP_ENABLE_FEATURE ? 1 : 0); +} + +static long ptp_perout_request(struct ptp_clock *ptp, unsigned int cmd, void __user *arg) +{ + struct ptp_clock_request req = { .type = PTP_CLK_REQ_PEROUT }; + struct ptp_perout_request *perout = &req.perout; + struct ptp_clock_info *ops = ptp->info; + + if (copy_from_user(perout, arg, sizeof(*perout))) + return -EFAULT; + + if (cmd == PTP_PEROUT_REQUEST2) { + if (perout->flags & ~PTP_PEROUT_VALID_FLAGS) + return -EINVAL; + + /* + * The "on" field has undefined meaning if + * PTP_PEROUT_DUTY_CYCLE isn't set, we must still treat it + * as reserved, which must be set to zero. + */ + if (!(perout->flags & PTP_PEROUT_DUTY_CYCLE) && + !mem_is_zero(perout->rsv, sizeof(perout->rsv))) + return -EINVAL; + + if (perout->flags & PTP_PEROUT_DUTY_CYCLE) { + /* The duty cycle must be subunitary. */ + if (perout->on.sec > perout->period.sec || + (perout->on.sec == perout->period.sec && + perout->on.nsec > perout->period.nsec)) + return -ERANGE; + } + + if (perout->flags & PTP_PEROUT_PHASE) { + /* + * The phase should be specified modulo the period, + * therefore anything equal or larger than 1 period + * is invalid. + */ + if (perout->phase.sec > perout->period.sec || + (perout->phase.sec == perout->period.sec && + perout->phase.nsec >= perout->period.nsec)) + return -ERANGE; + } + } else { + perout->flags &= PTP_PEROUT_V1_VALID_FLAGS; + memset(perout->rsv, 0, sizeof(perout->rsv)); + } + + if (perout->index >= ops->n_per_out) + return -EINVAL; + if (perout->flags & ~ops->supported_perout_flags) + return -EOPNOTSUPP; + + scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &ptp->pincfg_mux) + return ops->enable(ops, &req, perout->period.sec || perout->period.nsec); +} + +static long ptp_enable_pps(struct ptp_clock *ptp, bool enable) +{ + struct ptp_clock_request req = { .type = PTP_CLK_REQ_PPS }; + struct ptp_clock_info *ops = ptp->info; + + if (!capable(CAP_SYS_TIME)) + return -EPERM; + + scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &ptp->pincfg_mux) + return ops->enable(ops, &req, enable); +} + +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 system_device_crosststamp xtstamp = { .clock_id = CLOCK_REALTIME }; + struct ptp_sys_offset_precise precise_offset; + struct timespec64 ts; + int err; + + if (!crosststamp_fn) + return -EOPNOTSUPP; + + err = crosststamp_fn(ptp->info, &xtstamp); + if (err) + return err; + + memset(&precise_offset, 0, sizeof(precise_offset)); + ts = ktime_to_timespec64(xtstamp.device); + precise_offset.device.sec = ts.tv_sec; + precise_offset.device.nsec = ts.tv_nsec; + ts = ktime_to_timespec64(xtstamp.sys_systime); + precise_offset.sys_realtime.sec = ts.tv_sec; + precise_offset.sys_realtime.nsec = ts.tv_nsec; + ts = ktime_to_timespec64(xtstamp.sys_monoraw); + precise_offset.sys_monoraw.sec = ts.tv_sec; + precise_offset.sys_monoraw.nsec = ts.tv_nsec; + + return copy_to_user(arg, &precise_offset, sizeof(precise_offset)) ? -EFAULT : 0; +} + +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 (!gettimex_fn) + return -EOPNOTSUPP; + + extoff = memdup_user(arg, sizeof(*extoff)); + if (IS_ERR(extoff)) + return PTR_ERR(extoff); + + if (extoff->n_samples > PTP_MAX_SAMPLES || extoff->rsv[0] || extoff->rsv[1]) + return -EINVAL; + + switch (extoff->clockid) { + case CLOCK_REALTIME: + case CLOCK_MONOTONIC: + case CLOCK_MONOTONIC_RAW: + break; + case CLOCK_AUX ... CLOCK_AUX_LAST: + if (IS_ENABLED(CONFIG_POSIX_AUX_CLOCKS)) + break; + fallthrough; + default: + return -EINVAL; + } + + sts.clockid = extoff->clockid; + for (unsigned int i = 0; i < extoff->n_samples; i++) { + struct timespec64 ts; + int err; + + err = gettimex_fn(ptp->info, &ts, &sts); + if (err) + return err; + + /* Filter out disabled or unavailable clocks */ + if (!sts.pre_sts.valid || !sts.post_sts.valid) + return -EINVAL; + + extoff->ts[i][1].sec = ts.tv_sec; + extoff->ts[i][1].nsec = ts.tv_nsec; + + ts = ktime_to_timespec64(sts.pre_sts.systime); + extoff->ts[i][0].sec = ts.tv_sec; + extoff->ts[i][0].nsec = ts.tv_nsec; + + ts = ktime_to_timespec64(sts.post_sts.systime); + extoff->ts[i][2].sec = ts.tv_sec; + extoff->ts[i][2].nsec = ts.tv_nsec; + } + + return copy_to_user(arg, extoff, sizeof(*extoff)) ? -EFAULT : 0; +} + +static long ptp_sys_offset(struct ptp_clock *ptp, void __user *arg) +{ + struct ptp_sys_offset *sysoff __free(kfree) = NULL; + struct ptp_clock_time *pct; + struct timespec64 ts; + + sysoff = memdup_user(arg, sizeof(*sysoff)); + if (IS_ERR(sysoff)) + return PTR_ERR(sysoff); + + if (sysoff->n_samples > PTP_MAX_SAMPLES) + return -EINVAL; + + pct = &sysoff->ts[0]; + for (unsigned int i = 0; i < sysoff->n_samples; i++) { + struct ptp_clock_info *ops = ptp->info; + int err; + + ktime_get_real_ts64(&ts); + pct->sec = ts.tv_sec; + pct->nsec = ts.tv_nsec; + pct++; + if (ops->gettimex64) + err = ops->gettimex64(ops, &ts, NULL); + else + err = ops->gettime64(ops, &ts); + if (err) + return err; + pct->sec = ts.tv_sec; + pct->nsec = ts.tv_nsec; + pct++; + } + ktime_get_real_ts64(&ts); + pct->sec = ts.tv_sec; + pct->nsec = ts.tv_nsec; + + return copy_to_user(arg, sysoff, sizeof(*sysoff)) ? -EFAULT : 0; +} + +static long ptp_pin_getfunc(struct ptp_clock *ptp, unsigned int cmd, void __user *arg) +{ + struct ptp_clock_info *ops = ptp->info; + struct ptp_pin_desc pd; + + if (copy_from_user(&pd, arg, sizeof(pd))) + return -EFAULT; + + if (cmd == PTP_PIN_GETFUNC2 && !mem_is_zero(pd.rsv, sizeof(pd.rsv))) + return -EINVAL; + + if (pd.index >= ops->n_pins) + return -EINVAL; + + scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &ptp->pincfg_mux) + pd = ops->pin_config[array_index_nospec(pd.index, ops->n_pins)]; + + return copy_to_user(arg, &pd, sizeof(pd)) ? -EFAULT : 0; +} + +static long ptp_pin_setfunc(struct ptp_clock *ptp, unsigned int cmd, void __user *arg) +{ + struct ptp_clock_info *ops = ptp->info; + struct ptp_pin_desc pd; + unsigned int pin_index; + + if (copy_from_user(&pd, arg, sizeof(pd))) + return -EFAULT; + + if (cmd == PTP_PIN_SETFUNC2 && !mem_is_zero(pd.rsv, sizeof(pd.rsv))) + return -EINVAL; + + if (pd.index >= ops->n_pins) + return -EINVAL; + + pin_index = array_index_nospec(pd.index, ops->n_pins); + scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &ptp->pincfg_mux) + return ptp_set_pinfunc(ptp, pin_index, pd.func, pd.chan); +} + +static long ptp_mask_clear_all(struct timestamp_event_queue *tsevq) +{ + bitmap_clear(tsevq->mask, 0, PTP_MAX_CHANNELS); + return 0; +} + +static long ptp_mask_en_single(struct timestamp_event_queue *tsevq, void __user *arg) +{ + unsigned int channel; + + if (copy_from_user(&channel, arg, sizeof(channel))) + return -EFAULT; + if (channel >= PTP_MAX_CHANNELS) + return -EFAULT; + set_bit(channel, tsevq->mask); + return 0; +} + +long ptp_ioctl(struct posix_clock_context *pccontext, unsigned int cmd, + unsigned long arg) +{ + struct ptp_clock *ptp = container_of(pccontext->clk, struct ptp_clock, clock); + void __user *argptr; + + if (in_compat_syscall() && cmd != PTP_ENABLE_PPS && cmd != PTP_ENABLE_PPS2) + arg = (unsigned long)compat_ptr(arg); + argptr = (void __force __user *)arg; + + switch (cmd) { + case PTP_CLOCK_GETCAPS: + case PTP_CLOCK_GETCAPS2: + return ptp_clock_getcaps(ptp, argptr); + + case PTP_EXTTS_REQUEST: + case PTP_EXTTS_REQUEST2: + if ((pccontext->fp->f_mode & FMODE_WRITE) == 0) + return -EACCES; + return ptp_extts_request(ptp, cmd, argptr); + + case PTP_PEROUT_REQUEST: + case PTP_PEROUT_REQUEST2: + if ((pccontext->fp->f_mode & FMODE_WRITE) == 0) + return -EACCES; + return ptp_perout_request(ptp, cmd, argptr); + + case PTP_ENABLE_PPS: + case PTP_ENABLE_PPS2: + if ((pccontext->fp->f_mode & FMODE_WRITE) == 0) + return -EACCES; + return ptp_enable_pps(ptp, !!arg); + + case PTP_SYS_OFFSET_PRECISE: + case PTP_SYS_OFFSET_PRECISE2: + 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, + ptp->info->gettimex64); + + case PTP_SYS_OFFSET: + case PTP_SYS_OFFSET2: + return ptp_sys_offset(ptp, argptr); + + case PTP_PIN_GETFUNC: + case PTP_PIN_GETFUNC2: + return ptp_pin_getfunc(ptp, cmd, argptr); + + case PTP_PIN_SETFUNC: + case PTP_PIN_SETFUNC2: + if ((pccontext->fp->f_mode & FMODE_WRITE) == 0) + return -EACCES; + return ptp_pin_setfunc(ptp, cmd, argptr); + + case PTP_MASK_CLEAR_ALL: + return ptp_mask_clear_all(pccontext->private_clkdata); + + case PTP_MASK_EN_SINGLE: + return ptp_mask_en_single(pccontext->private_clkdata, argptr); + + case PTP_SYS_OFFSET_PRECISE_CYCLES: + if (!ptp->has_cycles) + return -EOPNOTSUPP; + return ptp_sys_offset_precise(ptp, argptr, + ptp->info->getcrosscycles); + + case PTP_SYS_OFFSET_EXTENDED_CYCLES: + if (!ptp->has_cycles) + return -EOPNOTSUPP; + return ptp_sys_offset_extended(ptp, argptr, + ptp->info->getcyclesx64); + default: + return -ENOTTY; + } +} + +__poll_t ptp_poll(struct posix_clock_context *pccontext, struct file *fp, + poll_table *wait) +{ + struct ptp_clock *ptp = + container_of(pccontext->clk, struct ptp_clock, clock); + struct timestamp_event_queue *queue; + + queue = pccontext->private_clkdata; + if (!queue) + return EPOLLERR; + + poll_wait(fp, &ptp->tsev_wq, wait); + + return queue_cnt(queue) ? EPOLLIN : 0; +} + +#define EXTTS_BUFSIZE (PTP_BUF_TIMESTAMPS * sizeof(struct ptp_extts_event)) + +ssize_t ptp_read(struct posix_clock_context *pccontext, uint rdflags, + char __user *buf, size_t cnt) +{ + struct ptp_clock *ptp = container_of(pccontext->clk, struct ptp_clock, clock); + struct timestamp_event_queue *queue; + struct ptp_extts_event *event; + ssize_t result; + + queue = pccontext->private_clkdata; + if (!queue) + return -EINVAL; + + if (cnt % sizeof(*event) != 0) + return -EINVAL; + + if (cnt > EXTTS_BUFSIZE) + cnt = EXTTS_BUFSIZE; + + if (wait_event_interruptible(ptp->tsev_wq, ptp->defunct || queue_cnt(queue))) + return -ERESTARTSYS; + + if (ptp->defunct) + return -ENODEV; + + event = kmalloc(EXTTS_BUFSIZE, GFP_KERNEL); + if (!event) + return -ENOMEM; + + scoped_guard(spinlock_irq, &queue->lock) { + size_t qcnt = min((size_t)queue_cnt(queue), cnt / sizeof(*event)); + + for (size_t i = 0; i < qcnt; i++) { + event[i] = queue->buf[queue->head]; + /* Paired with READ_ONCE() in queue_cnt() */ + WRITE_ONCE(queue->head, (queue->head + 1) % PTP_MAX_TIMESTAMPS); + } + cnt = qcnt * sizeof(*event); + } + + result = cnt; + if (copy_to_user(buf, event, cnt)) + result = -EFAULT; + + kfree(event); + return result; +} diff --git a/drivers/ptp/ptp_clock.c b/drivers/ptp/ptp_clock.c new file mode 100644 index 000000000..4111342d6 --- /dev/null +++ b/drivers/ptp/ptp_clock.c @@ -0,0 +1,723 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PTP 1588 clock support + * + * Copyright (C) 2010 OMICRON electronics GmbH + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ptp_private.h" + +#define PTP_MAX_ALARMS 4 +#define PTP_PPS_DEFAULTS (PPS_CAPTUREASSERT | PPS_OFFSETASSERT) +#define PTP_PPS_EVENT PPS_CAPTUREASSERT +#define PTP_PPS_MODE (PTP_PPS_DEFAULTS | PPS_CANWAIT | PPS_TSFMT_TSPEC) + +const struct class ptp_class = { + .name = "ptp", + .dev_groups = ptp_groups +}; + +/* private globals */ + +static dev_t ptp_devt; + +static DEFINE_XARRAY_ALLOC(ptp_clocks_map); + +/* time stamp event queue operations */ + +static inline int queue_free(struct timestamp_event_queue *q) +{ + return PTP_MAX_TIMESTAMPS - queue_cnt(q) - 1; +} + +static void enqueue_external_timestamp(struct timestamp_event_queue *queue, + struct ptp_clock_event *src) +{ + struct ptp_extts_event *dst; + struct timespec64 offset_ts; + unsigned long flags; + s64 seconds; + u32 remainder; + + if (src->type == PTP_CLOCK_EXTTS) { + seconds = div_u64_rem(src->timestamp, 1000000000, &remainder); + } else if (src->type == PTP_CLOCK_EXTOFF) { + offset_ts = ns_to_timespec64(src->offset); + seconds = offset_ts.tv_sec; + remainder = offset_ts.tv_nsec; + } else { + WARN(1, "%s: unknown type %d\n", __func__, src->type); + return; + } + + spin_lock_irqsave(&queue->lock, flags); + + dst = &queue->buf[queue->tail]; + dst->index = src->index; + dst->flags = PTP_EXTTS_EVENT_VALID; + dst->t.sec = seconds; + dst->t.nsec = remainder; + if (src->type == PTP_CLOCK_EXTOFF) + dst->flags |= PTP_EXT_OFFSET; + + /* Both WRITE_ONCE() are paired with READ_ONCE() in queue_cnt() */ + if (!queue_free(queue)) + WRITE_ONCE(queue->head, (queue->head + 1) % PTP_MAX_TIMESTAMPS); + + WRITE_ONCE(queue->tail, (queue->tail + 1) % PTP_MAX_TIMESTAMPS); + + spin_unlock_irqrestore(&queue->lock, flags); +} + +/* posix clock implementation */ + +static int ptp_clock_getres(struct posix_clock *pc, struct timespec64 *tp) +{ + tp->tv_sec = 0; + tp->tv_nsec = 1; + return 0; +} + +static int ptp_clock_settime(struct posix_clock *pc, const struct timespec64 *tp) +{ + struct ptp_clock *ptp = container_of(pc, struct ptp_clock, clock); + + if (ptp_clock_freerun(ptp)) { + pr_err_ratelimited("ptp: physical clock is free running\n"); + return -EBUSY; + } + + if (!timespec64_valid_settod(tp)) + return -EINVAL; + + return ptp->info->settime64(ptp->info, tp); +} + +static int ptp_clock_gettime(struct posix_clock *pc, struct timespec64 *tp) +{ + struct ptp_clock *ptp = container_of(pc, struct ptp_clock, clock); + int err; + + if (ptp->info->gettimex64) + err = ptp->info->gettimex64(ptp->info, tp, NULL); + else + err = ptp->info->gettime64(ptp->info, tp); + return err; +} + +static int ptp_clock_adjtime(struct posix_clock *pc, struct __kernel_timex *tx) +{ + struct ptp_clock *ptp = container_of(pc, struct ptp_clock, clock); + struct ptp_clock_info *ops; + int err = -EOPNOTSUPP; + + if (tx->modes & (ADJ_SETOFFSET | ADJ_FREQUENCY | ADJ_OFFSET) && + ptp_clock_freerun(ptp)) { + pr_err("ptp: physical clock is free running\n"); + return -EBUSY; + } + + ops = ptp->info; + + if (tx->modes & ADJ_SETOFFSET) { + struct timespec64 ts, ts2; + ktime_t kt; + s64 delta; + + ts.tv_sec = tx->time.tv_sec; + ts.tv_nsec = tx->time.tv_usec; + + if (!(tx->modes & ADJ_NANO)) + ts.tv_nsec *= 1000; + + 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); + } else if (tx->modes & ADJ_FREQUENCY) { + long ppb; + s64 tmp; + + /* + * scaled_ppm_to_ppb() multiplies (1 + freq) by 125 in s64; + * reject a ->freq large enough to overflow that, which would + * otherwise wrap the result back into the max_adj range. + */ + if (check_add_overflow((s64)tx->freq, (s64)1, &tmp) || + check_mul_overflow(tmp, (s64)125, &tmp)) + return -ERANGE; + ppb = scaled_ppm_to_ppb(tx->freq); + if (ppb > ops->max_adj || ppb < -ops->max_adj) + return -ERANGE; + err = ops->adjfine(ops, tx->freq); + if (!err) + ptp->dialed_frequency = tx->freq; + } else if (tx->modes & ADJ_OFFSET) { + if (ops->adjphase) { + s32 max_phase_adj = ops->getmaxphase(ops); + s32 offset = tx->offset; + + if (!(tx->modes & ADJ_NANO)) + offset *= NSEC_PER_USEC; + + if (offset > max_phase_adj || offset < -max_phase_adj) + return -ERANGE; + + err = ops->adjphase(ops, offset); + } + } else if (tx->modes == 0) { + tx->freq = ptp->dialed_frequency; + err = 0; + } + + return err; +} + +static struct posix_clock_operations ptp_clock_ops = { + .owner = THIS_MODULE, + .clock_adjtime = ptp_clock_adjtime, + .clock_gettime = ptp_clock_gettime, + .clock_getres = ptp_clock_getres, + .clock_settime = ptp_clock_settime, + .ioctl = ptp_ioctl, + .open = ptp_open, + .release = ptp_release, + .poll = ptp_poll, + .read = ptp_read, +}; + +static void ptp_clock_release(struct device *dev) +{ + struct ptp_clock *ptp = container_of(dev, struct ptp_clock, dev); + struct timestamp_event_queue *tsevq; + unsigned long flags; + + ptp_cleanup_pin_groups(ptp); + kfree(ptp->vclock_index); + mutex_destroy(&ptp->pincfg_mux); + mutex_destroy(&ptp->n_vclocks_mux); + /* Delete first entry */ + spin_lock_irqsave(&ptp->tsevqs_lock, flags); + tsevq = list_first_entry(&ptp->tsevqs, struct timestamp_event_queue, + qlist); + list_del(&tsevq->qlist); + spin_unlock_irqrestore(&ptp->tsevqs_lock, flags); + bitmap_free(tsevq->mask); + kfree(tsevq); + debugfs_remove(ptp->debugfs_root); + xa_erase(&ptp_clocks_map, ptp->index); + kfree(ptp); +} + +static int ptp_getcycles64(struct ptp_clock_info *info, struct timespec64 *ts) +{ + if (info->getcyclesx64) + return info->getcyclesx64(info, ts, NULL); + else + return info->gettime64(info, ts); +} + +static int ptp_enable(struct ptp_clock_info *ptp, struct ptp_clock_request *request, int on) +{ + return -EOPNOTSUPP; +} + +static void ptp_aux_kworker(struct kthread_work *work) +{ + struct ptp_clock *ptp = container_of(work, struct ptp_clock, + aux_work.work); + struct ptp_clock_info *info = ptp->info; + long delay; + + delay = info->do_aux_work(info); + + if (delay >= 0) + 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, + struct device *parent) +{ + struct ptp_clock *ptp; + struct timestamp_event_queue *queue = NULL; + int err, index, major = MAJOR(ptp_devt); + char debugfsname[16]; + size_t size; + + if (WARN_ON_ONCE(info->n_alarm > PTP_MAX_ALARMS || + (!info->gettimex64 && !info->gettime64) || + !info->settime64)) + return ERR_PTR(-EINVAL); + + /* Initialize a clock structure. */ + ptp = kzalloc_obj(struct ptp_clock); + if (!ptp) { + err = -ENOMEM; + goto no_memory; + } + + err = xa_alloc(&ptp_clocks_map, &index, ptp, xa_limit_31b, + GFP_KERNEL); + if (err) + goto no_slot; + + ptp->clock.ops = ptp_clock_ops; + ptp->info = info; + ptp->devid = MKDEV(major, index); + ptp->index = index; + INIT_LIST_HEAD(&ptp->tsevqs); + queue = kzalloc_obj(*queue); + if (!queue) { + err = -ENOMEM; + goto no_memory_queue; + } + list_add_tail(&queue->qlist, &ptp->tsevqs); + spin_lock_init(&ptp->tsevqs_lock); + queue->mask = bitmap_alloc(PTP_MAX_CHANNELS, GFP_KERNEL); + if (!queue->mask) { + err = -ENOMEM; + goto no_memory_bitmap; + } + bitmap_set(queue->mask, 0, PTP_MAX_CHANNELS); + spin_lock_init(&queue->lock); + mutex_init(&ptp->pincfg_mux); + mutex_init(&ptp->n_vclocks_mux); + init_waitqueue_head(&ptp->tsev_wq); + + if (ptp->info->getcycles64 || ptp->info->getcyclesx64) { + ptp->has_cycles = true; + if (!ptp->info->getcycles64 && ptp->info->getcyclesx64) + ptp->info->getcycles64 = ptp_getcycles64; + } else { + /* Free running cycle counter not supported, use time. */ + ptp->info->getcycles64 = ptp_getcycles64; + + if (ptp->info->gettimex64) + ptp->info->getcyclesx64 = ptp->info->gettimex64; + + if (ptp->info->getcrosststamp) + ptp->info->getcrosscycles = ptp->info->getcrosststamp; + } + + if (!ptp->info->enable) + ptp->info->enable = ptp_enable; + + if (ptp->info->do_aux_work) { + kthread_init_delayed_work(&ptp->aux_work, ptp_aux_kworker); + ptp->kworker = kthread_run_worker(0, "ptp%d", ptp->index); + if (IS_ERR(ptp->kworker)) { + err = PTR_ERR(ptp->kworker); + pr_err("failed to create ptp aux_worker %d\n", err); + goto kworker_err; + } + } + + /* PTP virtual clock is being registered under physical clock */ + if (parent && parent->class && parent->class->name && + strcmp(parent->class->name, "ptp") == 0) + ptp->is_virtual_clock = true; + + if (!ptp->is_virtual_clock) { + ptp->max_vclocks = PTP_DEFAULT_MAX_VCLOCKS; + + size = sizeof(int) * ptp->max_vclocks; + ptp->vclock_index = kzalloc(size, GFP_KERNEL); + if (!ptp->vclock_index) { + err = -ENOMEM; + goto no_mem_for_vclocks; + } + } + + err = ptp_populate_pin_groups(ptp); + if (err) + goto no_pin_groups; + + /* Register a new PPS source. */ + if (info->pps) { + struct pps_source_info pps; + memset(&pps, 0, sizeof(pps)); + snprintf(pps.name, PPS_MAX_NAME_LEN, "ptp%d", index); + pps.mode = PTP_PPS_MODE; + pps.owner = info->owner; + ptp->pps_source = pps_register_source(&pps, PTP_PPS_DEFAULTS); + if (IS_ERR(ptp->pps_source)) { + err = PTR_ERR(ptp->pps_source); + pr_err("failed to register pps source\n"); + goto no_pps; + } + ptp->pps_source->lookup_cookie = ptp; + } + + /* Initialize a new device of our class in our clock structure. */ + device_initialize(&ptp->dev); + ptp->dev.devt = ptp->devid; + ptp->dev.class = &ptp_class; + ptp->dev.parent = parent; + ptp->dev.groups = ptp->pin_attr_groups; + ptp->dev.release = ptp_clock_release; + dev_set_drvdata(&ptp->dev, ptp); + dev_set_name(&ptp->dev, "ptp%d", ptp->index); + + /* Create a posix clock and link it to the device. */ + err = posix_clock_register(&ptp->clock, &ptp->dev); + if (err) { + if (ptp->pps_source) + pps_unregister_source(ptp->pps_source); + + if (ptp->kworker) + kthread_destroy_worker(ptp->kworker); + + put_device(&ptp->dev); + + pr_err("failed to create posix clock\n"); + return ERR_PTR(err); + } + + /* 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; + +no_pps: + ptp_cleanup_pin_groups(ptp); +no_pin_groups: + kfree(ptp->vclock_index); +no_mem_for_vclocks: + if (ptp->kworker) + kthread_destroy_worker(ptp->kworker); +kworker_err: + mutex_destroy(&ptp->pincfg_mux); + mutex_destroy(&ptp->n_vclocks_mux); + bitmap_free(queue->mask); +no_memory_bitmap: + list_del(&queue->qlist); + kfree(queue); +no_memory_queue: + xa_erase(&ptp_clocks_map, index); +no_slot: + kfree(ptp); +no_memory: + return ERR_PTR(err); +} +EXPORT_SYMBOL(ptp_clock_register); + +static int unregister_vclock(struct device *dev, void *data) +{ + struct ptp_clock *ptp = dev_get_drvdata(dev); + + ptp_vclock_unregister(info_to_vclock(ptp->info)); + return 0; +} + +int ptp_clock_unregister(struct ptp_clock *ptp) +{ + if (ptp_vclock_in_use(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); + } + + /* Release the clock's resources. */ + if (ptp->pps_source) + pps_unregister_source(ptp->pps_source); + + /* The final put, normally here, will invoke ptp_clock_release(). */ + put_device(&ptp->dev); + + return 0; +} +EXPORT_SYMBOL(ptp_clock_unregister); + +void ptp_clock_event(struct ptp_clock *ptp, struct ptp_clock_event *event) +{ + struct timestamp_event_queue *tsevq; + struct pps_event_time evt; + unsigned long flags; + + switch (event->type) { + + case PTP_CLOCK_ALARM: + break; + + case PTP_CLOCK_EXTTS: + case PTP_CLOCK_EXTOFF: + /* Enqueue timestamp on selected queues */ + spin_lock_irqsave(&ptp->tsevqs_lock, flags); + list_for_each_entry(tsevq, &ptp->tsevqs, qlist) { + if (test_bit((unsigned int)event->index, tsevq->mask)) + enqueue_external_timestamp(tsevq, event); + } + spin_unlock_irqrestore(&ptp->tsevqs_lock, flags); + wake_up_interruptible(&ptp->tsev_wq); + break; + + case PTP_CLOCK_PPS: + pps_get_ts(&evt); + pps_event(ptp->pps_source, &evt, PTP_PPS_EVENT, NULL); + break; + + case PTP_CLOCK_PPSUSR: + pps_event(ptp->pps_source, &event->pps_times, + PTP_PPS_EVENT, NULL); + break; + } +} +EXPORT_SYMBOL(ptp_clock_event); + +int ptp_clock_index(struct ptp_clock *ptp) +{ + return ptp->index; +} +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) +{ + struct ptp_pin_desc *pin = NULL; + int i; + + for (i = 0; i < ptp->info->n_pins; i++) { + if (ptp->info->pin_config[i].func == func && + ptp->info->pin_config[i].chan == chan) { + pin = &ptp->info->pin_config[i]; + break; + } + } + + return pin ? i : -1; +} +EXPORT_SYMBOL(ptp_find_pin); + +int ptp_find_pin_unlocked(struct ptp_clock *ptp, + enum ptp_pin_function func, unsigned int chan) +{ + int result; + + mutex_lock(&ptp->pincfg_mux); + + result = ptp_find_pin(ptp, func, chan); + + mutex_unlock(&ptp->pincfg_mux); + + return result; +} +EXPORT_SYMBOL(ptp_find_pin_unlocked); + +int ptp_schedule_worker(struct ptp_clock *ptp, unsigned long delay) +{ + return kthread_mod_delayed_work(ptp->kworker, &ptp->aux_work, delay); +} +EXPORT_SYMBOL(ptp_schedule_worker); + +void ptp_cancel_worker_sync(struct ptp_clock *ptp) +{ + kthread_cancel_delayed_work_sync(&ptp->aux_work); +} +EXPORT_SYMBOL(ptp_cancel_worker_sync); + +/* module operations */ + +static void __exit ptp_exit(void) +{ + class_unregister(&ptp_class); + unregister_chrdev_region(ptp_devt, MINORMASK + 1); + xa_destroy(&ptp_clocks_map); +} + +static int __init ptp_init(void) +{ + int err; + + err = class_register(&ptp_class); + if (err) { + pr_err("ptp: failed to allocate class\n"); + return err; + } + + err = alloc_chrdev_region(&ptp_devt, 0, MINORMASK + 1, "ptp"); + if (err < 0) { + pr_err("ptp: failed to allocate device region\n"); + goto no_region; + } + + pr_info("PTP clock support registered\n"); + return 0; + +no_region: + class_unregister(&ptp_class); + return err; +} + +subsys_initcall(ptp_init); +module_exit(ptp_exit); + +MODULE_AUTHOR("Richard Cochran "); +MODULE_DESCRIPTION("PTP clocks support"); +MODULE_LICENSE("GPL"); diff --git a/drivers/ptp/ptp_clockmatrix.c b/drivers/ptp/ptp_clockmatrix.c new file mode 100644 index 000000000..59cd6bbb3 --- /dev/null +++ b/drivers/ptp/ptp_clockmatrix.c @@ -0,0 +1,2467 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * PTP hardware clock driver for the IDT ClockMatrix(TM) family of timing and + * synchronization devices. + * + * Copyright (C) 2019 Integrated Device Technology, Inc., a Renesas Company. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ptp_private.h" +#include "ptp_clockmatrix.h" + +MODULE_DESCRIPTION("Driver for IDT ClockMatrix(TM) family"); +MODULE_AUTHOR("Richard Cochran "); +MODULE_AUTHOR("IDT support-1588 "); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL"); + +/* + * The name of the firmware file to be loaded + * over-rides any automatic selection + */ +static char *firmware; +module_param(firmware, charp, 0); + +#define SETTIME_CORRECTION (0) +#define EXTTS_PERIOD_MS (95) + +static int _idtcm_adjfine(struct idtcm_channel *channel, long scaled_ppm); + +static inline int idtcm_read(struct idtcm *idtcm, + u16 module, + u16 regaddr, + u8 *buf, + u16 count) +{ + return regmap_bulk_read(idtcm->regmap, module + regaddr, buf, count); +} + +static inline int idtcm_write(struct idtcm *idtcm, + u16 module, + u16 regaddr, + u8 *buf, + u16 count) +{ + return regmap_bulk_write(idtcm->regmap, module + regaddr, buf, count); +} + +static int contains_full_configuration(struct idtcm *idtcm, + const struct firmware *fw) +{ + struct idtcm_fwrc *rec = (struct idtcm_fwrc *)fw->data; + u16 scratch = IDTCM_FW_REG(idtcm->fw_ver, V520, SCRATCH); + s32 full_count; + s32 count = 0; + u16 regaddr; + u8 loaddr; + s32 len; + + /* 4 bytes skipped every 0x80 */ + full_count = (scratch - GPIO_USER_CONTROL) - + ((scratch >> 7) - (GPIO_USER_CONTROL >> 7)) * 4; + + /* If the firmware contains 'full configuration' SM_RESET can be used + * to ensure proper configuration. + * + * Full configuration is defined as the number of programmable + * bytes within the configuration range minus page offset addr range. + */ + for (len = fw->size; len > 0; len -= sizeof(*rec)) { + regaddr = rec->hiaddr << 8; + regaddr |= rec->loaddr; + + loaddr = rec->loaddr; + + rec++; + + /* Top (status registers) and bottom are read-only */ + if (regaddr < GPIO_USER_CONTROL || regaddr >= scratch) + continue; + + /* Page size 128, last 4 bytes of page skipped */ + if ((loaddr > 0x7b && loaddr <= 0x7f) || loaddr > 0xfb) + continue; + + count++; + } + + return (count >= full_count); +} + +static int char_array_to_timespec(u8 *buf, + u8 count, + struct timespec64 *ts) +{ + u8 i; + u64 nsec; + time64_t sec; + + if (count < TOD_BYTE_COUNT) + return 1; + + /* Sub-nanoseconds are in buf[0]. */ + nsec = buf[4]; + for (i = 0; i < 3; i++) { + nsec <<= 8; + nsec |= buf[3 - i]; + } + + sec = buf[10]; + for (i = 0; i < 5; i++) { + sec <<= 8; + sec |= buf[9 - i]; + } + + ts->tv_sec = sec; + ts->tv_nsec = nsec; + + return 0; +} + +static int timespec_to_char_array(struct timespec64 const *ts, + u8 *buf, + u8 count) +{ + u8 i; + s32 nsec; + time64_t sec; + + if (count < TOD_BYTE_COUNT) + return 1; + + nsec = ts->tv_nsec; + sec = ts->tv_sec; + + /* Sub-nanoseconds are in buf[0]. */ + buf[0] = 0; + for (i = 1; i < 5; i++) { + buf[i] = nsec & 0xff; + nsec >>= 8; + } + + for (i = 5; i < TOD_BYTE_COUNT; i++) { + + buf[i] = sec & 0xff; + sec >>= 8; + } + + return 0; +} + +static int idtcm_strverscmp(const char *version1, const char *version2) +{ + u8 ver1[3], ver2[3]; + int i; + + if (sscanf(version1, "%hhu.%hhu.%hhu", + &ver1[0], &ver1[1], &ver1[2]) != 3) + return -1; + if (sscanf(version2, "%hhu.%hhu.%hhu", + &ver2[0], &ver2[1], &ver2[2]) != 3) + return -1; + + for (i = 0; i < 3; i++) { + if (ver1[i] > ver2[i]) + return 1; + if (ver1[i] < ver2[i]) + return -1; + } + + return 0; +} + +static enum fw_version idtcm_fw_version(const char *version) +{ + enum fw_version ver = V_DEFAULT; + + if (idtcm_strverscmp(version, "4.8.7") >= 0) + ver = V487; + + if (idtcm_strverscmp(version, "5.2.0") >= 0) + ver = V520; + + return ver; +} + +static int clear_boot_status(struct idtcm *idtcm) +{ + u8 buf[4] = {0}; + + return idtcm_write(idtcm, GENERAL_STATUS, BOOT_STATUS, buf, sizeof(buf)); +} + +static int read_boot_status(struct idtcm *idtcm, u32 *status) +{ + int err; + u8 buf[4] = {0}; + + err = idtcm_read(idtcm, GENERAL_STATUS, BOOT_STATUS, buf, sizeof(buf)); + + *status = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0]; + + return err; +} + +static int wait_for_boot_status_ready(struct idtcm *idtcm) +{ + u32 status = 0; + u8 i = 30; /* 30 * 100ms = 3s */ + int err; + + do { + err = read_boot_status(idtcm, &status); + if (err) + return err; + + if (status == 0xA0) + return 0; + + msleep(100); + i--; + + } while (i); + + dev_warn(idtcm->dev, "%s timed out", __func__); + + return -EBUSY; +} + +static int arm_tod_read_trig_sel_refclk(struct idtcm_channel *channel, u8 ref) +{ + struct idtcm *idtcm = channel->idtcm; + u16 tod_read_cmd = IDTCM_FW_REG(idtcm->fw_ver, V520, TOD_READ_SECONDARY_CMD); + u8 val = 0; + int err; + + val &= ~(WR_REF_INDEX_MASK << WR_REF_INDEX_SHIFT); + val |= (ref << WR_REF_INDEX_SHIFT); + + err = idtcm_write(idtcm, channel->tod_read_secondary, + TOD_READ_SECONDARY_SEL_CFG_0, &val, sizeof(val)); + if (err) + return err; + + val = 0 | (SCSR_TOD_READ_TRIG_SEL_REFCLK << TOD_READ_TRIGGER_SHIFT); + + err = idtcm_write(idtcm, channel->tod_read_secondary, tod_read_cmd, + &val, sizeof(val)); + if (err) + dev_err(idtcm->dev, "%s: err = %d", __func__, err); + + return err; +} + +static bool is_single_shot(u8 mask) +{ + /* Treat single bit ToD masks as continuous trigger */ + return !(mask <= 8 && is_power_of_2(mask)); +} + +static int idtcm_extts_enable(struct idtcm_channel *channel, + struct ptp_clock_request *rq, int on) +{ + u8 index = rq->extts.index; + struct idtcm *idtcm; + u8 mask = 1 << index; + int err = 0; + u8 old_mask; + int ref; + + idtcm = channel->idtcm; + old_mask = idtcm->extts_mask; + + if (index >= MAX_TOD) + return -EINVAL; + + if (on) { + /* Support triggering more than one TOD_0/1/2/3 by same pin */ + /* Use the pin configured for the channel */ + ref = ptp_find_pin(channel->ptp_clock, PTP_PF_EXTTS, channel->tod); + + if (ref < 0) { + dev_err(idtcm->dev, "%s: No valid pin found for TOD%d!\n", + __func__, channel->tod); + return -EBUSY; + } + + err = arm_tod_read_trig_sel_refclk(&idtcm->channel[index], ref); + + if (err == 0) { + idtcm->extts_mask |= mask; + idtcm->event_channel[index] = channel; + idtcm->channel[index].refn = ref; + idtcm->extts_single_shot = is_single_shot(idtcm->extts_mask); + + if (old_mask) + return 0; + + schedule_delayed_work(&idtcm->extts_work, + msecs_to_jiffies(EXTTS_PERIOD_MS)); + } + } else { + idtcm->extts_mask &= ~mask; + idtcm->extts_single_shot = is_single_shot(idtcm->extts_mask); + + if (idtcm->extts_mask == 0) + cancel_delayed_work(&idtcm->extts_work); + } + + return err; +} + +static int read_sys_apll_status(struct idtcm *idtcm, u8 *status) +{ + return idtcm_read(idtcm, STATUS, DPLL_SYS_APLL_STATUS, status, + sizeof(u8)); +} + +static int read_sys_dpll_status(struct idtcm *idtcm, u8 *status) +{ + return idtcm_read(idtcm, STATUS, DPLL_SYS_STATUS, status, sizeof(u8)); +} + +static int wait_for_sys_apll_dpll_lock(struct idtcm *idtcm) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(LOCK_TIMEOUT_MS); + u8 apll = 0; + u8 dpll = 0; + int err; + + do { + err = read_sys_apll_status(idtcm, &apll); + if (err) + return err; + + err = read_sys_dpll_status(idtcm, &dpll); + if (err) + return err; + + apll &= SYS_APLL_LOSS_LOCK_LIVE_MASK; + dpll &= DPLL_SYS_STATE_MASK; + + if (apll == SYS_APLL_LOSS_LOCK_LIVE_LOCKED && + dpll == DPLL_STATE_LOCKED) { + return 0; + } else if (dpll == DPLL_STATE_FREERUN || + dpll == DPLL_STATE_HOLDOVER || + dpll == DPLL_STATE_OPEN_LOOP) { + dev_warn(idtcm->dev, + "No wait state: DPLL_SYS_STATE %d", dpll); + return -EPERM; + } + + msleep(LOCK_POLL_INTERVAL_MS); + } while (time_is_after_jiffies(timeout)); + + dev_warn(idtcm->dev, + "%d ms lock timeout: SYS APLL Loss Lock %d SYS DPLL state %d", + LOCK_TIMEOUT_MS, apll, dpll); + + return -ETIME; +} + +static void wait_for_chip_ready(struct idtcm *idtcm) +{ + if (wait_for_boot_status_ready(idtcm)) + dev_warn(idtcm->dev, "BOOT_STATUS != 0xA0"); + + if (wait_for_sys_apll_dpll_lock(idtcm)) + dev_warn(idtcm->dev, + "Continuing while SYS APLL/DPLL is not locked"); +} + +static int _idtcm_gettime_triggered(struct idtcm_channel *channel, + struct timespec64 *ts) +{ + struct idtcm *idtcm = channel->idtcm; + u16 tod_read_cmd = IDTCM_FW_REG(idtcm->fw_ver, V520, TOD_READ_SECONDARY_CMD); + u8 buf[TOD_BYTE_COUNT]; + u8 trigger; + int err; + + err = idtcm_read(idtcm, channel->tod_read_secondary, + tod_read_cmd, &trigger, sizeof(trigger)); + if (err) + return err; + + if (trigger & TOD_READ_TRIGGER_MASK) + return -EBUSY; + + err = idtcm_read(idtcm, channel->tod_read_secondary, + TOD_READ_SECONDARY_BASE, buf, sizeof(buf)); + if (err) + return err; + + return char_array_to_timespec(buf, sizeof(buf), ts); +} + +static int _idtcm_gettime(struct idtcm_channel *channel, + struct timespec64 *ts, u8 timeout) +{ + struct idtcm *idtcm = channel->idtcm; + u16 tod_read_cmd = IDTCM_FW_REG(idtcm->fw_ver, V520, TOD_READ_PRIMARY_CMD); + u8 buf[TOD_BYTE_COUNT]; + u8 trigger; + int err; + + /* wait trigger to be 0 */ + do { + if (timeout-- == 0) + return -EIO; + + if (idtcm->calculate_overhead_flag) + idtcm->start_time = ktime_get_raw(); + + err = idtcm_read(idtcm, channel->tod_read_primary, + tod_read_cmd, &trigger, + sizeof(trigger)); + if (err) + return err; + } while (trigger & TOD_READ_TRIGGER_MASK); + + err = idtcm_read(idtcm, channel->tod_read_primary, + TOD_READ_PRIMARY_BASE, buf, sizeof(buf)); + if (err) + return err; + + err = char_array_to_timespec(buf, sizeof(buf), ts); + + return err; +} + +static int idtcm_extts_check_channel(struct idtcm *idtcm, u8 todn) +{ + struct idtcm_channel *ptp_channel, *extts_channel; + struct ptp_clock_event event; + struct timespec64 ts; + u32 dco_delay = 0; + int err; + + extts_channel = &idtcm->channel[todn]; + ptp_channel = idtcm->event_channel[todn]; + + if (extts_channel == ptp_channel) + dco_delay = ptp_channel->dco_delay; + + err = _idtcm_gettime_triggered(extts_channel, &ts); + if (err) + return err; + + /* Triggered - save timestamp */ + event.type = PTP_CLOCK_EXTTS; + event.index = todn; + event.timestamp = timespec64_to_ns(&ts) - dco_delay; + ptp_clock_event(ptp_channel->ptp_clock, &event); + + return err; +} + +static int _idtcm_gettime_immediate(struct idtcm_channel *channel, + struct timespec64 *ts) +{ + struct idtcm *idtcm = channel->idtcm; + + u16 tod_read_cmd = IDTCM_FW_REG(idtcm->fw_ver, V520, TOD_READ_PRIMARY_CMD); + u8 val = (SCSR_TOD_READ_TRIG_SEL_IMMEDIATE << TOD_READ_TRIGGER_SHIFT); + int err; + + err = idtcm_write(idtcm, channel->tod_read_primary, + tod_read_cmd, &val, sizeof(val)); + if (err) + return err; + + return _idtcm_gettime(channel, ts, 10); +} + +static int _sync_pll_output(struct idtcm *idtcm, + u8 pll, + u8 sync_src, + u8 qn, + u8 qn_plus_1) +{ + int err; + u8 val; + u16 sync_ctrl0; + u16 sync_ctrl1; + u8 temp; + + if (qn == 0 && qn_plus_1 == 0) + return 0; + + switch (pll) { + case 0: + sync_ctrl0 = HW_Q0_Q1_CH_SYNC_CTRL_0; + sync_ctrl1 = HW_Q0_Q1_CH_SYNC_CTRL_1; + break; + case 1: + sync_ctrl0 = HW_Q2_Q3_CH_SYNC_CTRL_0; + sync_ctrl1 = HW_Q2_Q3_CH_SYNC_CTRL_1; + break; + case 2: + sync_ctrl0 = HW_Q4_Q5_CH_SYNC_CTRL_0; + sync_ctrl1 = HW_Q4_Q5_CH_SYNC_CTRL_1; + break; + case 3: + sync_ctrl0 = HW_Q6_Q7_CH_SYNC_CTRL_0; + sync_ctrl1 = HW_Q6_Q7_CH_SYNC_CTRL_1; + break; + case 4: + sync_ctrl0 = HW_Q8_CH_SYNC_CTRL_0; + sync_ctrl1 = HW_Q8_CH_SYNC_CTRL_1; + break; + case 5: + sync_ctrl0 = HW_Q9_CH_SYNC_CTRL_0; + sync_ctrl1 = HW_Q9_CH_SYNC_CTRL_1; + break; + case 6: + sync_ctrl0 = HW_Q10_CH_SYNC_CTRL_0; + sync_ctrl1 = HW_Q10_CH_SYNC_CTRL_1; + break; + case 7: + sync_ctrl0 = HW_Q11_CH_SYNC_CTRL_0; + sync_ctrl1 = HW_Q11_CH_SYNC_CTRL_1; + break; + default: + return -EINVAL; + } + + val = SYNCTRL1_MASTER_SYNC_RST; + + /* Place master sync in reset */ + err = idtcm_write(idtcm, 0, sync_ctrl1, &val, sizeof(val)); + if (err) + return err; + + err = idtcm_write(idtcm, 0, sync_ctrl0, &sync_src, sizeof(sync_src)); + if (err) + return err; + + /* Set sync trigger mask */ + val |= SYNCTRL1_FBDIV_FRAME_SYNC_TRIG | SYNCTRL1_FBDIV_SYNC_TRIG; + + if (qn) + val |= SYNCTRL1_Q0_DIV_SYNC_TRIG; + + if (qn_plus_1) + val |= SYNCTRL1_Q1_DIV_SYNC_TRIG; + + err = idtcm_write(idtcm, 0, sync_ctrl1, &val, sizeof(val)); + if (err) + return err; + + /* PLL5 can have OUT8 as second additional output. */ + if (pll == 5 && qn_plus_1 != 0) { + err = idtcm_read(idtcm, 0, HW_Q8_CTRL_SPARE, + &temp, sizeof(temp)); + if (err) + return err; + + temp &= ~(Q9_TO_Q8_SYNC_TRIG); + + err = idtcm_write(idtcm, 0, HW_Q8_CTRL_SPARE, + &temp, sizeof(temp)); + if (err) + return err; + + temp |= Q9_TO_Q8_SYNC_TRIG; + + err = idtcm_write(idtcm, 0, HW_Q8_CTRL_SPARE, + &temp, sizeof(temp)); + if (err) + return err; + } + + /* PLL6 can have OUT11 as second additional output. */ + if (pll == 6 && qn_plus_1 != 0) { + err = idtcm_read(idtcm, 0, HW_Q11_CTRL_SPARE, + &temp, sizeof(temp)); + if (err) + return err; + + temp &= ~(Q10_TO_Q11_SYNC_TRIG); + + err = idtcm_write(idtcm, 0, HW_Q11_CTRL_SPARE, + &temp, sizeof(temp)); + if (err) + return err; + + temp |= Q10_TO_Q11_SYNC_TRIG; + + err = idtcm_write(idtcm, 0, HW_Q11_CTRL_SPARE, + &temp, sizeof(temp)); + if (err) + return err; + } + + /* Place master sync out of reset */ + val &= ~(SYNCTRL1_MASTER_SYNC_RST); + err = idtcm_write(idtcm, 0, sync_ctrl1, &val, sizeof(val)); + + return err; +} + +static int idtcm_sync_pps_output(struct idtcm_channel *channel) +{ + struct idtcm *idtcm = channel->idtcm; + u8 pll; + u8 qn; + u8 qn_plus_1; + int err = 0; + u8 out8_mux = 0; + u8 out11_mux = 0; + u8 temp; + u16 output_mask = channel->output_mask; + + err = idtcm_read(idtcm, 0, HW_Q8_CTRL_SPARE, + &temp, sizeof(temp)); + if (err) + return err; + + if ((temp & Q9_TO_Q8_FANOUT_AND_CLOCK_SYNC_ENABLE_MASK) == + Q9_TO_Q8_FANOUT_AND_CLOCK_SYNC_ENABLE_MASK) + out8_mux = 1; + + err = idtcm_read(idtcm, 0, HW_Q11_CTRL_SPARE, + &temp, sizeof(temp)); + if (err) + return err; + + if ((temp & Q10_TO_Q11_FANOUT_AND_CLOCK_SYNC_ENABLE_MASK) == + Q10_TO_Q11_FANOUT_AND_CLOCK_SYNC_ENABLE_MASK) + out11_mux = 1; + + for (pll = 0; pll < 8; pll++) { + qn = 0; + qn_plus_1 = 0; + + if (pll < 4) { + /* First 4 pll has 2 outputs */ + qn = output_mask & 0x1; + output_mask = output_mask >> 1; + qn_plus_1 = output_mask & 0x1; + output_mask = output_mask >> 1; + } else if (pll == 4) { + if (out8_mux == 0) { + qn = output_mask & 0x1; + output_mask = output_mask >> 1; + } + } else if (pll == 5) { + if (out8_mux) { + qn_plus_1 = output_mask & 0x1; + output_mask = output_mask >> 1; + } + qn = output_mask & 0x1; + output_mask = output_mask >> 1; + } else if (pll == 6) { + qn = output_mask & 0x1; + output_mask = output_mask >> 1; + if (out11_mux) { + qn_plus_1 = output_mask & 0x1; + output_mask = output_mask >> 1; + } + } else if (pll == 7) { + if (out11_mux == 0) { + qn = output_mask & 0x1; + output_mask = output_mask >> 1; + } + } + + if (qn != 0 || qn_plus_1 != 0) + err = _sync_pll_output(idtcm, pll, channel->sync_src, + qn, qn_plus_1); + + if (err) + return err; + } + + return err; +} + +static int _idtcm_set_dpll_hw_tod(struct idtcm_channel *channel, + struct timespec64 const *ts, + enum hw_tod_write_trig_sel wr_trig) +{ + struct idtcm *idtcm = channel->idtcm; + u8 buf[TOD_BYTE_COUNT]; + u8 cmd; + int err; + struct timespec64 local_ts = *ts; + s64 total_overhead_ns; + + /* Configure HW TOD write trigger. */ + err = idtcm_read(idtcm, channel->hw_dpll_n, HW_DPLL_TOD_CTRL_1, + &cmd, sizeof(cmd)); + if (err) + return err; + + cmd &= ~(0x0f); + cmd |= wr_trig | 0x08; + + err = idtcm_write(idtcm, channel->hw_dpll_n, HW_DPLL_TOD_CTRL_1, + &cmd, sizeof(cmd)); + if (err) + return err; + + if (wr_trig != HW_TOD_WR_TRIG_SEL_MSB) { + err = timespec_to_char_array(&local_ts, buf, sizeof(buf)); + if (err) + return err; + + err = idtcm_write(idtcm, channel->hw_dpll_n, + HW_DPLL_TOD_OVR__0, buf, sizeof(buf)); + if (err) + return err; + } + + /* ARM HW TOD write trigger. */ + cmd &= ~(0x08); + + err = idtcm_write(idtcm, channel->hw_dpll_n, HW_DPLL_TOD_CTRL_1, + &cmd, sizeof(cmd)); + + if (wr_trig == HW_TOD_WR_TRIG_SEL_MSB) { + if (idtcm->calculate_overhead_flag) { + /* Assumption: I2C @ 400KHz */ + ktime_t diff = ktime_sub(ktime_get_raw(), + idtcm->start_time); + total_overhead_ns = ktime_to_ns(diff) + + idtcm->tod_write_overhead_ns + + SETTIME_CORRECTION; + + timespec64_add_ns(&local_ts, total_overhead_ns); + + idtcm->calculate_overhead_flag = 0; + } + + err = timespec_to_char_array(&local_ts, buf, sizeof(buf)); + if (err) + return err; + + err = idtcm_write(idtcm, channel->hw_dpll_n, + HW_DPLL_TOD_OVR__0, buf, sizeof(buf)); + } + + return err; +} + +static int _idtcm_set_dpll_scsr_tod(struct idtcm_channel *channel, + struct timespec64 const *ts, + enum scsr_tod_write_trig_sel wr_trig, + enum scsr_tod_write_type_sel wr_type) +{ + struct idtcm *idtcm = channel->idtcm; + unsigned char buf[TOD_BYTE_COUNT], cmd; + struct timespec64 local_ts = *ts; + int err, count = 0; + + timespec64_add_ns(&local_ts, SETTIME_CORRECTION); + + err = timespec_to_char_array(&local_ts, buf, sizeof(buf)); + if (err) + return err; + + err = idtcm_write(idtcm, channel->tod_write, TOD_WRITE, + buf, sizeof(buf)); + if (err) + return err; + + /* Trigger the write operation. */ + err = idtcm_read(idtcm, channel->tod_write, TOD_WRITE_CMD, + &cmd, sizeof(cmd)); + if (err) + return err; + + cmd &= ~(TOD_WRITE_SELECTION_MASK << TOD_WRITE_SELECTION_SHIFT); + cmd &= ~(TOD_WRITE_TYPE_MASK << TOD_WRITE_TYPE_SHIFT); + cmd |= (wr_trig << TOD_WRITE_SELECTION_SHIFT); + cmd |= (wr_type << TOD_WRITE_TYPE_SHIFT); + + err = idtcm_write(idtcm, channel->tod_write, TOD_WRITE_CMD, + &cmd, sizeof(cmd)); + if (err) + return err; + + /* Wait for the operation to complete. */ + while (1) { + /* pps trigger takes up to 1 sec to complete */ + if (wr_trig == SCSR_TOD_WR_TRIG_SEL_TODPPS) + msleep(50); + + err = idtcm_read(idtcm, channel->tod_write, TOD_WRITE_CMD, + &cmd, sizeof(cmd)); + if (err) + return err; + + if ((cmd & TOD_WRITE_SELECTION_MASK) == 0) + break; + + if (++count > 20) { + dev_err(idtcm->dev, + "Timed out waiting for the write counter"); + return -EIO; + } + } + + return 0; +} + +static int get_output_base_addr(enum fw_version ver, u8 outn) +{ + int base; + + switch (outn) { + case 0: + base = IDTCM_FW_REG(ver, V520, OUTPUT_0); + break; + case 1: + base = IDTCM_FW_REG(ver, V520, OUTPUT_1); + break; + case 2: + base = IDTCM_FW_REG(ver, V520, OUTPUT_2); + break; + case 3: + base = IDTCM_FW_REG(ver, V520, OUTPUT_3); + break; + case 4: + base = IDTCM_FW_REG(ver, V520, OUTPUT_4); + break; + case 5: + base = IDTCM_FW_REG(ver, V520, OUTPUT_5); + break; + case 6: + base = IDTCM_FW_REG(ver, V520, OUTPUT_6); + break; + case 7: + base = IDTCM_FW_REG(ver, V520, OUTPUT_7); + break; + case 8: + base = IDTCM_FW_REG(ver, V520, OUTPUT_8); + break; + case 9: + base = IDTCM_FW_REG(ver, V520, OUTPUT_9); + break; + case 10: + base = IDTCM_FW_REG(ver, V520, OUTPUT_10); + break; + case 11: + base = IDTCM_FW_REG(ver, V520, OUTPUT_11); + break; + default: + base = -EINVAL; + } + + return base; +} + +static int _idtcm_settime_deprecated(struct idtcm_channel *channel, + struct timespec64 const *ts) +{ + struct idtcm *idtcm = channel->idtcm; + int err; + + err = _idtcm_set_dpll_hw_tod(channel, ts, HW_TOD_WR_TRIG_SEL_MSB); + if (err) { + dev_err(idtcm->dev, + "%s: Set HW ToD failed", __func__); + return err; + } + + return idtcm_sync_pps_output(channel); +} + +static int _idtcm_settime(struct idtcm_channel *channel, + struct timespec64 const *ts, + enum scsr_tod_write_type_sel wr_type) +{ + return _idtcm_set_dpll_scsr_tod(channel, ts, + SCSR_TOD_WR_TRIG_SEL_IMMEDIATE, + wr_type); +} + +static int idtcm_set_phase_pull_in_offset(struct idtcm_channel *channel, + s32 offset_ns) +{ + int err; + int i; + struct idtcm *idtcm = channel->idtcm; + u8 buf[4]; + + for (i = 0; i < 4; i++) { + buf[i] = 0xff & (offset_ns); + offset_ns >>= 8; + } + + err = idtcm_write(idtcm, channel->dpll_phase_pull_in, PULL_IN_OFFSET, + buf, sizeof(buf)); + + return err; +} + +static int idtcm_set_phase_pull_in_slope_limit(struct idtcm_channel *channel, + u32 max_ffo_ppb) +{ + int err; + u8 i; + struct idtcm *idtcm = channel->idtcm; + u8 buf[3]; + + if (max_ffo_ppb & 0xff000000) + max_ffo_ppb = 0; + + for (i = 0; i < 3; i++) { + buf[i] = 0xff & (max_ffo_ppb); + max_ffo_ppb >>= 8; + } + + err = idtcm_write(idtcm, channel->dpll_phase_pull_in, + PULL_IN_SLOPE_LIMIT, buf, sizeof(buf)); + + return err; +} + +static int idtcm_start_phase_pull_in(struct idtcm_channel *channel) +{ + int err; + struct idtcm *idtcm = channel->idtcm; + u8 buf; + + err = idtcm_read(idtcm, channel->dpll_phase_pull_in, PULL_IN_CTRL, + &buf, sizeof(buf)); + if (err) + return err; + + if (buf == 0) { + buf = 0x01; + err = idtcm_write(idtcm, channel->dpll_phase_pull_in, + PULL_IN_CTRL, &buf, sizeof(buf)); + } else { + err = -EBUSY; + } + + return err; +} + +static int do_phase_pull_in_fw(struct idtcm_channel *channel, + s32 offset_ns, + u32 max_ffo_ppb) +{ + int err; + + err = idtcm_set_phase_pull_in_offset(channel, -offset_ns); + if (err) + return err; + + err = idtcm_set_phase_pull_in_slope_limit(channel, max_ffo_ppb); + if (err) + return err; + + err = idtcm_start_phase_pull_in(channel); + + return err; +} + +static int set_tod_write_overhead(struct idtcm_channel *channel) +{ + struct idtcm *idtcm = channel->idtcm; + s64 current_ns = 0; + s64 lowest_ns = 0; + int err; + u8 i; + ktime_t start; + ktime_t stop; + ktime_t diff; + + char buf[TOD_BYTE_COUNT] = {0}; + + /* Set page offset */ + idtcm_write(idtcm, channel->hw_dpll_n, HW_DPLL_TOD_OVR__0, + buf, sizeof(buf)); + + for (i = 0; i < TOD_WRITE_OVERHEAD_COUNT_MAX; i++) { + start = ktime_get_raw(); + + err = idtcm_write(idtcm, channel->hw_dpll_n, + HW_DPLL_TOD_OVR__0, buf, sizeof(buf)); + if (err) + return err; + + stop = ktime_get_raw(); + + diff = ktime_sub(stop, start); + + current_ns = ktime_to_ns(diff); + + if (i == 0) { + lowest_ns = current_ns; + } else { + if (current_ns < lowest_ns) + lowest_ns = current_ns; + } + } + + idtcm->tod_write_overhead_ns = lowest_ns; + + return err; +} + +static int _idtcm_adjtime_deprecated(struct idtcm_channel *channel, s64 delta) +{ + int err; + struct idtcm *idtcm = channel->idtcm; + struct timespec64 ts; + s64 now; + + if (abs(delta) < PHASE_PULL_IN_THRESHOLD_NS_DEPRECATED) { + err = channel->do_phase_pull_in(channel, delta, 0); + } else { + idtcm->calculate_overhead_flag = 1; + + err = set_tod_write_overhead(channel); + if (err) + return err; + + err = _idtcm_gettime_immediate(channel, &ts); + if (err) + return err; + + now = timespec64_to_ns(&ts); + now += delta; + + ts = ns_to_timespec64(now); + + err = _idtcm_settime_deprecated(channel, &ts); + } + + return err; +} + +static int idtcm_state_machine_reset(struct idtcm *idtcm) +{ + u8 byte = SM_RESET_CMD; + u32 status = 0; + int err; + u8 i; + + clear_boot_status(idtcm); + + err = idtcm_write(idtcm, RESET_CTRL, + IDTCM_FW_REG(idtcm->fw_ver, V520, SM_RESET), + &byte, sizeof(byte)); + + if (!err) { + for (i = 0; i < 30; i++) { + msleep_interruptible(100); + read_boot_status(idtcm, &status); + + if (status == 0xA0) { + dev_dbg(idtcm->dev, + "SM_RESET completed in %d ms", i * 100); + break; + } + } + + if (!status) + dev_err(idtcm->dev, + "Timed out waiting for CM_RESET to complete"); + } + + return err; +} + +static int idtcm_read_hw_rev_id(struct idtcm *idtcm, u8 *hw_rev_id) +{ + return idtcm_read(idtcm, HW_REVISION, REV_ID, hw_rev_id, sizeof(u8)); +} + +static int idtcm_read_product_id(struct idtcm *idtcm, u16 *product_id) +{ + int err; + u8 buf[2] = {0}; + + err = idtcm_read(idtcm, GENERAL_STATUS, PRODUCT_ID, buf, sizeof(buf)); + + *product_id = (buf[1] << 8) | buf[0]; + + return err; +} + +static int idtcm_read_major_release(struct idtcm *idtcm, u8 *major) +{ + int err; + u8 buf = 0; + + err = idtcm_read(idtcm, GENERAL_STATUS, MAJ_REL, &buf, sizeof(buf)); + + *major = buf >> 1; + + return err; +} + +static int idtcm_read_minor_release(struct idtcm *idtcm, u8 *minor) +{ + return idtcm_read(idtcm, GENERAL_STATUS, MIN_REL, minor, sizeof(u8)); +} + +static int idtcm_read_hotfix_release(struct idtcm *idtcm, u8 *hotfix) +{ + return idtcm_read(idtcm, + GENERAL_STATUS, + HOTFIX_REL, + hotfix, + sizeof(u8)); +} + +static int idtcm_read_otp_scsr_config_select(struct idtcm *idtcm, + u8 *config_select) +{ + return idtcm_read(idtcm, GENERAL_STATUS, OTP_SCSR_CONFIG_SELECT, + config_select, sizeof(u8)); +} + +static int set_pll_output_mask(struct idtcm *idtcm, u16 addr, u8 val) +{ + int err = 0; + + switch (addr) { + case TOD0_OUT_ALIGN_MASK_ADDR: + SET_U16_LSB(idtcm->channel[0].output_mask, val); + break; + case TOD0_OUT_ALIGN_MASK_ADDR + 1: + SET_U16_MSB(idtcm->channel[0].output_mask, val); + break; + case TOD1_OUT_ALIGN_MASK_ADDR: + SET_U16_LSB(idtcm->channel[1].output_mask, val); + break; + case TOD1_OUT_ALIGN_MASK_ADDR + 1: + SET_U16_MSB(idtcm->channel[1].output_mask, val); + break; + case TOD2_OUT_ALIGN_MASK_ADDR: + SET_U16_LSB(idtcm->channel[2].output_mask, val); + break; + case TOD2_OUT_ALIGN_MASK_ADDR + 1: + SET_U16_MSB(idtcm->channel[2].output_mask, val); + break; + case TOD3_OUT_ALIGN_MASK_ADDR: + SET_U16_LSB(idtcm->channel[3].output_mask, val); + break; + case TOD3_OUT_ALIGN_MASK_ADDR + 1: + SET_U16_MSB(idtcm->channel[3].output_mask, val); + break; + default: + err = -EFAULT; /* Bad address */ + break; + } + + return err; +} + +static int set_tod_ptp_pll(struct idtcm *idtcm, u8 index, u8 pll) +{ + if (index >= MAX_TOD) { + dev_err(idtcm->dev, "ToD%d not supported", index); + return -EINVAL; + } + + if (pll >= MAX_PLL) { + dev_err(idtcm->dev, "Pll%d not supported", pll); + return -EINVAL; + } + + idtcm->channel[index].pll = pll; + + return 0; +} + +static int check_and_set_masks(struct idtcm *idtcm, + u16 regaddr, + u8 val) +{ + int err = 0; + + switch (regaddr) { + case TOD_MASK_ADDR: + if ((val & 0xf0) || !(val & 0x0f)) { + dev_err(idtcm->dev, "Invalid TOD mask 0x%02x", val); + err = -EINVAL; + } else { + idtcm->tod_mask = val; + } + break; + case TOD0_PTP_PLL_ADDR: + err = set_tod_ptp_pll(idtcm, 0, val); + break; + case TOD1_PTP_PLL_ADDR: + err = set_tod_ptp_pll(idtcm, 1, val); + break; + case TOD2_PTP_PLL_ADDR: + err = set_tod_ptp_pll(idtcm, 2, val); + break; + case TOD3_PTP_PLL_ADDR: + err = set_tod_ptp_pll(idtcm, 3, val); + break; + default: + err = set_pll_output_mask(idtcm, regaddr, val); + break; + } + + return err; +} + +static void display_pll_and_masks(struct idtcm *idtcm) +{ + u8 i; + u8 mask; + + dev_dbg(idtcm->dev, "tod_mask = 0x%02x", idtcm->tod_mask); + + for (i = 0; i < MAX_TOD; i++) { + mask = 1 << i; + + if (mask & idtcm->tod_mask) + dev_dbg(idtcm->dev, + "TOD%d pll = %d output_mask = 0x%04x", + i, idtcm->channel[i].pll, + idtcm->channel[i].output_mask); + } +} + +static int idtcm_load_firmware(struct idtcm *idtcm, + struct device *dev) +{ + u16 scratch = IDTCM_FW_REG(idtcm->fw_ver, V520, SCRATCH); + char fname[128] = FW_FILENAME; + const struct firmware *fw; + struct idtcm_fwrc *rec; + u32 regaddr; + int err; + s32 len; + u8 val; + u8 loaddr; + + if (firmware) /* module parameter */ + snprintf(fname, sizeof(fname), "%s", firmware); + + dev_info(idtcm->dev, "requesting firmware '%s'", fname); + + err = request_firmware(&fw, fname, dev); + if (err) { + dev_err(idtcm->dev, + "Failed at line %d in %s!", __LINE__, __func__); + return err; + } + + dev_dbg(idtcm->dev, "firmware size %zu bytes", fw->size); + + rec = (struct idtcm_fwrc *) fw->data; + + if (contains_full_configuration(idtcm, fw)) + idtcm_state_machine_reset(idtcm); + + for (len = fw->size; len > 0; len -= sizeof(*rec)) { + if (rec->reserved) { + dev_err(idtcm->dev, + "bad firmware, reserved field non-zero"); + err = -EINVAL; + } else { + regaddr = rec->hiaddr << 8; + regaddr |= rec->loaddr; + + val = rec->value; + loaddr = rec->loaddr; + + rec++; + + err = check_and_set_masks(idtcm, regaddr, val); + } + + if (err != -EINVAL) { + err = 0; + + /* Top (status registers) and bottom are read-only */ + if (regaddr < GPIO_USER_CONTROL || regaddr >= scratch) + continue; + + /* Page size 128, last 4 bytes of page skipped */ + if ((loaddr > 0x7b && loaddr <= 0x7f) || loaddr > 0xfb) + continue; + + err = idtcm_write(idtcm, regaddr, 0, &val, sizeof(val)); + } + + if (err) + goto out; + } + + display_pll_and_masks(idtcm); + +out: + release_firmware(fw); + return err; +} + +static int idtcm_output_enable(struct idtcm_channel *channel, + bool enable, unsigned int outn) +{ + struct idtcm *idtcm = channel->idtcm; + int base; + int err; + u8 val; + + base = get_output_base_addr(idtcm->fw_ver, outn); + + if (!(base > 0)) { + dev_err(idtcm->dev, + "%s - Unsupported out%d", __func__, outn); + return base; + } + + err = idtcm_read(idtcm, (u16)base, OUT_CTRL_1, &val, sizeof(val)); + if (err) + return err; + + if (enable) + val |= SQUELCH_DISABLE; + else + val &= ~SQUELCH_DISABLE; + + return idtcm_write(idtcm, (u16)base, OUT_CTRL_1, &val, sizeof(val)); +} + +static int idtcm_perout_enable(struct idtcm_channel *channel, + struct ptp_perout_request *perout, + bool enable) +{ + struct idtcm *idtcm = channel->idtcm; + struct timespec64 ts = {0, 0}; + int err; + + err = idtcm_output_enable(channel, enable, perout->index); + + if (err) { + dev_err(idtcm->dev, "Unable to set output enable"); + return err; + } + + /* Align output to internal 1 PPS */ + return _idtcm_settime(channel, &ts, SCSR_TOD_WR_TYPE_SEL_DELTA_PLUS); +} + +static int idtcm_get_pll_mode(struct idtcm_channel *channel, + enum pll_mode *mode) +{ + struct idtcm *idtcm = channel->idtcm; + int err; + u8 dpll_mode; + + err = idtcm_read(idtcm, channel->dpll_n, + IDTCM_FW_REG(idtcm->fw_ver, V520, DPLL_MODE), + &dpll_mode, sizeof(dpll_mode)); + if (err) + return err; + + *mode = (dpll_mode >> PLL_MODE_SHIFT) & PLL_MODE_MASK; + + return 0; +} + +static int idtcm_set_pll_mode(struct idtcm_channel *channel, + enum pll_mode mode) +{ + struct idtcm *idtcm = channel->idtcm; + int err; + u8 dpll_mode; + + err = idtcm_read(idtcm, channel->dpll_n, + IDTCM_FW_REG(idtcm->fw_ver, V520, DPLL_MODE), + &dpll_mode, sizeof(dpll_mode)); + if (err) + return err; + + dpll_mode &= ~(PLL_MODE_MASK << PLL_MODE_SHIFT); + + dpll_mode |= (mode << PLL_MODE_SHIFT); + + err = idtcm_write(idtcm, channel->dpll_n, + IDTCM_FW_REG(idtcm->fw_ver, V520, DPLL_MODE), + &dpll_mode, sizeof(dpll_mode)); + return err; +} + +static int idtcm_get_manual_reference(struct idtcm_channel *channel, + enum manual_reference *ref) +{ + struct idtcm *idtcm = channel->idtcm; + u8 dpll_manu_ref_cfg; + int err; + + err = idtcm_read(idtcm, channel->dpll_ctrl_n, + DPLL_CTRL_DPLL_MANU_REF_CFG, + &dpll_manu_ref_cfg, sizeof(dpll_manu_ref_cfg)); + if (err) + return err; + + dpll_manu_ref_cfg &= (MANUAL_REFERENCE_MASK << MANUAL_REFERENCE_SHIFT); + + *ref = dpll_manu_ref_cfg >> MANUAL_REFERENCE_SHIFT; + + return 0; +} + +static int idtcm_set_manual_reference(struct idtcm_channel *channel, + enum manual_reference ref) +{ + struct idtcm *idtcm = channel->idtcm; + u8 dpll_manu_ref_cfg; + int err; + + err = idtcm_read(idtcm, channel->dpll_ctrl_n, + DPLL_CTRL_DPLL_MANU_REF_CFG, + &dpll_manu_ref_cfg, sizeof(dpll_manu_ref_cfg)); + if (err) + return err; + + dpll_manu_ref_cfg &= ~(MANUAL_REFERENCE_MASK << MANUAL_REFERENCE_SHIFT); + + dpll_manu_ref_cfg |= (ref << MANUAL_REFERENCE_SHIFT); + + err = idtcm_write(idtcm, channel->dpll_ctrl_n, + DPLL_CTRL_DPLL_MANU_REF_CFG, + &dpll_manu_ref_cfg, sizeof(dpll_manu_ref_cfg)); + + return err; +} + +static int configure_dpll_mode_write_frequency(struct idtcm_channel *channel) +{ + struct idtcm *idtcm = channel->idtcm; + int err; + + err = idtcm_set_pll_mode(channel, PLL_MODE_WRITE_FREQUENCY); + + if (err) + dev_err(idtcm->dev, "Failed to set pll mode to write frequency"); + else + channel->mode = PTP_PLL_MODE_WRITE_FREQUENCY; + + return err; +} + +static int configure_dpll_mode_write_phase(struct idtcm_channel *channel) +{ + struct idtcm *idtcm = channel->idtcm; + int err; + + err = idtcm_set_pll_mode(channel, PLL_MODE_WRITE_PHASE); + + if (err) + dev_err(idtcm->dev, "Failed to set pll mode to write phase"); + else + channel->mode = PTP_PLL_MODE_WRITE_PHASE; + + return err; +} + +static int configure_manual_reference_write_frequency(struct idtcm_channel *channel) +{ + struct idtcm *idtcm = channel->idtcm; + int err; + + err = idtcm_set_manual_reference(channel, MANU_REF_WRITE_FREQUENCY); + + if (err) + dev_err(idtcm->dev, "Failed to set manual reference to write frequency"); + else + channel->mode = PTP_PLL_MODE_WRITE_FREQUENCY; + + return err; +} + +static int configure_manual_reference_write_phase(struct idtcm_channel *channel) +{ + struct idtcm *idtcm = channel->idtcm; + int err; + + err = idtcm_set_manual_reference(channel, MANU_REF_WRITE_PHASE); + + if (err) + dev_err(idtcm->dev, "Failed to set manual reference to write phase"); + else + channel->mode = PTP_PLL_MODE_WRITE_PHASE; + + return err; +} + +static int idtcm_stop_phase_pull_in(struct idtcm_channel *channel) +{ + int err; + + err = _idtcm_adjfine(channel, channel->current_freq_scaled_ppm); + if (err) + return err; + + channel->phase_pull_in = false; + + return 0; +} + +static long idtcm_work_handler(struct ptp_clock_info *ptp) +{ + struct idtcm_channel *channel = container_of(ptp, struct idtcm_channel, caps); + struct idtcm *idtcm = channel->idtcm; + + mutex_lock(idtcm->lock); + + (void)idtcm_stop_phase_pull_in(channel); + + mutex_unlock(idtcm->lock); + + /* Return a negative value here to not reschedule */ + return -1; +} + +static s32 phase_pull_in_scaled_ppm(s32 current_ppm, s32 phase_pull_in_ppb) +{ + /* ppb = scaled_ppm * 125 / 2^13 */ + /* scaled_ppm = ppb * 2^13 / 125 */ + + s64 max_scaled_ppm = div_s64((s64)PHASE_PULL_IN_MAX_PPB << 13, 125); + s64 scaled_ppm = div_s64((s64)phase_pull_in_ppb << 13, 125); + + current_ppm += scaled_ppm; + + if (current_ppm > max_scaled_ppm) + current_ppm = max_scaled_ppm; + else if (current_ppm < -max_scaled_ppm) + current_ppm = -max_scaled_ppm; + + return current_ppm; +} + +static int do_phase_pull_in_sw(struct idtcm_channel *channel, + s32 delta_ns, + u32 max_ffo_ppb) +{ + s32 current_ppm = channel->current_freq_scaled_ppm; + u32 duration_ms = MSEC_PER_SEC; + s32 delta_ppm; + s32 ppb; + int err; + + /* If the ToD correction is less than PHASE_PULL_IN_MIN_THRESHOLD_NS, + * skip. The error introduced by the ToD adjustment procedure would + * be bigger than the required ToD correction + */ + if (abs(delta_ns) < PHASE_PULL_IN_MIN_THRESHOLD_NS) + return 0; + + if (max_ffo_ppb == 0) + max_ffo_ppb = PHASE_PULL_IN_MAX_PPB; + + /* For most cases, keep phase pull-in duration 1 second */ + ppb = delta_ns; + while (abs(ppb) > max_ffo_ppb) { + duration_ms *= 2; + ppb /= 2; + } + + delta_ppm = phase_pull_in_scaled_ppm(current_ppm, ppb); + + err = _idtcm_adjfine(channel, delta_ppm); + + if (err) + return err; + + /* schedule the worker to cancel phase pull-in */ + ptp_schedule_worker(channel->ptp_clock, + msecs_to_jiffies(duration_ms) - 1); + + channel->phase_pull_in = true; + + return 0; +} + +static int initialize_operating_mode_with_manual_reference(struct idtcm_channel *channel, + enum manual_reference ref) +{ + struct idtcm *idtcm = channel->idtcm; + + channel->mode = PTP_PLL_MODE_UNSUPPORTED; + channel->configure_write_frequency = configure_manual_reference_write_frequency; + channel->configure_write_phase = configure_manual_reference_write_phase; + channel->do_phase_pull_in = do_phase_pull_in_sw; + + switch (ref) { + case MANU_REF_WRITE_PHASE: + channel->mode = PTP_PLL_MODE_WRITE_PHASE; + break; + case MANU_REF_WRITE_FREQUENCY: + channel->mode = PTP_PLL_MODE_WRITE_FREQUENCY; + break; + default: + dev_warn(idtcm->dev, + "Unsupported MANUAL_REFERENCE: 0x%02x", ref); + } + + return 0; +} + +static int initialize_operating_mode_with_pll_mode(struct idtcm_channel *channel, + enum pll_mode mode) +{ + struct idtcm *idtcm = channel->idtcm; + int err = 0; + + channel->mode = PTP_PLL_MODE_UNSUPPORTED; + channel->configure_write_frequency = configure_dpll_mode_write_frequency; + channel->configure_write_phase = configure_dpll_mode_write_phase; + channel->do_phase_pull_in = do_phase_pull_in_fw; + + switch (mode) { + case PLL_MODE_WRITE_PHASE: + channel->mode = PTP_PLL_MODE_WRITE_PHASE; + break; + case PLL_MODE_WRITE_FREQUENCY: + channel->mode = PTP_PLL_MODE_WRITE_FREQUENCY; + break; + default: + dev_err(idtcm->dev, + "Unsupported PLL_MODE: 0x%02x", mode); + err = -EINVAL; + } + + return err; +} + +static int initialize_dco_operating_mode(struct idtcm_channel *channel) +{ + enum manual_reference ref = MANU_REF_XO_DPLL; + enum pll_mode mode = PLL_MODE_DISABLED; + struct idtcm *idtcm = channel->idtcm; + int err; + + channel->mode = PTP_PLL_MODE_UNSUPPORTED; + + err = idtcm_get_pll_mode(channel, &mode); + if (err) { + dev_err(idtcm->dev, "Unable to read pll mode!"); + return err; + } + + if (mode == PLL_MODE_PLL) { + err = idtcm_get_manual_reference(channel, &ref); + if (err) { + dev_err(idtcm->dev, "Unable to read manual reference!"); + return err; + } + err = initialize_operating_mode_with_manual_reference(channel, ref); + } else { + err = initialize_operating_mode_with_pll_mode(channel, mode); + } + + if (channel->mode == PTP_PLL_MODE_WRITE_PHASE) + channel->configure_write_frequency(channel); + + return err; +} + +/* PTP Hardware Clock interface */ + +/* + * Maximum absolute value for write phase offset in nanoseconds + * + * Destination signed register is 32-bit register in resolution of 50ps + * + * 0x7fffffff * 50 = 2147483647 * 50 = 107374182350 ps + * Represent 107374182350 ps as 107374182 ns + */ +static s32 idtcm_getmaxphase(struct ptp_clock_info *ptp __always_unused) +{ + return MAX_ABS_WRITE_PHASE_NANOSECONDS; +} + +/* + * Internal function for implementing support for write phase offset + * + * @channel: channel + * @delta_ns: delta in nanoseconds + */ +static int _idtcm_adjphase(struct idtcm_channel *channel, s32 delta_ns) +{ + struct idtcm *idtcm = channel->idtcm; + int err; + u8 i; + u8 buf[4] = {0}; + s32 phase_50ps; + + if (channel->mode != PTP_PLL_MODE_WRITE_PHASE) { + err = channel->configure_write_phase(channel); + if (err) + return err; + } + + phase_50ps = div_s64((s64)delta_ns * 1000, 50); + + for (i = 0; i < 4; i++) { + buf[i] = phase_50ps & 0xff; + phase_50ps >>= 8; + } + + err = idtcm_write(idtcm, channel->dpll_phase, DPLL_WR_PHASE, + buf, sizeof(buf)); + + return err; +} + +static int _idtcm_adjfine(struct idtcm_channel *channel, long scaled_ppm) +{ + struct idtcm *idtcm = channel->idtcm; + u8 i; + int err; + u8 buf[6] = {0}; + s64 fcw; + + if (channel->mode != PTP_PLL_MODE_WRITE_FREQUENCY) { + err = channel->configure_write_frequency(channel); + if (err) + return err; + } + + /* + * Frequency Control Word unit is: 1.11 * 10^-10 ppm + * + * adjfreq: + * ppb * 10^9 + * FCW = ---------- + * 111 + * + * adjfine: + * ppm_16 * 5^12 + * FCW = ------------- + * 111 * 2^4 + */ + + /* 2 ^ -53 = 1.1102230246251565404236316680908e-16 */ + fcw = scaled_ppm * 244140625ULL; + + fcw = div_s64(fcw, 1776); + + for (i = 0; i < 6; i++) { + buf[i] = fcw & 0xff; + fcw >>= 8; + } + + err = idtcm_write(idtcm, channel->dpll_freq, DPLL_WR_FREQ, + buf, sizeof(buf)); + + return err; +} + +static int idtcm_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts) +{ + struct idtcm_channel *channel = container_of(ptp, struct idtcm_channel, caps); + struct idtcm *idtcm = channel->idtcm; + int err; + + mutex_lock(idtcm->lock); + err = _idtcm_gettime_immediate(channel, ts); + mutex_unlock(idtcm->lock); + + if (err) + dev_err(idtcm->dev, "Failed at line %d in %s!", + __LINE__, __func__); + + return err; +} + +static int idtcm_settime_deprecated(struct ptp_clock_info *ptp, + const struct timespec64 *ts) +{ + struct idtcm_channel *channel = container_of(ptp, struct idtcm_channel, caps); + struct idtcm *idtcm = channel->idtcm; + int err; + + mutex_lock(idtcm->lock); + err = _idtcm_settime_deprecated(channel, ts); + mutex_unlock(idtcm->lock); + + if (err) + dev_err(idtcm->dev, + "Failed at line %d in %s!", __LINE__, __func__); + + return err; +} + +static int idtcm_settime(struct ptp_clock_info *ptp, + const struct timespec64 *ts) +{ + struct idtcm_channel *channel = container_of(ptp, struct idtcm_channel, caps); + struct idtcm *idtcm = channel->idtcm; + int err; + + mutex_lock(idtcm->lock); + err = _idtcm_settime(channel, ts, SCSR_TOD_WR_TYPE_SEL_ABSOLUTE); + mutex_unlock(idtcm->lock); + + if (err) + dev_err(idtcm->dev, + "Failed at line %d in %s!", __LINE__, __func__); + + return err; +} + +static int idtcm_adjtime_deprecated(struct ptp_clock_info *ptp, s64 delta) +{ + struct idtcm_channel *channel = container_of(ptp, struct idtcm_channel, caps); + struct idtcm *idtcm = channel->idtcm; + int err; + + mutex_lock(idtcm->lock); + err = _idtcm_adjtime_deprecated(channel, delta); + mutex_unlock(idtcm->lock); + + if (err) + dev_err(idtcm->dev, + "Failed at line %d in %s!", __LINE__, __func__); + + return err; +} + +static int idtcm_adjtime(struct ptp_clock_info *ptp, s64 delta) +{ + struct idtcm_channel *channel = container_of(ptp, struct idtcm_channel, caps); + struct idtcm *idtcm = channel->idtcm; + struct timespec64 ts; + enum scsr_tod_write_type_sel type; + int err; + + if (channel->phase_pull_in == true) + return -EBUSY; + + mutex_lock(idtcm->lock); + + if (abs(delta) < PHASE_PULL_IN_THRESHOLD_NS) { + err = channel->do_phase_pull_in(channel, delta, 0); + } else { + if (delta >= 0) { + ts = ns_to_timespec64(delta); + type = SCSR_TOD_WR_TYPE_SEL_DELTA_PLUS; + } else { + ts = ns_to_timespec64(-delta); + type = SCSR_TOD_WR_TYPE_SEL_DELTA_MINUS; + } + err = _idtcm_settime(channel, &ts, type); + } + + mutex_unlock(idtcm->lock); + + if (err) + dev_err(idtcm->dev, + "Failed at line %d in %s!", __LINE__, __func__); + + return err; +} + +static int idtcm_adjphase(struct ptp_clock_info *ptp, s32 delta) +{ + struct idtcm_channel *channel = container_of(ptp, struct idtcm_channel, caps); + struct idtcm *idtcm = channel->idtcm; + int err; + + mutex_lock(idtcm->lock); + err = _idtcm_adjphase(channel, delta); + mutex_unlock(idtcm->lock); + + if (err) + dev_err(idtcm->dev, + "Failed at line %d in %s!", __LINE__, __func__); + + return err; +} + +static int idtcm_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) +{ + struct idtcm_channel *channel = container_of(ptp, struct idtcm_channel, caps); + struct idtcm *idtcm = channel->idtcm; + int err; + + if (channel->phase_pull_in == true) + return 0; + + if (scaled_ppm == channel->current_freq_scaled_ppm) + return 0; + + mutex_lock(idtcm->lock); + err = _idtcm_adjfine(channel, scaled_ppm); + mutex_unlock(idtcm->lock); + + if (err) + dev_err(idtcm->dev, + "Failed at line %d in %s!", __LINE__, __func__); + else + channel->current_freq_scaled_ppm = scaled_ppm; + + return err; +} + +static int idtcm_enable(struct ptp_clock_info *ptp, + struct ptp_clock_request *rq, int on) +{ + struct idtcm_channel *channel = container_of(ptp, struct idtcm_channel, caps); + struct idtcm *idtcm = channel->idtcm; + int err = -EOPNOTSUPP; + + mutex_lock(idtcm->lock); + + switch (rq->type) { + case PTP_CLK_REQ_PEROUT: + if (!on) + err = idtcm_perout_enable(channel, &rq->perout, false); + /* Only accept a 1-PPS aligned to the second. */ + else if (rq->perout.start.nsec || rq->perout.period.sec != 1 || + rq->perout.period.nsec) + err = -ERANGE; + else + err = idtcm_perout_enable(channel, &rq->perout, true); + break; + case PTP_CLK_REQ_EXTTS: + err = idtcm_extts_enable(channel, rq, on); + break; + default: + break; + } + + mutex_unlock(idtcm->lock); + + if (err) + dev_err(channel->idtcm->dev, + "Failed in %s with err %d!", __func__, err); + + return err; +} + +static int idtcm_enable_tod(struct idtcm_channel *channel) +{ + struct idtcm *idtcm = channel->idtcm; + struct timespec64 ts = {0, 0}; + u16 tod_cfg = IDTCM_FW_REG(idtcm->fw_ver, V520, TOD_CFG); + u8 cfg; + int err; + + /* + * Start the TOD clock ticking. + */ + err = idtcm_read(idtcm, channel->tod_n, tod_cfg, &cfg, sizeof(cfg)); + if (err) + return err; + + cfg |= TOD_ENABLE; + + err = idtcm_write(idtcm, channel->tod_n, tod_cfg, &cfg, sizeof(cfg)); + if (err) + return err; + + if (idtcm->fw_ver < V487) + return _idtcm_settime_deprecated(channel, &ts); + else + return _idtcm_settime(channel, &ts, + SCSR_TOD_WR_TYPE_SEL_ABSOLUTE); +} + +static void idtcm_set_version_info(struct idtcm *idtcm) +{ + u8 major; + u8 minor; + u8 hotfix; + u16 product_id; + u8 hw_rev_id; + u8 config_select; + + idtcm_read_major_release(idtcm, &major); + idtcm_read_minor_release(idtcm, &minor); + idtcm_read_hotfix_release(idtcm, &hotfix); + + idtcm_read_product_id(idtcm, &product_id); + idtcm_read_hw_rev_id(idtcm, &hw_rev_id); + + idtcm_read_otp_scsr_config_select(idtcm, &config_select); + + snprintf(idtcm->version, sizeof(idtcm->version), "%u.%u.%u", + major, minor, hotfix); + + idtcm->fw_ver = idtcm_fw_version(idtcm->version); + + dev_info(idtcm->dev, + "%d.%d.%d, Id: 0x%04x HW Rev: %d OTP Config Select: %d", + major, minor, hotfix, + product_id, hw_rev_id, config_select); +} + +static int idtcm_verify_pin(struct ptp_clock_info *ptp, unsigned int pin, + enum ptp_pin_function func, unsigned int chan) +{ + switch (func) { + case PTP_PF_NONE: + case PTP_PF_EXTTS: + break; + case PTP_PF_PEROUT: + case PTP_PF_PHYSYNC: + return -1; + } + return 0; +} + +static struct ptp_pin_desc pin_config[MAX_TOD][MAX_REF_CLK]; + +static const struct ptp_clock_info idtcm_caps = { + .owner = THIS_MODULE, + .max_adj = 244000, + .n_per_out = 12, + .n_ext_ts = MAX_TOD, + .n_pins = MAX_REF_CLK, + .supported_extts_flags = PTP_RISING_EDGE | PTP_STRICT_FLAGS, + .adjphase = &idtcm_adjphase, + .getmaxphase = &idtcm_getmaxphase, + .adjfine = &idtcm_adjfine, + .adjtime = &idtcm_adjtime, + .gettime64 = &idtcm_gettime, + .settime64 = &idtcm_settime, + .enable = &idtcm_enable, + .verify = &idtcm_verify_pin, + .do_aux_work = &idtcm_work_handler, +}; + +static const struct ptp_clock_info idtcm_caps_deprecated = { + .owner = THIS_MODULE, + .max_adj = 244000, + .n_per_out = 12, + .n_ext_ts = MAX_TOD, + .n_pins = MAX_REF_CLK, + .supported_extts_flags = PTP_RISING_EDGE | PTP_STRICT_FLAGS, + .adjphase = &idtcm_adjphase, + .getmaxphase = &idtcm_getmaxphase, + .adjfine = &idtcm_adjfine, + .adjtime = &idtcm_adjtime_deprecated, + .gettime64 = &idtcm_gettime, + .settime64 = &idtcm_settime_deprecated, + .enable = &idtcm_enable, + .verify = &idtcm_verify_pin, + .do_aux_work = &idtcm_work_handler, +}; + +static int configure_channel_pll(struct idtcm_channel *channel) +{ + struct idtcm *idtcm = channel->idtcm; + int err = 0; + + switch (channel->pll) { + case 0: + channel->dpll_freq = DPLL_FREQ_0; + channel->dpll_n = DPLL_0; + channel->hw_dpll_n = HW_DPLL_0; + channel->dpll_phase = DPLL_PHASE_0; + channel->dpll_ctrl_n = DPLL_CTRL_0; + channel->dpll_phase_pull_in = DPLL_PHASE_PULL_IN_0; + break; + case 1: + channel->dpll_freq = DPLL_FREQ_1; + channel->dpll_n = DPLL_1; + channel->hw_dpll_n = HW_DPLL_1; + channel->dpll_phase = DPLL_PHASE_1; + channel->dpll_ctrl_n = DPLL_CTRL_1; + channel->dpll_phase_pull_in = DPLL_PHASE_PULL_IN_1; + break; + case 2: + channel->dpll_freq = DPLL_FREQ_2; + channel->dpll_n = IDTCM_FW_REG(idtcm->fw_ver, V520, DPLL_2); + channel->hw_dpll_n = HW_DPLL_2; + channel->dpll_phase = DPLL_PHASE_2; + channel->dpll_ctrl_n = DPLL_CTRL_2; + channel->dpll_phase_pull_in = DPLL_PHASE_PULL_IN_2; + break; + case 3: + channel->dpll_freq = DPLL_FREQ_3; + channel->dpll_n = DPLL_3; + channel->hw_dpll_n = HW_DPLL_3; + channel->dpll_phase = DPLL_PHASE_3; + channel->dpll_ctrl_n = DPLL_CTRL_3; + channel->dpll_phase_pull_in = DPLL_PHASE_PULL_IN_3; + break; + case 4: + channel->dpll_freq = DPLL_FREQ_4; + channel->dpll_n = IDTCM_FW_REG(idtcm->fw_ver, V520, DPLL_4); + channel->hw_dpll_n = HW_DPLL_4; + channel->dpll_phase = DPLL_PHASE_4; + channel->dpll_ctrl_n = DPLL_CTRL_4; + channel->dpll_phase_pull_in = DPLL_PHASE_PULL_IN_4; + break; + case 5: + channel->dpll_freq = DPLL_FREQ_5; + channel->dpll_n = DPLL_5; + channel->hw_dpll_n = HW_DPLL_5; + channel->dpll_phase = DPLL_PHASE_5; + channel->dpll_ctrl_n = DPLL_CTRL_5; + channel->dpll_phase_pull_in = DPLL_PHASE_PULL_IN_5; + break; + case 6: + channel->dpll_freq = DPLL_FREQ_6; + channel->dpll_n = IDTCM_FW_REG(idtcm->fw_ver, V520, DPLL_6); + channel->hw_dpll_n = HW_DPLL_6; + channel->dpll_phase = DPLL_PHASE_6; + channel->dpll_ctrl_n = DPLL_CTRL_6; + channel->dpll_phase_pull_in = DPLL_PHASE_PULL_IN_6; + break; + case 7: + channel->dpll_freq = DPLL_FREQ_7; + channel->dpll_n = DPLL_7; + channel->hw_dpll_n = HW_DPLL_7; + channel->dpll_phase = DPLL_PHASE_7; + channel->dpll_ctrl_n = DPLL_CTRL_7; + channel->dpll_phase_pull_in = DPLL_PHASE_PULL_IN_7; + break; + default: + err = -EINVAL; + } + + return err; +} + +/* + * Compensate for the PTP DCO input-to-output delay. + * This delay is 18 FOD cycles. + */ +static u32 idtcm_get_dco_delay(struct idtcm_channel *channel) +{ + struct idtcm *idtcm = channel->idtcm; + u8 mbuf[8] = {0}; + u8 nbuf[2] = {0}; + u32 fodFreq; + int err; + u64 m; + u16 n; + + err = idtcm_read(idtcm, channel->dpll_ctrl_n, + DPLL_CTRL_DPLL_FOD_FREQ, mbuf, 6); + if (err) + return 0; + + err = idtcm_read(idtcm, channel->dpll_ctrl_n, + DPLL_CTRL_DPLL_FOD_FREQ + 6, nbuf, 2); + if (err) + return 0; + + m = get_unaligned_le64(mbuf); + n = get_unaligned_le16(nbuf); + + if (n == 0) + n = 1; + + fodFreq = (u32)div_u64(m, n); + + if (fodFreq >= 500000000) + return (u32)div_u64(18 * (u64)NSEC_PER_SEC, fodFreq); + + return 0; +} + +static int configure_channel_tod(struct idtcm_channel *channel, u32 index) +{ + enum fw_version fw_ver = channel->idtcm->fw_ver; + + /* Set tod addresses */ + switch (index) { + case 0: + channel->tod_read_primary = IDTCM_FW_REG(fw_ver, V520, TOD_READ_PRIMARY_0); + channel->tod_read_secondary = IDTCM_FW_REG(fw_ver, V520, TOD_READ_SECONDARY_0); + channel->tod_write = IDTCM_FW_REG(fw_ver, V520, TOD_WRITE_0); + channel->tod_n = IDTCM_FW_REG(fw_ver, V520, TOD_0); + channel->sync_src = SYNC_SOURCE_DPLL0_TOD_PPS; + break; + case 1: + channel->tod_read_primary = IDTCM_FW_REG(fw_ver, V520, TOD_READ_PRIMARY_1); + channel->tod_read_secondary = IDTCM_FW_REG(fw_ver, V520, TOD_READ_SECONDARY_1); + channel->tod_write = IDTCM_FW_REG(fw_ver, V520, TOD_WRITE_1); + channel->tod_n = IDTCM_FW_REG(fw_ver, V520, TOD_1); + channel->sync_src = SYNC_SOURCE_DPLL1_TOD_PPS; + break; + case 2: + channel->tod_read_primary = IDTCM_FW_REG(fw_ver, V520, TOD_READ_PRIMARY_2); + channel->tod_read_secondary = IDTCM_FW_REG(fw_ver, V520, TOD_READ_SECONDARY_2); + channel->tod_write = IDTCM_FW_REG(fw_ver, V520, TOD_WRITE_2); + channel->tod_n = IDTCM_FW_REG(fw_ver, V520, TOD_2); + channel->sync_src = SYNC_SOURCE_DPLL2_TOD_PPS; + break; + case 3: + channel->tod_read_primary = IDTCM_FW_REG(fw_ver, V520, TOD_READ_PRIMARY_3); + channel->tod_read_secondary = IDTCM_FW_REG(fw_ver, V520, TOD_READ_SECONDARY_3); + channel->tod_write = IDTCM_FW_REG(fw_ver, V520, TOD_WRITE_3); + channel->tod_n = IDTCM_FW_REG(fw_ver, V520, TOD_3); + channel->sync_src = SYNC_SOURCE_DPLL3_TOD_PPS; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int idtcm_enable_channel(struct idtcm *idtcm, u32 index) +{ + struct idtcm_channel *channel; + int err; + int i; + + if (!(index < MAX_TOD)) + return -EINVAL; + + channel = &idtcm->channel[index]; + + channel->idtcm = idtcm; + channel->current_freq_scaled_ppm = 0; + + /* Set pll addresses */ + err = configure_channel_pll(channel); + if (err) + return err; + + /* Set tod addresses */ + err = configure_channel_tod(channel, index); + if (err) + return err; + + if (idtcm->fw_ver < V487) + channel->caps = idtcm_caps_deprecated; + else + channel->caps = idtcm_caps; + + snprintf(channel->caps.name, sizeof(channel->caps.name), + "IDT CM TOD%u", index); + + channel->caps.pin_config = pin_config[index]; + + for (i = 0; i < channel->caps.n_pins; ++i) { + struct ptp_pin_desc *ppd = &channel->caps.pin_config[i]; + + snprintf(ppd->name, sizeof(ppd->name), "input_ref%d", i); + ppd->index = i; + ppd->func = PTP_PF_NONE; + ppd->chan = index; + } + + err = initialize_dco_operating_mode(channel); + if (err) + return err; + + err = idtcm_enable_tod(channel); + if (err) { + dev_err(idtcm->dev, + "Failed at line %d in %s!", __LINE__, __func__); + return err; + } + + channel->dco_delay = idtcm_get_dco_delay(channel); + + channel->ptp_clock = ptp_clock_register(&channel->caps, NULL); + + if (IS_ERR(channel->ptp_clock)) { + err = PTR_ERR(channel->ptp_clock); + channel->ptp_clock = NULL; + return err; + } + + if (!channel->ptp_clock) + return -ENOTSUPP; + + dev_info(idtcm->dev, "PLL%d registered as ptp%d", + index, channel->ptp_clock->index); + + return 0; +} + +static int idtcm_enable_extts_channel(struct idtcm *idtcm, u32 index) +{ + struct idtcm_channel *channel; + int err; + + if (!(index < MAX_TOD)) + return -EINVAL; + + channel = &idtcm->channel[index]; + channel->idtcm = idtcm; + + /* Set tod addresses */ + err = configure_channel_tod(channel, index); + if (err) + return err; + + channel->idtcm = idtcm; + + return 0; +} + +static void idtcm_extts_check(struct work_struct *work) +{ + struct idtcm *idtcm = container_of(work, struct idtcm, extts_work.work); + struct idtcm_channel *channel; + u8 mask; + int err; + int i; + + if (idtcm->extts_mask == 0) + return; + + mutex_lock(idtcm->lock); + + for (i = 0; i < MAX_TOD; i++) { + mask = 1 << i; + + if ((idtcm->extts_mask & mask) == 0) + continue; + + err = idtcm_extts_check_channel(idtcm, i); + + if (err == 0) { + /* trigger clears itself, so clear the mask */ + if (idtcm->extts_single_shot) { + idtcm->extts_mask &= ~mask; + } else { + /* Re-arm */ + channel = &idtcm->channel[i]; + arm_tod_read_trig_sel_refclk(channel, channel->refn); + } + } + } + + if (idtcm->extts_mask) + schedule_delayed_work(&idtcm->extts_work, + msecs_to_jiffies(EXTTS_PERIOD_MS)); + + mutex_unlock(idtcm->lock); +} + +static void ptp_clock_unregister_all(struct idtcm *idtcm) +{ + u8 i; + struct idtcm_channel *channel; + + for (i = 0; i < MAX_TOD; i++) { + channel = &idtcm->channel[i]; + if (channel->ptp_clock) + ptp_clock_unregister(channel->ptp_clock); + } +} + +static void set_default_masks(struct idtcm *idtcm) +{ + idtcm->tod_mask = DEFAULT_TOD_MASK; + idtcm->extts_mask = 0; + + idtcm->channel[0].tod = 0; + idtcm->channel[1].tod = 1; + idtcm->channel[2].tod = 2; + idtcm->channel[3].tod = 3; + + idtcm->channel[0].pll = DEFAULT_TOD0_PTP_PLL; + idtcm->channel[1].pll = DEFAULT_TOD1_PTP_PLL; + idtcm->channel[2].pll = DEFAULT_TOD2_PTP_PLL; + idtcm->channel[3].pll = DEFAULT_TOD3_PTP_PLL; + + idtcm->channel[0].output_mask = DEFAULT_OUTPUT_MASK_PLL0; + idtcm->channel[1].output_mask = DEFAULT_OUTPUT_MASK_PLL1; + idtcm->channel[2].output_mask = DEFAULT_OUTPUT_MASK_PLL2; + idtcm->channel[3].output_mask = DEFAULT_OUTPUT_MASK_PLL3; +} + +static int idtcm_probe(struct platform_device *pdev) +{ + struct rsmu_ddata *ddata = dev_get_drvdata(pdev->dev.parent); + struct idtcm *idtcm; + int err; + u8 i; + + idtcm = devm_kzalloc(&pdev->dev, sizeof(struct idtcm), GFP_KERNEL); + + if (!idtcm) + return -ENOMEM; + + idtcm->dev = &pdev->dev; + idtcm->mfd = pdev->dev.parent; + idtcm->lock = &ddata->lock; + idtcm->regmap = ddata->regmap; + idtcm->calculate_overhead_flag = 0; + + INIT_DELAYED_WORK(&idtcm->extts_work, idtcm_extts_check); + + set_default_masks(idtcm); + + mutex_lock(idtcm->lock); + + idtcm_set_version_info(idtcm); + + err = idtcm_load_firmware(idtcm, &pdev->dev); + + if (err) + dev_warn(idtcm->dev, "loading firmware failed with %d", err); + + wait_for_chip_ready(idtcm); + + if (idtcm->tod_mask) { + for (i = 0; i < MAX_TOD; i++) { + if (idtcm->tod_mask & (1 << i)) + err = idtcm_enable_channel(idtcm, i); + else + err = idtcm_enable_extts_channel(idtcm, i); + if (err) { + dev_err(idtcm->dev, + "idtcm_enable_channel %d failed!", i); + break; + } + } + } else { + dev_err(idtcm->dev, + "no PLLs flagged as PHCs, nothing to do"); + err = -ENODEV; + } + + mutex_unlock(idtcm->lock); + + if (err) { + ptp_clock_unregister_all(idtcm); + return err; + } + + platform_set_drvdata(pdev, idtcm); + + return 0; +} + +static void idtcm_remove(struct platform_device *pdev) +{ + struct idtcm *idtcm = platform_get_drvdata(pdev); + + idtcm->extts_mask = 0; + ptp_clock_unregister_all(idtcm); + cancel_delayed_work_sync(&idtcm->extts_work); +} + +static struct platform_driver idtcm_driver = { + .driver = { + .name = "8a3400x-phc", + }, + .probe = idtcm_probe, + .remove = idtcm_remove, +}; + +module_platform_driver(idtcm_driver); diff --git a/drivers/ptp/ptp_clockmatrix.h b/drivers/ptp/ptp_clockmatrix.h new file mode 100644 index 000000000..7c17c4f7f --- /dev/null +++ b/drivers/ptp/ptp_clockmatrix.h @@ -0,0 +1,142 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * PTP hardware clock driver for the IDT ClockMatrix(TM) family of timing and + * synchronization devices. + * + * Copyright (C) 2019 Integrated Device Technology, Inc., a Renesas Company. + */ +#ifndef PTP_IDTCLOCKMATRIX_H +#define PTP_IDTCLOCKMATRIX_H + +#include +#include +#include +#include + +#define FW_FILENAME "idtcm.bin" +#define MAX_TOD (4) +#define MAX_PLL (8) +#define MAX_REF_CLK (16) + +#define MAX_ABS_WRITE_PHASE_NANOSECONDS (107374182L) + +#define TOD_MASK_ADDR (0xFFA5) +#define DEFAULT_TOD_MASK (0x04) + +#define SET_U16_LSB(orig, val8) (orig = (0xff00 & (orig)) | (val8)) +#define SET_U16_MSB(orig, val8) (orig = (0x00ff & (orig)) | (val8 << 8)) + +#define TOD0_PTP_PLL_ADDR (0xFFA8) +#define TOD1_PTP_PLL_ADDR (0xFFA9) +#define TOD2_PTP_PLL_ADDR (0xFFAA) +#define TOD3_PTP_PLL_ADDR (0xFFAB) + +#define TOD0_OUT_ALIGN_MASK_ADDR (0xFFB0) +#define TOD1_OUT_ALIGN_MASK_ADDR (0xFFB2) +#define TOD2_OUT_ALIGN_MASK_ADDR (0xFFB4) +#define TOD3_OUT_ALIGN_MASK_ADDR (0xFFB6) + +#define DEFAULT_OUTPUT_MASK_PLL0 (0x003) +#define DEFAULT_OUTPUT_MASK_PLL1 (0x00c) +#define DEFAULT_OUTPUT_MASK_PLL2 (0x030) +#define DEFAULT_OUTPUT_MASK_PLL3 (0x0c0) + +#define DEFAULT_TOD0_PTP_PLL (0) +#define DEFAULT_TOD1_PTP_PLL (1) +#define DEFAULT_TOD2_PTP_PLL (2) +#define DEFAULT_TOD3_PTP_PLL (3) + +#define PHASE_PULL_IN_THRESHOLD_NS_DEPRECATED (150000) +#define PHASE_PULL_IN_THRESHOLD_NS (15000) +#define TOD_WRITE_OVERHEAD_COUNT_MAX (2) +#define TOD_BYTE_COUNT (11) + +#define LOCK_TIMEOUT_MS (2000) +#define LOCK_POLL_INTERVAL_MS (10) + +#define IDTCM_MAX_WRITE_COUNT (512) + +#define PHASE_PULL_IN_MAX_PPB (144000) +#define PHASE_PULL_IN_MIN_THRESHOLD_NS (2) + +/* + * Return register address based on passed in firmware version + */ +#define IDTCM_FW_REG(FW, VER, REG) (((FW) < (VER)) ? (REG) : (REG##_##VER)) +enum fw_version { + V_DEFAULT = 0, + V487 = 1, + V520 = 2, +}; + +/* PTP PLL Mode */ +enum ptp_pll_mode { + PTP_PLL_MODE_MIN = 0, + PTP_PLL_MODE_WRITE_FREQUENCY = PTP_PLL_MODE_MIN, + PTP_PLL_MODE_WRITE_PHASE, + PTP_PLL_MODE_UNSUPPORTED, + PTP_PLL_MODE_MAX = PTP_PLL_MODE_UNSUPPORTED, +}; + +struct idtcm; + +struct idtcm_channel { + struct ptp_clock_info caps; + struct ptp_clock *ptp_clock; + struct idtcm *idtcm; + u16 dpll_phase; + u16 dpll_freq; + u16 dpll_n; + u16 dpll_ctrl_n; + u16 dpll_phase_pull_in; + u16 tod_read_primary; + u16 tod_read_secondary; + u16 tod_write; + u16 tod_n; + u16 hw_dpll_n; + u8 sync_src; + enum ptp_pll_mode mode; + int (*configure_write_frequency)(struct idtcm_channel *channel); + int (*configure_write_phase)(struct idtcm_channel *channel); + int (*do_phase_pull_in)(struct idtcm_channel *channel, + s32 offset_ns, u32 max_ffo_ppb); + s32 current_freq_scaled_ppm; + bool phase_pull_in; + u32 dco_delay; + /* last input trigger for extts */ + u8 refn; + u8 pll; + u8 tod; + u16 output_mask; +}; + +struct idtcm { + struct idtcm_channel channel[MAX_TOD]; + struct device *dev; + u8 tod_mask; + char version[16]; + enum fw_version fw_ver; + /* Polls for external time stamps */ + u8 extts_mask; + bool extts_single_shot; + struct delayed_work extts_work; + /* Remember the ptp channel to report extts */ + struct idtcm_channel *event_channel[MAX_TOD]; + /* Mutex to protect operations from being interrupted */ + struct mutex *lock; + struct device *mfd; + struct regmap *regmap; + /* Overhead calculation for adjtime */ + u8 calculate_overhead_flag; + s64 tod_write_overhead_ns; + ktime_t start_time; +}; + +struct idtcm_fwrc { + u8 hiaddr; + u8 loaddr; + u8 value; + u8 reserved; +} __packed; + +#endif /* PTP_IDTCLOCKMATRIX_H */ diff --git a/drivers/ptp/ptp_dfl_tod.c b/drivers/ptp/ptp_dfl_tod.c new file mode 100644 index 000000000..f699d541b --- /dev/null +++ b/drivers/ptp/ptp_dfl_tod.c @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * DFL device driver for Time-of-Day (ToD) private feature + * + * Copyright (C) 2023 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FME_FEATURE_ID_TOD 0x22 + +/* ToD clock register space. */ +#define TOD_CLK_FREQ 0x038 + +/* + * The read sequence of ToD timestamp registers: TOD_NANOSEC, TOD_SECONDSL and + * TOD_SECONDSH, because there is a hardware snapshot whenever the TOD_NANOSEC + * register is read. + * + * The ToD IP requires writing registers in the reverse order to the read sequence. + * The timestamp is corrected when the TOD_NANOSEC register is written, so the + * sequence of write TOD registers: TOD_SECONDSH, TOD_SECONDSL and TOD_NANOSEC. + */ +#define TOD_SECONDSH 0x100 +#define TOD_SECONDSL 0x104 +#define TOD_NANOSEC 0x108 +#define TOD_PERIOD 0x110 +#define TOD_ADJUST_PERIOD 0x114 +#define TOD_ADJUST_COUNT 0x118 +#define TOD_DRIFT_ADJUST 0x11c +#define TOD_DRIFT_ADJUST_RATE 0x120 +#define PERIOD_FRAC_OFFSET 16 +#define SECONDS_MSB GENMASK_ULL(47, 32) +#define SECONDS_LSB GENMASK_ULL(31, 0) +#define TOD_SECONDSH_SEC_MSB GENMASK_ULL(15, 0) + +#define CAL_SECONDS(m, l) ((FIELD_GET(TOD_SECONDSH_SEC_MSB, (m)) << 32) | (l)) + +#define TOD_PERIOD_MASK GENMASK_ULL(19, 0) +#define TOD_PERIOD_MAX FIELD_MAX(TOD_PERIOD_MASK) +#define TOD_PERIOD_MIN 0 +#define TOD_DRIFT_ADJUST_MASK GENMASK_ULL(15, 0) +#define TOD_DRIFT_ADJUST_FNS_MAX FIELD_MAX(TOD_DRIFT_ADJUST_MASK) +#define TOD_DRIFT_ADJUST_RATE_MAX TOD_DRIFT_ADJUST_FNS_MAX +#define TOD_ADJUST_COUNT_MASK GENMASK_ULL(19, 0) +#define TOD_ADJUST_COUNT_MAX FIELD_MAX(TOD_ADJUST_COUNT_MASK) +#define TOD_ADJUST_INTERVAL_US 10 +#define TOD_ADJUST_MS \ + (((TOD_PERIOD_MAX >> 16) + 1) * (TOD_ADJUST_COUNT_MAX + 1)) +#define TOD_ADJUST_MS_MAX (TOD_ADJUST_MS / MICRO) +#define TOD_ADJUST_MAX_US (TOD_ADJUST_MS_MAX * USEC_PER_MSEC) +#define TOD_MAX_ADJ (500 * MEGA) + +struct dfl_tod { + struct ptp_clock_info ptp_clock_ops; + struct device *dev; + struct ptp_clock *ptp_clock; + + /* ToD Clock address space */ + void __iomem *tod_ctrl; + + /* ToD clock registers protection */ + spinlock_t tod_lock; +}; + +/* + * A fine ToD HW clock offset adjustment. To perform the fine offset adjustment, the + * adjust_period and adjust_count argument are used to update the TOD_ADJUST_PERIOD + * and TOD_ADJUST_COUNT register for in hardware. The dt->tod_lock spinlock must be + * held when calling this function. + */ +static int fine_adjust_tod_clock(struct dfl_tod *dt, u32 adjust_period, + u32 adjust_count) +{ + void __iomem *base = dt->tod_ctrl; + u32 val; + + writel(adjust_period, base + TOD_ADJUST_PERIOD); + writel(adjust_count, base + TOD_ADJUST_COUNT); + + /* Wait for present offset adjustment update to complete */ + return readl_poll_timeout_atomic(base + TOD_ADJUST_COUNT, val, !val, TOD_ADJUST_INTERVAL_US, + TOD_ADJUST_MAX_US); +} + +/* + * A coarse ToD HW clock offset adjustment. The coarse time adjustment performs by + * adding or subtracting the delta value from the current ToD HW clock time. + */ +static int coarse_adjust_tod_clock(struct dfl_tod *dt, s64 delta) +{ + u32 seconds_msb, seconds_lsb, nanosec; + void __iomem *base = dt->tod_ctrl; + u64 seconds, now; + + if (delta == 0) + return 0; + + nanosec = readl(base + TOD_NANOSEC); + seconds_lsb = readl(base + TOD_SECONDSL); + seconds_msb = readl(base + TOD_SECONDSH); + + /* Calculate new time */ + seconds = CAL_SECONDS(seconds_msb, seconds_lsb); + now = seconds * NSEC_PER_SEC + nanosec + delta; + + seconds = div_u64_rem(now, NSEC_PER_SEC, &nanosec); + seconds_msb = FIELD_GET(SECONDS_MSB, seconds); + seconds_lsb = FIELD_GET(SECONDS_LSB, seconds); + + writel(seconds_msb, base + TOD_SECONDSH); + writel(seconds_lsb, base + TOD_SECONDSL); + writel(nanosec, base + TOD_NANOSEC); + + return 0; +} + +static int dfl_tod_adjust_fine(struct ptp_clock_info *ptp, long scaled_ppm) +{ + struct dfl_tod *dt = container_of(ptp, struct dfl_tod, ptp_clock_ops); + u32 tod_period, tod_rem, tod_drift_adjust_fns, tod_drift_adjust_rate; + void __iomem *base = dt->tod_ctrl; + unsigned long flags, rate; + u64 ppb; + + /* Get the clock rate from clock frequency register offset */ + rate = readl(base + TOD_CLK_FREQ); + + /* add GIGA as nominal ppb */ + ppb = scaled_ppm_to_ppb(scaled_ppm) + GIGA; + + tod_period = div_u64_rem(ppb << PERIOD_FRAC_OFFSET, rate, &tod_rem); + if (tod_period > TOD_PERIOD_MAX) + return -ERANGE; + + /* + * The drift of ToD adjusted periodically by adding a drift_adjust_fns + * correction value every drift_adjust_rate count of clock cycles. + */ + tod_drift_adjust_fns = tod_rem / gcd(tod_rem, rate); + tod_drift_adjust_rate = rate / gcd(tod_rem, rate); + + while ((tod_drift_adjust_fns > TOD_DRIFT_ADJUST_FNS_MAX) || + (tod_drift_adjust_rate > TOD_DRIFT_ADJUST_RATE_MAX)) { + tod_drift_adjust_fns >>= 1; + tod_drift_adjust_rate >>= 1; + } + + if (tod_drift_adjust_fns == 0) + tod_drift_adjust_rate = 0; + + spin_lock_irqsave(&dt->tod_lock, flags); + writel(tod_period, base + TOD_PERIOD); + writel(0, base + TOD_ADJUST_PERIOD); + writel(0, base + TOD_ADJUST_COUNT); + writel(tod_drift_adjust_fns, base + TOD_DRIFT_ADJUST); + writel(tod_drift_adjust_rate, base + TOD_DRIFT_ADJUST_RATE); + spin_unlock_irqrestore(&dt->tod_lock, flags); + + return 0; +} + +static int dfl_tod_adjust_time(struct ptp_clock_info *ptp, s64 delta) +{ + struct dfl_tod *dt = container_of(ptp, struct dfl_tod, ptp_clock_ops); + u32 period, diff, rem, rem_period, adj_period; + void __iomem *base = dt->tod_ctrl; + unsigned long flags; + bool neg_adj; + u64 count; + int ret; + + neg_adj = delta < 0; + if (neg_adj) + delta = -delta; + + spin_lock_irqsave(&dt->tod_lock, flags); + + /* + * Get the maximum possible value of the Period register offset + * adjustment in nanoseconds scale. This depends on the current + * Period register setting and the maximum and minimum possible + * values of the Period register. + */ + period = readl(base + TOD_PERIOD); + + if (neg_adj) { + diff = (period - TOD_PERIOD_MIN) >> PERIOD_FRAC_OFFSET; + adj_period = period - (diff << PERIOD_FRAC_OFFSET); + count = div_u64_rem(delta, diff, &rem); + rem_period = period - (rem << PERIOD_FRAC_OFFSET); + } else { + diff = (TOD_PERIOD_MAX - period) >> PERIOD_FRAC_OFFSET; + adj_period = period + (diff << PERIOD_FRAC_OFFSET); + count = div_u64_rem(delta, diff, &rem); + rem_period = period + (rem << PERIOD_FRAC_OFFSET); + } + + ret = 0; + + if (count > TOD_ADJUST_COUNT_MAX) { + ret = coarse_adjust_tod_clock(dt, delta); + } else { + /* Adjust the period by count cycles to adjust the time */ + if (count) + ret = fine_adjust_tod_clock(dt, adj_period, count); + + /* If there is a remainder, adjust the period for an additional cycle */ + if (rem) + ret = fine_adjust_tod_clock(dt, rem_period, 1); + } + + spin_unlock_irqrestore(&dt->tod_lock, flags); + + return ret; +} + +static int dfl_tod_get_timex(struct ptp_clock_info *ptp, struct timespec64 *ts, + struct ptp_system_timestamp *sts) +{ + struct dfl_tod *dt = container_of(ptp, struct dfl_tod, ptp_clock_ops); + u32 seconds_msb, seconds_lsb, nanosec; + void __iomem *base = dt->tod_ctrl; + unsigned long flags; + u64 seconds; + + spin_lock_irqsave(&dt->tod_lock, flags); + ptp_read_system_prets(sts); + nanosec = readl(base + TOD_NANOSEC); + seconds_lsb = readl(base + TOD_SECONDSL); + seconds_msb = readl(base + TOD_SECONDSH); + ptp_read_system_postts(sts); + spin_unlock_irqrestore(&dt->tod_lock, flags); + + seconds = CAL_SECONDS(seconds_msb, seconds_lsb); + + ts->tv_nsec = nanosec; + ts->tv_sec = seconds; + + return 0; +} + +static int dfl_tod_set_time(struct ptp_clock_info *ptp, + const struct timespec64 *ts) +{ + struct dfl_tod *dt = container_of(ptp, struct dfl_tod, ptp_clock_ops); + u32 seconds_msb = FIELD_GET(SECONDS_MSB, ts->tv_sec); + u32 seconds_lsb = FIELD_GET(SECONDS_LSB, ts->tv_sec); + u32 nanosec = FIELD_GET(SECONDS_LSB, ts->tv_nsec); + void __iomem *base = dt->tod_ctrl; + unsigned long flags; + + spin_lock_irqsave(&dt->tod_lock, flags); + writel(seconds_msb, base + TOD_SECONDSH); + writel(seconds_lsb, base + TOD_SECONDSL); + writel(nanosec, base + TOD_NANOSEC); + spin_unlock_irqrestore(&dt->tod_lock, flags); + + return 0; +} + +static struct ptp_clock_info dfl_tod_clock_ops = { + .owner = THIS_MODULE, + .name = "dfl_tod", + .max_adj = TOD_MAX_ADJ, + .adjfine = dfl_tod_adjust_fine, + .adjtime = dfl_tod_adjust_time, + .gettimex64 = dfl_tod_get_timex, + .settime64 = dfl_tod_set_time, +}; + +static int dfl_tod_probe(struct dfl_device *ddev) +{ + struct device *dev = &ddev->dev; + struct dfl_tod *dt; + + dt = devm_kzalloc(dev, sizeof(*dt), GFP_KERNEL); + if (!dt) + return -ENOMEM; + + dt->tod_ctrl = devm_ioremap_resource(dev, &ddev->mmio_res); + if (IS_ERR(dt->tod_ctrl)) + return PTR_ERR(dt->tod_ctrl); + + dt->dev = dev; + spin_lock_init(&dt->tod_lock); + dev_set_drvdata(dev, dt); + + dt->ptp_clock_ops = dfl_tod_clock_ops; + + dt->ptp_clock = ptp_clock_register(&dt->ptp_clock_ops, dev); + if (IS_ERR(dt->ptp_clock)) + return dev_err_probe(dt->dev, PTR_ERR(dt->ptp_clock), + "Unable to register PTP clock\n"); + + return 0; +} + +static void dfl_tod_remove(struct dfl_device *ddev) +{ + struct dfl_tod *dt = dev_get_drvdata(&ddev->dev); + + ptp_clock_unregister(dt->ptp_clock); +} + +static const struct dfl_device_id dfl_tod_ids[] = { + { FME_ID, FME_FEATURE_ID_TOD }, + { } +}; +MODULE_DEVICE_TABLE(dfl, dfl_tod_ids); + +static struct dfl_driver dfl_tod_driver = { + .drv = { + .name = "dfl-tod", + }, + .id_table = dfl_tod_ids, + .probe = dfl_tod_probe, + .remove = dfl_tod_remove, +}; +module_dfl_driver(dfl_tod_driver); + +MODULE_DESCRIPTION("FPGA DFL ToD driver"); +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL"); diff --git a/drivers/ptp/ptp_dte.c b/drivers/ptp/ptp_dte.c new file mode 100644 index 000000000..834ecacc7 --- /dev/null +++ b/drivers/ptp/ptp_dte.c @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright 2017 Broadcom + +#include +#include +#include +#include +#include +#include + +#define DTE_NCO_LOW_TIME_REG 0x00 +#define DTE_NCO_TIME_REG 0x04 +#define DTE_NCO_OVERFLOW_REG 0x08 +#define DTE_NCO_INC_REG 0x0c + +#define DTE_NCO_SUM2_MASK 0xffffffff +#define DTE_NCO_SUM2_SHIFT 4ULL + +#define DTE_NCO_SUM3_MASK 0xff +#define DTE_NCO_SUM3_SHIFT 36ULL +#define DTE_NCO_SUM3_WR_SHIFT 8 + +#define DTE_NCO_TS_WRAP_MASK 0xfff +#define DTE_NCO_TS_WRAP_LSHIFT 32 + +#define DTE_NCO_INC_DEFAULT 0x80000000 +#define DTE_NUM_REGS_TO_RESTORE 4 + +/* Full wrap around is 44bits in ns (~4.887 hrs) */ +#define DTE_WRAP_AROUND_NSEC_SHIFT 44 + +/* 44 bits NCO */ +#define DTE_NCO_MAX_NS 0xFFFFFFFFFFFLL + +/* 125MHz with 3.29 reg cfg */ +#define DTE_PPB_ADJ(ppb) (u32)(div64_u64((((u64)abs(ppb) * BIT(28)) +\ + 62500000ULL), 125000000ULL)) + +/* ptp dte priv structure */ +struct ptp_dte { + void __iomem *regs; + struct ptp_clock *ptp_clk; + struct ptp_clock_info caps; + struct device *dev; + u32 ts_ovf_last; + u32 ts_wrap_cnt; + spinlock_t lock; + u32 reg_val[DTE_NUM_REGS_TO_RESTORE]; +}; + +static void dte_write_nco(void __iomem *regs, s64 ns) +{ + u32 sum2, sum3; + + sum2 = (u32)((ns >> DTE_NCO_SUM2_SHIFT) & DTE_NCO_SUM2_MASK); + /* compensate for ignoring sum1 */ + if (sum2 != DTE_NCO_SUM2_MASK) + sum2++; + + /* to write sum3, bits [15:8] needs to be written */ + sum3 = (u32)(((ns >> DTE_NCO_SUM3_SHIFT) & DTE_NCO_SUM3_MASK) << + DTE_NCO_SUM3_WR_SHIFT); + + writel(0, (regs + DTE_NCO_LOW_TIME_REG)); + writel(sum2, (regs + DTE_NCO_TIME_REG)); + writel(sum3, (regs + DTE_NCO_OVERFLOW_REG)); +} + +static s64 dte_read_nco(void __iomem *regs) +{ + u32 sum2, sum3; + s64 ns; + + /* + * ignoring sum1 (4 bits) gives a 16ns resolution, which + * works due to the async register read. + */ + sum3 = readl(regs + DTE_NCO_OVERFLOW_REG) & DTE_NCO_SUM3_MASK; + sum2 = readl(regs + DTE_NCO_TIME_REG); + ns = ((s64)sum3 << DTE_NCO_SUM3_SHIFT) | + ((s64)sum2 << DTE_NCO_SUM2_SHIFT); + + return ns; +} + +static void dte_write_nco_delta(struct ptp_dte *ptp_dte, s64 delta) +{ + s64 ns; + + ns = dte_read_nco(ptp_dte->regs); + + /* handle wraparound conditions */ + if ((delta < 0) && (abs(delta) > ns)) { + if (ptp_dte->ts_wrap_cnt) { + ns += DTE_NCO_MAX_NS + delta; + ptp_dte->ts_wrap_cnt--; + } else { + ns = 0; + } + } else { + ns += delta; + if (ns > DTE_NCO_MAX_NS) { + ptp_dte->ts_wrap_cnt++; + ns -= DTE_NCO_MAX_NS; + } + } + + dte_write_nco(ptp_dte->regs, ns); + + ptp_dte->ts_ovf_last = (ns >> DTE_NCO_TS_WRAP_LSHIFT) & + DTE_NCO_TS_WRAP_MASK; +} + +static s64 dte_read_nco_with_ovf(struct ptp_dte *ptp_dte) +{ + u32 ts_ovf; + s64 ns = 0; + + ns = dte_read_nco(ptp_dte->regs); + + /*Timestamp overflow: 8 LSB bits of sum3, 4 MSB bits of sum2 */ + ts_ovf = (ns >> DTE_NCO_TS_WRAP_LSHIFT) & DTE_NCO_TS_WRAP_MASK; + + /* Check for wrap around */ + if (ts_ovf < ptp_dte->ts_ovf_last) + ptp_dte->ts_wrap_cnt++; + + ptp_dte->ts_ovf_last = ts_ovf; + + /* adjust for wraparounds */ + ns += (s64)(BIT_ULL(DTE_WRAP_AROUND_NSEC_SHIFT) * ptp_dte->ts_wrap_cnt); + + return ns; +} + +static int ptp_dte_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) +{ + s32 ppb = scaled_ppm_to_ppb(scaled_ppm); + u32 nco_incr; + unsigned long flags; + struct ptp_dte *ptp_dte = container_of(ptp, struct ptp_dte, caps); + + if (abs(ppb) > ptp_dte->caps.max_adj) { + dev_err(ptp_dte->dev, "ppb adj too big\n"); + return -EINVAL; + } + + if (ppb < 0) + nco_incr = DTE_NCO_INC_DEFAULT - DTE_PPB_ADJ(ppb); + else + nco_incr = DTE_NCO_INC_DEFAULT + DTE_PPB_ADJ(ppb); + + spin_lock_irqsave(&ptp_dte->lock, flags); + writel(nco_incr, ptp_dte->regs + DTE_NCO_INC_REG); + spin_unlock_irqrestore(&ptp_dte->lock, flags); + + return 0; +} + +static int ptp_dte_adjtime(struct ptp_clock_info *ptp, s64 delta) +{ + unsigned long flags; + struct ptp_dte *ptp_dte = container_of(ptp, struct ptp_dte, caps); + + spin_lock_irqsave(&ptp_dte->lock, flags); + dte_write_nco_delta(ptp_dte, delta); + spin_unlock_irqrestore(&ptp_dte->lock, flags); + + return 0; +} + +static int ptp_dte_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts) +{ + unsigned long flags; + struct ptp_dte *ptp_dte = container_of(ptp, struct ptp_dte, caps); + + spin_lock_irqsave(&ptp_dte->lock, flags); + *ts = ns_to_timespec64(dte_read_nco_with_ovf(ptp_dte)); + spin_unlock_irqrestore(&ptp_dte->lock, flags); + + return 0; +} + +static int ptp_dte_settime(struct ptp_clock_info *ptp, + const struct timespec64 *ts) +{ + unsigned long flags; + struct ptp_dte *ptp_dte = container_of(ptp, struct ptp_dte, caps); + + spin_lock_irqsave(&ptp_dte->lock, flags); + + /* Disable nco increment */ + writel(0, ptp_dte->regs + DTE_NCO_INC_REG); + + dte_write_nco(ptp_dte->regs, timespec64_to_ns(ts)); + + /* reset overflow and wrap counter */ + ptp_dte->ts_ovf_last = 0; + ptp_dte->ts_wrap_cnt = 0; + + /* Enable nco increment */ + writel(DTE_NCO_INC_DEFAULT, ptp_dte->regs + DTE_NCO_INC_REG); + + spin_unlock_irqrestore(&ptp_dte->lock, flags); + + return 0; +} + +static int ptp_dte_enable(struct ptp_clock_info *ptp, + struct ptp_clock_request *rq, int on) +{ + return -EOPNOTSUPP; +} + +static const struct ptp_clock_info ptp_dte_caps = { + .owner = THIS_MODULE, + .name = "DTE PTP timer", + .max_adj = 50000000, + .n_ext_ts = 0, + .n_pins = 0, + .pps = 0, + .adjfine = ptp_dte_adjfine, + .adjtime = ptp_dte_adjtime, + .gettime64 = ptp_dte_gettime, + .settime64 = ptp_dte_settime, + .enable = ptp_dte_enable, +}; + +static int ptp_dte_probe(struct platform_device *pdev) +{ + struct ptp_dte *ptp_dte; + struct device *dev = &pdev->dev; + + ptp_dte = devm_kzalloc(dev, sizeof(struct ptp_dte), GFP_KERNEL); + if (!ptp_dte) + return -ENOMEM; + + ptp_dte->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ptp_dte->regs)) + return PTR_ERR(ptp_dte->regs); + + spin_lock_init(&ptp_dte->lock); + + ptp_dte->dev = dev; + ptp_dte->caps = ptp_dte_caps; + ptp_dte->ptp_clk = ptp_clock_register(&ptp_dte->caps, &pdev->dev); + if (IS_ERR(ptp_dte->ptp_clk)) { + dev_err(dev, + "%s: Failed to register ptp clock\n", __func__); + return PTR_ERR(ptp_dte->ptp_clk); + } + + platform_set_drvdata(pdev, ptp_dte); + + dev_info(dev, "ptp clk probe done\n"); + + return 0; +} + +static void ptp_dte_remove(struct platform_device *pdev) +{ + struct ptp_dte *ptp_dte = platform_get_drvdata(pdev); + u8 i; + + ptp_clock_unregister(ptp_dte->ptp_clk); + + for (i = 0; i < DTE_NUM_REGS_TO_RESTORE; i++) + writel(0, ptp_dte->regs + (i * sizeof(u32))); +} + +#ifdef CONFIG_PM_SLEEP +static int ptp_dte_suspend(struct device *dev) +{ + struct ptp_dte *ptp_dte = dev_get_drvdata(dev); + u8 i; + + for (i = 0; i < DTE_NUM_REGS_TO_RESTORE; i++) { + ptp_dte->reg_val[i] = + readl(ptp_dte->regs + (i * sizeof(u32))); + } + + /* disable the nco */ + writel(0, ptp_dte->regs + DTE_NCO_INC_REG); + + return 0; +} + +static int ptp_dte_resume(struct device *dev) +{ + struct ptp_dte *ptp_dte = dev_get_drvdata(dev); + u8 i; + + for (i = 0; i < DTE_NUM_REGS_TO_RESTORE; i++) { + if ((i * sizeof(u32)) != DTE_NCO_OVERFLOW_REG) + writel(ptp_dte->reg_val[i], + (ptp_dte->regs + (i * sizeof(u32)))); + else + writel(((ptp_dte->reg_val[i] & + DTE_NCO_SUM3_MASK) << DTE_NCO_SUM3_WR_SHIFT), + (ptp_dte->regs + (i * sizeof(u32)))); + } + + return 0; +} + +static const struct dev_pm_ops ptp_dte_pm_ops = { + .suspend = ptp_dte_suspend, + .resume = ptp_dte_resume +}; + +#define PTP_DTE_PM_OPS (&ptp_dte_pm_ops) +#else +#define PTP_DTE_PM_OPS NULL +#endif + +static const struct of_device_id ptp_dte_of_match[] = { + { .compatible = "brcm,ptp-dte", }, + {}, +}; +MODULE_DEVICE_TABLE(of, ptp_dte_of_match); + +static struct platform_driver ptp_dte_driver = { + .driver = { + .name = "ptp-dte", + .pm = PTP_DTE_PM_OPS, + .of_match_table = ptp_dte_of_match, + }, + .probe = ptp_dte_probe, + .remove = ptp_dte_remove, +}; +module_platform_driver(ptp_dte_driver); + +MODULE_AUTHOR("Broadcom"); +MODULE_DESCRIPTION("Broadcom DTE PTP Clock driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/ptp/ptp_fc3.c b/drivers/ptp/ptp_fc3.c new file mode 100644 index 000000000..02b973995 --- /dev/null +++ b/drivers/ptp/ptp_fc3.c @@ -0,0 +1,1010 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * PTP hardware clock driver for the FemtoClock3 family of timing and + * synchronization devices. + * + * Copyright (C) 2023 Integrated Device Technology, Inc., a Renesas Company. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ptp_private.h" +#include "ptp_fc3.h" + +MODULE_DESCRIPTION("Driver for IDT FemtoClock3(TM) family"); +MODULE_AUTHOR("IDT support-1588 "); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL"); + +/* + * The name of the firmware file to be loaded + * over-rides any automatic selection + */ +static char *firmware; +module_param(firmware, charp, 0); + +static s64 ns2counters(struct idtfc3 *idtfc3, s64 nsec, u32 *sub_ns) +{ + s64 sync; + s32 rem; + + if (likely(nsec >= 0)) { + sync = div_u64_rem(nsec, idtfc3->ns_per_sync, &rem); + *sub_ns = rem; + } else { + sync = -div_u64_rem(-nsec - 1, idtfc3->ns_per_sync, &rem) - 1; + *sub_ns = idtfc3->ns_per_sync - rem - 1; + } + + return sync * idtfc3->ns_per_sync; +} + +static s64 tdc_meas2offset(struct idtfc3 *idtfc3, u64 meas_read) +{ + s64 coarse, fine; + + fine = FIELD_GET_SIGNED(FINE_MEAS_MASK, meas_read); + coarse = FIELD_GET_SIGNED(COARSE_MEAS_MASK, meas_read); + + fine = div64_s64(fine * NSEC_PER_SEC, idtfc3->tdc_apll_freq * 62LL); + coarse = div64_s64(coarse * NSEC_PER_SEC, idtfc3->time_ref_freq); + + return coarse + fine; +} + +static s64 tdc_offset2phase(struct idtfc3 *idtfc3, s64 offset_ns) +{ + if (offset_ns > idtfc3->ns_per_sync / 2) + offset_ns -= idtfc3->ns_per_sync; + + return offset_ns * idtfc3->tdc_offset_sign; +} + +static int idtfc3_set_lpf_mode(struct idtfc3 *idtfc3, u8 mode) +{ + int err; + + if (mode >= LPF_INVALID) + return -EINVAL; + + if (idtfc3->lpf_mode == mode) + return 0; + + err = regmap_bulk_write(idtfc3->regmap, LPF_MODE_CNFG, &mode, sizeof(mode)); + if (err) + return err; + + idtfc3->lpf_mode = mode; + + return 0; +} + +static int idtfc3_enable_lpf(struct idtfc3 *idtfc3, bool enable) +{ + u8 val; + int err; + + err = regmap_bulk_read(idtfc3->regmap, LPF_CTRL, &val, sizeof(val)); + if (err) + return err; + + if (enable == true) + val |= LPF_EN; + else + val &= ~LPF_EN; + + return regmap_bulk_write(idtfc3->regmap, LPF_CTRL, &val, sizeof(val)); +} + +static int idtfc3_get_time_ref_freq(struct idtfc3 *idtfc3) +{ + int err; + u8 buf[4]; + u8 time_ref_div; + u8 time_clk_div; + + err = regmap_bulk_read(idtfc3->regmap, TIME_CLOCK_MEAS_DIV_CNFG, buf, sizeof(buf)); + if (err) + return err; + time_ref_div = FIELD_GET(TIME_REF_DIV_MASK, get_unaligned_le32(buf)) + 1; + + err = regmap_bulk_read(idtfc3->regmap, TIME_CLOCK_COUNT, buf, 1); + if (err) + return err; + time_clk_div = (buf[0] & TIME_CLOCK_COUNT_MASK) + 1; + idtfc3->time_ref_freq = idtfc3->hw_param.time_clk_freq * + time_clk_div / time_ref_div; + + return 0; +} + +static int idtfc3_get_tdc_offset_sign(struct idtfc3 *idtfc3) +{ + int err; + u8 buf[4]; + u32 val; + u8 sig1, sig2; + + err = regmap_bulk_read(idtfc3->regmap, TIME_CLOCK_TDC_FANOUT_CNFG, buf, sizeof(buf)); + if (err) + return err; + + val = get_unaligned_le32(buf); + if ((val & TIME_SYNC_TO_TDC_EN) != TIME_SYNC_TO_TDC_EN) { + dev_err(idtfc3->dev, "TIME_SYNC_TO_TDC_EN is off !!!"); + return -EINVAL; + } + + sig1 = FIELD_GET(SIG1_MUX_SEL_MASK, val); + sig2 = FIELD_GET(SIG2_MUX_SEL_MASK, val); + + if ((sig1 == sig2) || ((sig1 != TIME_SYNC) && (sig2 != TIME_SYNC))) { + dev_err(idtfc3->dev, "Invalid tdc_mux_sel sig1=%d sig2=%d", sig1, sig2); + return -EINVAL; + } else if (sig1 == TIME_SYNC) { + idtfc3->tdc_offset_sign = 1; + } else if (sig2 == TIME_SYNC) { + idtfc3->tdc_offset_sign = -1; + } + + return 0; +} + +static int idtfc3_lpf_bw(struct idtfc3 *idtfc3, u8 shift, u8 mult) +{ + u8 val = FIELD_PREP(LPF_BW_SHIFT, shift) | FIELD_PREP(LPF_BW_MULT, mult); + + return regmap_bulk_write(idtfc3->regmap, LPF_BW_CNFG, &val, sizeof(val)); +} + +static int idtfc3_enable_tdc(struct idtfc3 *idtfc3, bool enable, u8 meas_mode) +{ + int err; + u8 val = 0; + + /* Disable TDC first */ + err = regmap_bulk_write(idtfc3->regmap, TIME_CLOCK_MEAS_CTRL, &val, sizeof(val)); + if (err) + return err; + + if (enable == false) + return idtfc3_lpf_bw(idtfc3, LPF_BW_SHIFT_DEFAULT, LPF_BW_MULT_DEFAULT); + + if (meas_mode >= MEAS_MODE_INVALID) + return -EINVAL; + + /* Change TDC meas mode */ + err = regmap_bulk_write(idtfc3->regmap, TIME_CLOCK_MEAS_CNFG, + &meas_mode, sizeof(meas_mode)); + if (err) + return err; + + /* Enable TDC */ + val = TDC_MEAS_EN; + if (meas_mode == CONTINUOUS) + val |= TDC_MEAS_START; + err = regmap_bulk_write(idtfc3->regmap, TIME_CLOCK_MEAS_CTRL, &val, sizeof(val)); + if (err) + return err; + + return idtfc3_lpf_bw(idtfc3, LPF_BW_SHIFT_1PPS, LPF_BW_MULT_DEFAULT); +} + +static bool get_tdc_meas(struct idtfc3 *idtfc3, s64 *offset_ns) +{ + bool valid = false; + u8 buf[9]; + u8 val; + int err; + + while (true) { + err = regmap_bulk_read(idtfc3->regmap, TDC_FIFO_STS, + &val, sizeof(val)); + if (err) + return false; + + if (val & FIFO_EMPTY) + break; + + err = regmap_bulk_read(idtfc3->regmap, TDC_FIFO_READ_REQ, + &buf, sizeof(buf)); + if (err) + return false; + + valid = true; + } + + if (valid) + *offset_ns = tdc_meas2offset(idtfc3, get_unaligned_le64(&buf[1])); + + return valid; +} + +static int check_tdc_fifo_overrun(struct idtfc3 *idtfc3) +{ + u8 val; + int err; + + /* Check if FIFO is overrun */ + err = regmap_bulk_read(idtfc3->regmap, TDC_FIFO_STS, &val, sizeof(val)); + if (err) + return err; + + if (!(val & FIFO_FULL)) + return 0; + + dev_warn(idtfc3->dev, "TDC FIFO overrun !!!"); + + err = idtfc3_enable_tdc(idtfc3, true, CONTINUOUS); + if (err) + return err; + + return 0; +} + +static int get_tdc_meas_continuous(struct idtfc3 *idtfc3) +{ + int err; + s64 offset_ns; + struct ptp_clock_event event; + + err = check_tdc_fifo_overrun(idtfc3); + if (err) + return err; + + if (get_tdc_meas(idtfc3, &offset_ns) && offset_ns >= 0) { + event.index = 0; + event.offset = tdc_offset2phase(idtfc3, offset_ns); + event.type = PTP_CLOCK_EXTOFF; + ptp_clock_event(idtfc3->ptp_clock, &event); + } + + return 0; +} + +static int idtfc3_read_subcounter(struct idtfc3 *idtfc3) +{ + u8 buf[5] = {0}; + int err; + + err = regmap_bulk_read(idtfc3->regmap, TOD_COUNTER_READ_REQ, + &buf, sizeof(buf)); + if (err) + return err; + + /* sync_counter_value is [31:82] and sub_sync_counter_value is [0:30] */ + return get_unaligned_le32(&buf[1]) & SUB_SYNC_COUNTER_MASK; +} + +static int idtfc3_tod_update_is_done(struct idtfc3 *idtfc3) +{ + int err; + u8 req; + + err = read_poll_timeout_atomic(regmap_bulk_read, err, !req, USEC_PER_MSEC, + idtfc3->tc_write_timeout, true, idtfc3->regmap, + TOD_SYNC_LOAD_REQ_CTRL, &req, 1); + if (err) + dev_err(idtfc3->dev, "TOD counter write timeout !!!"); + + return err; +} + +static int idtfc3_write_subcounter(struct idtfc3 *idtfc3, u32 counter) +{ + u8 buf[18] = {0}; + int err; + + /* sync_counter_value is [31:82] and sub_sync_counter_value is [0:30] */ + put_unaligned_le32(counter & SUB_SYNC_COUNTER_MASK, &buf[0]); + + buf[16] = SUB_SYNC_LOAD_ENABLE | SYNC_LOAD_ENABLE; + buf[17] = SYNC_LOAD_REQ; + + err = regmap_bulk_write(idtfc3->regmap, TOD_SYNC_LOAD_VAL_CTRL, + &buf, sizeof(buf)); + if (err) + return err; + + return idtfc3_tod_update_is_done(idtfc3); +} + +static int idtfc3_timecounter_update(struct idtfc3 *idtfc3, u32 counter, s64 ns) +{ + int err; + + err = idtfc3_write_subcounter(idtfc3, counter); + if (err) + return err; + + /* Update time counter */ + idtfc3->ns = ns; + idtfc3->last_counter = counter; + + return 0; +} + +static int idtfc3_timecounter_read(struct idtfc3 *idtfc3) +{ + int now, delta; + + now = idtfc3_read_subcounter(idtfc3); + if (now < 0) + return now; + + /* calculate the delta since the last idtfc3_timecounter_read(): */ + if (now >= idtfc3->last_counter) + delta = now - idtfc3->last_counter; + else + delta = idtfc3->sub_sync_count - idtfc3->last_counter + now; + + /* Update time counter */ + idtfc3->ns += delta * idtfc3->ns_per_counter; + idtfc3->last_counter = now; + + return 0; +} + +static int _idtfc3_gettime(struct idtfc3 *idtfc3, struct timespec64 *ts) +{ + int err; + + err = idtfc3_timecounter_read(idtfc3); + if (err) + return err; + + *ts = ns_to_timespec64(idtfc3->ns); + + return 0; +} + +static int idtfc3_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts) +{ + struct idtfc3 *idtfc3 = container_of(ptp, struct idtfc3, caps); + int err; + + mutex_lock(idtfc3->lock); + err = _idtfc3_gettime(idtfc3, ts); + mutex_unlock(idtfc3->lock); + + return err; +} + +static int _idtfc3_settime(struct idtfc3 *idtfc3, const struct timespec64 *ts) +{ + s64 offset_ns, now_ns; + u32 counter, sub_ns; + int now; + + if (timespec64_valid(ts) == false) { + dev_err(idtfc3->dev, "%s: invalid timespec", __func__); + return -EINVAL; + } + + now = idtfc3_read_subcounter(idtfc3); + if (now < 0) + return now; + + offset_ns = (idtfc3->sub_sync_count - now) * idtfc3->ns_per_counter; + now_ns = timespec64_to_ns(ts); + (void)ns2counters(idtfc3, offset_ns + now_ns, &sub_ns); + + counter = sub_ns / idtfc3->ns_per_counter; + return idtfc3_timecounter_update(idtfc3, counter, now_ns); +} + +static int idtfc3_settime(struct ptp_clock_info *ptp, const struct timespec64 *ts) +{ + struct idtfc3 *idtfc3 = container_of(ptp, struct idtfc3, caps); + int err; + + mutex_lock(idtfc3->lock); + err = _idtfc3_settime(idtfc3, ts); + mutex_unlock(idtfc3->lock); + + return err; +} + +static int _idtfc3_adjtime(struct idtfc3 *idtfc3, s64 delta) +{ + /* + * The TOD counter can be synchronously loaded with any value, + * to be loaded on the next Time Sync pulse + */ + s64 sync_ns; + u32 sub_ns; + u32 counter; + + if (idtfc3->ns + delta < 0) { + dev_err(idtfc3->dev, "%lld ns adj is too large", delta); + return -EINVAL; + } + + sync_ns = ns2counters(idtfc3, delta + idtfc3->ns_per_sync, &sub_ns); + + counter = sub_ns / idtfc3->ns_per_counter; + return idtfc3_timecounter_update(idtfc3, counter, idtfc3->ns + sync_ns + + counter * idtfc3->ns_per_counter); +} + +static int idtfc3_adjtime(struct ptp_clock_info *ptp, s64 delta) +{ + struct idtfc3 *idtfc3 = container_of(ptp, struct idtfc3, caps); + int err; + + mutex_lock(idtfc3->lock); + err = _idtfc3_adjtime(idtfc3, delta); + mutex_unlock(idtfc3->lock); + + return err; +} + +static int _idtfc3_adjphase(struct idtfc3 *idtfc3, s32 delta) +{ + u8 buf[8] = {0}; + int err; + s64 pcw; + + err = idtfc3_set_lpf_mode(idtfc3, LPF_WP); + if (err) + return err; + + /* + * Phase Control Word unit is: 10^9 / (TDC_APLL_FREQ * 124) + * + * delta * TDC_APLL_FREQ * 124 + * PCW = --------------------------- + * 10^9 + * + */ + pcw = div_s64((s64)delta * idtfc3->tdc_apll_freq * 124, NSEC_PER_SEC); + + put_unaligned_le64(pcw, buf); + + return regmap_bulk_write(idtfc3->regmap, LPF_WR_PHASE_CTRL, buf, sizeof(buf)); +} + +static int idtfc3_adjphase(struct ptp_clock_info *ptp, s32 delta) +{ + struct idtfc3 *idtfc3 = container_of(ptp, struct idtfc3, caps); + int err; + + mutex_lock(idtfc3->lock); + err = _idtfc3_adjphase(idtfc3, delta); + mutex_unlock(idtfc3->lock); + + return err; +} + +static int _idtfc3_adjfine(struct idtfc3 *idtfc3, long scaled_ppm) +{ + u8 buf[8] = {0}; + int err; + s64 fcw; + + err = idtfc3_set_lpf_mode(idtfc3, LPF_WF); + if (err) + return err; + + /* + * Frequency Control Word unit is: 2^-44 * 10^6 ppm + * + * adjfreq: + * ppb * 2^44 + * FCW = ---------- + * 10^9 + * + * adjfine: + * ppm_16 * 2^28 + * FCW = ------------- + * 10^6 + */ + fcw = scaled_ppm * BIT(28); + fcw = div_s64(fcw, 1000000); + + put_unaligned_le64(fcw, buf); + + return regmap_bulk_write(idtfc3->regmap, LPF_WR_FREQ_CTRL, buf, sizeof(buf)); +} + +static int idtfc3_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) +{ + struct idtfc3 *idtfc3 = container_of(ptp, struct idtfc3, caps); + int err; + + mutex_lock(idtfc3->lock); + err = _idtfc3_adjfine(idtfc3, scaled_ppm); + mutex_unlock(idtfc3->lock); + + return err; +} + +static int idtfc3_enable(struct ptp_clock_info *ptp, + struct ptp_clock_request *rq, int on) +{ + struct idtfc3 *idtfc3 = container_of(ptp, struct idtfc3, caps); + int err = -EOPNOTSUPP; + + mutex_lock(idtfc3->lock); + switch (rq->type) { + case PTP_CLK_REQ_PEROUT: + if (!on) + err = 0; + /* Only accept a 1-PPS aligned to the second. */ + else if (rq->perout.start.nsec || rq->perout.period.sec != 1 || + rq->perout.period.nsec) + err = -ERANGE; + else + err = 0; + break; + case PTP_CLK_REQ_EXTTS: + if (on) { + /* Only accept requests for external phase offset */ + if ((rq->extts.flags & PTP_EXT_OFFSET) != (PTP_EXT_OFFSET)) + err = -EOPNOTSUPP; + else + err = idtfc3_enable_tdc(idtfc3, true, CONTINUOUS); + } else { + err = idtfc3_enable_tdc(idtfc3, false, MEAS_MODE_INVALID); + } + break; + default: + break; + } + mutex_unlock(idtfc3->lock); + + if (err) + dev_err(idtfc3->dev, "Failed in %s with err %d!", __func__, err); + + return err; +} + +static long idtfc3_aux_work(struct ptp_clock_info *ptp) +{ + struct idtfc3 *idtfc3 = container_of(ptp, struct idtfc3, caps); + static int tdc_get; + + mutex_lock(idtfc3->lock); + tdc_get %= TDC_GET_PERIOD; + if ((tdc_get == 0) || (tdc_get == TDC_GET_PERIOD / 2)) + idtfc3_timecounter_read(idtfc3); + get_tdc_meas_continuous(idtfc3); + tdc_get++; + mutex_unlock(idtfc3->lock); + + return idtfc3->tc_update_period; +} + +static const struct ptp_clock_info idtfc3_caps = { + .owner = THIS_MODULE, + .max_adj = MAX_FFO_PPB, + .n_per_out = 1, + .n_ext_ts = 1, + .supported_extts_flags = PTP_STRICT_FLAGS | PTP_EXT_OFFSET, + .adjphase = &idtfc3_adjphase, + .adjfine = &idtfc3_adjfine, + .adjtime = &idtfc3_adjtime, + .gettime64 = &idtfc3_gettime, + .settime64 = &idtfc3_settime, + .enable = &idtfc3_enable, + .do_aux_work = &idtfc3_aux_work, +}; + +static int idtfc3_hw_calibrate(struct idtfc3 *idtfc3) +{ + int err = 0; + u8 val; + + mdelay(10); + /* + * Toggle TDC_DAC_RECAL_REQ: + * (1) set tdc_en to 1 + * (2) set tdc_dac_recal_req to 0 + * (3) set tdc_dac_recal_req to 1 + */ + val = TDC_EN; + err = regmap_bulk_write(idtfc3->regmap, TDC_CTRL, + &val, sizeof(val)); + if (err) + return err; + val = TDC_EN | TDC_DAC_RECAL_REQ; + err = regmap_bulk_write(idtfc3->regmap, TDC_CTRL, + &val, sizeof(val)); + if (err) + return err; + mdelay(10); + + /* + * Toggle APLL_REINIT: + * (1) set apll_reinit to 0 + * (2) set apll_reinit to 1 + */ + val = 0; + err = regmap_bulk_write(idtfc3->regmap, SOFT_RESET_CTRL, + &val, sizeof(val)); + if (err) + return err; + val = APLL_REINIT; + err = regmap_bulk_write(idtfc3->regmap, SOFT_RESET_CTRL, + &val, sizeof(val)); + if (err) + return err; + mdelay(10); + + return err; +} + +static int idtfc3_init_timecounter(struct idtfc3 *idtfc3) +{ + int err; + u32 period_ms; + + period_ms = idtfc3->sub_sync_count * MSEC_PER_SEC / + idtfc3->hw_param.time_clk_freq; + + idtfc3->tc_update_period = msecs_to_jiffies(period_ms / TDC_GET_PERIOD); + idtfc3->tc_write_timeout = period_ms * USEC_PER_MSEC; + + err = idtfc3_timecounter_update(idtfc3, 0, 0); + if (err) + return err; + + err = idtfc3_timecounter_read(idtfc3); + if (err) + return err; + + return 0; +} + +static int idtfc3_get_tdc_apll_freq(struct idtfc3 *idtfc3) +{ + int err; + u8 tdc_fb_div_int; + u8 tdc_ref_div; + struct idtfc3_hw_param *param = &idtfc3->hw_param; + + err = regmap_bulk_read(idtfc3->regmap, TDC_REF_DIV_CNFG, + &tdc_ref_div, sizeof(tdc_ref_div)); + if (err) + return err; + + err = regmap_bulk_read(idtfc3->regmap, TDC_FB_DIV_INT_CNFG, + &tdc_fb_div_int, sizeof(tdc_fb_div_int)); + if (err) + return err; + + tdc_fb_div_int &= TDC_FB_DIV_INT_MASK; + tdc_ref_div &= TDC_REF_DIV_CONFIG_MASK; + + idtfc3->tdc_apll_freq = div_u64(param->xtal_freq * (u64)tdc_fb_div_int, + 1 << tdc_ref_div); + + return 0; +} + +static int idtfc3_get_fod(struct idtfc3 *idtfc3) +{ + int err; + u8 fod; + + err = regmap_bulk_read(idtfc3->regmap, TIME_CLOCK_SRC, &fod, sizeof(fod)); + if (err) + return err; + + switch (fod) { + case 0: + idtfc3->fod_n = FOD_0; + break; + case 1: + idtfc3->fod_n = FOD_1; + break; + case 2: + idtfc3->fod_n = FOD_2; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int idtfc3_get_sync_count(struct idtfc3 *idtfc3) +{ + int err; + u8 buf[4]; + + err = regmap_bulk_read(idtfc3->regmap, SUB_SYNC_GEN_CNFG, buf, sizeof(buf)); + if (err) + return err; + + idtfc3->sub_sync_count = (get_unaligned_le32(buf) & SUB_SYNC_COUNTER_MASK) + 1; + idtfc3->ns_per_counter = NSEC_PER_SEC / idtfc3->hw_param.time_clk_freq; + idtfc3->ns_per_sync = idtfc3->sub_sync_count * idtfc3->ns_per_counter; + + return 0; +} + +static int idtfc3_setup_hw_param(struct idtfc3 *idtfc3) +{ + int err; + + err = idtfc3_get_fod(idtfc3); + if (err) + return err; + + err = idtfc3_get_sync_count(idtfc3); + if (err) + return err; + + err = idtfc3_get_time_ref_freq(idtfc3); + if (err) + return err; + + return idtfc3_get_tdc_apll_freq(idtfc3); +} + +static int idtfc3_configure_hw(struct idtfc3 *idtfc3) +{ + int err = 0; + + err = idtfc3_hw_calibrate(idtfc3); + if (err) + return err; + + err = idtfc3_enable_lpf(idtfc3, true); + if (err) + return err; + + err = idtfc3_enable_tdc(idtfc3, false, MEAS_MODE_INVALID); + if (err) + return err; + + err = idtfc3_get_tdc_offset_sign(idtfc3); + if (err) + return err; + + return idtfc3_setup_hw_param(idtfc3); +} + +static int idtfc3_set_overhead(struct idtfc3 *idtfc3) +{ + s64 current_ns = 0; + s64 lowest_ns = 0; + int err; + u8 i; + ktime_t start; + ktime_t stop; + ktime_t diff; + + char buf[18] = {0}; + + for (i = 0; i < 5; i++) { + start = ktime_get_raw(); + + err = regmap_bulk_write(idtfc3->regmap, TOD_SYNC_LOAD_VAL_CTRL, + &buf, sizeof(buf)); + if (err) + return err; + + stop = ktime_get_raw(); + + diff = ktime_sub(stop, start); + + current_ns = ktime_to_ns(diff); + + if (i == 0) { + lowest_ns = current_ns; + } else { + if (current_ns < lowest_ns) + lowest_ns = current_ns; + } + } + + idtfc3->tod_write_overhead = lowest_ns; + + return err; +} + +static int idtfc3_enable_ptp(struct idtfc3 *idtfc3) +{ + int err; + + idtfc3->caps = idtfc3_caps; + snprintf(idtfc3->caps.name, sizeof(idtfc3->caps.name), "IDT FC3W"); + err = idtfc3_set_overhead(idtfc3); + if (err) + return err; + + err = idtfc3_init_timecounter(idtfc3); + if (err) + return err; + + idtfc3->ptp_clock = ptp_clock_register(&idtfc3->caps, NULL); + + if (IS_ERR(idtfc3->ptp_clock)) { + err = PTR_ERR(idtfc3->ptp_clock); + idtfc3->ptp_clock = NULL; + return err; + } + + ptp_schedule_worker(idtfc3->ptp_clock, idtfc3->tc_update_period); + + dev_info(idtfc3->dev, "TIME_SYNC_CHANNEL registered as ptp%d", + idtfc3->ptp_clock->index); + + return 0; +} + +static int idtfc3_load_firmware(struct idtfc3 *idtfc3) +{ + char fname[128] = FW_FILENAME; + const struct firmware *fw; + struct idtfc3_fwrc *rec; + u16 addr; + u8 val; + int err; + s32 len; + + idtfc3_default_hw_param(&idtfc3->hw_param); + + if (firmware) /* module parameter */ + snprintf(fname, sizeof(fname), "%s", firmware); + + dev_info(idtfc3->dev, "requesting firmware '%s'\n", fname); + + err = request_firmware(&fw, fname, idtfc3->dev); + + if (err) { + dev_err(idtfc3->dev, + "requesting firmware failed with err %d!\n", err); + return err; + } + + dev_dbg(idtfc3->dev, "firmware size %zu bytes\n", fw->size); + + rec = (struct idtfc3_fwrc *)fw->data; + + for (len = fw->size; len > 0; len -= sizeof(*rec)) { + if (rec->reserved) { + dev_err(idtfc3->dev, + "bad firmware, reserved field non-zero\n"); + err = -EINVAL; + } else { + val = rec->value; + addr = rec->hiaddr << 8 | rec->loaddr; + + rec++; + + err = idtfc3_set_hw_param(&idtfc3->hw_param, addr, val); + } + + if (err != -EINVAL) { + err = 0; + + /* Max register */ + if (addr >= 0xE88) + continue; + + err = regmap_bulk_write(idtfc3->regmap, addr, + &val, sizeof(val)); + } + + if (err) + goto out; + } + + err = idtfc3_configure_hw(idtfc3); +out: + release_firmware(fw); + return err; +} + +static int idtfc3_read_device_id(struct idtfc3 *idtfc3, u16 *device_id) +{ + int err; + u8 buf[2] = {0}; + + err = regmap_bulk_read(idtfc3->regmap, DEVICE_ID, + &buf, sizeof(buf)); + if (err) { + dev_err(idtfc3->dev, "%s failed with %d", __func__, err); + return err; + } + + *device_id = get_unaligned_le16(buf); + + return 0; +} + +static int idtfc3_check_device_compatibility(struct idtfc3 *idtfc3) +{ + int err; + u16 device_id; + + err = idtfc3_read_device_id(idtfc3, &device_id); + if (err) + return err; + + if ((device_id & DEVICE_ID_MASK) == 0) { + dev_err(idtfc3->dev, "invalid device"); + return -EINVAL; + } + + return 0; +} + +static int idtfc3_probe(struct platform_device *pdev) +{ + struct rsmu_ddata *ddata = dev_get_drvdata(pdev->dev.parent); + struct idtfc3 *idtfc3; + int err; + + idtfc3 = devm_kzalloc(&pdev->dev, sizeof(struct idtfc3), GFP_KERNEL); + + if (!idtfc3) + return -ENOMEM; + + idtfc3->dev = &pdev->dev; + idtfc3->mfd = pdev->dev.parent; + idtfc3->lock = &ddata->lock; + idtfc3->regmap = ddata->regmap; + + mutex_lock(idtfc3->lock); + + err = idtfc3_check_device_compatibility(idtfc3); + if (err) { + mutex_unlock(idtfc3->lock); + return err; + } + + err = idtfc3_load_firmware(idtfc3); + if (err) { + if (err == -ENOENT) { + mutex_unlock(idtfc3->lock); + return -EPROBE_DEFER; + } + dev_warn(idtfc3->dev, "loading firmware failed with %d", err); + } + + err = idtfc3_enable_ptp(idtfc3); + if (err) { + dev_err(idtfc3->dev, "idtfc3_enable_ptp failed with %d", err); + mutex_unlock(idtfc3->lock); + return err; + } + + mutex_unlock(idtfc3->lock); + + platform_set_drvdata(pdev, idtfc3); + + return 0; +} + +static void idtfc3_remove(struct platform_device *pdev) +{ + struct idtfc3 *idtfc3 = platform_get_drvdata(pdev); + + ptp_clock_unregister(idtfc3->ptp_clock); +} + +static struct platform_driver idtfc3_driver = { + .driver = { + .name = "rc38xxx-phc", + }, + .probe = idtfc3_probe, + .remove = idtfc3_remove, +}; + +module_platform_driver(idtfc3_driver); diff --git a/drivers/ptp/ptp_fc3.h b/drivers/ptp/ptp_fc3.h new file mode 100644 index 000000000..897101579 --- /dev/null +++ b/drivers/ptp/ptp_fc3.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * PTP hardware clock driver for the FemtoClock3 family of timing and + * synchronization devices. + * + * Copyright (C) 2023 Integrated Device Technology, Inc., a Renesas Company. + */ +#ifndef PTP_IDTFC3_H +#define PTP_IDTFC3_H + +#include +#include +#include + +#define FW_FILENAME "idtfc3.bin" + +#define MAX_FFO_PPB (244000) +#define TDC_GET_PERIOD (10) + +struct idtfc3 { + struct ptp_clock_info caps; + struct ptp_clock *ptp_clock; + struct device *dev; + /* Mutex to protect operations from being interrupted */ + struct mutex *lock; + struct device *mfd; + struct regmap *regmap; + struct idtfc3_hw_param hw_param; + u32 sub_sync_count; + u32 ns_per_sync; + int tdc_offset_sign; + u64 tdc_apll_freq; + u32 time_ref_freq; + u16 fod_n; + u8 lpf_mode; + /* Time counter */ + u32 last_counter; + s64 ns; + u32 ns_per_counter; + u32 tc_update_period; + u32 tc_write_timeout; + s64 tod_write_overhead; +}; + +#endif /* PTP_IDTFC3_H */ diff --git a/drivers/ptp/ptp_idt82p33.c b/drivers/ptp/ptp_idt82p33.c new file mode 100644 index 000000000..f01c50dfa --- /dev/null +++ b/drivers/ptp/ptp_idt82p33.c @@ -0,0 +1,1458 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (C) 2018 Integrated Device Technology, Inc +// + +#define pr_fmt(fmt) "IDT_82p33xxx: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ptp_private.h" +#include "ptp_idt82p33.h" + +MODULE_DESCRIPTION("Driver for IDT 82p33xxx clock devices"); +MODULE_AUTHOR("IDT support-1588 "); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(FW_FILENAME); + +#define EXTTS_PERIOD_MS (95) + +/* Module Parameters */ +static u32 phase_snap_threshold = SNAP_THRESHOLD_NS; +module_param(phase_snap_threshold, uint, 0); +MODULE_PARM_DESC(phase_snap_threshold, +"threshold (10000ns by default) below which adjtime would use double dco"); + +static char *firmware; +module_param(firmware, charp, 0); + +static struct ptp_pin_desc pin_config[MAX_PHC_PLL][MAX_TRIG_CLK]; + +static inline int idt82p33_read(struct idt82p33 *idt82p33, u16 regaddr, + u8 *buf, u16 count) +{ + return regmap_bulk_read(idt82p33->regmap, regaddr, buf, count); +} + +static inline int idt82p33_write(struct idt82p33 *idt82p33, u16 regaddr, + u8 *buf, u16 count) +{ + return regmap_bulk_write(idt82p33->regmap, regaddr, buf, count); +} + +static void idt82p33_byte_array_to_timespec(struct timespec64 *ts, + u8 buf[TOD_BYTE_COUNT]) +{ + time64_t sec; + s32 nsec; + u8 i; + + nsec = buf[3]; + for (i = 0; i < 3; i++) { + nsec <<= 8; + nsec |= buf[2 - i]; + } + + sec = buf[9]; + for (i = 0; i < 5; i++) { + sec <<= 8; + sec |= buf[8 - i]; + } + + ts->tv_sec = sec; + ts->tv_nsec = nsec; +} + +static void idt82p33_timespec_to_byte_array(struct timespec64 const *ts, + u8 buf[TOD_BYTE_COUNT]) +{ + time64_t sec; + s32 nsec; + u8 i; + + nsec = ts->tv_nsec; + sec = ts->tv_sec; + + for (i = 0; i < 4; i++) { + buf[i] = nsec & 0xff; + nsec >>= 8; + } + + for (i = 4; i < TOD_BYTE_COUNT; i++) { + buf[i] = sec & 0xff; + sec >>= 8; + } +} + +static int idt82p33_dpll_set_mode(struct idt82p33_channel *channel, + enum pll_mode mode) +{ + struct idt82p33 *idt82p33 = channel->idt82p33; + u8 dpll_mode; + int err; + + if (channel->pll_mode == mode) + return 0; + + err = idt82p33_read(idt82p33, channel->dpll_mode_cnfg, + &dpll_mode, sizeof(dpll_mode)); + if (err) + return err; + + dpll_mode &= ~(PLL_MODE_MASK << PLL_MODE_SHIFT); + + dpll_mode |= (mode << PLL_MODE_SHIFT); + + err = idt82p33_write(idt82p33, channel->dpll_mode_cnfg, + &dpll_mode, sizeof(dpll_mode)); + if (err) + return err; + + channel->pll_mode = mode; + + return 0; +} + +static int idt82p33_set_tod_trigger(struct idt82p33_channel *channel, + u8 trigger, bool write) +{ + struct idt82p33 *idt82p33 = channel->idt82p33; + int err; + u8 cfg; + + if (trigger > WR_TRIG_SEL_MAX) + return -EINVAL; + + err = idt82p33_read(idt82p33, channel->dpll_tod_trigger, + &cfg, sizeof(cfg)); + + if (err) + return err; + + if (write == true) + trigger = (trigger << WRITE_TRIGGER_SHIFT) | + (cfg & READ_TRIGGER_MASK); + else + trigger = (trigger << READ_TRIGGER_SHIFT) | + (cfg & WRITE_TRIGGER_MASK); + + return idt82p33_write(idt82p33, channel->dpll_tod_trigger, + &trigger, sizeof(trigger)); +} + +static int idt82p33_get_extts(struct idt82p33_channel *channel, + struct timespec64 *ts) +{ + struct idt82p33 *idt82p33 = channel->idt82p33; + u8 buf[TOD_BYTE_COUNT]; + int err; + + err = idt82p33_read(idt82p33, channel->dpll_tod_sts, buf, sizeof(buf)); + + if (err) + return err; + + /* Since trigger is not self clearing itself, we have to poll tod_sts */ + if (memcmp(buf, channel->extts_tod_sts, TOD_BYTE_COUNT) == 0) + return -EAGAIN; + + memcpy(channel->extts_tod_sts, buf, TOD_BYTE_COUNT); + + idt82p33_byte_array_to_timespec(ts, buf); + + if (channel->discard_next_extts) { + channel->discard_next_extts = false; + return -EAGAIN; + } + + return 0; +} + +static int map_ref_to_tod_trig_sel(int ref, u8 *trigger) +{ + int err = 0; + + switch (ref) { + case 0: + *trigger = HW_TOD_TRIG_SEL_IN12; + break; + case 1: + *trigger = HW_TOD_TRIG_SEL_IN13; + break; + case 2: + *trigger = HW_TOD_TRIG_SEL_IN14; + break; + default: + err = -EINVAL; + } + + return err; +} + +static bool is_one_shot(u8 mask) +{ + /* Treat single bit PLL masks as continuous trigger */ + if ((mask == 1) || (mask == 2)) + return false; + else + return true; +} + +static int arm_tod_read_with_trigger(struct idt82p33_channel *channel, u8 trigger) +{ + struct idt82p33 *idt82p33 = channel->idt82p33; + u8 buf[TOD_BYTE_COUNT]; + int err; + + /* Remember the current tod_sts before setting the trigger */ + err = idt82p33_read(idt82p33, channel->dpll_tod_sts, buf, sizeof(buf)); + + if (err) + return err; + + memcpy(channel->extts_tod_sts, buf, TOD_BYTE_COUNT); + + err = idt82p33_set_tod_trigger(channel, trigger, false); + + if (err) + dev_err(idt82p33->dev, "%s: err = %d", __func__, err); + + return err; +} + +static int idt82p33_extts_enable(struct idt82p33_channel *channel, + struct ptp_clock_request *rq, int on) +{ + u8 index = rq->extts.index; + struct idt82p33 *idt82p33; + u8 mask = 1 << index; + int err = 0; + u8 old_mask; + u8 trigger; + int ref; + + idt82p33 = channel->idt82p33; + old_mask = idt82p33->extts_mask; + + if (index >= MAX_PHC_PLL) + return -EINVAL; + + if (on) { + /* Return if it was already enabled */ + if (idt82p33->extts_mask & mask) + return 0; + + /* Use the pin configured for the channel */ + ref = ptp_find_pin(channel->ptp_clock, PTP_PF_EXTTS, channel->plln); + + if (ref < 0) { + dev_err(idt82p33->dev, "%s: No valid pin found for Pll%d!\n", + __func__, channel->plln); + return -EBUSY; + } + + err = map_ref_to_tod_trig_sel(ref, &trigger); + + if (err) { + dev_err(idt82p33->dev, + "%s: Unsupported ref %d!\n", __func__, ref); + return err; + } + + err = arm_tod_read_with_trigger(&idt82p33->channel[index], trigger); + + if (err == 0) { + idt82p33->extts_mask |= mask; + idt82p33->channel[index].tod_trigger = trigger; + idt82p33->event_channel[index] = channel; + idt82p33->extts_single_shot = is_one_shot(idt82p33->extts_mask); + + if (old_mask) + return 0; + + schedule_delayed_work(&idt82p33->extts_work, + msecs_to_jiffies(EXTTS_PERIOD_MS)); + } + } else { + idt82p33->extts_mask &= ~mask; + idt82p33->extts_single_shot = is_one_shot(idt82p33->extts_mask); + + if (idt82p33->extts_mask == 0) + cancel_delayed_work(&idt82p33->extts_work); + } + + return err; +} + +static int idt82p33_extts_check_channel(struct idt82p33 *idt82p33, u8 todn) +{ + struct idt82p33_channel *event_channel; + struct ptp_clock_event event; + struct timespec64 ts; + int err; + + err = idt82p33_get_extts(&idt82p33->channel[todn], &ts); + if (err == 0) { + event_channel = idt82p33->event_channel[todn]; + event.type = PTP_CLOCK_EXTTS; + event.index = todn; + event.timestamp = timespec64_to_ns(&ts); + ptp_clock_event(event_channel->ptp_clock, + &event); + } + return err; +} + +static u8 idt82p33_extts_enable_mask(struct idt82p33_channel *channel, + u8 extts_mask, bool enable) +{ + struct idt82p33 *idt82p33 = channel->idt82p33; + u8 trigger = channel->tod_trigger; + u8 mask; + int err; + int i; + + if (extts_mask == 0) + return 0; + + if (enable == false) + cancel_delayed_work_sync(&idt82p33->extts_work); + + for (i = 0; i < MAX_PHC_PLL; i++) { + mask = 1 << i; + + if ((extts_mask & mask) == 0) + continue; + + if (enable) { + err = arm_tod_read_with_trigger(&idt82p33->channel[i], trigger); + if (err) + dev_err(idt82p33->dev, + "%s: Arm ToD read trigger failed, err = %d", + __func__, err); + } else { + err = idt82p33_extts_check_channel(idt82p33, i); + if (err == 0 && idt82p33->extts_single_shot) + /* trigger happened so we won't re-enable it */ + extts_mask &= ~mask; + } + } + + if (enable) + schedule_delayed_work(&idt82p33->extts_work, + msecs_to_jiffies(EXTTS_PERIOD_MS)); + + return extts_mask; +} + +static int _idt82p33_gettime(struct idt82p33_channel *channel, + struct timespec64 *ts) +{ + struct idt82p33 *idt82p33 = channel->idt82p33; + u8 old_mask = idt82p33->extts_mask; + u8 buf[TOD_BYTE_COUNT]; + u8 new_mask = 0; + int err; + + /* Disable extts */ + if (old_mask) + new_mask = idt82p33_extts_enable_mask(channel, old_mask, false); + + err = idt82p33_set_tod_trigger(channel, HW_TOD_RD_TRIG_SEL_LSB_TOD_STS, + false); + if (err) + return err; + + channel->discard_next_extts = true; + + if (idt82p33->calculate_overhead_flag) + idt82p33->start_time = ktime_get_raw(); + + err = idt82p33_read(idt82p33, channel->dpll_tod_sts, buf, sizeof(buf)); + + if (err) + return err; + + /* Re-enable extts */ + if (new_mask) + idt82p33_extts_enable_mask(channel, new_mask, true); + + idt82p33_byte_array_to_timespec(ts, buf); + + return 0; +} + +/* + * TOD Trigger: + * Bits[7:4] Write 0x9, MSB write + * Bits[3:0] Read 0x9, LSB read + */ + +static int _idt82p33_settime(struct idt82p33_channel *channel, + struct timespec64 const *ts) +{ + struct idt82p33 *idt82p33 = channel->idt82p33; + struct timespec64 local_ts = *ts; + char buf[TOD_BYTE_COUNT]; + s64 dynamic_overhead_ns; + int err; + u8 i; + + err = idt82p33_set_tod_trigger(channel, HW_TOD_WR_TRIG_SEL_MSB_TOD_CNFG, + true); + if (err) + return err; + + channel->discard_next_extts = true; + + if (idt82p33->calculate_overhead_flag) { + dynamic_overhead_ns = ktime_to_ns(ktime_get_raw()) + - ktime_to_ns(idt82p33->start_time); + + timespec64_add_ns(&local_ts, dynamic_overhead_ns); + + idt82p33->calculate_overhead_flag = 0; + } + + idt82p33_timespec_to_byte_array(&local_ts, buf); + + /* + * Store the new time value. + */ + for (i = 0; i < TOD_BYTE_COUNT; i++) { + err = idt82p33_write(idt82p33, channel->dpll_tod_cnfg + i, + &buf[i], sizeof(buf[i])); + if (err) + return err; + } + + return err; +} + +static int _idt82p33_adjtime_immediate(struct idt82p33_channel *channel, + s64 delta_ns) +{ + struct idt82p33 *idt82p33 = channel->idt82p33; + struct timespec64 ts; + s64 now_ns; + int err; + + idt82p33->calculate_overhead_flag = 1; + + err = _idt82p33_gettime(channel, &ts); + + if (err) + return err; + + now_ns = timespec64_to_ns(&ts); + now_ns += delta_ns + idt82p33->tod_write_overhead_ns; + + ts = ns_to_timespec64(now_ns); + + err = _idt82p33_settime(channel, &ts); + + return err; +} + +static int _idt82p33_adjtime_internal_triggered(struct idt82p33_channel *channel, + s64 delta_ns) +{ + struct idt82p33 *idt82p33 = channel->idt82p33; + char buf[TOD_BYTE_COUNT]; + struct timespec64 ts; + const u8 delay_ns = 32; + s32 remainder; + s64 ns; + int err; + + err = _idt82p33_gettime(channel, &ts); + + if (err) + return err; + + if (ts.tv_nsec > (NSEC_PER_SEC - 5 * NSEC_PER_MSEC)) { + /* Too close to miss next trigger, so skip it */ + mdelay(6); + ns = (ts.tv_sec + 2) * NSEC_PER_SEC + delta_ns + delay_ns; + } else + ns = (ts.tv_sec + 1) * NSEC_PER_SEC + delta_ns + delay_ns; + + ts = ns_to_timespec64(ns); + idt82p33_timespec_to_byte_array(&ts, buf); + + /* + * Store the new time value. + */ + err = idt82p33_write(idt82p33, channel->dpll_tod_cnfg, buf, sizeof(buf)); + if (err) + return err; + + /* Schedule to implement the workaround in one second */ + (void)div_s64_rem(delta_ns, NSEC_PER_SEC, &remainder); + if (remainder != 0) + schedule_delayed_work(&channel->adjtime_work, HZ); + + return idt82p33_set_tod_trigger(channel, HW_TOD_TRIG_SEL_TOD_PPS, true); +} + +static void idt82p33_adjtime_workaround(struct work_struct *work) +{ + struct idt82p33_channel *channel = container_of(work, + struct idt82p33_channel, + adjtime_work.work); + struct idt82p33 *idt82p33 = channel->idt82p33; + + mutex_lock(idt82p33->lock); + /* Workaround for TOD-to-output alignment issue */ + _idt82p33_adjtime_internal_triggered(channel, 0); + mutex_unlock(idt82p33->lock); +} + +static int _idt82p33_adjfine(struct idt82p33_channel *channel, long scaled_ppm) +{ + struct idt82p33 *idt82p33 = channel->idt82p33; + unsigned char buf[5] = {0}; + int err, i; + s64 fcw; + + /* + * Frequency Control Word unit is: 1.6861512 * 10^-10 ppm + * + * adjfreq: + * ppb * 10^14 + * FCW = ----------- + * 16861512 + * + * adjfine: + * scaled_ppm * 5^12 * 10^5 + * FCW = ------------------------ + * 16861512 * 2^4 + */ + + fcw = scaled_ppm * 762939453125ULL; + fcw = div_s64(fcw, 8430756LL); + + for (i = 0; i < 5; i++) { + buf[i] = fcw & 0xff; + fcw >>= 8; + } + + err = idt82p33_dpll_set_mode(channel, PLL_MODE_DCO); + + if (err) + return err; + + err = idt82p33_write(idt82p33, channel->dpll_freq_cnfg, + buf, sizeof(buf)); + + return err; +} + +/* ppb = scaled_ppm * 125 / 2^13 */ +static s32 idt82p33_ddco_scaled_ppm(long current_ppm, s32 ddco_ppb) +{ + s64 scaled_ppm = div_s64(((s64)ddco_ppb << 13), 125); + s64 max_scaled_ppm = div_s64(((s64)DCO_MAX_PPB << 13), 125); + + current_ppm += scaled_ppm; + + if (current_ppm > max_scaled_ppm) + current_ppm = max_scaled_ppm; + else if (current_ppm < -max_scaled_ppm) + current_ppm = -max_scaled_ppm; + + return (s32)current_ppm; +} + +static int idt82p33_stop_ddco(struct idt82p33_channel *channel) +{ + int err; + + err = _idt82p33_adjfine(channel, channel->current_freq); + if (err) + return err; + + channel->ddco = false; + + return 0; +} + +static int idt82p33_start_ddco(struct idt82p33_channel *channel, s32 delta_ns) +{ + s32 current_ppm = channel->current_freq; + u32 duration_ms = MSEC_PER_SEC; + s32 ppb; + int err; + + /* If the ToD correction is less than 5 nanoseconds, then skip it. + * The error introduced by the ToD adjustment procedure would be bigger + * than the required ToD correction + */ + if (abs(delta_ns) < DDCO_THRESHOLD_NS) + return 0; + + /* For most cases, keep ddco duration 1 second */ + ppb = delta_ns; + while (abs(ppb) > DCO_MAX_PPB) { + duration_ms *= 2; + ppb /= 2; + } + + err = _idt82p33_adjfine(channel, + idt82p33_ddco_scaled_ppm(current_ppm, ppb)); + if (err) + return err; + + /* schedule the worker to cancel ddco */ + ptp_schedule_worker(channel->ptp_clock, + msecs_to_jiffies(duration_ms) - 1); + channel->ddco = true; + + return 0; +} + +static int idt82p33_measure_one_byte_write_overhead( + struct idt82p33_channel *channel, s64 *overhead_ns) +{ + struct idt82p33 *idt82p33 = channel->idt82p33; + ktime_t start, stop; + u8 trigger = 0; + s64 total_ns; + int err; + u8 i; + + total_ns = 0; + *overhead_ns = 0; + + for (i = 0; i < MAX_MEASURMENT_COUNT; i++) { + + start = ktime_get_raw(); + + err = idt82p33_write(idt82p33, channel->dpll_tod_trigger, + &trigger, sizeof(trigger)); + + stop = ktime_get_raw(); + + if (err) + return err; + + total_ns += ktime_to_ns(stop) - ktime_to_ns(start); + } + + *overhead_ns = div_s64(total_ns, MAX_MEASURMENT_COUNT); + + return err; +} + +static int idt82p33_measure_one_byte_read_overhead( + struct idt82p33_channel *channel, s64 *overhead_ns) +{ + struct idt82p33 *idt82p33 = channel->idt82p33; + ktime_t start, stop; + u8 trigger = 0; + s64 total_ns; + int err; + u8 i; + + total_ns = 0; + *overhead_ns = 0; + + for (i = 0; i < MAX_MEASURMENT_COUNT; i++) { + + start = ktime_get_raw(); + + err = idt82p33_read(idt82p33, channel->dpll_tod_trigger, + &trigger, sizeof(trigger)); + + stop = ktime_get_raw(); + + if (err) + return err; + + total_ns += ktime_to_ns(stop) - ktime_to_ns(start); + } + + *overhead_ns = div_s64(total_ns, MAX_MEASURMENT_COUNT); + + return err; +} + +static int idt82p33_measure_tod_write_9_byte_overhead( + struct idt82p33_channel *channel) +{ + struct idt82p33 *idt82p33 = channel->idt82p33; + u8 buf[TOD_BYTE_COUNT]; + ktime_t start, stop; + s64 total_ns; + int err = 0; + u8 i, j; + + total_ns = 0; + idt82p33->tod_write_overhead_ns = 0; + + for (i = 0; i < MAX_MEASURMENT_COUNT; i++) { + + start = ktime_get_raw(); + + /* Need one less byte for applicable overhead */ + for (j = 0; j < (TOD_BYTE_COUNT - 1); j++) { + err = idt82p33_write(idt82p33, + channel->dpll_tod_cnfg + i, + &buf[i], sizeof(buf[i])); + if (err) + return err; + } + + stop = ktime_get_raw(); + + total_ns += ktime_to_ns(stop) - ktime_to_ns(start); + } + + idt82p33->tod_write_overhead_ns = div_s64(total_ns, + MAX_MEASURMENT_COUNT); + + return err; +} + +static int idt82p33_measure_settime_gettime_gap_overhead( + struct idt82p33_channel *channel, s64 *overhead_ns) +{ + struct timespec64 ts1 = {0, 0}; + struct timespec64 ts2; + int err; + + *overhead_ns = 0; + + err = _idt82p33_settime(channel, &ts1); + + if (err) + return err; + + err = _idt82p33_gettime(channel, &ts2); + + if (!err) + *overhead_ns = timespec64_to_ns(&ts2) - timespec64_to_ns(&ts1); + + return err; +} + +static int idt82p33_measure_tod_write_overhead(struct idt82p33_channel *channel) +{ + s64 trailing_overhead_ns, one_byte_write_ns, gap_ns, one_byte_read_ns; + struct idt82p33 *idt82p33 = channel->idt82p33; + int err; + + idt82p33->tod_write_overhead_ns = 0; + + err = idt82p33_measure_settime_gettime_gap_overhead(channel, &gap_ns); + + if (err) { + dev_err(idt82p33->dev, + "Failed in %s with err %d!\n", __func__, err); + return err; + } + + err = idt82p33_measure_one_byte_write_overhead(channel, + &one_byte_write_ns); + + if (err) + return err; + + err = idt82p33_measure_one_byte_read_overhead(channel, + &one_byte_read_ns); + + if (err) + return err; + + err = idt82p33_measure_tod_write_9_byte_overhead(channel); + + if (err) + return err; + + trailing_overhead_ns = gap_ns - 2 * one_byte_write_ns + - one_byte_read_ns; + + idt82p33->tod_write_overhead_ns -= trailing_overhead_ns; + + return err; +} + +static int idt82p33_check_and_set_masks(struct idt82p33 *idt82p33, + u8 page, + u8 offset, + u8 val) +{ + int err = 0; + + if (page == PLLMASK_ADDR_HI && offset == PLLMASK_ADDR_LO) { + if ((val & 0xfc) || !(val & 0x3)) { + dev_err(idt82p33->dev, + "Invalid PLL mask 0x%x\n", val); + err = -EINVAL; + } else { + idt82p33->pll_mask = val; + } + } else if (page == PLL0_OUTMASK_ADDR_HI && + offset == PLL0_OUTMASK_ADDR_LO) { + idt82p33->channel[0].output_mask = val; + } else if (page == PLL1_OUTMASK_ADDR_HI && + offset == PLL1_OUTMASK_ADDR_LO) { + idt82p33->channel[1].output_mask = val; + } + + return err; +} + +static void idt82p33_display_masks(struct idt82p33 *idt82p33) +{ + u8 mask, i; + + dev_info(idt82p33->dev, + "pllmask = 0x%02x\n", idt82p33->pll_mask); + + for (i = 0; i < MAX_PHC_PLL; i++) { + mask = 1 << i; + + if (mask & idt82p33->pll_mask) + dev_info(idt82p33->dev, + "PLL%d output_mask = 0x%04x\n", + i, idt82p33->channel[i].output_mask); + } +} + +static int idt82p33_sync_tod(struct idt82p33_channel *channel, bool enable) +{ + struct idt82p33 *idt82p33 = channel->idt82p33; + u8 sync_cnfg; + int err; + + err = idt82p33_read(idt82p33, channel->dpll_sync_cnfg, + &sync_cnfg, sizeof(sync_cnfg)); + if (err) + return err; + + sync_cnfg &= ~SYNC_TOD; + if (enable) + sync_cnfg |= SYNC_TOD; + + return idt82p33_write(idt82p33, channel->dpll_sync_cnfg, + &sync_cnfg, sizeof(sync_cnfg)); +} + +static long idt82p33_work_handler(struct ptp_clock_info *ptp) +{ + struct idt82p33_channel *channel = + container_of(ptp, struct idt82p33_channel, caps); + struct idt82p33 *idt82p33 = channel->idt82p33; + + mutex_lock(idt82p33->lock); + (void)idt82p33_stop_ddco(channel); + mutex_unlock(idt82p33->lock); + + /* Return a negative value here to not reschedule */ + return -1; +} + +static int idt82p33_output_enable(struct idt82p33_channel *channel, + bool enable, unsigned int outn) +{ + struct idt82p33 *idt82p33 = channel->idt82p33; + int err; + u8 val; + + err = idt82p33_read(idt82p33, OUT_MUX_CNFG(outn), &val, sizeof(val)); + if (err) + return err; + if (enable) + val &= ~SQUELCH_ENABLE; + else + val |= SQUELCH_ENABLE; + + return idt82p33_write(idt82p33, OUT_MUX_CNFG(outn), &val, sizeof(val)); +} + +static int idt82p33_perout_enable(struct idt82p33_channel *channel, + bool enable, + struct ptp_perout_request *perout) +{ + /* Enable/disable individual output instead */ + return idt82p33_output_enable(channel, enable, perout->index); +} + +static int idt82p33_enable_tod(struct idt82p33_channel *channel) +{ + struct idt82p33 *idt82p33 = channel->idt82p33; + struct timespec64 ts = {0, 0}; + int err; + + err = idt82p33_measure_tod_write_overhead(channel); + + if (err) { + dev_err(idt82p33->dev, + "Failed in %s with err %d!\n", __func__, err); + return err; + } + + err = _idt82p33_settime(channel, &ts); + + if (err) + return err; + + return idt82p33_sync_tod(channel, true); +} + +static void idt82p33_ptp_clock_unregister_all(struct idt82p33 *idt82p33) +{ + struct idt82p33_channel *channel; + u8 i; + + for (i = 0; i < MAX_PHC_PLL; i++) { + channel = &idt82p33->channel[i]; + cancel_delayed_work_sync(&channel->adjtime_work); + if (channel->ptp_clock) + ptp_clock_unregister(channel->ptp_clock); + } +} + + + +static int idt82p33_enable(struct ptp_clock_info *ptp, + struct ptp_clock_request *rq, int on) +{ + struct idt82p33_channel *channel = + container_of(ptp, struct idt82p33_channel, caps); + struct idt82p33 *idt82p33 = channel->idt82p33; + int err = -EOPNOTSUPP; + + mutex_lock(idt82p33->lock); + + switch (rq->type) { + case PTP_CLK_REQ_PEROUT: + if (!on) + err = idt82p33_perout_enable(channel, false, + &rq->perout); + /* Only accept a 1-PPS aligned to the second. */ + else if (rq->perout.start.nsec || rq->perout.period.sec != 1 || + rq->perout.period.nsec) + err = -ERANGE; + else + err = idt82p33_perout_enable(channel, true, + &rq->perout); + break; + case PTP_CLK_REQ_EXTTS: + err = idt82p33_extts_enable(channel, rq, on); + break; + default: + break; + } + + mutex_unlock(idt82p33->lock); + + if (err) + dev_err(idt82p33->dev, + "Failed in %s with err %d!\n", __func__, err); + return err; +} + +static s32 idt82p33_getmaxphase(__always_unused struct ptp_clock_info *ptp) +{ + return WRITE_PHASE_OFFSET_LIMIT; +} + +static int idt82p33_adjwritephase(struct ptp_clock_info *ptp, s32 offset_ns) +{ + struct idt82p33_channel *channel = + container_of(ptp, struct idt82p33_channel, caps); + struct idt82p33 *idt82p33 = channel->idt82p33; + s64 offset_regval; + u8 val[4] = {0}; + int err; + + /* Convert from phaseoffset_fs to register value */ + offset_regval = div_s64((s64)(-offset_ns) * 1000000000ll, + IDT_T0DPLL_PHASE_RESOL); + + val[0] = offset_regval & 0xFF; + val[1] = (offset_regval >> 8) & 0xFF; + val[2] = (offset_regval >> 16) & 0xFF; + val[3] = (offset_regval >> 24) & 0x1F; + val[3] |= PH_OFFSET_EN; + + mutex_lock(idt82p33->lock); + + err = idt82p33_dpll_set_mode(channel, PLL_MODE_WPH); + if (err) { + dev_err(idt82p33->dev, + "Failed in %s with err %d!\n", __func__, err); + goto out; + } + + err = idt82p33_write(idt82p33, channel->dpll_phase_cnfg, val, + sizeof(val)); + +out: + mutex_unlock(idt82p33->lock); + return err; +} + +static int idt82p33_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) +{ + struct idt82p33_channel *channel = + container_of(ptp, struct idt82p33_channel, caps); + struct idt82p33 *idt82p33 = channel->idt82p33; + int err; + + if (channel->ddco == true) + return 0; + + if (scaled_ppm == channel->current_freq) + return 0; + + mutex_lock(idt82p33->lock); + err = _idt82p33_adjfine(channel, scaled_ppm); + + if (err == 0) + channel->current_freq = scaled_ppm; + mutex_unlock(idt82p33->lock); + + if (err) + dev_err(idt82p33->dev, + "Failed in %s with err %d!\n", __func__, err); + return err; +} + +static int idt82p33_adjtime(struct ptp_clock_info *ptp, s64 delta_ns) +{ + struct idt82p33_channel *channel = + container_of(ptp, struct idt82p33_channel, caps); + struct idt82p33 *idt82p33 = channel->idt82p33; + int err; + + if (channel->ddco == true) + return -EBUSY; + + mutex_lock(idt82p33->lock); + + if (abs(delta_ns) < phase_snap_threshold) { + err = idt82p33_start_ddco(channel, delta_ns); + mutex_unlock(idt82p33->lock); + return err; + } + + /* Use more accurate internal 1pps triggered write first */ + err = _idt82p33_adjtime_internal_triggered(channel, delta_ns); + if (err && delta_ns > IMMEDIATE_SNAP_THRESHOLD_NS) + err = _idt82p33_adjtime_immediate(channel, delta_ns); + + mutex_unlock(idt82p33->lock); + + if (err) + dev_err(idt82p33->dev, + "Failed in %s with err %d!\n", __func__, err); + return err; +} + +static int idt82p33_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts) +{ + struct idt82p33_channel *channel = + container_of(ptp, struct idt82p33_channel, caps); + struct idt82p33 *idt82p33 = channel->idt82p33; + int err; + + mutex_lock(idt82p33->lock); + err = _idt82p33_gettime(channel, ts); + mutex_unlock(idt82p33->lock); + + if (err) + dev_err(idt82p33->dev, + "Failed in %s with err %d!\n", __func__, err); + return err; +} + +static int idt82p33_settime(struct ptp_clock_info *ptp, + const struct timespec64 *ts) +{ + struct idt82p33_channel *channel = + container_of(ptp, struct idt82p33_channel, caps); + struct idt82p33 *idt82p33 = channel->idt82p33; + int err; + + mutex_lock(idt82p33->lock); + err = _idt82p33_settime(channel, ts); + mutex_unlock(idt82p33->lock); + + if (err) + dev_err(idt82p33->dev, + "Failed in %s with err %d!\n", __func__, err); + return err; +} + +static int idt82p33_channel_init(struct idt82p33 *idt82p33, u32 index) +{ + struct idt82p33_channel *channel = &idt82p33->channel[index]; + + switch (index) { + case 0: + channel->dpll_tod_cnfg = DPLL1_TOD_CNFG; + channel->dpll_tod_trigger = DPLL1_TOD_TRIGGER; + channel->dpll_tod_sts = DPLL1_TOD_STS; + channel->dpll_mode_cnfg = DPLL1_OPERATING_MODE_CNFG; + channel->dpll_freq_cnfg = DPLL1_HOLDOVER_FREQ_CNFG; + channel->dpll_phase_cnfg = DPLL1_PHASE_OFFSET_CNFG; + channel->dpll_sync_cnfg = DPLL1_SYNC_EDGE_CNFG; + channel->dpll_input_mode_cnfg = DPLL1_INPUT_MODE_CNFG; + break; + case 1: + channel->dpll_tod_cnfg = DPLL2_TOD_CNFG; + channel->dpll_tod_trigger = DPLL2_TOD_TRIGGER; + channel->dpll_tod_sts = DPLL2_TOD_STS; + channel->dpll_mode_cnfg = DPLL2_OPERATING_MODE_CNFG; + channel->dpll_freq_cnfg = DPLL2_HOLDOVER_FREQ_CNFG; + channel->dpll_phase_cnfg = DPLL2_PHASE_OFFSET_CNFG; + channel->dpll_sync_cnfg = DPLL2_SYNC_EDGE_CNFG; + channel->dpll_input_mode_cnfg = DPLL2_INPUT_MODE_CNFG; + break; + default: + return -EINVAL; + } + + channel->plln = index; + channel->current_freq = 0; + channel->idt82p33 = idt82p33; + INIT_DELAYED_WORK(&channel->adjtime_work, idt82p33_adjtime_workaround); + + return 0; +} + +static int idt82p33_verify_pin(struct ptp_clock_info *ptp, unsigned int pin, + enum ptp_pin_function func, unsigned int chan) +{ + switch (func) { + case PTP_PF_NONE: + case PTP_PF_EXTTS: + break; + case PTP_PF_PEROUT: + case PTP_PF_PHYSYNC: + return -1; + } + return 0; +} + +static void idt82p33_caps_init(u32 index, struct ptp_clock_info *caps, + struct ptp_pin_desc *pin_cfg, u8 max_pins) +{ + struct ptp_pin_desc *ppd; + int i; + + caps->owner = THIS_MODULE; + caps->max_adj = DCO_MAX_PPB; + caps->n_per_out = MAX_PER_OUT; + caps->n_ext_ts = MAX_PHC_PLL; + caps->n_pins = max_pins; + caps->adjphase = idt82p33_adjwritephase; + caps->getmaxphase = idt82p33_getmaxphase; + caps->adjfine = idt82p33_adjfine; + caps->adjtime = idt82p33_adjtime; + caps->gettime64 = idt82p33_gettime; + caps->settime64 = idt82p33_settime; + caps->enable = idt82p33_enable; + caps->verify = idt82p33_verify_pin; + caps->do_aux_work = idt82p33_work_handler; + + snprintf(caps->name, sizeof(caps->name), "IDT 82P33 PLL%u", index); + + caps->pin_config = pin_cfg; + + caps->supported_extts_flags = PTP_RISING_EDGE | + PTP_STRICT_FLAGS; + + for (i = 0; i < max_pins; ++i) { + ppd = &pin_cfg[i]; + + ppd->index = i; + ppd->func = PTP_PF_NONE; + ppd->chan = index; + snprintf(ppd->name, sizeof(ppd->name), "in%d", 12 + i); + } +} + +static int idt82p33_enable_channel(struct idt82p33 *idt82p33, u32 index) +{ + struct idt82p33_channel *channel; + int err; + + if (!(index < MAX_PHC_PLL)) + return -EINVAL; + + channel = &idt82p33->channel[index]; + + err = idt82p33_channel_init(idt82p33, index); + if (err) { + dev_err(idt82p33->dev, + "Channel_init failed in %s with err %d!\n", + __func__, err); + return err; + } + + idt82p33_caps_init(index, &channel->caps, + pin_config[index], MAX_TRIG_CLK); + + channel->ptp_clock = ptp_clock_register(&channel->caps, NULL); + + if (IS_ERR(channel->ptp_clock)) { + err = PTR_ERR(channel->ptp_clock); + channel->ptp_clock = NULL; + return err; + } + + if (!channel->ptp_clock) + return -ENOTSUPP; + + err = idt82p33_dpll_set_mode(channel, PLL_MODE_DCO); + if (err) { + dev_err(idt82p33->dev, + "Dpll_set_mode failed in %s with err %d!\n", + __func__, err); + return err; + } + + err = idt82p33_enable_tod(channel); + if (err) { + dev_err(idt82p33->dev, + "Enable_tod failed in %s with err %d!\n", + __func__, err); + return err; + } + + dev_info(idt82p33->dev, "PLL%d registered as ptp%d\n", + index, channel->ptp_clock->index); + + return 0; +} + +static int idt82p33_reset(struct idt82p33 *idt82p33, bool cold) +{ + int err; + u8 cfg = SOFT_RESET_EN; + + if (cold == true) + goto cold_reset; + + err = idt82p33_read(idt82p33, REG_SOFT_RESET, &cfg, sizeof(cfg)); + if (err) { + dev_err(idt82p33->dev, + "Soft reset failed with err %d!\n", err); + return err; + } + + cfg |= SOFT_RESET_EN; + +cold_reset: + err = idt82p33_write(idt82p33, REG_SOFT_RESET, &cfg, sizeof(cfg)); + if (err) + dev_err(idt82p33->dev, + "Cold reset failed with err %d!\n", err); + return err; +} + +static int idt82p33_load_firmware(struct idt82p33 *idt82p33) +{ + char fname[128] = FW_FILENAME; + const struct firmware *fw; + struct idt82p33_fwrc *rec; + u8 loaddr, page, val; + int err; + s32 len; + + if (firmware) /* module parameter */ + snprintf(fname, sizeof(fname), "%s", firmware); + + dev_info(idt82p33->dev, "requesting firmware '%s'\n", fname); + + err = request_firmware(&fw, fname, idt82p33->dev); + + if (err) { + dev_err(idt82p33->dev, + "Failed in %s with err %d!\n", __func__, err); + return err; + } + + dev_dbg(idt82p33->dev, "firmware size %zu bytes\n", fw->size); + + rec = (struct idt82p33_fwrc *) fw->data; + + for (len = fw->size; len > 0; len -= sizeof(*rec)) { + + if (rec->reserved) { + dev_err(idt82p33->dev, + "bad firmware, reserved field non-zero\n"); + err = -EINVAL; + } else { + val = rec->value; + loaddr = rec->loaddr; + page = rec->hiaddr; + + rec++; + + err = idt82p33_check_and_set_masks(idt82p33, page, + loaddr, val); + } + + if (err == 0) { + /* Page size 128, last 4 bytes of page skipped */ + if (loaddr > 0x7b) + continue; + + err = idt82p33_write(idt82p33, REG_ADDR(page, loaddr), + &val, sizeof(val)); + } + + if (err) + goto out; + } + + idt82p33_display_masks(idt82p33); +out: + release_firmware(fw); + return err; +} + +static void idt82p33_extts_check(struct work_struct *work) +{ + struct idt82p33 *idt82p33 = container_of(work, struct idt82p33, + extts_work.work); + struct idt82p33_channel *channel; + int err; + u8 mask; + int i; + + if (idt82p33->extts_mask == 0) + return; + + mutex_lock(idt82p33->lock); + + for (i = 0; i < MAX_PHC_PLL; i++) { + mask = 1 << i; + + if ((idt82p33->extts_mask & mask) == 0) + continue; + + err = idt82p33_extts_check_channel(idt82p33, i); + + if (err == 0) { + /* trigger clears itself, so clear the mask */ + if (idt82p33->extts_single_shot) { + idt82p33->extts_mask &= ~mask; + } else { + /* Re-arm */ + channel = &idt82p33->channel[i]; + arm_tod_read_with_trigger(channel, channel->tod_trigger); + } + } + } + + if (idt82p33->extts_mask) + schedule_delayed_work(&idt82p33->extts_work, + msecs_to_jiffies(EXTTS_PERIOD_MS)); + + mutex_unlock(idt82p33->lock); +} + +static int idt82p33_probe(struct platform_device *pdev) +{ + struct rsmu_ddata *ddata = dev_get_drvdata(pdev->dev.parent); + struct idt82p33 *idt82p33; + int err; + u8 i; + + idt82p33 = devm_kzalloc(&pdev->dev, + sizeof(struct idt82p33), GFP_KERNEL); + if (!idt82p33) + return -ENOMEM; + + idt82p33->dev = &pdev->dev; + idt82p33->mfd = pdev->dev.parent; + idt82p33->lock = &ddata->lock; + idt82p33->regmap = ddata->regmap; + idt82p33->tod_write_overhead_ns = 0; + idt82p33->calculate_overhead_flag = 0; + idt82p33->pll_mask = DEFAULT_PLL_MASK; + idt82p33->channel[0].output_mask = DEFAULT_OUTPUT_MASK_PLL0; + idt82p33->channel[1].output_mask = DEFAULT_OUTPUT_MASK_PLL1; + idt82p33->extts_mask = 0; + INIT_DELAYED_WORK(&idt82p33->extts_work, idt82p33_extts_check); + + mutex_lock(idt82p33->lock); + + /* cold reset before loading firmware */ + idt82p33_reset(idt82p33, true); + + err = idt82p33_load_firmware(idt82p33); + if (err) + dev_warn(idt82p33->dev, + "loading firmware failed with %d\n", err); + + /* soft reset after loading firmware */ + idt82p33_reset(idt82p33, false); + + if (idt82p33->pll_mask) { + for (i = 0; i < MAX_PHC_PLL; i++) { + if (idt82p33->pll_mask & (1 << i)) + err = idt82p33_enable_channel(idt82p33, i); + else + err = idt82p33_channel_init(idt82p33, i); + if (err) { + dev_err(idt82p33->dev, + "Failed in %s with err %d!\n", + __func__, err); + break; + } + } + } else { + dev_err(idt82p33->dev, + "no PLLs flagged as PHCs, nothing to do\n"); + err = -ENODEV; + } + + mutex_unlock(idt82p33->lock); + + if (err) { + idt82p33_ptp_clock_unregister_all(idt82p33); + return err; + } + + platform_set_drvdata(pdev, idt82p33); + + return 0; +} + +static void idt82p33_remove(struct platform_device *pdev) +{ + struct idt82p33 *idt82p33 = platform_get_drvdata(pdev); + + cancel_delayed_work_sync(&idt82p33->extts_work); + + idt82p33_ptp_clock_unregister_all(idt82p33); +} + +static struct platform_driver idt82p33_driver = { + .driver = { + .name = "82p33x1x-phc", + }, + .probe = idt82p33_probe, + .remove = idt82p33_remove, +}; + +module_platform_driver(idt82p33_driver); diff --git a/drivers/ptp/ptp_idt82p33.h b/drivers/ptp/ptp_idt82p33.h new file mode 100644 index 000000000..6a63c14b6 --- /dev/null +++ b/drivers/ptp/ptp_idt82p33.h @@ -0,0 +1,114 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * PTP hardware clock driver for the IDT 82P33XXX family of clocks. + * + * Copyright (C) 2019 Integrated Device Technology, Inc., a Renesas Company. + */ +#ifndef PTP_IDT82P33_H +#define PTP_IDT82P33_H + +#include +#include +#include + +#define FW_FILENAME "idt82p33xxx.bin" +#define MAX_PHC_PLL (2) +#define MAX_TRIG_CLK (3) +#define MAX_PER_OUT (11) +#define TOD_BYTE_COUNT (10) +#define DCO_MAX_PPB (92000) +#define MAX_MEASURMENT_COUNT (5) +#define SNAP_THRESHOLD_NS (10000) +#define IMMEDIATE_SNAP_THRESHOLD_NS (50000) +#define DDCO_THRESHOLD_NS (5) +#define IDT82P33_MAX_WRITE_COUNT (512) + +#define PLLMASK_ADDR_HI 0xFF +#define PLLMASK_ADDR_LO 0xA5 + +#define PLL0_OUTMASK_ADDR_HI 0xFF +#define PLL0_OUTMASK_ADDR_LO 0xB0 + +#define PLL1_OUTMASK_ADDR_HI 0xFF +#define PLL1_OUTMASK_ADDR_LO 0xB2 + +#define PLL2_OUTMASK_ADDR_HI 0xFF +#define PLL2_OUTMASK_ADDR_LO 0xB4 + +#define PLL3_OUTMASK_ADDR_HI 0xFF +#define PLL3_OUTMASK_ADDR_LO 0xB6 + +#define DEFAULT_PLL_MASK (0x01) +#define DEFAULT_OUTPUT_MASK_PLL0 (0xc0) +#define DEFAULT_OUTPUT_MASK_PLL1 DEFAULT_OUTPUT_MASK_PLL0 + +/** + * @brief Maximum absolute value for write phase offset in nanoseconds + */ +#define WRITE_PHASE_OFFSET_LIMIT (20000l) + +/** @brief Phase offset resolution + * + * DPLL phase offset = 10^15 fs / ( System Clock * 2^13) + * = 10^15 fs / ( 1638400000 * 2^23) + * = 74.5058059692382 fs + */ +#define IDT_T0DPLL_PHASE_RESOL 74506 + +/* PTP Hardware Clock interface */ +struct idt82p33_channel { + struct ptp_clock_info caps; + struct ptp_clock *ptp_clock; + struct idt82p33 *idt82p33; + enum pll_mode pll_mode; + /* Workaround for TOD-to-output alignment issue */ + struct delayed_work adjtime_work; + s32 current_freq; + /* double dco mode */ + bool ddco; + u8 output_mask; + /* last input trigger for extts */ + u8 tod_trigger; + bool discard_next_extts; + u8 plln; + /* remember last tod_sts for extts */ + u8 extts_tod_sts[TOD_BYTE_COUNT]; + u16 dpll_tod_cnfg; + u16 dpll_tod_trigger; + u16 dpll_tod_sts; + u16 dpll_mode_cnfg; + u16 dpll_freq_cnfg; + u16 dpll_phase_cnfg; + u16 dpll_sync_cnfg; + u16 dpll_input_mode_cnfg; +}; + +struct idt82p33 { + struct idt82p33_channel channel[MAX_PHC_PLL]; + struct device *dev; + u8 pll_mask; + /* Polls for external time stamps */ + u8 extts_mask; + bool extts_single_shot; + struct delayed_work extts_work; + /* Remember the ptp channel to report extts */ + struct idt82p33_channel *event_channel[MAX_PHC_PLL]; + /* Mutex to protect operations from being interrupted */ + struct mutex *lock; + struct regmap *regmap; + struct device *mfd; + /* Overhead calculation for adjtime */ + ktime_t start_time; + int calculate_overhead_flag; + s64 tod_write_overhead_ns; +}; + +/* firmware interface */ +struct idt82p33_fwrc { + u8 hiaddr; + u8 loaddr; + u8 value; + u8 reserved; +} __packed; + +#endif /* PTP_IDT82P33_H */ diff --git a/drivers/ptp/ptp_ines.c b/drivers/ptp/ptp_ines.c new file mode 100644 index 000000000..9e7af6a10 --- /dev/null +++ b/drivers/ptp/ptp_ines.c @@ -0,0 +1,814 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (C) 2018 MOSER-BAER AG +// + +#define pr_fmt(fmt) "InES_PTP: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_DESCRIPTION("Driver for the ZHAW InES PTP time stamping IP core"); +MODULE_AUTHOR("Richard Cochran "); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL"); + +/* GLOBAL register */ +#define MCAST_MAC_SELECT_SHIFT 2 +#define MCAST_MAC_SELECT_MASK 0x3 +#define IO_RESET BIT(1) +#define PTP_RESET BIT(0) + +/* VERSION register */ +#define IF_MAJOR_VER_SHIFT 12 +#define IF_MAJOR_VER_MASK 0xf +#define IF_MINOR_VER_SHIFT 8 +#define IF_MINOR_VER_MASK 0xf +#define FPGA_MAJOR_VER_SHIFT 4 +#define FPGA_MAJOR_VER_MASK 0xf +#define FPGA_MINOR_VER_SHIFT 0 +#define FPGA_MINOR_VER_MASK 0xf + +/* INT_STAT register */ +#define RX_INTR_STATUS_3 BIT(5) +#define RX_INTR_STATUS_2 BIT(4) +#define RX_INTR_STATUS_1 BIT(3) +#define TX_INTR_STATUS_3 BIT(2) +#define TX_INTR_STATUS_2 BIT(1) +#define TX_INTR_STATUS_1 BIT(0) + +/* INT_MSK register */ +#define RX_INTR_MASK_3 BIT(5) +#define RX_INTR_MASK_2 BIT(4) +#define RX_INTR_MASK_1 BIT(3) +#define TX_INTR_MASK_3 BIT(2) +#define TX_INTR_MASK_2 BIT(1) +#define TX_INTR_MASK_1 BIT(0) + +/* BUF_STAT register */ +#define RX_FIFO_NE_3 BIT(5) +#define RX_FIFO_NE_2 BIT(4) +#define RX_FIFO_NE_1 BIT(3) +#define TX_FIFO_NE_3 BIT(2) +#define TX_FIFO_NE_2 BIT(1) +#define TX_FIFO_NE_1 BIT(0) + +/* PORT_CONF register */ +#define CM_ONE_STEP BIT(6) +#define PHY_SPEED_SHIFT 4 +#define PHY_SPEED_MASK 0x3 +#define P2P_DELAY_WR_POS_SHIFT 2 +#define P2P_DELAY_WR_POS_MASK 0x3 +#define PTP_MODE_SHIFT 0 +#define PTP_MODE_MASK 0x3 + +/* TS_STAT_TX register */ +#define TS_ENABLE BIT(15) +#define DATA_READ_POS_SHIFT 8 +#define DATA_READ_POS_MASK 0x1f +#define DISCARDED_EVENTS_SHIFT 4 +#define DISCARDED_EVENTS_MASK 0xf + +#define INES_N_PORTS 3 +#define INES_REGISTER_SIZE 0x80 +#define INES_PORT_OFFSET 0x20 +#define INES_PORT_SIZE 0x20 +#define INES_FIFO_DEPTH 90 +#define INES_MAX_EVENTS 100 + +#define BC_PTP_V1 0 +#define BC_PTP_V2 1 +#define TC_E2E_PTP_V2 2 +#define TC_P2P_PTP_V2 3 + +#define PHY_SPEED_10 0 +#define PHY_SPEED_100 1 +#define PHY_SPEED_1000 2 + +#define PORT_CONF \ + ((PHY_SPEED_1000 << PHY_SPEED_SHIFT) | (BC_PTP_V2 << PTP_MODE_SHIFT)) + +#define ines_read32(s, r) __raw_readl((void __iomem *)&s->regs->r) +#define ines_write32(s, v, r) __raw_writel(v, (void __iomem *)&s->regs->r) + +#define MESSAGE_TYPE_SYNC 1 +#define MESSAGE_TYPE_P_DELAY_REQ 2 +#define MESSAGE_TYPE_P_DELAY_RESP 3 +#define MESSAGE_TYPE_DELAY_REQ 4 + +static LIST_HEAD(ines_clocks); +static DEFINE_MUTEX(ines_clocks_lock); + +struct ines_global_regs { + u32 id; + u32 test; + u32 global; + u32 version; + u32 test2; + u32 int_stat; + u32 int_msk; + u32 buf_stat; +}; + +struct ines_port_registers { + u32 port_conf; + u32 p_delay; + u32 ts_stat_tx; + u32 ts_stat_rx; + u32 ts_tx; + u32 ts_rx; +}; + +struct ines_timestamp { + struct list_head list; + unsigned long tmo; + u16 tag; + u64 sec; + u64 nsec; + u64 clkid; + u16 portnum; + u16 seqid; +}; + +struct ines_port { + struct ines_port_registers *regs; + struct mii_timestamper mii_ts; + struct ines_clock *clock; + bool rxts_enabled; + bool txts_enabled; + unsigned int index; + struct delayed_work ts_work; + /* lock protects event list and tx_skb */ + spinlock_t lock; + struct sk_buff *tx_skb; + struct list_head events; + struct list_head pool; + struct ines_timestamp pool_data[INES_MAX_EVENTS]; +}; + +struct ines_clock { + struct ines_port port[INES_N_PORTS]; + struct ines_global_regs __iomem *regs; + void __iomem *base; + struct device_node *node; + struct device *dev; + struct list_head list; +}; + +static bool ines_match(struct sk_buff *skb, unsigned int ptp_class, + struct ines_timestamp *ts, struct device *dev); +static int ines_rxfifo_read(struct ines_port *port); +static u64 ines_rxts64(struct ines_port *port, unsigned int words); +static bool ines_timestamp_expired(struct ines_timestamp *ts); +static u64 ines_txts64(struct ines_port *port, unsigned int words); +static void ines_txtstamp_work(struct work_struct *work); +static bool is_sync_pdelay_resp(struct sk_buff *skb, int type); +static u8 tag_to_msgtype(u8 tag); + +static void ines_clock_cleanup(struct ines_clock *clock) +{ + struct ines_port *port; + int i; + + for (i = 0; i < INES_N_PORTS; i++) { + port = &clock->port[i]; + cancel_delayed_work_sync(&port->ts_work); + } +} + +static int ines_clock_init(struct ines_clock *clock, struct device *device, + void __iomem *addr) +{ + struct device_node *node = device->of_node; + unsigned long port_addr; + struct ines_port *port; + int i, j; + + INIT_LIST_HEAD(&clock->list); + clock->node = node; + clock->dev = device; + clock->base = addr; + clock->regs = clock->base; + + for (i = 0; i < INES_N_PORTS; i++) { + port = &clock->port[i]; + port_addr = (unsigned long) clock->base + + INES_PORT_OFFSET + i * INES_PORT_SIZE; + port->regs = (struct ines_port_registers *) port_addr; + port->clock = clock; + port->index = i; + INIT_DELAYED_WORK(&port->ts_work, ines_txtstamp_work); + spin_lock_init(&port->lock); + INIT_LIST_HEAD(&port->events); + INIT_LIST_HEAD(&port->pool); + for (j = 0; j < INES_MAX_EVENTS; j++) + list_add(&port->pool_data[j].list, &port->pool); + } + + ines_write32(clock, 0xBEEF, test); + ines_write32(clock, 0xBEEF, test2); + + dev_dbg(device, "ID 0x%x\n", ines_read32(clock, id)); + dev_dbg(device, "TEST 0x%x\n", ines_read32(clock, test)); + dev_dbg(device, "VERSION 0x%x\n", ines_read32(clock, version)); + dev_dbg(device, "TEST2 0x%x\n", ines_read32(clock, test2)); + + for (i = 0; i < INES_N_PORTS; i++) { + port = &clock->port[i]; + ines_write32(port, PORT_CONF, port_conf); + } + + return 0; +} + +static struct ines_port *ines_find_port(struct device_node *node, u32 index) +{ + struct ines_port *port = NULL; + struct ines_clock *clock; + struct list_head *this; + + mutex_lock(&ines_clocks_lock); + list_for_each(this, &ines_clocks) { + clock = list_entry(this, struct ines_clock, list); + if (clock->node == node) { + port = &clock->port[index]; + break; + } + } + mutex_unlock(&ines_clocks_lock); + return port; +} + +static u64 ines_find_rxts(struct ines_port *port, struct sk_buff *skb, int type) +{ + struct list_head *this, *next; + struct ines_timestamp *ts; + unsigned long flags; + u64 ns = 0; + + if (type == PTP_CLASS_NONE) + return 0; + + spin_lock_irqsave(&port->lock, flags); + ines_rxfifo_read(port); + list_for_each_safe(this, next, &port->events) { + ts = list_entry(this, struct ines_timestamp, list); + if (ines_timestamp_expired(ts)) { + list_del_init(&ts->list); + list_add(&ts->list, &port->pool); + continue; + } + if (ines_match(skb, type, ts, port->clock->dev)) { + ns = ts->sec * 1000000000ULL + ts->nsec; + list_del_init(&ts->list); + list_add(&ts->list, &port->pool); + break; + } + } + spin_unlock_irqrestore(&port->lock, flags); + + return ns; +} + +static u64 ines_find_txts(struct ines_port *port, struct sk_buff *skb) +{ + unsigned int class = ptp_classify_raw(skb), i; + u32 data_rd_pos, buf_stat, mask, ts_stat_tx; + struct ines_timestamp ts; + unsigned long flags; + u64 ns = 0; + + mask = TX_FIFO_NE_1 << port->index; + + spin_lock_irqsave(&port->lock, flags); + + for (i = 0; i < INES_FIFO_DEPTH; i++) { + + buf_stat = ines_read32(port->clock, buf_stat); + if (!(buf_stat & mask)) { + dev_dbg(port->clock->dev, + "Tx timestamp FIFO unexpectedly empty\n"); + break; + } + ts_stat_tx = ines_read32(port, ts_stat_tx); + data_rd_pos = (ts_stat_tx >> DATA_READ_POS_SHIFT) & + DATA_READ_POS_MASK; + if (data_rd_pos) { + dev_err(port->clock->dev, + "unexpected Tx read pos %u\n", data_rd_pos); + break; + } + + ts.tag = ines_read32(port, ts_tx); + ts.sec = ines_txts64(port, 3); + ts.nsec = ines_txts64(port, 2); + ts.clkid = ines_txts64(port, 4); + ts.portnum = ines_read32(port, ts_tx); + ts.seqid = ines_read32(port, ts_tx); + + if (ines_match(skb, class, &ts, port->clock->dev)) { + ns = ts.sec * 1000000000ULL + ts.nsec; + break; + } + } + + spin_unlock_irqrestore(&port->lock, flags); + return ns; +} + +static int ines_hwtstamp_get(struct mii_timestamper *mii_ts, + struct kernel_hwtstamp_config *cfg) +{ + struct ines_port *port = container_of(mii_ts, struct ines_port, mii_ts); + unsigned long flags; + u32 port_conf; + + cfg->rx_filter = port->rxts_enabled ? HWTSTAMP_FILTER_PTP_V2_EVENT + : HWTSTAMP_FILTER_NONE; + if (port->txts_enabled) { + spin_lock_irqsave(&port->lock, flags); + port_conf = ines_read32(port, port_conf); + spin_unlock_irqrestore(&port->lock, flags); + cfg->tx_type = (port_conf & CM_ONE_STEP) ? HWTSTAMP_TX_ONESTEP_P2P + : HWTSTAMP_TX_OFF; + } else { + cfg->tx_type = HWTSTAMP_TX_OFF; + } + + return 0; +} + +static int ines_hwtstamp_set(struct mii_timestamper *mii_ts, + struct kernel_hwtstamp_config *cfg, + struct netlink_ext_ack *extack) +{ + struct ines_port *port = container_of(mii_ts, struct ines_port, mii_ts); + u32 cm_one_step = 0, port_conf, ts_stat_rx, ts_stat_tx; + unsigned long flags; + + switch (cfg->tx_type) { + case HWTSTAMP_TX_OFF: + ts_stat_tx = 0; + break; + case HWTSTAMP_TX_ON: + ts_stat_tx = TS_ENABLE; + break; + case HWTSTAMP_TX_ONESTEP_P2P: + ts_stat_tx = TS_ENABLE; + cm_one_step = CM_ONE_STEP; + break; + default: + return -ERANGE; + } + + switch (cfg->rx_filter) { + case HWTSTAMP_FILTER_NONE: + ts_stat_rx = 0; + break; + case HWTSTAMP_FILTER_ALL: + case HWTSTAMP_FILTER_PTP_V1_L4_EVENT: + case HWTSTAMP_FILTER_PTP_V1_L4_SYNC: + case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ: + return -ERANGE; + case HWTSTAMP_FILTER_PTP_V2_L4_EVENT: + case HWTSTAMP_FILTER_PTP_V2_L4_SYNC: + case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ: + case HWTSTAMP_FILTER_PTP_V2_L2_EVENT: + case HWTSTAMP_FILTER_PTP_V2_L2_SYNC: + case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ: + case HWTSTAMP_FILTER_PTP_V2_EVENT: + case HWTSTAMP_FILTER_PTP_V2_SYNC: + case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ: + ts_stat_rx = TS_ENABLE; + cfg->rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT; + break; + default: + return -ERANGE; + } + + spin_lock_irqsave(&port->lock, flags); + + port_conf = ines_read32(port, port_conf); + port_conf &= ~CM_ONE_STEP; + port_conf |= cm_one_step; + + ines_write32(port, port_conf, port_conf); + ines_write32(port, ts_stat_rx, ts_stat_rx); + ines_write32(port, ts_stat_tx, ts_stat_tx); + + port->rxts_enabled = ts_stat_rx == TS_ENABLE; + port->txts_enabled = ts_stat_tx == TS_ENABLE; + + spin_unlock_irqrestore(&port->lock, flags); + + return 0; +} + +static void ines_link_state(struct mii_timestamper *mii_ts, + struct phy_device *phydev) +{ + struct ines_port *port = container_of(mii_ts, struct ines_port, mii_ts); + u32 port_conf, speed_conf; + unsigned long flags; + + switch (phydev->speed) { + case SPEED_10: + speed_conf = PHY_SPEED_10 << PHY_SPEED_SHIFT; + break; + case SPEED_100: + speed_conf = PHY_SPEED_100 << PHY_SPEED_SHIFT; + break; + case SPEED_1000: + speed_conf = PHY_SPEED_1000 << PHY_SPEED_SHIFT; + break; + default: + dev_err(port->clock->dev, "bad speed: %d\n", phydev->speed); + return; + } + spin_lock_irqsave(&port->lock, flags); + + port_conf = ines_read32(port, port_conf); + port_conf &= ~(0x3 << PHY_SPEED_SHIFT); + port_conf |= speed_conf; + + ines_write32(port, port_conf, port_conf); + + spin_unlock_irqrestore(&port->lock, flags); +} + +static bool ines_match(struct sk_buff *skb, unsigned int ptp_class, + struct ines_timestamp *ts, struct device *dev) +{ + struct ptp_header *hdr; + u16 portn, seqid; + u8 msgtype; + u64 clkid; + + if (unlikely(ptp_class & PTP_CLASS_V1)) + return false; + + hdr = ptp_parse_header(skb, ptp_class); + if (!hdr) + return false; + + msgtype = ptp_get_msgtype(hdr, ptp_class); + clkid = be64_to_cpup((__be64 *)&hdr->source_port_identity.clock_identity.id[0]); + portn = be16_to_cpu(hdr->source_port_identity.port_number); + seqid = be16_to_cpu(hdr->sequence_id); + + if (tag_to_msgtype(ts->tag & 0x7) != msgtype) { + dev_dbg(dev, "msgtype mismatch ts %hhu != skb %hhu\n", + tag_to_msgtype(ts->tag & 0x7), msgtype); + return false; + } + if (ts->clkid != clkid) { + dev_dbg(dev, "clkid mismatch ts %llx != skb %llx\n", + ts->clkid, clkid); + return false; + } + if (ts->portnum != portn) { + dev_dbg(dev, "portn mismatch ts %hu != skb %hu\n", + ts->portnum, portn); + return false; + } + if (ts->seqid != seqid) { + dev_dbg(dev, "seqid mismatch ts %hu != skb %hu\n", + ts->seqid, seqid); + return false; + } + + return true; +} + +static bool ines_rxtstamp(struct mii_timestamper *mii_ts, + struct sk_buff *skb, int type) +{ + struct ines_port *port = container_of(mii_ts, struct ines_port, mii_ts); + struct skb_shared_hwtstamps *ssh; + u64 ns; + + if (!port->rxts_enabled) + return false; + + ns = ines_find_rxts(port, skb, type); + if (!ns) + return false; + + ssh = skb_hwtstamps(skb); + ssh->hwtstamp = ns_to_ktime(ns); + netif_rx(skb); + + return true; +} + +static int ines_rxfifo_read(struct ines_port *port) +{ + u32 data_rd_pos, buf_stat, mask, ts_stat_rx; + struct ines_timestamp *ts; + unsigned int i; + + mask = RX_FIFO_NE_1 << port->index; + + for (i = 0; i < INES_FIFO_DEPTH; i++) { + if (list_empty(&port->pool)) { + dev_err(port->clock->dev, "event pool is empty\n"); + return -1; + } + buf_stat = ines_read32(port->clock, buf_stat); + if (!(buf_stat & mask)) + break; + + ts_stat_rx = ines_read32(port, ts_stat_rx); + data_rd_pos = (ts_stat_rx >> DATA_READ_POS_SHIFT) & + DATA_READ_POS_MASK; + if (data_rd_pos) { + dev_err(port->clock->dev, "unexpected Rx read pos %u\n", + data_rd_pos); + break; + } + + ts = list_first_entry(&port->pool, struct ines_timestamp, list); + ts->tmo = jiffies + HZ; + ts->tag = ines_read32(port, ts_rx); + ts->sec = ines_rxts64(port, 3); + ts->nsec = ines_rxts64(port, 2); + ts->clkid = ines_rxts64(port, 4); + ts->portnum = ines_read32(port, ts_rx); + ts->seqid = ines_read32(port, ts_rx); + + list_del_init(&ts->list); + list_add_tail(&ts->list, &port->events); + } + + return 0; +} + +static u64 ines_rxts64(struct ines_port *port, unsigned int words) +{ + unsigned int i; + u64 result; + u16 word; + + word = ines_read32(port, ts_rx); + result = word; + words--; + for (i = 0; i < words; i++) { + word = ines_read32(port, ts_rx); + result <<= 16; + result |= word; + } + return result; +} + +static bool ines_timestamp_expired(struct ines_timestamp *ts) +{ + return time_after(jiffies, ts->tmo); +} + +static int ines_ts_info(struct mii_timestamper *mii_ts, + struct kernel_ethtool_ts_info *info) +{ + info->so_timestamping = + SOF_TIMESTAMPING_TX_HARDWARE | + SOF_TIMESTAMPING_TX_SOFTWARE | + SOF_TIMESTAMPING_RX_HARDWARE | + SOF_TIMESTAMPING_RAW_HARDWARE; + + info->tx_types = + (1 << HWTSTAMP_TX_OFF) | + (1 << HWTSTAMP_TX_ON) | + (1 << HWTSTAMP_TX_ONESTEP_P2P); + + info->rx_filters = + (1 << HWTSTAMP_FILTER_NONE) | + (1 << HWTSTAMP_FILTER_PTP_V2_EVENT); + + return 0; +} + +static u64 ines_txts64(struct ines_port *port, unsigned int words) +{ + unsigned int i; + u64 result; + u16 word; + + word = ines_read32(port, ts_tx); + result = word; + words--; + for (i = 0; i < words; i++) { + word = ines_read32(port, ts_tx); + result <<= 16; + result |= word; + } + return result; +} + +static bool ines_txts_onestep(struct ines_port *port, struct sk_buff *skb, int type) +{ + unsigned long flags; + u32 port_conf; + + spin_lock_irqsave(&port->lock, flags); + port_conf = ines_read32(port, port_conf); + spin_unlock_irqrestore(&port->lock, flags); + + if (port_conf & CM_ONE_STEP) + return is_sync_pdelay_resp(skb, type); + + return false; +} + +static void ines_txtstamp(struct mii_timestamper *mii_ts, + struct sk_buff *skb, int type) +{ + struct ines_port *port = container_of(mii_ts, struct ines_port, mii_ts); + struct sk_buff *old_skb = NULL; + unsigned long flags; + + if (!port->txts_enabled || ines_txts_onestep(port, skb, type)) { + kfree_skb(skb); + return; + } + + spin_lock_irqsave(&port->lock, flags); + + if (port->tx_skb) + old_skb = port->tx_skb; + + port->tx_skb = skb; + + spin_unlock_irqrestore(&port->lock, flags); + + kfree_skb(old_skb); + + schedule_delayed_work(&port->ts_work, 1); +} + +static void ines_txtstamp_work(struct work_struct *work) +{ + struct ines_port *port = + container_of(work, struct ines_port, ts_work.work); + struct skb_shared_hwtstamps ssh; + struct sk_buff *skb; + unsigned long flags; + u64 ns; + + spin_lock_irqsave(&port->lock, flags); + skb = port->tx_skb; + port->tx_skb = NULL; + spin_unlock_irqrestore(&port->lock, flags); + + ns = ines_find_txts(port, skb); + if (!ns) { + kfree_skb(skb); + return; + } + ssh.hwtstamp = ns_to_ktime(ns); + skb_complete_tx_timestamp(skb, &ssh); +} + +static bool is_sync_pdelay_resp(struct sk_buff *skb, int type) +{ + struct ptp_header *hdr; + u8 msgtype; + + hdr = ptp_parse_header(skb, type); + if (!hdr) + return false; + + msgtype = ptp_get_msgtype(hdr, type); + + switch (msgtype) { + case PTP_MSGTYPE_SYNC: + case PTP_MSGTYPE_PDELAY_RESP: + return true; + default: + return false; + } +} + +static u8 tag_to_msgtype(u8 tag) +{ + switch (tag) { + case MESSAGE_TYPE_SYNC: + return PTP_MSGTYPE_SYNC; + case MESSAGE_TYPE_P_DELAY_REQ: + return PTP_MSGTYPE_PDELAY_REQ; + case MESSAGE_TYPE_P_DELAY_RESP: + return PTP_MSGTYPE_PDELAY_RESP; + case MESSAGE_TYPE_DELAY_REQ: + return PTP_MSGTYPE_DELAY_REQ; + } + return 0xf; +} + +static struct mii_timestamper *ines_ptp_probe_channel(struct device *device, + unsigned int index) +{ + struct device_node *node = device->of_node; + struct ines_port *port; + + if (index > INES_N_PORTS - 1) { + dev_err(device, "bad port index %u\n", index); + return ERR_PTR(-EINVAL); + } + port = ines_find_port(node, index); + if (!port) { + dev_err(device, "missing port index %u\n", index); + return ERR_PTR(-ENODEV); + } + port->mii_ts.rxtstamp = ines_rxtstamp; + port->mii_ts.txtstamp = ines_txtstamp; + port->mii_ts.hwtstamp_set = ines_hwtstamp_set; + port->mii_ts.hwtstamp_get = ines_hwtstamp_get; + port->mii_ts.link_state = ines_link_state; + port->mii_ts.ts_info = ines_ts_info; + + return &port->mii_ts; +} + +static void ines_ptp_release_channel(struct device *device, + struct mii_timestamper *mii_ts) +{ +} + +static struct mii_timestamping_ctrl ines_ctrl = { + .probe_channel = ines_ptp_probe_channel, + .release_channel = ines_ptp_release_channel, +}; + +static int ines_ptp_ctrl_probe(struct platform_device *pld) +{ + struct ines_clock *clock; + void __iomem *addr; + int err = 0; + + addr = devm_platform_ioremap_resource(pld, 0); + if (IS_ERR(addr)) { + err = PTR_ERR(addr); + goto out; + } + clock = kzalloc_obj(*clock); + if (!clock) { + err = -ENOMEM; + goto out; + } + if (ines_clock_init(clock, &pld->dev, addr)) { + kfree(clock); + err = -ENOMEM; + goto out; + } + err = register_mii_tstamp_controller(&pld->dev, &ines_ctrl); + if (err) { + kfree(clock); + goto out; + } + mutex_lock(&ines_clocks_lock); + list_add_tail(&ines_clocks, &clock->list); + mutex_unlock(&ines_clocks_lock); + + dev_set_drvdata(&pld->dev, clock); +out: + return err; +} + +static void ines_ptp_ctrl_remove(struct platform_device *pld) +{ + struct ines_clock *clock = dev_get_drvdata(&pld->dev); + + unregister_mii_tstamp_controller(&pld->dev); + mutex_lock(&ines_clocks_lock); + list_del(&clock->list); + mutex_unlock(&ines_clocks_lock); + ines_clock_cleanup(clock); + kfree(clock); +} + +static const struct of_device_id ines_ptp_ctrl_of_match[] = { + { .compatible = "ines,ptp-ctrl" }, + { } +}; + +MODULE_DEVICE_TABLE(of, ines_ptp_ctrl_of_match); + +static struct platform_driver ines_ptp_ctrl_driver = { + .probe = ines_ptp_ctrl_probe, + .remove = ines_ptp_ctrl_remove, + .driver = { + .name = "ines_ptp_ctrl", + .of_match_table = ines_ptp_ctrl_of_match, + }, +}; +module_platform_driver(ines_ptp_ctrl_driver); diff --git a/drivers/ptp/ptp_kvm_arm.c b/drivers/ptp/ptp_kvm_arm.c new file mode 100644 index 000000000..e68e69431 --- /dev/null +++ b/drivers/ptp/ptp_kvm_arm.c @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Virtual PTP 1588 clock for use with KVM guests + * Copyright (C) 2019 ARM Ltd. + * All Rights Reserved + */ + +#include +#include + +#include +#include + +int kvm_arch_ptp_init(void) +{ + int ret; + + ret = kvm_arm_hyp_service_available(ARM_SMCCC_KVM_FUNC_PTP); + if (ret <= 0) + return -EOPNOTSUPP; + + return 0; +} + +void kvm_arch_ptp_exit(void) +{ +} + +int kvm_arch_ptp_get_clock(struct timespec64 *ts) +{ + return kvm_arch_ptp_get_crosststamp(NULL, ts, NULL); +} diff --git a/drivers/ptp/ptp_kvm_common.c b/drivers/ptp/ptp_kvm_common.c new file mode 100644 index 000000000..15ccb7dd2 --- /dev/null +++ b/drivers/ptp/ptp_kvm_common.c @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Virtual PTP 1588 clock for use with KVM guests + * + * Copyright (C) 2017 Red Hat Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +struct kvm_ptp_clock { + struct ptp_clock *ptp_clock; + struct ptp_clock_info caps; +}; + +static DEFINE_SPINLOCK(kvm_ptp_lock); + +static int ptp_kvm_get_time_fn(ktime_t *device_time, + struct system_counterval_t *system_counter, + void *ctx) +{ + enum clocksource_ids cs_id; + struct timespec64 tspec; + u64 cycle; + int ret; + + spin_lock(&kvm_ptp_lock); + + preempt_disable_notrace(); + ret = kvm_arch_ptp_get_crosststamp(&cycle, &tspec, &cs_id); + if (ret) { + spin_unlock(&kvm_ptp_lock); + preempt_enable_notrace(); + return ret; + } + + preempt_enable_notrace(); + + system_counter->cycles = cycle; + system_counter->cs_id = cs_id; + + *device_time = timespec64_to_ktime(tspec); + + spin_unlock(&kvm_ptp_lock); + + return 0; +} + +static int ptp_kvm_getcrosststamp(struct ptp_clock_info *ptp, + struct system_device_crosststamp *xtstamp) +{ + return get_device_system_crosststamp(ptp_kvm_get_time_fn, NULL, + NULL, xtstamp); +} + +/* + * PTP clock operations + */ + +static int ptp_kvm_adjfine(struct ptp_clock_info *ptp, long delta) +{ + return -EOPNOTSUPP; +} + +static int ptp_kvm_adjtime(struct ptp_clock_info *ptp, s64 delta) +{ + return -EOPNOTSUPP; +} + +static int ptp_kvm_settime(struct ptp_clock_info *ptp, + const struct timespec64 *ts) +{ + return -EOPNOTSUPP; +} + +static int ptp_kvm_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts) +{ + long ret; + struct timespec64 tspec; + + spin_lock(&kvm_ptp_lock); + + ret = kvm_arch_ptp_get_clock(&tspec); + if (ret) { + spin_unlock(&kvm_ptp_lock); + return ret; + } + + spin_unlock(&kvm_ptp_lock); + + memcpy(ts, &tspec, sizeof(struct timespec64)); + + return 0; +} + +static int ptp_kvm_enable(struct ptp_clock_info *ptp, + struct ptp_clock_request *rq, int on) +{ + return -EOPNOTSUPP; +} + +static const struct ptp_clock_info ptp_kvm_caps = { + .owner = THIS_MODULE, + .name = "KVM virtual PTP", + .max_adj = 0, + .n_ext_ts = 0, + .n_pins = 0, + .pps = 0, + .adjfine = ptp_kvm_adjfine, + .adjtime = ptp_kvm_adjtime, + .gettime64 = ptp_kvm_gettime, + .settime64 = ptp_kvm_settime, + .enable = ptp_kvm_enable, + .getcrosststamp = ptp_kvm_getcrosststamp, +}; + +/* module operations */ + +static struct kvm_ptp_clock kvm_ptp_clock; + +static void __exit ptp_kvm_exit(void) +{ + ptp_clock_unregister(kvm_ptp_clock.ptp_clock); + kvm_arch_ptp_exit(); +} + +static int __init ptp_kvm_init(void) +{ + long ret; + + ret = kvm_arch_ptp_init(); + if (ret) { + if (ret != -EOPNOTSUPP) + pr_err("fail to initialize ptp_kvm"); + return ret; + } + + kvm_ptp_clock.caps = ptp_kvm_caps; + + kvm_ptp_clock.ptp_clock = ptp_clock_register(&kvm_ptp_clock.caps, NULL); + + return PTR_ERR_OR_ZERO(kvm_ptp_clock.ptp_clock); +} + +module_init(ptp_kvm_init); +module_exit(ptp_kvm_exit); + +MODULE_AUTHOR("Marcelo Tosatti "); +MODULE_DESCRIPTION("PTP clock using KVMCLOCK"); +MODULE_LICENSE("GPL"); diff --git a/drivers/ptp/ptp_kvm_x86.c b/drivers/ptp/ptp_kvm_x86.c new file mode 100644 index 000000000..6cea4fe39 --- /dev/null +++ b/drivers/ptp/ptp_kvm_x86.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Virtual PTP 1588 clock for use with KVM guests + * + * Copyright (C) 2017 Red Hat Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static phys_addr_t clock_pair_gpa; +static struct kvm_clock_pairing clock_pair_glbl; +static struct kvm_clock_pairing *clock_pair; + +int kvm_arch_ptp_init(void) +{ + struct page *p; + long ret; + + if (!kvm_para_available()) + return -EOPNOTSUPP; + + if (cc_platform_has(CC_ATTR_GUEST_MEM_ENCRYPT)) { + p = alloc_page(GFP_KERNEL | __GFP_ZERO); + if (!p) + return -ENOMEM; + + clock_pair = page_address(p); + ret = set_memory_decrypted((unsigned long)clock_pair, 1); + if (ret) { + __free_page(p); + clock_pair = NULL; + goto nofree; + } + } else { + clock_pair = &clock_pair_glbl; + } + + clock_pair_gpa = slow_virt_to_phys(clock_pair); + if (!pvclock_get_pvti_cpu0_va()) { + ret = -EOPNOTSUPP; + goto err; + } + + ret = kvm_hypercall2(KVM_HC_CLOCK_PAIRING, clock_pair_gpa, + KVM_CLOCK_PAIRING_WALLCLOCK); + if (ret == -KVM_ENOSYS) { + ret = -EOPNOTSUPP; + goto err; + } + + return ret; + +err: + kvm_arch_ptp_exit(); +nofree: + return ret; +} + +void kvm_arch_ptp_exit(void) +{ + if (cc_platform_has(CC_ATTR_GUEST_MEM_ENCRYPT)) { + WARN_ON(set_memory_encrypted((unsigned long)clock_pair, 1)); + free_page((unsigned long)clock_pair); + clock_pair = NULL; + } +} + +int kvm_arch_ptp_get_clock(struct timespec64 *ts) +{ + long ret; + + ret = kvm_hypercall2(KVM_HC_CLOCK_PAIRING, + clock_pair_gpa, + KVM_CLOCK_PAIRING_WALLCLOCK); + if (ret != 0) { + pr_err_ratelimited("clock offset hypercall ret %lu\n", ret); + return -EOPNOTSUPP; + } + + ts->tv_sec = clock_pair->sec; + ts->tv_nsec = clock_pair->nsec; + + return 0; +} + +int kvm_arch_ptp_get_crosststamp(u64 *cycle, struct timespec64 *tspec, + enum clocksource_ids *cs_id) +{ + struct pvclock_vcpu_time_info *src; + unsigned int version; + long ret; + + src = this_cpu_pvti(); + + do { + /* + * We are using a TSC value read in the hosts + * kvm_hc_clock_pairing handling. + * So any changes to tsc_to_system_mul + * and tsc_shift or any other pvclock + * data invalidate that measurement. + */ + version = pvclock_read_begin(src); + + ret = kvm_hypercall2(KVM_HC_CLOCK_PAIRING, + clock_pair_gpa, + KVM_CLOCK_PAIRING_WALLCLOCK); + if (ret != 0) { + pr_err_ratelimited("clock pairing hypercall ret %lu\n", ret); + return -EOPNOTSUPP; + } + tspec->tv_sec = clock_pair->sec; + tspec->tv_nsec = clock_pair->nsec; + *cycle = __pvclock_read_cycles(src, clock_pair->tsc); + } while (pvclock_read_retry(src, version)); + + *cs_id = CSID_X86_KVM_CLK; + + return 0; +} diff --git a/drivers/ptp/ptp_mock.c b/drivers/ptp/ptp_mock.c new file mode 100644 index 000000000..4d66b6147 --- /dev/null +++ b/drivers/ptp/ptp_mock.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2023 NXP + * + * Mock-up PTP Hardware Clock driver for virtual network devices + * + * Create a PTP clock which offers PTP time manipulation operations + * using a timecounter/cyclecounter on top of CLOCK_MONOTONIC_RAW. + */ + +#include +#include +#include + +/* Clamp scaled_ppm between -2,097,152,000 and 2,097,152,000, + * and thus "adj" between -68,719,476 and 68,719,476 + */ +#define MOCK_PHC_MAX_ADJ_PPB 32000000 +/* Timestamps from ktime_get_raw() have 1 ns resolution, so the scale factor + * (MULT >> SHIFT) needs to be 1. Pick SHIFT as 31 bits, which translates + * MULT(freq 0) into 0x80000000. + */ +#define MOCK_PHC_CC_SHIFT 31 +#define MOCK_PHC_CC_MULT (1 << MOCK_PHC_CC_SHIFT) +#define MOCK_PHC_FADJ_SHIFT 9 +#define MOCK_PHC_FADJ_DENOMINATOR 15625ULL + +/* The largest cycle_delta that timecounter_read_delta() can handle without a + * 64-bit overflow during the multiplication with cc->mult, given the max "adj" + * we permit, is ~8.3 seconds. Make sure readouts are more frequent than that. + */ +#define MOCK_PHC_REFRESH_INTERVAL (HZ * 5) + +#define info_to_phc(d) container_of((d), struct mock_phc, info) + +struct mock_phc { + struct ptp_clock_info info; + struct ptp_clock *clock; + struct timecounter tc; + struct cyclecounter cc; + spinlock_t lock; +}; + +static u64 mock_phc_cc_read(struct cyclecounter *cc) +{ + return ktime_get_raw_ns(); +} + +static int mock_phc_adjfine(struct ptp_clock_info *info, long scaled_ppm) +{ + struct mock_phc *phc = info_to_phc(info); + s64 adj; + + adj = (s64)scaled_ppm << MOCK_PHC_FADJ_SHIFT; + adj = div_s64(adj, MOCK_PHC_FADJ_DENOMINATOR); + + spin_lock(&phc->lock); + timecounter_read(&phc->tc); + phc->cc.mult = MOCK_PHC_CC_MULT + adj; + spin_unlock(&phc->lock); + + return 0; +} + +static int mock_phc_adjtime(struct ptp_clock_info *info, s64 delta) +{ + struct mock_phc *phc = info_to_phc(info); + + spin_lock(&phc->lock); + timecounter_adjtime(&phc->tc, delta); + spin_unlock(&phc->lock); + + return 0; +} + +static int mock_phc_settime64(struct ptp_clock_info *info, + const struct timespec64 *ts) +{ + struct mock_phc *phc = info_to_phc(info); + u64 ns = timespec64_to_ns(ts); + + spin_lock(&phc->lock); + timecounter_init(&phc->tc, &phc->cc, ns); + spin_unlock(&phc->lock); + + return 0; +} + +static int mock_phc_gettime64(struct ptp_clock_info *info, struct timespec64 *ts) +{ + struct mock_phc *phc = info_to_phc(info); + u64 ns; + + spin_lock(&phc->lock); + ns = timecounter_read(&phc->tc); + spin_unlock(&phc->lock); + + *ts = ns_to_timespec64(ns); + + return 0; +} + +static long mock_phc_refresh(struct ptp_clock_info *info) +{ + struct timespec64 ts; + + mock_phc_gettime64(info, &ts); + + return MOCK_PHC_REFRESH_INTERVAL; +} + +int mock_phc_index(struct mock_phc *phc) +{ + return ptp_clock_index(phc->clock); +} +EXPORT_SYMBOL_GPL(mock_phc_index); + +struct mock_phc *mock_phc_create(struct device *dev) +{ + struct mock_phc *phc; + int err; + + phc = kzalloc_obj(*phc); + if (!phc) { + err = -ENOMEM; + goto out; + } + + phc->info = (struct ptp_clock_info) { + .owner = THIS_MODULE, + .name = "Mock-up PTP clock", + .max_adj = MOCK_PHC_MAX_ADJ_PPB, + .adjfine = mock_phc_adjfine, + .adjtime = mock_phc_adjtime, + .gettime64 = mock_phc_gettime64, + .settime64 = mock_phc_settime64, + .do_aux_work = mock_phc_refresh, + }; + + phc->cc = (struct cyclecounter) { + .read = mock_phc_cc_read, + .mask = CYCLECOUNTER_MASK(64), + .mult = MOCK_PHC_CC_MULT, + .shift = MOCK_PHC_CC_SHIFT, + }; + + spin_lock_init(&phc->lock); + timecounter_init(&phc->tc, &phc->cc, 0); + + phc->clock = ptp_clock_register(&phc->info, dev); + if (IS_ERR(phc->clock)) { + err = PTR_ERR(phc->clock); + goto out_free_phc; + } + + ptp_schedule_worker(phc->clock, MOCK_PHC_REFRESH_INTERVAL); + + return phc; + +out_free_phc: + kfree(phc); +out: + return ERR_PTR(err); +} +EXPORT_SYMBOL_GPL(mock_phc_create); + +void mock_phc_destroy(struct mock_phc *phc) +{ + ptp_clock_unregister(phc->clock); + kfree(phc); +} +EXPORT_SYMBOL_GPL(mock_phc_destroy); + +MODULE_DESCRIPTION("Mock-up PTP Hardware Clock driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/ptp/ptp_netc.c b/drivers/ptp/ptp_netc.c new file mode 100644 index 000000000..59db08e18 --- /dev/null +++ b/drivers/ptp/ptp_netc.c @@ -0,0 +1,1056 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) +/* + * NXP NETC V4 Timer driver + * Copyright 2025 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 { + if (!pp->enabled) + goto unlock_spinlock; + + 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); + netc_timer_wr(priv, NETC_TMR_TEMASK, 0); + + /* 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); + + netc_timer_offset_write(priv, 0); + 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) { + spin_unlock(&priv->lock); + return IRQ_NONE; + } + + 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, IRQF_NO_AUTOEN, + 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; + + 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; + } + + enable_irq(priv->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); + + disable_irq(priv->irq); + ptp_clock_unregister(priv->clock); + netc_timer_wr(priv, NETC_TMR_TEMASK, 0); + netc_timer_wr(priv, NETC_TMR_CTRL, 0); + 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 new file mode 100644 index 000000000..3d26ec1f7 --- /dev/null +++ b/drivers/ptp/ptp_ocp.c @@ -0,0 +1,5282 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2020 Facebook */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PCI_DEVICE_ID_META_TIMECARD 0x0400 + +#define PCI_VENDOR_ID_CELESTICA 0x18d4 +#define PCI_DEVICE_ID_CELESTICA_TIMECARD 0x1008 + +#define PCI_VENDOR_ID_OROLIA 0x1ad7 +#define PCI_DEVICE_ID_OROLIA_ARTCARD 0xa000 + +#define PCI_VENDOR_ID_ADVA 0xad5a +#define PCI_DEVICE_ID_ADVA_TIMECARD 0x0400 +#define PCI_DEVICE_ID_ADVA_TIMECARD_X1 0x0410 + +static struct class timecard_class = { + .name = "timecard", +}; + +struct ocp_reg { + u32 ctrl; + u32 status; + u32 select; + u32 version; + u32 time_ns; + u32 time_sec; + u32 __pad0[2]; + u32 adjust_ns; + u32 adjust_sec; + u32 __pad1[2]; + u32 offset_ns; + u32 offset_window_ns; + u32 __pad2[2]; + u32 drift_ns; + u32 drift_window_ns; + u32 __pad3[6]; + u32 servo_offset_p; + u32 servo_offset_i; + u32 servo_drift_p; + u32 servo_drift_i; + u32 status_offset; + u32 status_drift; +}; + +struct ptp_ocp_servo_conf { + u32 servo_offset_p; + u32 servo_offset_i; + u32 servo_drift_p; + u32 servo_drift_i; +}; + +/* + * Combined servo + board-variant parameters for ADVA boards. + * Embedded in the resource table .extra so a single ptp_ocp_adva_board_init() + * can handle both ADVA and ADVA-X1 without per-variant init functions. + */ +struct ptp_ocp_adva_info { + struct ptp_ocp_servo_conf servo; + u32 flash_start; + const struct ocp_sma_op *sma_op; + u8 signals_nr; + u8 freq_in_nr; + const struct ocp_attr_group *attr_groups; +}; + +#define OCP_CTRL_ENABLE BIT(0) +#define OCP_CTRL_ADJUST_TIME BIT(1) +#define OCP_CTRL_ADJUST_OFFSET BIT(2) +#define OCP_CTRL_ADJUST_DRIFT BIT(3) +#define OCP_CTRL_ADJUST_SERVO BIT(8) +#define OCP_CTRL_READ_TIME_REQ BIT(30) +#define OCP_CTRL_READ_TIME_DONE BIT(31) + +#define OCP_STATUS_IN_SYNC BIT(0) +#define OCP_STATUS_IN_HOLDOVER BIT(1) + +#define OCP_SELECT_CLK_NONE 0 +#define OCP_SELECT_CLK_REG 0xfe + +struct tod_reg { + u32 ctrl; + u32 status; + u32 uart_polarity; + u32 version; + u32 adj_sec; + u32 __pad0[3]; + u32 uart_baud; + u32 __pad1[3]; + u32 utc_status; + u32 leap; +}; + +#define TOD_CTRL_PROTOCOL BIT(28) +#define TOD_CTRL_DISABLE_FMT_A BIT(17) +#define TOD_CTRL_DISABLE_FMT_B BIT(16) +#define TOD_CTRL_ENABLE BIT(0) +#define TOD_CTRL_GNSS_MASK GENMASK(3, 0) +#define TOD_CTRL_GNSS_SHIFT 24 + +#define TOD_STATUS_UTC_MASK GENMASK(7, 0) +#define TOD_STATUS_UTC_VALID BIT(8) +#define TOD_STATUS_LEAP_ANNOUNCE BIT(12) +#define TOD_STATUS_LEAP_VALID BIT(16) + +struct ts_reg { + u32 enable; + u32 error; + u32 polarity; + u32 version; + u32 __pad0[4]; + u32 cable_delay; + u32 __pad1[3]; + u32 intr; + u32 intr_mask; + u32 event_count; + u32 __pad2[1]; + u32 ts_count; + u32 time_ns; + u32 time_sec; + u32 data_width; + u32 data; +}; + +struct pps_reg { + u32 ctrl; + u32 status; + u32 __pad0[6]; + u32 cable_delay; +}; + +#define PPS_STATUS_FILTER_ERR BIT(0) +#define PPS_STATUS_SUPERV_ERR BIT(1) + +struct img_reg { + u32 version; +}; + +struct gpio_reg { + u32 gpio1; + u32 __pad0; + u32 gpio2; + u32 __pad1; +}; + +struct irig_master_reg { + u32 ctrl; + u32 status; + u32 __pad0; + u32 version; + u32 adj_sec; + u32 mode_ctrl; +}; + +#define IRIG_M_CTRL_ENABLE BIT(0) + +struct irig_slave_reg { + u32 ctrl; + u32 status; + u32 __pad0; + u32 version; + u32 adj_sec; + u32 mode_ctrl; +}; + +#define IRIG_S_CTRL_ENABLE BIT(0) + +struct dcf_master_reg { + u32 ctrl; + u32 status; + u32 __pad0; + u32 version; + u32 adj_sec; +}; + +#define DCF_M_CTRL_ENABLE BIT(0) + +struct dcf_slave_reg { + u32 ctrl; + u32 status; + u32 __pad0; + u32 version; + u32 adj_sec; +}; + +#define DCF_S_CTRL_ENABLE BIT(0) + +struct signal_reg { + u32 enable; + u32 status; + u32 polarity; + u32 version; + u32 __pad0[4]; + u32 cable_delay; + u32 __pad1[3]; + u32 intr; + u32 intr_mask; + u32 __pad2[2]; + u32 start_ns; + u32 start_sec; + u32 pulse_ns; + u32 pulse_sec; + u32 period_ns; + u32 period_sec; + u32 repeat_count; +}; + +struct frequency_reg { + u32 ctrl; + u32 status; +}; + +struct board_config_reg { + u32 mro50_serial_activate; +}; + +#define FREQ_STATUS_VALID BIT(31) +#define FREQ_STATUS_ERROR BIT(30) +#define FREQ_STATUS_OVERRUN BIT(29) +#define FREQ_STATUS_MASK GENMASK(23, 0) + +struct ptp_ocp_flash_info { + const char *name; + int pci_offset; + int data_size; + void *data; +}; + +struct ptp_ocp_firmware_header { + char magic[4]; + __be16 pci_vendor_id; + __be16 pci_device_id; + __be32 image_size; + __be16 hw_revision; + __be16 crc; +}; + +#define OCP_FIRMWARE_MAGIC_HEADER "OCPC" + +struct ptp_ocp_i2c_info { + const char *name; + unsigned long fixed_rate; + size_t data_size; + void *data; +}; + +struct ptp_ocp_ext_info { + int index; + irqreturn_t (*irq_fcn)(int irq, void *priv); + int (*enable)(void *priv, u32 req, bool enable); +}; + +struct ptp_ocp_ext_src { + void __iomem *mem; + struct ptp_ocp *bp; + struct ptp_ocp_ext_info *info; + int irq_vec; +}; + +enum ptp_ocp_sma_mode { + SMA_MODE_IN, + SMA_MODE_OUT, +}; + +static struct dpll_pin_frequency ptp_ocp_sma_freq[] = { + DPLL_PIN_FREQUENCY_1PPS, + DPLL_PIN_FREQUENCY_10MHZ, + DPLL_PIN_FREQUENCY_IRIG_B, + DPLL_PIN_FREQUENCY_DCF77, +}; + +struct ptp_ocp_sma_connector { + enum ptp_ocp_sma_mode mode; + bool fixed_fcn; + bool fixed_dir; + bool disabled; + u8 default_fcn; + struct dpll_pin *dpll_pin; + struct dpll_pin_properties dpll_prop; + dpll_tracker tracker; +}; + +struct ocp_attr_group { + u64 cap; + const struct attribute_group *group; +}; + +struct ocp_selector { + const char *name; + int value; + u64 frequency; +}; + +struct ocp_sma_op { + const struct ocp_selector *tbl[2]; + void (*init)(struct ptp_ocp *bp); + u32 (*get)(struct ptp_ocp *bp, int sma_nr); + int (*set_inputs)(struct ptp_ocp *bp, int sma_nr, u32 val); + int (*set_output)(struct ptp_ocp *bp, int sma_nr, u32 val); +}; + +#define OCP_CAP_BASIC BIT(0) +#define OCP_CAP_SIGNAL BIT(1) +#define OCP_CAP_FREQ BIT(2) + +struct ptp_ocp_signal { + ktime_t period; + ktime_t pulse; + ktime_t phase; + ktime_t start; + int duty; + bool polarity; + bool running; +}; + +struct ptp_ocp_serial_port { + int line; + int baud; +}; + +#define OCP_BOARD_ID_LEN 13 +#define OCP_SERIAL_LEN 6 +#define OCP_SMA_NUM 4 +#define OCP_SIGNAL_NUM 4 +#define OCP_FREQ_NUM 4 + +enum { + PORT_GNSS, + PORT_GNSS2, + PORT_MAC, /* miniature atomic clock */ + PORT_NMEA, + + __PORT_COUNT, +}; + +struct ptp_ocp { + struct pci_dev *pdev; + struct device dev; + spinlock_t lock; + struct ocp_reg __iomem *reg; + struct tod_reg __iomem *tod; + struct pps_reg __iomem *pps_to_ext; + struct pps_reg __iomem *pps_to_clk; + struct board_config_reg __iomem *board_config; + struct gpio_reg __iomem *pps_select; + struct gpio_reg __iomem *sma_map1; + struct gpio_reg __iomem *sma_map2; + struct irig_master_reg __iomem *irig_out; + struct irig_slave_reg __iomem *irig_in; + struct dcf_master_reg __iomem *dcf_out; + struct dcf_slave_reg __iomem *dcf_in; + struct tod_reg __iomem *nmea_out; + struct frequency_reg __iomem *freq_in[OCP_FREQ_NUM]; + struct ptp_ocp_ext_src *signal_out[OCP_SIGNAL_NUM]; + struct ptp_ocp_ext_src *pps; + struct ptp_ocp_ext_src *ts0; + struct ptp_ocp_ext_src *ts1; + struct ptp_ocp_ext_src *ts2; + struct ptp_ocp_ext_src *ts3; + struct ptp_ocp_ext_src *ts4; + struct ocp_art_gpio_reg __iomem *art_sma; + struct img_reg __iomem *image; + struct ptp_clock *ptp; + struct ptp_clock_info ptp_info; + struct platform_device *i2c_ctrl; + struct platform_device *spi_flash; + struct clk_hw *i2c_clk; + struct timer_list watchdog; + const struct attribute_group **attr_group; + const struct ptp_ocp_eeprom_map *eeprom_map; + struct dentry *debug_root; + bool sync; + time64_t gnss_lost; + struct delayed_work sync_work; + int id; + int n_irqs; + struct ptp_ocp_serial_port port[__PORT_COUNT]; + bool fw_loader; + u8 fw_tag; + u16 fw_version; + u8 board_id[OCP_BOARD_ID_LEN]; + u8 serial[OCP_SERIAL_LEN]; + bool has_eeprom_data; + u32 pps_req_map; + int flash_start; + u32 utc_tai_offset; + u32 ts_window_adjust; + u64 fw_cap; + struct ptp_ocp_signal signal[OCP_SIGNAL_NUM]; + struct ptp_ocp_sma_connector sma[OCP_SMA_NUM]; + const struct ocp_sma_op *sma_op; + struct dpll_device *dpll; + dpll_tracker tracker; + int signals_nr; + int freq_in_nr; +}; + +#define OCP_REQ_TIMESTAMP BIT(0) +#define OCP_REQ_PPS BIT(1) + +struct ocp_resource { + unsigned long offset; + int size; + int irq_vec; + int (*setup)(struct ptp_ocp *bp, struct ocp_resource *r); + void *extra; + unsigned long bp_offset; + const char * const name; +}; + +static int ptp_ocp_register_mem(struct ptp_ocp *bp, struct ocp_resource *r); +static int ptp_ocp_register_i2c(struct ptp_ocp *bp, struct ocp_resource *r); +static int ptp_ocp_register_spi(struct ptp_ocp *bp, struct ocp_resource *r); +static int ptp_ocp_register_serial(struct ptp_ocp *bp, struct ocp_resource *r); +static int ptp_ocp_register_ext(struct ptp_ocp *bp, struct ocp_resource *r); +static int ptp_ocp_fb_board_init(struct ptp_ocp *bp, struct ocp_resource *r); +static irqreturn_t ptp_ocp_ts_irq(int irq, void *priv); +static irqreturn_t ptp_ocp_signal_irq(int irq, void *priv); +static int ptp_ocp_ts_enable(void *priv, u32 req, bool enable); +static int ptp_ocp_signal_from_perout(struct ptp_ocp *bp, int gen, + struct ptp_perout_request *req); +static int ptp_ocp_signal_enable(void *priv, u32 req, bool enable); +static int ptp_ocp_sma_store(struct ptp_ocp *bp, const char *buf, int sma_nr); + +static int ptp_ocp_art_board_init(struct ptp_ocp *bp, struct ocp_resource *r); + +static int ptp_ocp_adva_board_init(struct ptp_ocp *bp, struct ocp_resource *r); + +static const struct ocp_sma_op ocp_adva_sma_op; +static const struct ocp_sma_op ocp_adva_x1_sma_op; + +static const struct ocp_attr_group fb_timecard_groups[]; + +static const struct ocp_attr_group art_timecard_groups[]; + +static const struct ocp_attr_group adva_timecard_groups[]; + +static const struct ocp_attr_group adva_timecard_x1_groups[]; + +struct ptp_ocp_eeprom_map { + u16 off; + u16 len; + u32 bp_offset; + const void * const tag; +}; + +#define EEPROM_ENTRY(addr, member) \ + .off = addr, \ + .len = sizeof_field(struct ptp_ocp, member), \ + .bp_offset = offsetof(struct ptp_ocp, member) + +#define BP_MAP_ENTRY_ADDR(bp, map) ({ \ + (void *)((uintptr_t)(bp) + (map)->bp_offset); \ +}) + +static struct ptp_ocp_eeprom_map fb_eeprom_map[] = { + { EEPROM_ENTRY(0x43, board_id) }, + { EEPROM_ENTRY(0x00, serial), .tag = "mac" }, + { } +}; + +static struct ptp_ocp_eeprom_map art_eeprom_map[] = { + { EEPROM_ENTRY(0x200 + 0x43, board_id) }, + { EEPROM_ENTRY(0x200 + 0x63, serial) }, + { } +}; + +#define bp_assign_entry(bp, res, val) ({ \ + uintptr_t addr = (uintptr_t)(bp) + (res)->bp_offset; \ + *(typeof(val) *)addr = val; \ +}) + +#define OCP_RES_LOCATION(member) \ + .name = #member, .bp_offset = offsetof(struct ptp_ocp, member) + +#define OCP_MEM_RESOURCE(member) \ + OCP_RES_LOCATION(member), .setup = ptp_ocp_register_mem + +#define OCP_SERIAL_RESOURCE(member) \ + OCP_RES_LOCATION(member), .setup = ptp_ocp_register_serial + +#define OCP_I2C_RESOURCE(member) \ + OCP_RES_LOCATION(member), .setup = ptp_ocp_register_i2c + +#define OCP_SPI_RESOURCE(member) \ + OCP_RES_LOCATION(member), .setup = ptp_ocp_register_spi + +#define OCP_EXT_RESOURCE(member) \ + OCP_RES_LOCATION(member), .setup = ptp_ocp_register_ext + +/* This is the MSI vector mapping used. + * 0: PPS (TS5) + * 1: TS0 + * 2: TS1 + * 3: GNSS1 + * 4: GNSS2 + * 5: MAC + * 6: TS2 + * 7: I2C controller + * 8: HWICAP (notused) + * 9: SPI Flash + * 10: NMEA + * 11: Signal Generator 1 + * 12: Signal Generator 2 + * 13: Signal Generator 3 + * 14: Signal Generator 4 + * 15: TS3 + * 16: TS4 + -- + * 8: Orolia TS1 + * 10: Orolia TS2 + * 11: Orolia TS0 (GNSS) + * 12: Orolia PPS + * 14: Orolia TS3 + * 15: Orolia TS4 + */ + +static struct ocp_resource ocp_fb_resource[] = { + { + OCP_MEM_RESOURCE(reg), + .offset = 0x01000000, .size = 0x10000, + }, + { + OCP_EXT_RESOURCE(ts0), + .offset = 0x01010000, .size = 0x10000, .irq_vec = 1, + .extra = &(struct ptp_ocp_ext_info) { + .index = 0, + .irq_fcn = ptp_ocp_ts_irq, + .enable = ptp_ocp_ts_enable, + }, + }, + { + OCP_EXT_RESOURCE(ts1), + .offset = 0x01020000, .size = 0x10000, .irq_vec = 2, + .extra = &(struct ptp_ocp_ext_info) { + .index = 1, + .irq_fcn = ptp_ocp_ts_irq, + .enable = ptp_ocp_ts_enable, + }, + }, + { + OCP_EXT_RESOURCE(ts2), + .offset = 0x01060000, .size = 0x10000, .irq_vec = 6, + .extra = &(struct ptp_ocp_ext_info) { + .index = 2, + .irq_fcn = ptp_ocp_ts_irq, + .enable = ptp_ocp_ts_enable, + }, + }, + { + OCP_EXT_RESOURCE(ts3), + .offset = 0x01110000, .size = 0x10000, .irq_vec = 15, + .extra = &(struct ptp_ocp_ext_info) { + .index = 3, + .irq_fcn = ptp_ocp_ts_irq, + .enable = ptp_ocp_ts_enable, + }, + }, + { + OCP_EXT_RESOURCE(ts4), + .offset = 0x01120000, .size = 0x10000, .irq_vec = 16, + .extra = &(struct ptp_ocp_ext_info) { + .index = 4, + .irq_fcn = ptp_ocp_ts_irq, + .enable = ptp_ocp_ts_enable, + }, + }, + /* Timestamp for PHC and/or PPS generator */ + { + OCP_EXT_RESOURCE(pps), + .offset = 0x010C0000, .size = 0x10000, .irq_vec = 0, + .extra = &(struct ptp_ocp_ext_info) { + .index = 5, + .irq_fcn = ptp_ocp_ts_irq, + .enable = ptp_ocp_ts_enable, + }, + }, + { + OCP_EXT_RESOURCE(signal_out[0]), + .offset = 0x010D0000, .size = 0x10000, .irq_vec = 11, + .extra = &(struct ptp_ocp_ext_info) { + .index = 1, + .irq_fcn = ptp_ocp_signal_irq, + .enable = ptp_ocp_signal_enable, + }, + }, + { + OCP_EXT_RESOURCE(signal_out[1]), + .offset = 0x010E0000, .size = 0x10000, .irq_vec = 12, + .extra = &(struct ptp_ocp_ext_info) { + .index = 2, + .irq_fcn = ptp_ocp_signal_irq, + .enable = ptp_ocp_signal_enable, + }, + }, + { + OCP_EXT_RESOURCE(signal_out[2]), + .offset = 0x010F0000, .size = 0x10000, .irq_vec = 13, + .extra = &(struct ptp_ocp_ext_info) { + .index = 3, + .irq_fcn = ptp_ocp_signal_irq, + .enable = ptp_ocp_signal_enable, + }, + }, + { + OCP_EXT_RESOURCE(signal_out[3]), + .offset = 0x01100000, .size = 0x10000, .irq_vec = 14, + .extra = &(struct ptp_ocp_ext_info) { + .index = 4, + .irq_fcn = ptp_ocp_signal_irq, + .enable = ptp_ocp_signal_enable, + }, + }, + { + OCP_MEM_RESOURCE(pps_to_ext), + .offset = 0x01030000, .size = 0x10000, + }, + { + OCP_MEM_RESOURCE(pps_to_clk), + .offset = 0x01040000, .size = 0x10000, + }, + { + OCP_MEM_RESOURCE(tod), + .offset = 0x01050000, .size = 0x10000, + }, + { + OCP_MEM_RESOURCE(irig_in), + .offset = 0x01070000, .size = 0x10000, + }, + { + OCP_MEM_RESOURCE(irig_out), + .offset = 0x01080000, .size = 0x10000, + }, + { + OCP_MEM_RESOURCE(dcf_in), + .offset = 0x01090000, .size = 0x10000, + }, + { + OCP_MEM_RESOURCE(dcf_out), + .offset = 0x010A0000, .size = 0x10000, + }, + { + OCP_MEM_RESOURCE(nmea_out), + .offset = 0x010B0000, .size = 0x10000, + }, + { + OCP_MEM_RESOURCE(image), + .offset = 0x00020000, .size = 0x1000, + }, + { + OCP_MEM_RESOURCE(pps_select), + .offset = 0x00130000, .size = 0x1000, + }, + { + OCP_MEM_RESOURCE(sma_map1), + .offset = 0x00140000, .size = 0x1000, + }, + { + OCP_MEM_RESOURCE(sma_map2), + .offset = 0x00220000, .size = 0x1000, + }, + { + OCP_I2C_RESOURCE(i2c_ctrl), + .offset = 0x00150000, .size = 0x10000, .irq_vec = 7, + .extra = &(struct ptp_ocp_i2c_info) { + .name = "xiic-i2c", + .fixed_rate = 50000000, + .data_size = sizeof(struct xiic_i2c_platform_data), + .data = &(struct xiic_i2c_platform_data) { + .num_devices = 2, + .devices = (struct i2c_board_info[]) { + { I2C_BOARD_INFO("24c02", 0x50) }, + { I2C_BOARD_INFO("24mac402", 0x58), + .platform_data = "mac" }, + }, + }, + }, + }, + { + OCP_SERIAL_RESOURCE(port[PORT_GNSS]), + .offset = 0x00160000 + 0x1000, .irq_vec = 3, + .extra = &(struct ptp_ocp_serial_port) { + .baud = 115200, + }, + }, + { + OCP_SERIAL_RESOURCE(port[PORT_GNSS2]), + .offset = 0x00170000 + 0x1000, .irq_vec = 4, + .extra = &(struct ptp_ocp_serial_port) { + .baud = 115200, + }, + }, + { + OCP_SERIAL_RESOURCE(port[PORT_MAC]), + .offset = 0x00180000 + 0x1000, .irq_vec = 5, + .extra = &(struct ptp_ocp_serial_port) { + .baud = 57600, + }, + }, + { + OCP_SERIAL_RESOURCE(port[PORT_NMEA]), + .offset = 0x00190000 + 0x1000, .irq_vec = 10, + }, + { + OCP_SPI_RESOURCE(spi_flash), + .offset = 0x00310000, .size = 0x10000, .irq_vec = 9, + .extra = &(struct ptp_ocp_flash_info) { + .name = "xilinx_spi", .pci_offset = 0, + .data_size = sizeof(struct xspi_platform_data), + .data = &(struct xspi_platform_data) { + .num_chipselect = 1, + .bits_per_word = 8, + .num_devices = 1, + .force_irq = true, + .devices = &(struct spi_board_info) { + .modalias = "spi-nor", + }, + }, + }, + }, + { + OCP_MEM_RESOURCE(freq_in[0]), + .offset = 0x01200000, .size = 0x10000, + }, + { + OCP_MEM_RESOURCE(freq_in[1]), + .offset = 0x01210000, .size = 0x10000, + }, + { + OCP_MEM_RESOURCE(freq_in[2]), + .offset = 0x01220000, .size = 0x10000, + }, + { + OCP_MEM_RESOURCE(freq_in[3]), + .offset = 0x01230000, .size = 0x10000, + }, + { + .setup = ptp_ocp_fb_board_init, + .extra = &(struct ptp_ocp_servo_conf) { + .servo_offset_p = 0x2000, + .servo_offset_i = 0x1000, + .servo_drift_p = 0, + .servo_drift_i = 0, + }, + }, + { } +}; + +#define OCP_ART_CONFIG_SIZE 144 +#define OCP_ART_TEMP_TABLE_SIZE 368 + +struct ocp_art_gpio_reg { + struct { + u32 gpio; + u32 __pad[3]; + } map[4]; +}; + +static struct ocp_resource ocp_art_resource[] = { + { + OCP_MEM_RESOURCE(reg), + .offset = 0x01000000, .size = 0x10000, + }, + { + OCP_SERIAL_RESOURCE(port[PORT_GNSS]), + .offset = 0x00160000 + 0x1000, .irq_vec = 3, + .extra = &(struct ptp_ocp_serial_port) { + .baud = 115200, + }, + }, + { + OCP_MEM_RESOURCE(art_sma), + .offset = 0x003C0000, .size = 0x1000, + }, + /* Timestamp associated with GNSS1 receiver PPS */ + { + OCP_EXT_RESOURCE(ts0), + .offset = 0x360000, .size = 0x20, .irq_vec = 12, + .extra = &(struct ptp_ocp_ext_info) { + .index = 0, + .irq_fcn = ptp_ocp_ts_irq, + .enable = ptp_ocp_ts_enable, + }, + }, + { + OCP_EXT_RESOURCE(ts1), + .offset = 0x380000, .size = 0x20, .irq_vec = 8, + .extra = &(struct ptp_ocp_ext_info) { + .index = 1, + .irq_fcn = ptp_ocp_ts_irq, + .enable = ptp_ocp_ts_enable, + }, + }, + { + OCP_EXT_RESOURCE(ts2), + .offset = 0x390000, .size = 0x20, .irq_vec = 10, + .extra = &(struct ptp_ocp_ext_info) { + .index = 2, + .irq_fcn = ptp_ocp_ts_irq, + .enable = ptp_ocp_ts_enable, + }, + }, + { + OCP_EXT_RESOURCE(ts3), + .offset = 0x3A0000, .size = 0x20, .irq_vec = 14, + .extra = &(struct ptp_ocp_ext_info) { + .index = 3, + .irq_fcn = ptp_ocp_ts_irq, + .enable = ptp_ocp_ts_enable, + }, + }, + { + OCP_EXT_RESOURCE(ts4), + .offset = 0x3B0000, .size = 0x20, .irq_vec = 15, + .extra = &(struct ptp_ocp_ext_info) { + .index = 4, + .irq_fcn = ptp_ocp_ts_irq, + .enable = ptp_ocp_ts_enable, + }, + }, + /* Timestamp associated with Internal PPS of the card */ + { + OCP_EXT_RESOURCE(pps), + .offset = 0x00330000, .size = 0x20, .irq_vec = 11, + .extra = &(struct ptp_ocp_ext_info) { + .index = 5, + .irq_fcn = ptp_ocp_ts_irq, + .enable = ptp_ocp_ts_enable, + }, + }, + { + OCP_SPI_RESOURCE(spi_flash), + .offset = 0x00310000, .size = 0x10000, .irq_vec = 9, + .extra = &(struct ptp_ocp_flash_info) { + .name = "spi_altera", .pci_offset = 0, + .data_size = sizeof(struct altera_spi_platform_data), + .data = &(struct altera_spi_platform_data) { + .num_chipselect = 1, + .num_devices = 1, + .devices = &(struct spi_board_info) { + .modalias = "spi-nor", + }, + }, + }, + }, + { + OCP_I2C_RESOURCE(i2c_ctrl), + .offset = 0x350000, .size = 0x100, .irq_vec = 4, + .extra = &(struct ptp_ocp_i2c_info) { + .name = "ocores-i2c", + .fixed_rate = 400000, + .data_size = sizeof(struct ocores_i2c_platform_data), + .data = &(struct ocores_i2c_platform_data) { + .clock_khz = 125000, + .bus_khz = 400, + .num_devices = 1, + .devices = &(struct i2c_board_info) { + I2C_BOARD_INFO("24c08", 0x50), + }, + }, + }, + }, + { + OCP_SERIAL_RESOURCE(port[PORT_MAC]), + .offset = 0x00190000, .irq_vec = 7, + .extra = &(struct ptp_ocp_serial_port) { + .baud = 9600, + }, + }, + { + OCP_MEM_RESOURCE(board_config), + .offset = 0x210000, .size = 0x1000, + }, + { + .setup = ptp_ocp_art_board_init, + .extra = &(struct ptp_ocp_servo_conf) { + .servo_offset_p = 0x2000, + .servo_offset_i = 0x1000, + .servo_drift_p = 0, + .servo_drift_i = 0, + }, + }, + { } +}; + +static struct ocp_resource ocp_adva_resource[] = { + { + OCP_MEM_RESOURCE(reg), + .offset = 0x01000000, .size = 0x10000, + }, + { + OCP_EXT_RESOURCE(ts0), + .offset = 0x01010000, .size = 0x10000, .irq_vec = 1, + .extra = &(struct ptp_ocp_ext_info) { + .index = 0, + .irq_fcn = ptp_ocp_ts_irq, + .enable = ptp_ocp_ts_enable, + }, + }, + { + OCP_EXT_RESOURCE(ts1), + .offset = 0x01020000, .size = 0x10000, .irq_vec = 2, + .extra = &(struct ptp_ocp_ext_info) { + .index = 1, + .irq_fcn = ptp_ocp_ts_irq, + .enable = ptp_ocp_ts_enable, + }, + }, + { + OCP_EXT_RESOURCE(ts2), + .offset = 0x01060000, .size = 0x10000, .irq_vec = 6, + .extra = &(struct ptp_ocp_ext_info) { + .index = 2, + .irq_fcn = ptp_ocp_ts_irq, + .enable = ptp_ocp_ts_enable, + }, + }, + /* Timestamp for PHC and/or PPS generator */ + { + OCP_EXT_RESOURCE(pps), + .offset = 0x010C0000, .size = 0x10000, .irq_vec = 0, + .extra = &(struct ptp_ocp_ext_info) { + .index = 5, + .irq_fcn = ptp_ocp_ts_irq, + .enable = ptp_ocp_ts_enable, + }, + }, + { + OCP_EXT_RESOURCE(signal_out[0]), + .offset = 0x010D0000, .size = 0x10000, .irq_vec = 11, + .extra = &(struct ptp_ocp_ext_info) { + .index = 1, + .irq_fcn = ptp_ocp_signal_irq, + .enable = ptp_ocp_signal_enable, + }, + }, + { + OCP_EXT_RESOURCE(signal_out[1]), + .offset = 0x010E0000, .size = 0x10000, .irq_vec = 12, + .extra = &(struct ptp_ocp_ext_info) { + .index = 2, + .irq_fcn = ptp_ocp_signal_irq, + .enable = ptp_ocp_signal_enable, + }, + }, + { + OCP_MEM_RESOURCE(pps_to_ext), + .offset = 0x01030000, .size = 0x10000, + }, + { + OCP_MEM_RESOURCE(pps_to_clk), + .offset = 0x01040000, .size = 0x10000, + }, + { + OCP_MEM_RESOURCE(tod), + .offset = 0x01050000, .size = 0x10000, + }, + { + OCP_MEM_RESOURCE(image), + .offset = 0x00020000, .size = 0x1000, + }, + { + OCP_MEM_RESOURCE(pps_select), + .offset = 0x00130000, .size = 0x1000, + }, + { + OCP_MEM_RESOURCE(sma_map1), + .offset = 0x00140000, .size = 0x1000, + }, + { + OCP_MEM_RESOURCE(sma_map2), + .offset = 0x00220000, .size = 0x1000, + }, + { + OCP_SERIAL_RESOURCE(port[PORT_GNSS]), + .offset = 0x00160000 + 0x1000, .irq_vec = 3, + .extra = &(struct ptp_ocp_serial_port) { + .baud = 9600, + }, + }, + { + OCP_SERIAL_RESOURCE(port[PORT_MAC]), + .offset = 0x00180000 + 0x1000, .irq_vec = 5, + .extra = &(struct ptp_ocp_serial_port) { + .baud = 115200, + }, + }, + { + OCP_MEM_RESOURCE(freq_in[0]), + .offset = 0x01200000, .size = 0x10000, + }, + { + OCP_MEM_RESOURCE(freq_in[1]), + .offset = 0x01210000, .size = 0x10000, + }, + { + OCP_SPI_RESOURCE(spi_flash), + .offset = 0x00310400, .size = 0x10000, .irq_vec = 9, + .extra = &(struct ptp_ocp_flash_info) { + .name = "spi_altera", .pci_offset = 0, + .data_size = sizeof(struct altera_spi_platform_data), + .data = &(struct altera_spi_platform_data) { + .num_chipselect = 1, + .num_devices = 1, + .devices = &(struct spi_board_info) { + .modalias = "spi-nor", + }, + }, + }, + }, + { + OCP_I2C_RESOURCE(i2c_ctrl), + .offset = 0x150000, .size = 0x100, .irq_vec = 7, + .extra = &(struct ptp_ocp_i2c_info) { + .name = "ocores-i2c", + .fixed_rate = 50000000, + .data_size = sizeof(struct ocores_i2c_platform_data), + .data = &(struct ocores_i2c_platform_data) { + .clock_khz = 50000, + .bus_khz = 100, + .reg_io_width = 4, // 32-bit/4-byte + .reg_shift = 2, // 32-bit addressing + .num_devices = 2, + .devices = (struct i2c_board_info[]) { + { I2C_BOARD_INFO("24c02", 0x50) }, + { I2C_BOARD_INFO("24mac402", 0x58), + .platform_data = "mac" }, + }, + }, + }, + }, + { + .setup = ptp_ocp_adva_board_init, + .extra = &(struct ptp_ocp_adva_info) { + .servo = { + .servo_offset_p = 0xc000, + .servo_offset_i = 0x1000, + .servo_drift_p = 0, + .servo_drift_i = 0, + }, + .flash_start = 0xA00000, + .sma_op = &ocp_adva_sma_op, + .signals_nr = 2, + .freq_in_nr = 2, + .attr_groups = adva_timecard_groups, + }, + }, + { } +}; + +static struct ocp_resource ocp_adva_x1_resource[] = { + { + OCP_MEM_RESOURCE(reg), + .offset = 0x01000000, .size = 0x10000, + }, + { + OCP_EXT_RESOURCE(ts0), + .offset = 0x01010000, .size = 0x10000, .irq_vec = 1, + .extra = &(struct ptp_ocp_ext_info) { + .index = 0, + .irq_fcn = ptp_ocp_ts_irq, + .enable = ptp_ocp_ts_enable, + }, + }, + { + OCP_EXT_RESOURCE(ts1), + .offset = 0x01020000, .size = 0x10000, .irq_vec = 2, + .extra = &(struct ptp_ocp_ext_info) { + .index = 1, + .irq_fcn = ptp_ocp_ts_irq, + .enable = ptp_ocp_ts_enable, + }, + }, + { + OCP_EXT_RESOURCE(ts2), + .offset = 0x01060000, .size = 0x10000, .irq_vec = 6, + .extra = &(struct ptp_ocp_ext_info) { + .index = 2, + .irq_fcn = ptp_ocp_ts_irq, + .enable = ptp_ocp_ts_enable, + }, + }, + { + OCP_EXT_RESOURCE(ts3), + .offset = 0x01110000, .size = 0x10000, .irq_vec = 15, + .extra = &(struct ptp_ocp_ext_info) { + .index = 3, + .irq_fcn = ptp_ocp_ts_irq, + .enable = ptp_ocp_ts_enable, + }, + }, + { + OCP_EXT_RESOURCE(ts4), + .offset = 0x01120000, .size = 0x10000, .irq_vec = 16, + .extra = &(struct ptp_ocp_ext_info) { + .index = 4, + .irq_fcn = ptp_ocp_ts_irq, + .enable = ptp_ocp_ts_enable, + }, + }, + /* Timestamp for PHC and/or PPS generator */ + { + OCP_EXT_RESOURCE(pps), + .offset = 0x010C0000, .size = 0x10000, .irq_vec = 0, + .extra = &(struct ptp_ocp_ext_info) { + .index = 5, + .irq_fcn = ptp_ocp_ts_irq, + .enable = ptp_ocp_ts_enable, + }, + }, + { + OCP_EXT_RESOURCE(signal_out[0]), + .offset = 0x010D0000, .size = 0x10000, .irq_vec = 11, + .extra = &(struct ptp_ocp_ext_info) { + .index = 1, + .irq_fcn = ptp_ocp_signal_irq, + .enable = ptp_ocp_signal_enable, + }, + }, + { + OCP_EXT_RESOURCE(signal_out[1]), + .offset = 0x010E0000, .size = 0x10000, .irq_vec = 12, + .extra = &(struct ptp_ocp_ext_info) { + .index = 2, + .irq_fcn = ptp_ocp_signal_irq, + .enable = ptp_ocp_signal_enable, + }, + }, + { + OCP_EXT_RESOURCE(signal_out[2]), + .offset = 0x010F0000, .size = 0x10000, .irq_vec = 13, + .extra = &(struct ptp_ocp_ext_info) { + .index = 3, + .irq_fcn = ptp_ocp_signal_irq, + .enable = ptp_ocp_signal_enable, + }, + }, + { + OCP_EXT_RESOURCE(signal_out[3]), + .offset = 0x01100000, .size = 0x10000, .irq_vec = 14, + .extra = &(struct ptp_ocp_ext_info) { + .index = 4, + .irq_fcn = ptp_ocp_signal_irq, + .enable = ptp_ocp_signal_enable, + }, + }, + { + OCP_MEM_RESOURCE(pps_to_ext), + .offset = 0x01030000, .size = 0x10000, + }, + { + OCP_MEM_RESOURCE(pps_to_clk), + .offset = 0x01040000, .size = 0x10000, + }, + { + OCP_MEM_RESOURCE(tod), + .offset = 0x01050000, .size = 0x10000, + }, + { + OCP_MEM_RESOURCE(image), + .offset = 0x00020000, .size = 0x1000, + }, + { + OCP_MEM_RESOURCE(pps_select), + .offset = 0x00130000, .size = 0x1000, + }, + { + OCP_MEM_RESOURCE(sma_map1), + .offset = 0x00140000, .size = 0x1000, + }, + { + OCP_MEM_RESOURCE(sma_map2), + .offset = 0x00220000, .size = 0x1000, + }, + { + OCP_SERIAL_RESOURCE(port[PORT_GNSS]), + .offset = 0x00160000 + 0x1000, .irq_vec = 3, + .extra = &(struct ptp_ocp_serial_port) { + .baud = 9600, + }, + }, + { + OCP_SERIAL_RESOURCE(port[PORT_MAC]), + .offset = 0x00180000 + 0x1000, .irq_vec = 5, + .extra = &(struct ptp_ocp_serial_port) { + .baud = 115200, + }, + }, + { + OCP_MEM_RESOURCE(freq_in[0]), + .offset = 0x01200000, .size = 0x10000, + }, + { + OCP_MEM_RESOURCE(freq_in[1]), + .offset = 0x01210000, .size = 0x10000, + }, + { + OCP_MEM_RESOURCE(freq_in[2]), + .offset = 0x01220000, .size = 0x10000, + }, + { + OCP_MEM_RESOURCE(freq_in[3]), + .offset = 0x01230000, .size = 0x10000, + }, + { + OCP_SPI_RESOURCE(spi_flash), + .offset = 0x00310000, .size = 0x10000, .irq_vec = 9, + .extra = &(struct ptp_ocp_flash_info) { + .name = "xilinx_spi", .pci_offset = 0, + .data_size = sizeof(struct xspi_platform_data), + .data = &(struct xspi_platform_data) { + .num_chipselect = 1, + .bits_per_word = 8, + .num_devices = 1, + .force_irq = true, + .devices = &(struct spi_board_info) { + .modalias = "spi-nor", + }, + }, + }, + }, + { + OCP_I2C_RESOURCE(i2c_ctrl), + .offset = 0x00150000, .size = 0x10000, .irq_vec = 7, + .extra = &(struct ptp_ocp_i2c_info) { + .name = "xiic-i2c", + .fixed_rate = 50000000, + .data_size = sizeof(struct xiic_i2c_platform_data), + .data = &(struct xiic_i2c_platform_data) { + .num_devices = 2, + .devices = (struct i2c_board_info[]) { + { I2C_BOARD_INFO("24c02", 0x50) }, + { I2C_BOARD_INFO("24mac402", 0x58), + .platform_data = "mac" }, + }, + }, + }, + }, + { + .setup = ptp_ocp_adva_board_init, + .extra = &(struct ptp_ocp_adva_info) { + .servo = { + .servo_offset_p = 0xc000, + .servo_offset_i = 0x1000, + .servo_drift_p = 0, + .servo_drift_i = 0, + }, + .flash_start = 0x1000000, + .sma_op = &ocp_adva_x1_sma_op, + .signals_nr = 4, + .freq_in_nr = 4, + .attr_groups = adva_timecard_x1_groups, + }, + }, + { } +}; + +static const struct pci_device_id ptp_ocp_pcidev_id[] = { + { PCI_DEVICE_DATA(META, TIMECARD, &ocp_fb_resource) }, + { PCI_DEVICE_DATA(CELESTICA, TIMECARD, &ocp_fb_resource) }, + { PCI_DEVICE_DATA(OROLIA, ARTCARD, &ocp_art_resource) }, + { PCI_DEVICE_DATA(ADVA, TIMECARD, &ocp_adva_resource) }, + { PCI_DEVICE_DATA(ADVA, TIMECARD_X1, &ocp_adva_x1_resource) }, + { } +}; +MODULE_DEVICE_TABLE(pci, ptp_ocp_pcidev_id); + +static DEFINE_MUTEX(ptp_ocp_lock); +static DEFINE_IDR(ptp_ocp_idr); + +static const struct ocp_selector ptp_ocp_clock[] = { + { .name = "NONE", .value = 0 }, + { .name = "TOD", .value = 1 }, + { .name = "IRIG", .value = 2 }, + { .name = "PPS", .value = 3 }, + { .name = "PTP", .value = 4 }, + { .name = "RTC", .value = 5 }, + { .name = "DCF", .value = 6 }, + { .name = "REGS", .value = 0xfe }, + { .name = "EXT", .value = 0xff }, + { } +}; + +#define SMA_DISABLE BIT(16) +#define SMA_ENABLE BIT(15) +#define SMA_SELECT_MASK GENMASK(14, 0) + +static const struct ocp_selector ptp_ocp_sma_in[] = { + { .name = "10Mhz", .value = 0x0000, .frequency = 10000000 }, + { .name = "PPS1", .value = 0x0001, .frequency = 1 }, + { .name = "PPS2", .value = 0x0002, .frequency = 1 }, + { .name = "TS1", .value = 0x0004, .frequency = 0 }, + { .name = "TS2", .value = 0x0008, .frequency = 0 }, + { .name = "IRIG", .value = 0x0010, .frequency = 10000 }, + { .name = "DCF", .value = 0x0020, .frequency = 77500 }, + { .name = "TS3", .value = 0x0040, .frequency = 0 }, + { .name = "TS4", .value = 0x0080, .frequency = 0 }, + { .name = "FREQ1", .value = 0x0100, .frequency = 0 }, + { .name = "FREQ2", .value = 0x0200, .frequency = 0 }, + { .name = "FREQ3", .value = 0x0400, .frequency = 0 }, + { .name = "FREQ4", .value = 0x0800, .frequency = 0 }, + { .name = "None", .value = SMA_DISABLE, .frequency = 0 }, + { } +}; + +static const struct ocp_selector ptp_ocp_sma_out[] = { + { .name = "10Mhz", .value = 0x0000, .frequency = 10000000 }, + { .name = "PHC", .value = 0x0001, .frequency = 1 }, + { .name = "MAC", .value = 0x0002, .frequency = 1 }, + { .name = "GNSS1", .value = 0x0004, .frequency = 1 }, + { .name = "GNSS2", .value = 0x0008, .frequency = 1 }, + { .name = "IRIG", .value = 0x0010, .frequency = 10000 }, + { .name = "DCF", .value = 0x0020, .frequency = 77000 }, + { .name = "GEN1", .value = 0x0040 }, + { .name = "GEN2", .value = 0x0080 }, + { .name = "GEN3", .value = 0x0100 }, + { .name = "GEN4", .value = 0x0200 }, + { .name = "GND", .value = 0x2000 }, + { .name = "VCC", .value = 0x4000 }, + { } +}; + +static const struct ocp_selector ptp_ocp_art_sma_in[] = { + { .name = "PPS1", .value = 0x0001, .frequency = 1 }, + { .name = "10Mhz", .value = 0x0008, .frequency = 1000000 }, + { } +}; + +static const struct ocp_selector ptp_ocp_art_sma_out[] = { + { .name = "PHC", .value = 0x0002, .frequency = 1 }, + { .name = "GNSS", .value = 0x0004, .frequency = 1 }, + { .name = "10Mhz", .value = 0x0010, .frequency = 10000000 }, + { } +}; + +static const struct ocp_selector ptp_ocp_adva_sma_in[] = { + { .name = "10Mhz", .value = 0x0000, .frequency = 10000000}, + { .name = "PPS1", .value = 0x0001, .frequency = 1 }, + { .name = "PPS2", .value = 0x0002, .frequency = 1 }, + { .name = "TS1", .value = 0x0004, .frequency = 0 }, + { .name = "TS2", .value = 0x0008, .frequency = 0 }, + { .name = "FREQ1", .value = 0x0100, .frequency = 0 }, + { .name = "FREQ2", .value = 0x0200, .frequency = 0 }, + { .name = "None", .value = SMA_DISABLE, .frequency = 0 }, + { } +}; + +static const struct ocp_selector ptp_ocp_adva_sma_out[] = { + { .name = "10Mhz", .value = 0x0000, .frequency = 10000000}, + { .name = "PHC", .value = 0x0001, .frequency = 1 }, + { .name = "MAC", .value = 0x0002, .frequency = 1 }, + { .name = "GNSS1", .value = 0x0004, .frequency = 1 }, + { .name = "GEN1", .value = 0x0040 }, + { .name = "GEN2", .value = 0x0080 }, + { .name = "GND", .value = 0x2000 }, + { .name = "VCC", .value = 0x4000 }, + { } +}; + +static const struct ocp_selector ptp_ocp_adva_x1_sma_in[] = { + { .name = "PPS1", .value = 0x0001, .frequency = 1 }, + { .name = "TS1", .value = 0x0004, .frequency = 0 }, + { .name = "TS2", .value = 0x0008, .frequency = 0 }, + { .name = "TS3", .value = 0x0040, .frequency = 0 }, + { .name = "TS4", .value = 0x0080, .frequency = 0 }, + { .name = "FREQ1", .value = 0x0100, .frequency = 0 }, + { .name = "FREQ2", .value = 0x0200, .frequency = 0 }, + { .name = "FREQ3", .value = 0x0400, .frequency = 0 }, + { .name = "FREQ4", .value = 0x0800, .frequency = 0 }, + { .name = "None", .value = SMA_DISABLE, .frequency = 0 }, + { } +}; + +static const struct ocp_selector ptp_ocp_adva_x1_sma_out[] = { + { .name = "10Mhz", .value = 0x0000, .frequency = 10000000}, + { .name = "PHC", .value = 0x0001, .frequency = 1 }, + { .name = "MAC", .value = 0x0002, .frequency = 1 }, + { .name = "GNSS1", .value = 0x0004, .frequency = 1 }, + { .name = "GEN1", .value = 0x0040 }, + { .name = "GEN2", .value = 0x0080 }, + { .name = "GEN3", .value = 0x0100 }, + { .name = "GEN4", .value = 0x0200 }, + { .name = "GND", .value = 0x2000 }, + { .name = "VCC", .value = 0x4000 }, + { } +}; + +static void +ptp_ocp_sma_init(struct ptp_ocp *bp) +{ + return bp->sma_op->init(bp); +} + +static u32 +ptp_ocp_sma_get(struct ptp_ocp *bp, int sma_nr) +{ + return bp->sma_op->get(bp, sma_nr); +} + +static int +ptp_ocp_sma_set_inputs(struct ptp_ocp *bp, int sma_nr, u32 val) +{ + return bp->sma_op->set_inputs(bp, sma_nr, val); +} + +static int +ptp_ocp_sma_set_output(struct ptp_ocp *bp, int sma_nr, u32 val) +{ + return bp->sma_op->set_output(bp, sma_nr, val); +} + +static const char * +ptp_ocp_select_name_from_val(const struct ocp_selector *tbl, int val) +{ + int i; + + for (i = 0; tbl[i].name; i++) + if (tbl[i].value == val) + return tbl[i].name; + return NULL; +} + +static int +ptp_ocp_select_val_from_name(const struct ocp_selector *tbl, const char *name) +{ + const char *select; + int i; + + for (i = 0; tbl[i].name; i++) { + select = tbl[i].name; + if (!strncasecmp(name, select, strlen(select))) + return tbl[i].value; + } + return -EINVAL; +} + +static ssize_t +ptp_ocp_select_table_show(const struct ocp_selector *tbl, char *buf) +{ + ssize_t count; + int i; + + count = 0; + for (i = 0; tbl[i].name; i++) + count += sysfs_emit_at(buf, count, "%s ", tbl[i].name); + if (count) + count--; + count += sysfs_emit_at(buf, count, "\n"); + return count; +} + +static int +__ptp_ocp_gettime_locked(struct ptp_ocp *bp, struct timespec64 *ts, + struct ptp_system_timestamp *sts) +{ + u32 ctrl, time_sec, time_ns; + int i; + + ptp_read_system_prets(sts); + + ctrl = OCP_CTRL_READ_TIME_REQ | OCP_CTRL_ENABLE; + iowrite32(ctrl, &bp->reg->ctrl); + + for (i = 0; i < 100; i++) { + ctrl = ioread32(&bp->reg->ctrl); + if (ctrl & OCP_CTRL_READ_TIME_DONE) + break; + } + ptp_read_system_postts(sts); + + if (sts && bp->ts_window_adjust) + sts->post_sts.systime -= bp->ts_window_adjust; + + time_ns = ioread32(&bp->reg->time_ns); + time_sec = ioread32(&bp->reg->time_sec); + + ts->tv_sec = time_sec; + ts->tv_nsec = time_ns; + + return ctrl & OCP_CTRL_READ_TIME_DONE ? 0 : -ETIMEDOUT; +} + +static int +ptp_ocp_gettimex(struct ptp_clock_info *ptp_info, struct timespec64 *ts, + struct ptp_system_timestamp *sts) +{ + struct ptp_ocp *bp = container_of(ptp_info, struct ptp_ocp, ptp_info); + unsigned long flags; + int err; + + spin_lock_irqsave(&bp->lock, flags); + err = __ptp_ocp_gettime_locked(bp, ts, sts); + spin_unlock_irqrestore(&bp->lock, flags); + + return err; +} + +static void +__ptp_ocp_settime_locked(struct ptp_ocp *bp, const struct timespec64 *ts) +{ + u32 ctrl, time_sec, time_ns; + u32 select; + + time_ns = ts->tv_nsec; + time_sec = ts->tv_sec; + + select = ioread32(&bp->reg->select); + iowrite32(OCP_SELECT_CLK_REG, &bp->reg->select); + + iowrite32(time_ns, &bp->reg->adjust_ns); + iowrite32(time_sec, &bp->reg->adjust_sec); + + ctrl = OCP_CTRL_ADJUST_TIME | OCP_CTRL_ENABLE; + iowrite32(ctrl, &bp->reg->ctrl); + + /* restore clock selection */ + iowrite32(select >> 16, &bp->reg->select); +} + +static int +ptp_ocp_settime(struct ptp_clock_info *ptp_info, const struct timespec64 *ts) +{ + struct ptp_ocp *bp = container_of(ptp_info, struct ptp_ocp, ptp_info); + unsigned long flags; + + spin_lock_irqsave(&bp->lock, flags); + __ptp_ocp_settime_locked(bp, ts); + spin_unlock_irqrestore(&bp->lock, flags); + + return 0; +} + +static void +__ptp_ocp_adjtime_locked(struct ptp_ocp *bp, u32 adj_val) +{ + u32 select, ctrl; + + select = ioread32(&bp->reg->select); + iowrite32(OCP_SELECT_CLK_REG, &bp->reg->select); + + iowrite32(adj_val, &bp->reg->offset_ns); + iowrite32(NSEC_PER_SEC, &bp->reg->offset_window_ns); + + ctrl = OCP_CTRL_ADJUST_OFFSET | OCP_CTRL_ENABLE; + iowrite32(ctrl, &bp->reg->ctrl); + + /* restore clock selection */ + iowrite32(select >> 16, &bp->reg->select); +} + +static void +ptp_ocp_adjtime_coarse(struct ptp_ocp *bp, s64 delta_ns) +{ + struct timespec64 ts; + unsigned long flags; + int err; + + spin_lock_irqsave(&bp->lock, flags); + err = __ptp_ocp_gettime_locked(bp, &ts, NULL); + if (likely(!err)) { + set_normalized_timespec64(&ts, ts.tv_sec, + ts.tv_nsec + delta_ns); + __ptp_ocp_settime_locked(bp, &ts); + } + spin_unlock_irqrestore(&bp->lock, flags); +} + +static int +ptp_ocp_adjtime(struct ptp_clock_info *ptp_info, s64 delta_ns) +{ + struct ptp_ocp *bp = container_of(ptp_info, struct ptp_ocp, ptp_info); + unsigned long flags; + u32 adj_ns, sign; + + if (delta_ns > NSEC_PER_SEC || -delta_ns > NSEC_PER_SEC) { + ptp_ocp_adjtime_coarse(bp, delta_ns); + return 0; + } + + sign = delta_ns < 0 ? BIT(31) : 0; + adj_ns = sign ? -delta_ns : delta_ns; + + spin_lock_irqsave(&bp->lock, flags); + __ptp_ocp_adjtime_locked(bp, sign | adj_ns); + spin_unlock_irqrestore(&bp->lock, flags); + + return 0; +} + +static int +ptp_ocp_null_adjfine(struct ptp_clock_info *ptp_info, long scaled_ppm) +{ + if (scaled_ppm == 0) + return 0; + + return -EOPNOTSUPP; +} + +static s32 +ptp_ocp_null_getmaxphase(struct ptp_clock_info *ptp_info) +{ + return 0; +} + +static int +ptp_ocp_null_adjphase(struct ptp_clock_info *ptp_info, s32 phase_ns) +{ + return -EOPNOTSUPP; +} + +static int +ptp_ocp_enable(struct ptp_clock_info *ptp_info, struct ptp_clock_request *rq, + int on) +{ + struct ptp_ocp *bp = container_of(ptp_info, struct ptp_ocp, ptp_info); + struct ptp_ocp_ext_src *ext = NULL; + u32 req; + int err; + + switch (rq->type) { + case PTP_CLK_REQ_EXTTS: + req = OCP_REQ_TIMESTAMP; + switch (rq->extts.index) { + case 0: + ext = bp->ts0; + break; + case 1: + ext = bp->ts1; + break; + case 2: + ext = bp->ts2; + break; + case 3: + ext = bp->ts3; + break; + case 4: + ext = bp->ts4; + break; + case 5: + ext = bp->pps; + break; + } + break; + case PTP_CLK_REQ_PPS: + req = OCP_REQ_PPS; + ext = bp->pps; + break; + case PTP_CLK_REQ_PEROUT: + switch (rq->perout.index) { + case 0: + /* This is a request for 1PPS on an output SMA. + * Allow, but assume manual configuration. + */ + if (on && (rq->perout.period.sec != 1 || + rq->perout.period.nsec != 0)) + return -EINVAL; + return 0; + case 1: + case 2: + case 3: + case 4: + req = rq->perout.index - 1; + ext = bp->signal_out[req]; + err = ptp_ocp_signal_from_perout(bp, req, &rq->perout); + if (err) + return err; + break; + } + break; + default: + return -EOPNOTSUPP; + } + + err = -ENXIO; + if (ext) + err = ext->info->enable(ext, req, on); + + return err; +} + +static int +ptp_ocp_verify(struct ptp_clock_info *ptp_info, unsigned pin, + enum ptp_pin_function func, unsigned chan) +{ + struct ptp_ocp *bp = container_of(ptp_info, struct ptp_ocp, ptp_info); + char buf[16]; + + switch (func) { + case PTP_PF_NONE: + snprintf(buf, sizeof(buf), "IN: None"); + break; + case PTP_PF_EXTTS: + /* Allow timestamps, but require sysfs configuration. */ + return 0; + case PTP_PF_PEROUT: + /* channel 0 is 1PPS from PHC. + * channels 1..4 are the frequency generators. + */ + if (chan) + snprintf(buf, sizeof(buf), "OUT: GEN%d", chan); + else + snprintf(buf, sizeof(buf), "OUT: PHC"); + break; + default: + return -EOPNOTSUPP; + } + + return ptp_ocp_sma_store(bp, buf, pin + 1); +} + +static const struct ptp_clock_info ptp_ocp_clock_info = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .max_adj = 100000000, + .gettimex64 = ptp_ocp_gettimex, + .settime64 = ptp_ocp_settime, + .adjtime = ptp_ocp_adjtime, + .adjfine = ptp_ocp_null_adjfine, + .adjphase = ptp_ocp_null_adjphase, + .getmaxphase = ptp_ocp_null_getmaxphase, + .enable = ptp_ocp_enable, + .verify = ptp_ocp_verify, + .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 +__ptp_ocp_clear_drift_locked(struct ptp_ocp *bp) +{ + u32 ctrl, select; + + select = ioread32(&bp->reg->select); + iowrite32(OCP_SELECT_CLK_REG, &bp->reg->select); + + iowrite32(0, &bp->reg->drift_ns); + + ctrl = OCP_CTRL_ADJUST_DRIFT | OCP_CTRL_ENABLE; + iowrite32(ctrl, &bp->reg->ctrl); + + /* restore clock selection */ + iowrite32(select >> 16, &bp->reg->select); +} + +static void +ptp_ocp_utc_distribute(struct ptp_ocp *bp, u32 val) +{ + unsigned long flags; + + spin_lock_irqsave(&bp->lock, flags); + + bp->utc_tai_offset = val; + + if (bp->irig_out) + iowrite32(val, &bp->irig_out->adj_sec); + if (bp->dcf_out) + iowrite32(val, &bp->dcf_out->adj_sec); + if (bp->nmea_out) + iowrite32(val, &bp->nmea_out->adj_sec); + + spin_unlock_irqrestore(&bp->lock, flags); +} + +static void +ptp_ocp_watchdog(struct timer_list *t) +{ + struct ptp_ocp *bp = timer_container_of(bp, t, watchdog); + unsigned long flags; + u32 status, utc_offset; + + status = ioread32(&bp->pps_to_clk->status); + + if (status & PPS_STATUS_SUPERV_ERR) { + iowrite32(status, &bp->pps_to_clk->status); + if (!bp->gnss_lost) { + spin_lock_irqsave(&bp->lock, flags); + __ptp_ocp_clear_drift_locked(bp); + spin_unlock_irqrestore(&bp->lock, flags); + bp->gnss_lost = ktime_get_real_seconds(); + } + + } else if (bp->gnss_lost) { + bp->gnss_lost = 0; + } + + /* if GNSS provides correct data we can rely on + * it to get leap second information + */ + if (bp->tod) { + status = ioread32(&bp->tod->utc_status); + utc_offset = status & TOD_STATUS_UTC_MASK; + if (status & TOD_STATUS_UTC_VALID && + utc_offset != bp->utc_tai_offset) + ptp_ocp_utc_distribute(bp, utc_offset); + } + + mod_timer(&bp->watchdog, jiffies + HZ); +} + +static void +ptp_ocp_estimate_pci_timing(struct ptp_ocp *bp) +{ + ktime_t start, end, delay = U64_MAX; + u32 ctrl; + int i; + + for (i = 0; i < 3; i++) { + ctrl = ioread32(&bp->reg->ctrl); + ctrl = OCP_CTRL_READ_TIME_REQ | OCP_CTRL_ENABLE; + + iowrite32(ctrl, &bp->reg->ctrl); + + start = ktime_get_raw_ns(); + + ctrl = ioread32(&bp->reg->ctrl); + + end = ktime_get_raw_ns(); + + delay = min(delay, end - start); + } + bp->ts_window_adjust = (delay >> 5) * 3; +} + +static int +ptp_ocp_init_clock(struct ptp_ocp *bp, struct ptp_ocp_servo_conf *servo_conf) +{ + struct timespec64 ts; + u32 ctrl; + + ctrl = OCP_CTRL_ENABLE; + iowrite32(ctrl, &bp->reg->ctrl); + + /* servo configuration */ + iowrite32(servo_conf->servo_offset_p, &bp->reg->servo_offset_p); + iowrite32(servo_conf->servo_offset_i, &bp->reg->servo_offset_i); + iowrite32(servo_conf->servo_drift_p, &bp->reg->servo_drift_p); + iowrite32(servo_conf->servo_drift_p, &bp->reg->servo_drift_i); + + /* latch servo values */ + ctrl |= OCP_CTRL_ADJUST_SERVO; + iowrite32(ctrl, &bp->reg->ctrl); + + if ((ioread32(&bp->reg->ctrl) & OCP_CTRL_ENABLE) == 0) { + dev_err(&bp->pdev->dev, "clock not enabled\n"); + return -ENODEV; + } + + ptp_ocp_estimate_pci_timing(bp); + + bp->sync = ioread32(&bp->reg->status) & OCP_STATUS_IN_SYNC; + if (!bp->sync) { + ktime_get_clocktai_ts64(&ts); + ptp_ocp_settime(&bp->ptp_info, &ts); + } + + /* If there is a clock supervisor, then enable the watchdog */ + if (bp->pps_to_clk) { + timer_setup(&bp->watchdog, ptp_ocp_watchdog, 0); + mod_timer(&bp->watchdog, jiffies + HZ); + } + + return 0; +} + +static void +ptp_ocp_tod_init(struct ptp_ocp *bp) +{ + u32 ctrl, reg; + + ctrl = ioread32(&bp->tod->ctrl); + ctrl |= TOD_CTRL_PROTOCOL | TOD_CTRL_ENABLE; + ctrl &= ~(TOD_CTRL_DISABLE_FMT_A | TOD_CTRL_DISABLE_FMT_B); + iowrite32(ctrl, &bp->tod->ctrl); + + reg = ioread32(&bp->tod->utc_status); + if (reg & TOD_STATUS_UTC_VALID) + ptp_ocp_utc_distribute(bp, reg & TOD_STATUS_UTC_MASK); +} + +static const char * +ptp_ocp_tod_proto_name(const int idx) +{ + static const char * const proto_name[] = { + "NMEA", "NMEA_ZDA", "NMEA_RMC", "NMEA_none", + "UBX", "UBX_UTC", "UBX_LS", "UBX_none" + }; + return proto_name[idx]; +} + +static const char * +ptp_ocp_tod_gnss_name(int idx) +{ + static const char * const gnss_name[] = { + "ALL", "COMBINED", "GPS", "GLONASS", "GALILEO", "BEIDOU", + "Unknown" + }; + if (idx >= ARRAY_SIZE(gnss_name)) + idx = ARRAY_SIZE(gnss_name) - 1; + return gnss_name[idx]; +} + +static const char * +ptp_ocp_tty_port_name(int idx) +{ + static const char * const tty_name[] = { + "GNSS", "GNSS2", "MAC", "NMEA" + }; + return tty_name[idx]; +} + +struct ptp_ocp_nvmem_match_info { + struct ptp_ocp *bp; + const void * const tag; +}; + +static int +ptp_ocp_nvmem_match(struct device *dev, const void *data) +{ + const struct ptp_ocp_nvmem_match_info *info = data; + + dev = dev->parent; + if (!i2c_verify_client(dev) || info->tag != dev->platform_data) + return 0; + + while ((dev = dev->parent)) + if (dev->driver && !strcmp(dev->driver->name, KBUILD_MODNAME)) + return info->bp == dev_get_drvdata(dev); + return 0; +} + +static inline struct nvmem_device * +ptp_ocp_nvmem_device_get(struct ptp_ocp *bp, const void * const tag) +{ + struct ptp_ocp_nvmem_match_info info = { .bp = bp, .tag = tag }; + + return nvmem_device_find(&info, ptp_ocp_nvmem_match); +} + +static inline void +ptp_ocp_nvmem_device_put(struct nvmem_device **nvmemp) +{ + if (!IS_ERR_OR_NULL(*nvmemp)) + nvmem_device_put(*nvmemp); + *nvmemp = NULL; +} + +static void +ptp_ocp_read_eeprom(struct ptp_ocp *bp) +{ + const struct ptp_ocp_eeprom_map *map; + struct nvmem_device *nvmem; + const void *tag; + int ret; + + if (!bp->i2c_ctrl) + return; + + tag = NULL; + nvmem = NULL; + + for (map = bp->eeprom_map; map->len; map++) { + if (map->tag != tag) { + tag = map->tag; + ptp_ocp_nvmem_device_put(&nvmem); + } + if (!nvmem) { + nvmem = ptp_ocp_nvmem_device_get(bp, tag); + if (IS_ERR(nvmem)) { + ret = PTR_ERR(nvmem); + goto fail; + } + } + ret = nvmem_device_read(nvmem, map->off, map->len, + BP_MAP_ENTRY_ADDR(bp, map)); + if (ret != map->len) + goto fail; + } + + bp->has_eeprom_data = true; + +out: + ptp_ocp_nvmem_device_put(&nvmem); + return; + +fail: + dev_err(&bp->pdev->dev, "could not read eeprom: %d\n", ret); + goto out; +} + +static struct device * +ptp_ocp_find_flash(struct ptp_ocp *bp) +{ + struct device *dev, *last; + + last = NULL; + dev = &bp->spi_flash->dev; + + while ((dev = device_find_any_child(dev))) { + if (!strcmp("mtd", dev_bus_name(dev))) + break; + put_device(last); + last = dev; + } + put_device(last); + + return dev; +} + +static int +ptp_ocp_devlink_fw_image(struct devlink *devlink, const struct firmware *fw, + const u8 **data, size_t *size) +{ + struct ptp_ocp *bp = devlink_priv(devlink); + const struct ptp_ocp_firmware_header *hdr; + size_t offset, length; + u16 crc; + + hdr = (const struct ptp_ocp_firmware_header *)fw->data; + if (memcmp(hdr->magic, OCP_FIRMWARE_MAGIC_HEADER, 4)) { + devlink_flash_update_status_notify(devlink, + "No firmware header found, cancel firmware upgrade", + NULL, 0, 0); + return -EINVAL; + } + + if (be16_to_cpu(hdr->pci_vendor_id) != bp->pdev->vendor || + be16_to_cpu(hdr->pci_device_id) != bp->pdev->device) { + devlink_flash_update_status_notify(devlink, + "Firmware image compatibility check failed", + NULL, 0, 0); + return -EINVAL; + } + + offset = sizeof(*hdr); + length = be32_to_cpu(hdr->image_size); + if (length != (fw->size - offset)) { + devlink_flash_update_status_notify(devlink, + "Firmware image size check failed", + NULL, 0, 0); + return -EINVAL; + } + + crc = crc16(0xffff, &fw->data[offset], length); + if (be16_to_cpu(hdr->crc) != crc) { + devlink_flash_update_status_notify(devlink, + "Firmware image CRC check failed", + NULL, 0, 0); + return -EINVAL; + } + + *data = &fw->data[offset]; + *size = length; + + return 0; +} + +static int +ptp_ocp_devlink_flash(struct devlink *devlink, struct device *dev, + const struct firmware *fw) +{ + struct mtd_info *mtd = dev_get_drvdata(dev); + struct ptp_ocp *bp = devlink_priv(devlink); + size_t off, len, size, resid, wrote; + struct erase_info erase; + size_t base, blksz; + const u8 *data; + int err; + + err = ptp_ocp_devlink_fw_image(devlink, fw, &data, &size); + if (err) + goto out; + + off = 0; + base = bp->flash_start; + blksz = 4096; + resid = size; + + while (resid) { + devlink_flash_update_status_notify(devlink, "Flashing", + NULL, off, size); + + len = min_t(size_t, resid, blksz); + erase.addr = base + off; + erase.len = blksz; + + err = mtd_erase(mtd, &erase); + if (err) + goto out; + + err = mtd_write(mtd, base + off, len, &wrote, data + off); + if (err) + goto out; + + off += blksz; + resid -= len; + } +out: + return err; +} + +static int +ptp_ocp_devlink_flash_update(struct devlink *devlink, + struct devlink_flash_update_params *params, + struct netlink_ext_ack *extack) +{ + struct ptp_ocp *bp = devlink_priv(devlink); + struct device *dev; + const char *msg; + int err; + + dev = ptp_ocp_find_flash(bp); + if (!dev) { + dev_err(&bp->pdev->dev, "Can't find Flash SPI adapter\n"); + return -ENODEV; + } + + devlink_flash_update_status_notify(devlink, "Preparing to flash", + NULL, 0, 0); + + err = ptp_ocp_devlink_flash(devlink, dev, params->fw); + + msg = err ? "Flash error" : "Flash complete"; + devlink_flash_update_status_notify(devlink, msg, NULL, 0, 0); + + put_device(dev); + return err; +} + +static int +ptp_ocp_devlink_info_get(struct devlink *devlink, struct devlink_info_req *req, + struct netlink_ext_ack *extack) +{ + struct ptp_ocp *bp = devlink_priv(devlink); + const char *fw_image; + char buf[32]; + int err; + + fw_image = bp->fw_loader ? "loader" : "fw"; + sprintf(buf, "%d.%d", bp->fw_tag, bp->fw_version); + err = devlink_info_version_running_put(req, fw_image, buf); + if (err) + return err; + + if (!bp->has_eeprom_data) { + ptp_ocp_read_eeprom(bp); + if (!bp->has_eeprom_data) + return 0; + } + + sprintf(buf, "%pM", bp->serial); + err = devlink_info_serial_number_put(req, buf); + if (err) + return err; + + snprintf(buf, sizeof(buf), "%.*s", OCP_BOARD_ID_LEN, + (const char *)bp->board_id); + err = devlink_info_version_fixed_put(req, + DEVLINK_INFO_VERSION_GENERIC_BOARD_ID, + buf); + if (err) + return err; + + return 0; +} + +static const struct devlink_ops ptp_ocp_devlink_ops = { + .flash_update = ptp_ocp_devlink_flash_update, + .info_get = ptp_ocp_devlink_info_get, +}; + +static void __iomem * +__ptp_ocp_get_mem(struct ptp_ocp *bp, resource_size_t start, int size) +{ + struct resource res = DEFINE_RES_MEM_NAMED(start, size, "ptp_ocp"); + + return devm_ioremap_resource(&bp->pdev->dev, &res); +} + +static void __iomem * +ptp_ocp_get_mem(struct ptp_ocp *bp, struct ocp_resource *r) +{ + resource_size_t start; + + start = pci_resource_start(bp->pdev, 0) + r->offset; + return __ptp_ocp_get_mem(bp, start, r->size); +} + +static int +ptp_ocp_register_spi(struct ptp_ocp *bp, struct ocp_resource *r) +{ + struct ptp_ocp_flash_info *info; + struct pci_dev *pdev = bp->pdev; + struct platform_device *p; + struct resource res[2]; + resource_size_t start; + int id; + + start = pci_resource_start(pdev, 0) + r->offset; + res[0] = DEFINE_RES_MEM(start, r->size); + res[1] = DEFINE_RES_IRQ(pci_irq_vector(pdev, r->irq_vec)); + + info = r->extra; + id = pci_dev_id(pdev) << 1; + id += info->pci_offset; + + p = platform_device_register_resndata(&pdev->dev, info->name, id, + res, ARRAY_SIZE(res), info->data, + info->data_size); + if (IS_ERR(p)) + return PTR_ERR(p); + + bp_assign_entry(bp, r, p); + + return 0; +} + +static struct platform_device * +ptp_ocp_i2c_bus(struct pci_dev *pdev, struct ocp_resource *r, int id) +{ + struct ptp_ocp_i2c_info *info; + struct resource res[2]; + resource_size_t start; + + info = r->extra; + start = pci_resource_start(pdev, 0) + r->offset; + res[0] = DEFINE_RES_MEM(start, r->size); + res[1] = DEFINE_RES_IRQ(pci_irq_vector(pdev, r->irq_vec)); + + return platform_device_register_resndata(&pdev->dev, info->name, + id, res, ARRAY_SIZE(res), + info->data, info->data_size); +} + +static int +ptp_ocp_register_i2c(struct ptp_ocp *bp, struct ocp_resource *r) +{ + struct pci_dev *pdev = bp->pdev; + struct ptp_ocp_i2c_info *info; + struct platform_device *p; + struct clk_hw *clk; + char buf[32]; + int id; + + info = r->extra; + id = pci_dev_id(bp->pdev); + + sprintf(buf, "AXI.%d", id); + clk = clk_hw_register_fixed_rate(&pdev->dev, buf, NULL, 0, + info->fixed_rate); + if (IS_ERR(clk)) + return PTR_ERR(clk); + bp->i2c_clk = clk; + + sprintf(buf, "%s.%d", info->name, id); + devm_clk_hw_register_clkdev(&pdev->dev, clk, NULL, buf); + p = ptp_ocp_i2c_bus(bp->pdev, r, id); + if (IS_ERR(p)) + return PTR_ERR(p); + + bp_assign_entry(bp, r, p); + + return 0; +} + +/* The expectation is that this is triggered only on error. */ +static irqreturn_t +ptp_ocp_signal_irq(int irq, void *priv) +{ + struct ptp_ocp_ext_src *ext = priv; + struct signal_reg __iomem *reg = ext->mem; + struct ptp_ocp *bp = ext->bp; + u32 enable, status; + int gen; + + gen = ext->info->index - 1; + + enable = ioread32(®->enable); + status = ioread32(®->status); + + /* disable generator on error */ + if (status || !enable) { + iowrite32(0, ®->intr_mask); + iowrite32(0, ®->enable); + bp->signal[gen].running = false; + } + + iowrite32(0, ®->intr); /* ack interrupt */ + + return IRQ_HANDLED; +} + +static int +ptp_ocp_signal_set(struct ptp_ocp *bp, int gen, struct ptp_ocp_signal *s) +{ + struct ptp_system_timestamp sts; + struct timespec64 ts; + ktime_t start_ns; + int err; + + if (!s->period) + return 0; + + if (!s->pulse) + s->pulse = ktime_divns(s->period * s->duty, 100); + + err = ptp_ocp_gettimex(&bp->ptp_info, &ts, &sts); + if (err) + return err; + + start_ns = ktime_set(ts.tv_sec, ts.tv_nsec) + NSEC_PER_MSEC; + if (!s->start) { + /* roundup() does not work on 32-bit systems */ + s->start = DIV64_U64_ROUND_UP(start_ns, s->period); + s->start *= s->period; + s->start = ktime_add(s->start, s->phase); + } + + if (s->duty < 1 || s->duty > 99) + return -EINVAL; + + if (s->pulse < 1 || s->pulse > s->period) + return -EINVAL; + + if (s->start < start_ns) + return -EINVAL; + + bp->signal[gen] = *s; + + return 0; +} + +static int +ptp_ocp_signal_from_perout(struct ptp_ocp *bp, int gen, + struct ptp_perout_request *req) +{ + struct ptp_ocp_signal s = { }; + + s.polarity = bp->signal[gen].polarity; + s.period = ktime_set(req->period.sec, req->period.nsec); + if (!s.period) + return 0; + + if (req->flags & PTP_PEROUT_DUTY_CYCLE) { + s.pulse = ktime_set(req->on.sec, req->on.nsec); + s.duty = ktime_divns(s.pulse * 100, s.period); + } + + if (req->flags & PTP_PEROUT_PHASE) + s.phase = ktime_set(req->phase.sec, req->phase.nsec); + else + s.start = ktime_set(req->start.sec, req->start.nsec); + + return ptp_ocp_signal_set(bp, gen, &s); +} + +static int +ptp_ocp_signal_enable(void *priv, u32 req, bool enable) +{ + struct ptp_ocp_ext_src *ext = priv; + struct signal_reg __iomem *reg = ext->mem; + struct ptp_ocp *bp = ext->bp; + struct timespec64 ts; + int gen; + + gen = ext->info->index - 1; + + iowrite32(0, ®->intr_mask); + iowrite32(0, ®->enable); + bp->signal[gen].running = false; + if (!enable) + return 0; + + ts = ktime_to_timespec64(bp->signal[gen].start); + iowrite32(ts.tv_sec, ®->start_sec); + iowrite32(ts.tv_nsec, ®->start_ns); + + ts = ktime_to_timespec64(bp->signal[gen].period); + iowrite32(ts.tv_sec, ®->period_sec); + iowrite32(ts.tv_nsec, ®->period_ns); + + ts = ktime_to_timespec64(bp->signal[gen].pulse); + iowrite32(ts.tv_sec, ®->pulse_sec); + iowrite32(ts.tv_nsec, ®->pulse_ns); + + iowrite32(bp->signal[gen].polarity, ®->polarity); + iowrite32(0, ®->repeat_count); + + iowrite32(0, ®->intr); /* clear interrupt state */ + iowrite32(1, ®->intr_mask); /* enable interrupt */ + iowrite32(3, ®->enable); /* valid & enable */ + + bp->signal[gen].running = true; + + return 0; +} + +static irqreturn_t +ptp_ocp_ts_irq(int irq, void *priv) +{ + struct ptp_ocp_ext_src *ext = priv; + struct ts_reg __iomem *reg = ext->mem; + struct ptp_clock_event ev; + u32 sec, nsec; + + if (ext == ext->bp->pps) { + if (ext->bp->pps_req_map & OCP_REQ_PPS) { + ev.type = PTP_CLOCK_PPS; + ptp_clock_event(ext->bp->ptp, &ev); + } + + if ((ext->bp->pps_req_map & ~OCP_REQ_PPS) == 0) + goto out; + } + + /* XXX should fix API - this converts s/ns -> ts -> s/ns */ + sec = ioread32(®->time_sec); + nsec = ioread32(®->time_ns); + + ev.type = PTP_CLOCK_EXTTS; + ev.index = ext->info->index; + ev.timestamp = sec * NSEC_PER_SEC + nsec; + + ptp_clock_event(ext->bp->ptp, &ev); + +out: + iowrite32(1, ®->intr); /* write 1 to ack */ + + return IRQ_HANDLED; +} + +static int +ptp_ocp_ts_enable(void *priv, u32 req, bool enable) +{ + struct ptp_ocp_ext_src *ext = priv; + struct ts_reg __iomem *reg = ext->mem; + struct ptp_ocp *bp = ext->bp; + + if (ext == bp->pps) { + u32 old_map = bp->pps_req_map; + + if (enable) + bp->pps_req_map |= req; + else + bp->pps_req_map &= ~req; + + /* if no state change, just return */ + if ((!!old_map ^ !!bp->pps_req_map) == 0) + return 0; + } + + if (enable) { + iowrite32(1, ®->enable); + iowrite32(1, ®->intr_mask); + iowrite32(1, ®->intr); + } else { + int irq_vec = pci_irq_vector(bp->pdev, ext->irq_vec); + + iowrite32(0, ®->intr_mask); + iowrite32(0, ®->enable); + ioread32(®->intr_mask); + if (irq_vec > 0) + synchronize_irq(irq_vec); + } + + return 0; +} + +static void +ptp_ocp_unregister_ext(struct ptp_ocp_ext_src *ext) +{ + if (!ext) + return; + + ext->info->enable(ext, ~0, false); + pci_free_irq(ext->bp->pdev, ext->irq_vec, ext); + kfree(ext); +} + +static int +ptp_ocp_register_ext(struct ptp_ocp *bp, struct ocp_resource *r) +{ + struct pci_dev *pdev = bp->pdev; + struct ptp_ocp_ext_src *ext; + int err; + + ext = kzalloc_obj(*ext); + if (!ext) + return -ENOMEM; + + ext->mem = ptp_ocp_get_mem(bp, r); + if (IS_ERR(ext->mem)) { + err = PTR_ERR(ext->mem); + goto out; + } + + ext->bp = bp; + ext->info = r->extra; + ext->irq_vec = r->irq_vec; + + err = pci_request_irq(pdev, r->irq_vec, ext->info->irq_fcn, NULL, + ext, "ocp%d.%s", bp->id, r->name); + if (err) { + dev_err(&pdev->dev, "Could not get irq %d\n", r->irq_vec); + goto out; + } + + bp_assign_entry(bp, r, ext); + + return 0; + +out: + kfree(ext); + return err; +} + +static int +ptp_ocp_serial_line(struct ptp_ocp *bp, struct ocp_resource *r) +{ + struct pci_dev *pdev = bp->pdev; + struct uart_8250_port uart; + + /* Setting UPF_IOREMAP and leaving port.membase unspecified lets + * the serial port device claim and release the pci resource. + */ + memset(&uart, 0, sizeof(uart)); + uart.port.dev = &pdev->dev; + uart.port.iotype = UPIO_MEM; + uart.port.regshift = 2; + uart.port.mapbase = pci_resource_start(pdev, 0) + r->offset; + uart.port.irq = pci_irq_vector(pdev, r->irq_vec); + uart.port.uartclk = 50000000; + uart.port.flags = UPF_FIXED_TYPE | UPF_IOREMAP | UPF_NO_THRE_TEST; + uart.port.type = PORT_16550A; + + return serial8250_register_8250_port(&uart); +} + +static int +ptp_ocp_register_serial(struct ptp_ocp *bp, struct ocp_resource *r) +{ + struct ptp_ocp_serial_port *p = (struct ptp_ocp_serial_port *)r->extra; + struct ptp_ocp_serial_port port = {}; + + port.line = ptp_ocp_serial_line(bp, r); + if (port.line < 0) + return port.line; + + if (p) + port.baud = p->baud; + + bp_assign_entry(bp, r, port); + + return 0; +} + +static int +ptp_ocp_register_mem(struct ptp_ocp *bp, struct ocp_resource *r) +{ + void __iomem *mem; + + mem = ptp_ocp_get_mem(bp, r); + if (IS_ERR(mem)) + return PTR_ERR(mem); + + bp_assign_entry(bp, r, mem); + + return 0; +} + +static void +ptp_ocp_nmea_out_init(struct ptp_ocp *bp) +{ + if (!bp->nmea_out) + return; + + iowrite32(0, &bp->nmea_out->ctrl); /* disable */ + iowrite32(7, &bp->nmea_out->uart_baud); /* 115200 */ + iowrite32(1, &bp->nmea_out->ctrl); /* enable */ +} + +static void +_ptp_ocp_signal_init(struct ptp_ocp_signal *s, struct signal_reg __iomem *reg) +{ + u32 val; + + iowrite32(0, ®->enable); /* disable */ + + val = ioread32(®->polarity); + s->polarity = val ? true : false; + s->duty = 50; +} + +static void +ptp_ocp_signal_init(struct ptp_ocp *bp) +{ + int i; + + for (i = 0; i < 4; i++) + if (bp->signal_out[i]) + _ptp_ocp_signal_init(&bp->signal[i], + bp->signal_out[i]->mem); +} + +static void +ptp_ocp_attr_group_del(struct ptp_ocp *bp) +{ + sysfs_remove_groups(&bp->dev.kobj, bp->attr_group); + kfree(bp->attr_group); +} + +static int +ptp_ocp_attr_group_add(struct ptp_ocp *bp, + const struct ocp_attr_group *attr_tbl) +{ + int count, i; + int err; + + count = 0; + for (i = 0; attr_tbl[i].cap; i++) + if (attr_tbl[i].cap & bp->fw_cap) + count++; + + bp->attr_group = kzalloc_objs(*bp->attr_group, count + 1); + if (!bp->attr_group) + return -ENOMEM; + + count = 0; + for (i = 0; attr_tbl[i].cap; i++) + if (attr_tbl[i].cap & bp->fw_cap) + bp->attr_group[count++] = attr_tbl[i].group; + + err = sysfs_create_groups(&bp->dev.kobj, bp->attr_group); + if (err) + bp->attr_group[0] = NULL; + + return err; +} + +static void +ptp_ocp_enable_fpga(u32 __iomem *reg, u32 bit, bool enable) +{ + u32 ctrl; + bool on; + + ctrl = ioread32(reg); + on = ctrl & bit; + if (on ^ enable) { + ctrl &= ~bit; + ctrl |= enable ? bit : 0; + iowrite32(ctrl, reg); + } +} + +static void +ptp_ocp_irig_out(struct ptp_ocp *bp, bool enable) +{ + return ptp_ocp_enable_fpga(&bp->irig_out->ctrl, + IRIG_M_CTRL_ENABLE, enable); +} + +static void +ptp_ocp_irig_in(struct ptp_ocp *bp, bool enable) +{ + return ptp_ocp_enable_fpga(&bp->irig_in->ctrl, + IRIG_S_CTRL_ENABLE, enable); +} + +static void +ptp_ocp_dcf_out(struct ptp_ocp *bp, bool enable) +{ + return ptp_ocp_enable_fpga(&bp->dcf_out->ctrl, + DCF_M_CTRL_ENABLE, enable); +} + +static void +ptp_ocp_dcf_in(struct ptp_ocp *bp, bool enable) +{ + return ptp_ocp_enable_fpga(&bp->dcf_in->ctrl, + DCF_S_CTRL_ENABLE, enable); +} + +static void +__handle_signal_outputs(struct ptp_ocp *bp, u32 val) +{ + ptp_ocp_irig_out(bp, val & 0x00100010); + ptp_ocp_dcf_out(bp, val & 0x00200020); +} + +static void +__handle_signal_inputs(struct ptp_ocp *bp, u32 val) +{ + ptp_ocp_irig_in(bp, val & 0x00100010); + ptp_ocp_dcf_in(bp, val & 0x00200020); +} + +static u32 +ptp_ocp_sma_fb_get(struct ptp_ocp *bp, int sma_nr) +{ + u32 __iomem *gpio; + u32 shift; + + if (bp->sma[sma_nr - 1].fixed_fcn) + return (sma_nr - 1) & 1; + + if (bp->sma[sma_nr - 1].mode == SMA_MODE_IN) + gpio = sma_nr > 2 ? &bp->sma_map2->gpio1 : &bp->sma_map1->gpio1; + else + gpio = sma_nr > 2 ? &bp->sma_map1->gpio2 : &bp->sma_map2->gpio2; + shift = sma_nr & 1 ? 0 : 16; + + return (ioread32(gpio) >> shift) & 0xffff; +} + +static int +ptp_ocp_sma_fb_set_output(struct ptp_ocp *bp, int sma_nr, u32 val) +{ + u32 reg, mask, shift; + unsigned long flags; + u32 __iomem *gpio; + + gpio = sma_nr > 2 ? &bp->sma_map1->gpio2 : &bp->sma_map2->gpio2; + shift = sma_nr & 1 ? 0 : 16; + + mask = 0xffff << (16 - shift); + + spin_lock_irqsave(&bp->lock, flags); + + reg = ioread32(gpio); + reg = (reg & mask) | (val << shift); + + __handle_signal_outputs(bp, reg); + + iowrite32(reg, gpio); + + spin_unlock_irqrestore(&bp->lock, flags); + + return 0; +} + +static int +ptp_ocp_sma_fb_set_inputs(struct ptp_ocp *bp, int sma_nr, u32 val) +{ + u32 reg, mask, shift; + unsigned long flags; + u32 __iomem *gpio; + + gpio = sma_nr > 2 ? &bp->sma_map2->gpio1 : &bp->sma_map1->gpio1; + shift = sma_nr & 1 ? 0 : 16; + + mask = 0xffff << (16 - shift); + + spin_lock_irqsave(&bp->lock, flags); + + reg = ioread32(gpio); + reg = (reg & mask) | (val << shift); + + __handle_signal_inputs(bp, reg); + + iowrite32(reg, gpio); + + spin_unlock_irqrestore(&bp->lock, flags); + + return 0; +} + +static void +ptp_ocp_sma_fb_init(struct ptp_ocp *bp) +{ + struct dpll_pin_properties prop = { + .board_label = NULL, + .type = DPLL_PIN_TYPE_EXT, + .capabilities = DPLL_PIN_CAPABILITIES_DIRECTION_CAN_CHANGE, + .freq_supported_num = ARRAY_SIZE(ptp_ocp_sma_freq), + .freq_supported = ptp_ocp_sma_freq, + + }; + u32 reg; + int i; + + /* defaults */ + for (i = 0; i < OCP_SMA_NUM; i++) { + bp->sma[i].default_fcn = i & 1; + bp->sma[i].dpll_prop = prop; + bp->sma[i].dpll_prop.board_label = + bp->ptp_info.pin_config[i].name; + } + bp->sma[0].mode = SMA_MODE_IN; + bp->sma[1].mode = SMA_MODE_IN; + bp->sma[2].mode = SMA_MODE_OUT; + bp->sma[3].mode = SMA_MODE_OUT; + /* If no SMA1 map, the pin functions and directions are fixed. */ + if (!bp->sma_map1) { + for (i = 0; i < OCP_SMA_NUM; i++) { + bp->sma[i].fixed_fcn = true; + bp->sma[i].fixed_dir = true; + bp->sma[i].dpll_prop.capabilities &= + ~DPLL_PIN_CAPABILITIES_DIRECTION_CAN_CHANGE; + } + return; + } + + /* If SMA2 GPIO output map is all 1, it is not present. + * This indicates the firmware has fixed direction SMA pins. + */ + reg = ioread32(&bp->sma_map2->gpio2); + if (reg == 0xffffffff) { + for (i = 0; i < OCP_SMA_NUM; i++) + bp->sma[i].fixed_dir = true; + } else { + reg = ioread32(&bp->sma_map1->gpio1); + bp->sma[0].mode = reg & BIT(15) ? SMA_MODE_IN : SMA_MODE_OUT; + bp->sma[1].mode = reg & BIT(31) ? SMA_MODE_IN : SMA_MODE_OUT; + + reg = ioread32(&bp->sma_map1->gpio2); + bp->sma[2].mode = reg & BIT(15) ? SMA_MODE_OUT : SMA_MODE_IN; + bp->sma[3].mode = reg & BIT(31) ? SMA_MODE_OUT : SMA_MODE_IN; + } +} + +static const struct ocp_sma_op ocp_fb_sma_op = { + .tbl = { ptp_ocp_sma_in, ptp_ocp_sma_out }, + .init = ptp_ocp_sma_fb_init, + .get = ptp_ocp_sma_fb_get, + .set_inputs = ptp_ocp_sma_fb_set_inputs, + .set_output = ptp_ocp_sma_fb_set_output, +}; + +static int +ptp_ocp_sma_adva_set_output(struct ptp_ocp *bp, int sma_nr, u32 val) +{ + u32 reg, mask, shift; + unsigned long flags; + u32 __iomem *gpio; + + gpio = sma_nr > 2 ? &bp->sma_map1->gpio2 : &bp->sma_map2->gpio2; + shift = sma_nr & 1 ? 0 : 16; + + mask = 0xffff << (16 - shift); + + spin_lock_irqsave(&bp->lock, flags); + + reg = ioread32(gpio); + reg = (reg & mask) | (val << shift); + + iowrite32(reg, gpio); + + spin_unlock_irqrestore(&bp->lock, flags); + + return 0; +} + +static int +ptp_ocp_sma_adva_set_inputs(struct ptp_ocp *bp, int sma_nr, u32 val) +{ + u32 reg, mask, shift; + unsigned long flags; + u32 __iomem *gpio; + + gpio = sma_nr > 2 ? &bp->sma_map2->gpio1 : &bp->sma_map1->gpio1; + shift = sma_nr & 1 ? 0 : 16; + + mask = 0xffff << (16 - shift); + + spin_lock_irqsave(&bp->lock, flags); + + reg = ioread32(gpio); + reg = (reg & mask) | (val << shift); + + iowrite32(reg, gpio); + + spin_unlock_irqrestore(&bp->lock, flags); + + return 0; +} + +static const struct ocp_sma_op ocp_adva_sma_op = { + .tbl = { ptp_ocp_adva_sma_in, ptp_ocp_adva_sma_out }, + .init = ptp_ocp_sma_fb_init, + .get = ptp_ocp_sma_fb_get, + .set_inputs = ptp_ocp_sma_adva_set_inputs, + .set_output = ptp_ocp_sma_adva_set_output, +}; + +static const struct ocp_sma_op ocp_adva_x1_sma_op = { + .tbl = { ptp_ocp_adva_x1_sma_in, ptp_ocp_adva_x1_sma_out }, + .init = ptp_ocp_sma_fb_init, + .get = ptp_ocp_sma_fb_get, + .set_inputs = ptp_ocp_sma_adva_set_inputs, + .set_output = ptp_ocp_sma_adva_set_output, +}; + +static int +ptp_ocp_set_pins(struct ptp_ocp *bp) +{ + struct ptp_pin_desc *config; + int i; + + config = kzalloc_objs(*config, 4); + if (!config) + return -ENOMEM; + + for (i = 0; i < 4; i++) { + sprintf(config[i].name, "sma%d", i + 1); + config[i].index = i; + } + + bp->ptp_info.n_pins = 4; + bp->ptp_info.pin_config = config; + + return 0; +} + +static void +ptp_ocp_fb_set_version(struct ptp_ocp *bp) +{ + u64 cap = OCP_CAP_BASIC; + u32 version; + + version = ioread32(&bp->image->version); + + /* if lower 16 bits are empty, this is the fw loader. */ + if ((version & 0xffff) == 0) { + version = version >> 16; + bp->fw_loader = true; + } + + bp->fw_tag = version >> 15; + bp->fw_version = version & 0x7fff; + + if (bp->fw_tag) { + /* FPGA firmware */ + if (version >= 5) + cap |= OCP_CAP_SIGNAL | OCP_CAP_FREQ; + } else { + /* SOM firmware */ + if (version >= 19) + cap |= OCP_CAP_SIGNAL; + if (version >= 20) + cap |= OCP_CAP_FREQ; + } + + bp->fw_cap = cap; +} + +/* FB specific board initializers; last "resource" registered. */ +static int +ptp_ocp_fb_board_init(struct ptp_ocp *bp, struct ocp_resource *r) +{ + int err; + + bp->flash_start = 1024 * 4096; + bp->eeprom_map = fb_eeprom_map; + bp->fw_version = ioread32(&bp->image->version); + bp->sma_op = &ocp_fb_sma_op; + bp->signals_nr = 4; + bp->freq_in_nr = 4; + + ptp_ocp_fb_set_version(bp); + + ptp_ocp_tod_init(bp); + ptp_ocp_nmea_out_init(bp); + ptp_ocp_signal_init(bp); + + err = ptp_ocp_attr_group_add(bp, fb_timecard_groups); + if (err) + return err; + + err = ptp_ocp_set_pins(bp); + if (err) + return err; + ptp_ocp_sma_init(bp); + + return ptp_ocp_init_clock(bp, r->extra); +} + +static bool +ptp_ocp_allow_irq(struct ptp_ocp *bp, struct ocp_resource *r) +{ + bool allow = !r->irq_vec || r->irq_vec < bp->n_irqs; + + if (!allow) + dev_err(&bp->pdev->dev, "irq %d out of range, skipping %s\n", + r->irq_vec, r->name); + return allow; +} + +static int +ptp_ocp_register_resources(struct ptp_ocp *bp, kernel_ulong_t driver_data) +{ + struct ocp_resource *r, *table; + int err = 0; + + table = (struct ocp_resource *)driver_data; + for (r = table; r->setup; r++) { + if (!ptp_ocp_allow_irq(bp, r)) + continue; + err = r->setup(bp, r); + if (err) { + dev_err(&bp->pdev->dev, + "Could not register %s: err %d\n", + r->name, err); + break; + } + } + return err; +} + +static void +ptp_ocp_art_sma_init(struct ptp_ocp *bp) +{ + struct dpll_pin_properties prop = { + .board_label = NULL, + .type = DPLL_PIN_TYPE_EXT, + .capabilities = 0, + .freq_supported_num = ARRAY_SIZE(ptp_ocp_sma_freq), + .freq_supported = ptp_ocp_sma_freq, + + }; + u32 reg; + int i; + + /* defaults */ + bp->sma[0].mode = SMA_MODE_IN; + bp->sma[1].mode = SMA_MODE_IN; + bp->sma[2].mode = SMA_MODE_OUT; + bp->sma[3].mode = SMA_MODE_OUT; + + bp->sma[0].default_fcn = 0x08; /* IN: 10Mhz */ + bp->sma[1].default_fcn = 0x01; /* IN: PPS1 */ + bp->sma[2].default_fcn = 0x10; /* OUT: 10Mhz */ + bp->sma[3].default_fcn = 0x02; /* OUT: PHC */ + + for (i = 0; i < OCP_SMA_NUM; i++) { + /* If no SMA map, the pin functions and directions are fixed. */ + bp->sma[i].dpll_prop = prop; + bp->sma[i].dpll_prop.board_label = + bp->ptp_info.pin_config[i].name; + if (!bp->art_sma) { + bp->sma[i].fixed_fcn = true; + bp->sma[i].fixed_dir = true; + continue; + } + reg = ioread32(&bp->art_sma->map[i].gpio); + + switch (reg & 0xff) { + case 0: + bp->sma[i].fixed_fcn = true; + bp->sma[i].fixed_dir = true; + break; + case 1: + case 8: + bp->sma[i].mode = SMA_MODE_IN; + bp->sma[i].dpll_prop.capabilities = + DPLL_PIN_CAPABILITIES_DIRECTION_CAN_CHANGE; + break; + default: + bp->sma[i].mode = SMA_MODE_OUT; + bp->sma[i].dpll_prop.capabilities = + DPLL_PIN_CAPABILITIES_DIRECTION_CAN_CHANGE; + break; + } + } +} + +static u32 +ptp_ocp_art_sma_get(struct ptp_ocp *bp, int sma_nr) +{ + if (bp->sma[sma_nr - 1].fixed_fcn) + return bp->sma[sma_nr - 1].default_fcn; + + return ioread32(&bp->art_sma->map[sma_nr - 1].gpio) & 0xff; +} + +/* note: store 0 is considered invalid. */ +static int +ptp_ocp_art_sma_set(struct ptp_ocp *bp, int sma_nr, u32 val) +{ + unsigned long flags; + u32 __iomem *gpio; + int err = 0; + u32 reg; + + val &= SMA_SELECT_MASK; + if (hweight32(val) > 1) + return -EINVAL; + + gpio = &bp->art_sma->map[sma_nr - 1].gpio; + + spin_lock_irqsave(&bp->lock, flags); + reg = ioread32(gpio); + if (((reg >> 16) & val) == 0) { + err = -EOPNOTSUPP; + } else { + reg = (reg & 0xff00) | (val & 0xff); + iowrite32(reg, gpio); + } + spin_unlock_irqrestore(&bp->lock, flags); + + return err; +} + +static const struct ocp_sma_op ocp_art_sma_op = { + .tbl = { ptp_ocp_art_sma_in, ptp_ocp_art_sma_out }, + .init = ptp_ocp_art_sma_init, + .get = ptp_ocp_art_sma_get, + .set_inputs = ptp_ocp_art_sma_set, + .set_output = ptp_ocp_art_sma_set, +}; + +/* ART specific board initializers; last "resource" registered. */ +static int +ptp_ocp_art_board_init(struct ptp_ocp *bp, struct ocp_resource *r) +{ + int err; + + bp->flash_start = 0x1000000; + bp->eeprom_map = art_eeprom_map; + bp->fw_cap = OCP_CAP_BASIC; + bp->fw_version = ioread32(&bp->reg->version); + bp->fw_tag = 2; + bp->sma_op = &ocp_art_sma_op; + bp->signals_nr = 4; + bp->freq_in_nr = 4; + + /* Enable MAC serial port during initialisation */ + iowrite32(1, &bp->board_config->mro50_serial_activate); + + err = ptp_ocp_set_pins(bp); + if (err) + return err; + ptp_ocp_sma_init(bp); + + err = ptp_ocp_attr_group_add(bp, art_timecard_groups); + if (err) + return err; + + return ptp_ocp_init_clock(bp, r->extra); +} + +/* ADVA board initializer; variant differences come from r->extra. */ +static int +ptp_ocp_adva_board_init(struct ptp_ocp *bp, struct ocp_resource *r) +{ + struct ptp_ocp_adva_info *info = r->extra; + u32 version; + int err; + + bp->flash_start = info->flash_start; + bp->eeprom_map = fb_eeprom_map; + bp->sma_op = info->sma_op; + bp->signals_nr = info->signals_nr; + bp->freq_in_nr = info->freq_in_nr; + + version = ioread32(&bp->image->version); + /* if lower 16 bits are empty, this is the fw loader. */ + if ((version & 0xffff) == 0) { + version = version >> 16; + bp->fw_loader = true; + } + bp->fw_tag = 3; + bp->fw_version = version & 0xffff; + bp->fw_cap = OCP_CAP_BASIC | OCP_CAP_SIGNAL | OCP_CAP_FREQ; + + ptp_ocp_tod_init(bp); + ptp_ocp_nmea_out_init(bp); + ptp_ocp_signal_init(bp); + + err = ptp_ocp_attr_group_add(bp, info->attr_groups); + if (err) + return err; + + err = ptp_ocp_set_pins(bp); + if (err) + return err; + ptp_ocp_sma_init(bp); + + return ptp_ocp_init_clock(bp, &info->servo); +} + +static ssize_t +ptp_ocp_show_output(const struct ocp_selector *tbl, u32 val, char *buf, + int def_val) +{ + const char *name; + ssize_t count; + + count = sysfs_emit(buf, "OUT: "); + name = ptp_ocp_select_name_from_val(tbl, val); + if (!name) + name = ptp_ocp_select_name_from_val(tbl, def_val); + count += sysfs_emit_at(buf, count, "%s\n", name); + return count; +} + +static ssize_t +ptp_ocp_show_inputs(const struct ocp_selector *tbl, u32 val, char *buf, + int def_val) +{ + const char *name; + ssize_t count; + int i; + + count = sysfs_emit(buf, "IN: "); + for (i = 0; tbl[i].name; i++) { + if (val & tbl[i].value) { + name = tbl[i].name; + count += sysfs_emit_at(buf, count, "%s ", name); + } + } + if (!val && def_val >= 0) { + name = ptp_ocp_select_name_from_val(tbl, def_val); + count += sysfs_emit_at(buf, count, "%s ", name); + } + if (count) + count--; + count += sysfs_emit_at(buf, count, "\n"); + return count; +} + +static int +sma_parse_inputs(const struct ocp_selector * const tbl[], const char *buf, + enum ptp_ocp_sma_mode *mode) +{ + int idx, count, dir; + char **argv; + int ret; + + argv = argv_split(GFP_KERNEL, buf, &count); + if (!argv) + return -ENOMEM; + + ret = -EINVAL; + if (!count) + goto out; + + idx = 0; + dir = *mode == SMA_MODE_IN ? 0 : 1; + if (!strcasecmp("IN:", argv[0])) { + dir = 0; + idx++; + } + if (!strcasecmp("OUT:", argv[0])) { + dir = 1; + idx++; + } + *mode = dir == 0 ? SMA_MODE_IN : SMA_MODE_OUT; + + ret = 0; + for (; idx < count; idx++) + ret |= ptp_ocp_select_val_from_name(tbl[dir], argv[idx]); + if (ret < 0) + ret = -EINVAL; + +out: + argv_free(argv); + return ret; +} + +static ssize_t +ptp_ocp_sma_show(struct ptp_ocp *bp, int sma_nr, char *buf, + int default_in_val, int default_out_val) +{ + struct ptp_ocp_sma_connector *sma = &bp->sma[sma_nr - 1]; + const struct ocp_selector * const *tbl; + u32 val; + + tbl = bp->sma_op->tbl; + val = ptp_ocp_sma_get(bp, sma_nr) & SMA_SELECT_MASK; + + if (sma->mode == SMA_MODE_IN) { + if (sma->disabled) + val = SMA_DISABLE; + return ptp_ocp_show_inputs(tbl[0], val, buf, default_in_val); + } + + return ptp_ocp_show_output(tbl[1], val, buf, default_out_val); +} + +static ssize_t +sma1_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ptp_ocp *bp = dev_get_drvdata(dev); + + return ptp_ocp_sma_show(bp, 1, buf, 0, 1); +} + +static ssize_t +sma2_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ptp_ocp *bp = dev_get_drvdata(dev); + + return ptp_ocp_sma_show(bp, 2, buf, -1, 1); +} + +static ssize_t +sma3_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ptp_ocp *bp = dev_get_drvdata(dev); + + return ptp_ocp_sma_show(bp, 3, buf, -1, 0); +} + +static ssize_t +sma4_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ptp_ocp *bp = dev_get_drvdata(dev); + + return ptp_ocp_sma_show(bp, 4, buf, -1, 1); +} + +static int +ptp_ocp_sma_store_val(struct ptp_ocp *bp, int val, enum ptp_ocp_sma_mode mode, int sma_nr) +{ + struct ptp_ocp_sma_connector *sma = &bp->sma[sma_nr - 1]; + + if (sma->fixed_dir && (mode != sma->mode || val & SMA_DISABLE)) + return -EOPNOTSUPP; + + if (sma->fixed_fcn) { + if (val != sma->default_fcn) + return -EOPNOTSUPP; + return 0; + } + + sma->disabled = !!(val & SMA_DISABLE); + + if (mode != sma->mode) { + if (mode == SMA_MODE_IN) + ptp_ocp_sma_set_output(bp, sma_nr, 0); + else + ptp_ocp_sma_set_inputs(bp, sma_nr, 0); + sma->mode = mode; + } + + if (!sma->fixed_dir) + val |= SMA_ENABLE; /* add enable bit */ + + if (sma->disabled) + val = 0; + + if (mode == SMA_MODE_IN) + val = ptp_ocp_sma_set_inputs(bp, sma_nr, val); + else + val = ptp_ocp_sma_set_output(bp, sma_nr, val); + + return val; +} + +static int +ptp_ocp_sma_store(struct ptp_ocp *bp, const char *buf, int sma_nr) +{ + struct ptp_ocp_sma_connector *sma = &bp->sma[sma_nr - 1]; + enum ptp_ocp_sma_mode mode; + int val; + + mode = sma->mode; + val = sma_parse_inputs(bp->sma_op->tbl, buf, &mode); + if (val < 0) + return val; + return ptp_ocp_sma_store_val(bp, val, mode, sma_nr); +} + +static ssize_t +sma1_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ptp_ocp *bp = dev_get_drvdata(dev); + int err; + + err = ptp_ocp_sma_store(bp, buf, 1); + return err ? err : count; +} + +static ssize_t +sma2_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ptp_ocp *bp = dev_get_drvdata(dev); + int err; + + err = ptp_ocp_sma_store(bp, buf, 2); + return err ? err : count; +} + +static ssize_t +sma3_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ptp_ocp *bp = dev_get_drvdata(dev); + int err; + + err = ptp_ocp_sma_store(bp, buf, 3); + return err ? err : count; +} + +static ssize_t +sma4_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ptp_ocp *bp = dev_get_drvdata(dev); + int err; + + err = ptp_ocp_sma_store(bp, buf, 4); + return err ? err : count; +} +static DEVICE_ATTR_RW(sma1); +static DEVICE_ATTR_RW(sma2); +static DEVICE_ATTR_RW(sma3); +static DEVICE_ATTR_RW(sma4); + +static ssize_t +available_sma_inputs_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ptp_ocp *bp = dev_get_drvdata(dev); + + return ptp_ocp_select_table_show(bp->sma_op->tbl[0], buf); +} +static DEVICE_ATTR_RO(available_sma_inputs); + +static ssize_t +available_sma_outputs_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ptp_ocp *bp = dev_get_drvdata(dev); + + return ptp_ocp_select_table_show(bp->sma_op->tbl[1], buf); +} +static DEVICE_ATTR_RO(available_sma_outputs); + +#define EXT_ATTR_RO(_group, _name, _val) \ + struct dev_ext_attribute dev_attr_##_group##_val##_##_name = \ + { __ATTR_RO(_name), (void *)_val } +#define EXT_ATTR_RW(_group, _name, _val) \ + struct dev_ext_attribute dev_attr_##_group##_val##_##_name = \ + { __ATTR_RW(_name), (void *)_val } +#define to_ext_attr(x) container_of(x, struct dev_ext_attribute, attr) + +/* period [duty [phase [polarity]]] */ +static ssize_t +signal_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct dev_ext_attribute *ea = to_ext_attr(attr); + struct ptp_ocp *bp = dev_get_drvdata(dev); + struct ptp_ocp_signal s = { }; + int gen = (uintptr_t)ea->var; + int argc, err; + char **argv; + + argv = argv_split(GFP_KERNEL, buf, &argc); + if (!argv) + return -ENOMEM; + + err = -EINVAL; + s.duty = bp->signal[gen].duty; + s.phase = bp->signal[gen].phase; + s.period = bp->signal[gen].period; + s.polarity = bp->signal[gen].polarity; + + switch (argc) { + case 4: + argc--; + err = kstrtobool(argv[argc], &s.polarity); + if (err) + goto out; + fallthrough; + case 3: + argc--; + err = kstrtou64(argv[argc], 0, &s.phase); + if (err) + goto out; + fallthrough; + case 2: + argc--; + err = kstrtoint(argv[argc], 0, &s.duty); + if (err) + goto out; + fallthrough; + case 1: + argc--; + err = kstrtou64(argv[argc], 0, &s.period); + if (err) + goto out; + break; + default: + goto out; + } + + err = ptp_ocp_signal_set(bp, gen, &s); + if (err) + goto out; + + err = ptp_ocp_signal_enable(bp->signal_out[gen], gen, s.period != 0); + +out: + argv_free(argv); + return err ? err : count; +} + +static ssize_t +signal_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct dev_ext_attribute *ea = to_ext_attr(attr); + struct ptp_ocp *bp = dev_get_drvdata(dev); + struct ptp_ocp_signal *signal; + int gen = (uintptr_t)ea->var; + struct timespec64 ts; + + signal = &bp->signal[gen]; + + ts = ktime_to_timespec64(signal->start); + + return sysfs_emit(buf, "%llu %d %llu %d %ptT TAI\n", + signal->period, signal->duty, signal->phase, signal->polarity, + &ts.tv_sec); +} +static EXT_ATTR_RW(signal, signal, 0); +static EXT_ATTR_RW(signal, signal, 1); +static EXT_ATTR_RW(signal, signal, 2); +static EXT_ATTR_RW(signal, signal, 3); + +static ssize_t +duty_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct dev_ext_attribute *ea = to_ext_attr(attr); + struct ptp_ocp *bp = dev_get_drvdata(dev); + int i = (uintptr_t)ea->var; + + return sysfs_emit(buf, "%d\n", bp->signal[i].duty); +} +static EXT_ATTR_RO(signal, duty, 0); +static EXT_ATTR_RO(signal, duty, 1); +static EXT_ATTR_RO(signal, duty, 2); +static EXT_ATTR_RO(signal, duty, 3); + +static ssize_t +period_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct dev_ext_attribute *ea = to_ext_attr(attr); + struct ptp_ocp *bp = dev_get_drvdata(dev); + int i = (uintptr_t)ea->var; + + return sysfs_emit(buf, "%llu\n", bp->signal[i].period); +} +static EXT_ATTR_RO(signal, period, 0); +static EXT_ATTR_RO(signal, period, 1); +static EXT_ATTR_RO(signal, period, 2); +static EXT_ATTR_RO(signal, period, 3); + +static ssize_t +phase_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct dev_ext_attribute *ea = to_ext_attr(attr); + struct ptp_ocp *bp = dev_get_drvdata(dev); + int i = (uintptr_t)ea->var; + + return sysfs_emit(buf, "%llu\n", bp->signal[i].phase); +} +static EXT_ATTR_RO(signal, phase, 0); +static EXT_ATTR_RO(signal, phase, 1); +static EXT_ATTR_RO(signal, phase, 2); +static EXT_ATTR_RO(signal, phase, 3); + +static ssize_t +polarity_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct dev_ext_attribute *ea = to_ext_attr(attr); + struct ptp_ocp *bp = dev_get_drvdata(dev); + int i = (uintptr_t)ea->var; + + return sysfs_emit(buf, "%d\n", bp->signal[i].polarity); +} +static EXT_ATTR_RO(signal, polarity, 0); +static EXT_ATTR_RO(signal, polarity, 1); +static EXT_ATTR_RO(signal, polarity, 2); +static EXT_ATTR_RO(signal, polarity, 3); + +static ssize_t +running_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct dev_ext_attribute *ea = to_ext_attr(attr); + struct ptp_ocp *bp = dev_get_drvdata(dev); + int i = (uintptr_t)ea->var; + + return sysfs_emit(buf, "%d\n", bp->signal[i].running); +} +static EXT_ATTR_RO(signal, running, 0); +static EXT_ATTR_RO(signal, running, 1); +static EXT_ATTR_RO(signal, running, 2); +static EXT_ATTR_RO(signal, running, 3); + +static ssize_t +start_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct dev_ext_attribute *ea = to_ext_attr(attr); + struct ptp_ocp *bp = dev_get_drvdata(dev); + int i = (uintptr_t)ea->var; + struct timespec64 ts; + + ts = ktime_to_timespec64(bp->signal[i].start); + return sysfs_emit(buf, "%llu.%lu\n", ts.tv_sec, ts.tv_nsec); +} +static EXT_ATTR_RO(signal, start, 0); +static EXT_ATTR_RO(signal, start, 1); +static EXT_ATTR_RO(signal, start, 2); +static EXT_ATTR_RO(signal, start, 3); + +static ssize_t +seconds_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct dev_ext_attribute *ea = to_ext_attr(attr); + struct ptp_ocp *bp = dev_get_drvdata(dev); + int idx = (uintptr_t)ea->var; + u32 val; + int err; + + err = kstrtou32(buf, 0, &val); + if (err) + return err; + if (val > 0xff) + return -EINVAL; + + if (val) + val = (val << 8) | 0x1; + + iowrite32(val, &bp->freq_in[idx]->ctrl); + + return count; +} + +static ssize_t +seconds_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct dev_ext_attribute *ea = to_ext_attr(attr); + struct ptp_ocp *bp = dev_get_drvdata(dev); + int idx = (uintptr_t)ea->var; + u32 val; + + val = ioread32(&bp->freq_in[idx]->ctrl); + if (val & 1) + val = (val >> 8) & 0xff; + else + val = 0; + + return sysfs_emit(buf, "%u\n", val); +} +static EXT_ATTR_RW(freq, seconds, 0); +static EXT_ATTR_RW(freq, seconds, 1); +static EXT_ATTR_RW(freq, seconds, 2); +static EXT_ATTR_RW(freq, seconds, 3); + +static ssize_t +frequency_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct dev_ext_attribute *ea = to_ext_attr(attr); + struct ptp_ocp *bp = dev_get_drvdata(dev); + int idx = (uintptr_t)ea->var; + u32 val; + + val = ioread32(&bp->freq_in[idx]->status); + if (val & FREQ_STATUS_ERROR) + return sysfs_emit(buf, "error\n"); + if (val & FREQ_STATUS_OVERRUN) + return sysfs_emit(buf, "overrun\n"); + if (val & FREQ_STATUS_VALID) + return sysfs_emit(buf, "%lu\n", val & FREQ_STATUS_MASK); + return 0; +} +static EXT_ATTR_RO(freq, frequency, 0); +static EXT_ATTR_RO(freq, frequency, 1); +static EXT_ATTR_RO(freq, frequency, 2); +static EXT_ATTR_RO(freq, frequency, 3); + +static ssize_t +ptp_ocp_tty_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct dev_ext_attribute *ea = to_ext_attr(attr); + struct ptp_ocp *bp = dev_get_drvdata(dev); + + /* + * NOTE: This output does not include a trailing newline for backward + * compatibility. Existing userspace software uses this value directly + * as a device path (e.g., "/dev/ttyS4"), and adding a newline would + * break those applications. Do not add a newline to this output. + */ + return sysfs_emit(buf, "ttyS%d", bp->port[(uintptr_t)ea->var].line); +} + +static umode_t +ptp_ocp_timecard_tty_is_visible(struct kobject *kobj, struct attribute *attr, int n) +{ + struct ptp_ocp *bp = dev_get_drvdata(kobj_to_dev(kobj)); + struct ptp_ocp_serial_port *port; + struct device_attribute *dattr; + struct dev_ext_attribute *ea; + + if (strncmp(attr->name, "tty", 3)) + return attr->mode; + + dattr = container_of(attr, struct device_attribute, attr); + ea = container_of(dattr, struct dev_ext_attribute, attr); + port = &bp->port[(uintptr_t)ea->var]; + return port->line == -1 ? 0 : 0444; +} + +#define EXT_TTY_ATTR_RO(_name, _val) \ + struct dev_ext_attribute dev_attr_tty##_name = \ + { __ATTR(tty##_name, 0444, ptp_ocp_tty_show, NULL), (void *)_val } + +static EXT_TTY_ATTR_RO(GNSS, PORT_GNSS); +static EXT_TTY_ATTR_RO(GNSS2, PORT_GNSS2); +static EXT_TTY_ATTR_RO(MAC, PORT_MAC); +static EXT_TTY_ATTR_RO(NMEA, PORT_NMEA); +static struct attribute *ptp_ocp_timecard_tty_attrs[] = { + &dev_attr_ttyGNSS.attr.attr, + &dev_attr_ttyGNSS2.attr.attr, + &dev_attr_ttyMAC.attr.attr, + &dev_attr_ttyNMEA.attr.attr, + NULL, +}; + +static const struct attribute_group ptp_ocp_timecard_tty_group = { + .name = "tty", + .attrs = ptp_ocp_timecard_tty_attrs, + .is_visible = ptp_ocp_timecard_tty_is_visible, +}; + +static ssize_t +serialnum_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ptp_ocp *bp = dev_get_drvdata(dev); + + if (!bp->has_eeprom_data) + ptp_ocp_read_eeprom(bp); + + return sysfs_emit(buf, "%pM\n", bp->serial); +} +static DEVICE_ATTR_RO(serialnum); + +static ssize_t +gnss_sync_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ptp_ocp *bp = dev_get_drvdata(dev); + ssize_t ret; + + if (bp->gnss_lost) + ret = sysfs_emit(buf, "LOST @ %ptT\n", &bp->gnss_lost); + else + ret = sysfs_emit(buf, "SYNC\n"); + + return ret; +} +static DEVICE_ATTR_RO(gnss_sync); + +static ssize_t +utc_tai_offset_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ptp_ocp *bp = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", bp->utc_tai_offset); +} + +static ssize_t +utc_tai_offset_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ptp_ocp *bp = dev_get_drvdata(dev); + int err; + u32 val; + + err = kstrtou32(buf, 0, &val); + if (err) + return err; + + ptp_ocp_utc_distribute(bp, val); + + return count; +} +static DEVICE_ATTR_RW(utc_tai_offset); + +static ssize_t +ts_window_adjust_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ptp_ocp *bp = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", bp->ts_window_adjust); +} + +static ssize_t +ts_window_adjust_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ptp_ocp *bp = dev_get_drvdata(dev); + int err; + u32 val; + + err = kstrtou32(buf, 0, &val); + if (err) + return err; + + bp->ts_window_adjust = val; + + return count; +} +static DEVICE_ATTR_RW(ts_window_adjust); + +static ssize_t +irig_b_mode_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ptp_ocp *bp = dev_get_drvdata(dev); + u32 val; + + val = ioread32(&bp->irig_out->ctrl); + val = (val >> 16) & 0x07; + return sysfs_emit(buf, "%d\n", val); +} + +static ssize_t +irig_b_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ptp_ocp *bp = dev_get_drvdata(dev); + unsigned long flags; + int err; + u32 reg; + u8 val; + + err = kstrtou8(buf, 0, &val); + if (err) + return err; + if (val > 7) + return -EINVAL; + + reg = ((val & 0x7) << 16); + + spin_lock_irqsave(&bp->lock, flags); + iowrite32(0, &bp->irig_out->ctrl); /* disable */ + iowrite32(reg, &bp->irig_out->ctrl); /* change mode */ + iowrite32(reg | IRIG_M_CTRL_ENABLE, &bp->irig_out->ctrl); + spin_unlock_irqrestore(&bp->lock, flags); + + return count; +} +static DEVICE_ATTR_RW(irig_b_mode); + +static ssize_t +clock_source_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ptp_ocp *bp = dev_get_drvdata(dev); + const char *p; + u32 select; + + select = ioread32(&bp->reg->select); + p = ptp_ocp_select_name_from_val(ptp_ocp_clock, select >> 16); + + return sysfs_emit(buf, "%s\n", p); +} + +static ssize_t +clock_source_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ptp_ocp *bp = dev_get_drvdata(dev); + unsigned long flags; + int val; + + val = ptp_ocp_select_val_from_name(ptp_ocp_clock, buf); + if (val < 0) + return val; + + spin_lock_irqsave(&bp->lock, flags); + iowrite32(val, &bp->reg->select); + spin_unlock_irqrestore(&bp->lock, flags); + + return count; +} +static DEVICE_ATTR_RW(clock_source); + +static ssize_t +available_clock_sources_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return ptp_ocp_select_table_show(ptp_ocp_clock, buf); +} +static DEVICE_ATTR_RO(available_clock_sources); + +static ssize_t +clock_status_drift_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ptp_ocp *bp = dev_get_drvdata(dev); + u32 val; + int res; + + val = ioread32(&bp->reg->status_drift); + res = (val & ~INT_MAX) ? -1 : 1; + res *= (val & INT_MAX); + return sysfs_emit(buf, "%d\n", res); +} +static DEVICE_ATTR_RO(clock_status_drift); + +static ssize_t +clock_status_offset_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ptp_ocp *bp = dev_get_drvdata(dev); + u32 val; + int res; + + val = ioread32(&bp->reg->status_offset); + res = (val & ~INT_MAX) ? -1 : 1; + res *= (val & INT_MAX); + return sysfs_emit(buf, "%d\n", res); +} +static DEVICE_ATTR_RO(clock_status_offset); + +static ssize_t +tod_correction_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ptp_ocp *bp = dev_get_drvdata(dev); + u32 val; + int res; + + val = ioread32(&bp->tod->adj_sec); + res = (val & ~INT_MAX) ? -1 : 1; + res *= (val & INT_MAX); + return sysfs_emit(buf, "%d\n", res); +} + +static ssize_t +tod_correction_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ptp_ocp *bp = dev_get_drvdata(dev); + unsigned long flags; + int err, res; + u32 val = 0; + + err = kstrtos32(buf, 0, &res); + if (err) + return err; + if (res < 0) { + res *= -1; + val |= BIT(31); + } + val |= res; + + spin_lock_irqsave(&bp->lock, flags); + iowrite32(val, &bp->tod->adj_sec); + spin_unlock_irqrestore(&bp->lock, flags); + + return count; +} +static DEVICE_ATTR_RW(tod_correction); + +#define _DEVICE_SIGNAL_GROUP_ATTRS(_nr) \ + static struct attribute *fb_timecard_signal##_nr##_attrs[] = { \ + &dev_attr_signal##_nr##_signal.attr.attr, \ + &dev_attr_signal##_nr##_duty.attr.attr, \ + &dev_attr_signal##_nr##_phase.attr.attr, \ + &dev_attr_signal##_nr##_period.attr.attr, \ + &dev_attr_signal##_nr##_polarity.attr.attr, \ + &dev_attr_signal##_nr##_running.attr.attr, \ + &dev_attr_signal##_nr##_start.attr.attr, \ + NULL, \ + } + +#define DEVICE_SIGNAL_GROUP(_name, _nr) \ + _DEVICE_SIGNAL_GROUP_ATTRS(_nr); \ + static const struct attribute_group \ + fb_timecard_signal##_nr##_group = { \ + .name = #_name, \ + .attrs = fb_timecard_signal##_nr##_attrs, \ +} + +DEVICE_SIGNAL_GROUP(gen1, 0); +DEVICE_SIGNAL_GROUP(gen2, 1); +DEVICE_SIGNAL_GROUP(gen3, 2); +DEVICE_SIGNAL_GROUP(gen4, 3); + +#define _DEVICE_FREQ_GROUP_ATTRS(_nr) \ + static struct attribute *fb_timecard_freq##_nr##_attrs[] = { \ + &dev_attr_freq##_nr##_seconds.attr.attr, \ + &dev_attr_freq##_nr##_frequency.attr.attr, \ + NULL, \ + } + +#define DEVICE_FREQ_GROUP(_name, _nr) \ + _DEVICE_FREQ_GROUP_ATTRS(_nr); \ + static const struct attribute_group \ + fb_timecard_freq##_nr##_group = { \ + .name = #_name, \ + .attrs = fb_timecard_freq##_nr##_attrs, \ +} + +DEVICE_FREQ_GROUP(freq1, 0); +DEVICE_FREQ_GROUP(freq2, 1); +DEVICE_FREQ_GROUP(freq3, 2); +DEVICE_FREQ_GROUP(freq4, 3); + +static ssize_t +disciplining_config_read(struct file *filp, struct kobject *kobj, + const struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct ptp_ocp *bp = dev_get_drvdata(kobj_to_dev(kobj)); + size_t size = OCP_ART_CONFIG_SIZE; + struct nvmem_device *nvmem; + ssize_t err; + + nvmem = ptp_ocp_nvmem_device_get(bp, NULL); + if (IS_ERR(nvmem)) + return PTR_ERR(nvmem); + + if (off > size) { + err = 0; + goto out; + } + + if (off + count > size) + count = size - off; + + // the configuration is in the very beginning of the EEPROM + err = nvmem_device_read(nvmem, off, count, buf); + if (err != count) { + err = -EFAULT; + goto out; + } + +out: + ptp_ocp_nvmem_device_put(&nvmem); + + return err; +} + +static ssize_t +disciplining_config_write(struct file *filp, struct kobject *kobj, + const struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct ptp_ocp *bp = dev_get_drvdata(kobj_to_dev(kobj)); + struct nvmem_device *nvmem; + ssize_t err; + + /* Allow write of the whole area only */ + if (off || count != OCP_ART_CONFIG_SIZE) + return -EFAULT; + + nvmem = ptp_ocp_nvmem_device_get(bp, NULL); + if (IS_ERR(nvmem)) + return PTR_ERR(nvmem); + + err = nvmem_device_write(nvmem, 0x00, count, buf); + if (err != count) + err = -EFAULT; + + ptp_ocp_nvmem_device_put(&nvmem); + + return err; +} +static const BIN_ATTR_RW(disciplining_config, OCP_ART_CONFIG_SIZE); + +static ssize_t +temperature_table_read(struct file *filp, struct kobject *kobj, + const struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct ptp_ocp *bp = dev_get_drvdata(kobj_to_dev(kobj)); + size_t size = OCP_ART_TEMP_TABLE_SIZE; + struct nvmem_device *nvmem; + ssize_t err; + + nvmem = ptp_ocp_nvmem_device_get(bp, NULL); + if (IS_ERR(nvmem)) + return PTR_ERR(nvmem); + + if (off > size) { + err = 0; + goto out; + } + + if (off + count > size) + count = size - off; + + // the configuration is in the very beginning of the EEPROM + err = nvmem_device_read(nvmem, 0x90 + off, count, buf); + if (err != count) { + err = -EFAULT; + goto out; + } + +out: + ptp_ocp_nvmem_device_put(&nvmem); + + return err; +} + +static ssize_t +temperature_table_write(struct file *filp, struct kobject *kobj, + const struct bin_attribute *bin_attr, char *buf, + loff_t off, size_t count) +{ + struct ptp_ocp *bp = dev_get_drvdata(kobj_to_dev(kobj)); + struct nvmem_device *nvmem; + ssize_t err; + + /* Allow write of the whole area only */ + if (off || count != OCP_ART_TEMP_TABLE_SIZE) + return -EFAULT; + + nvmem = ptp_ocp_nvmem_device_get(bp, NULL); + if (IS_ERR(nvmem)) + return PTR_ERR(nvmem); + + err = nvmem_device_write(nvmem, 0x90, count, buf); + if (err != count) + err = -EFAULT; + + ptp_ocp_nvmem_device_put(&nvmem); + + return err; +} +static const BIN_ATTR_RW(temperature_table, OCP_ART_TEMP_TABLE_SIZE); + +static struct attribute *fb_timecard_attrs[] = { + &dev_attr_serialnum.attr, + &dev_attr_gnss_sync.attr, + &dev_attr_clock_source.attr, + &dev_attr_available_clock_sources.attr, + &dev_attr_sma1.attr, + &dev_attr_sma2.attr, + &dev_attr_sma3.attr, + &dev_attr_sma4.attr, + &dev_attr_available_sma_inputs.attr, + &dev_attr_available_sma_outputs.attr, + &dev_attr_clock_status_drift.attr, + &dev_attr_clock_status_offset.attr, + &dev_attr_irig_b_mode.attr, + &dev_attr_utc_tai_offset.attr, + &dev_attr_ts_window_adjust.attr, + &dev_attr_tod_correction.attr, + NULL, +}; + +static const struct attribute_group fb_timecard_group = { + .attrs = fb_timecard_attrs, +}; + +static const struct ocp_attr_group fb_timecard_groups[] = { + { .cap = OCP_CAP_BASIC, .group = &fb_timecard_group }, + { .cap = OCP_CAP_BASIC, .group = &ptp_ocp_timecard_tty_group }, + { .cap = OCP_CAP_SIGNAL, .group = &fb_timecard_signal0_group }, + { .cap = OCP_CAP_SIGNAL, .group = &fb_timecard_signal1_group }, + { .cap = OCP_CAP_SIGNAL, .group = &fb_timecard_signal2_group }, + { .cap = OCP_CAP_SIGNAL, .group = &fb_timecard_signal3_group }, + { .cap = OCP_CAP_FREQ, .group = &fb_timecard_freq0_group }, + { .cap = OCP_CAP_FREQ, .group = &fb_timecard_freq1_group }, + { .cap = OCP_CAP_FREQ, .group = &fb_timecard_freq2_group }, + { .cap = OCP_CAP_FREQ, .group = &fb_timecard_freq3_group }, + { }, +}; + +static struct attribute *art_timecard_attrs[] = { + &dev_attr_serialnum.attr, + &dev_attr_clock_source.attr, + &dev_attr_available_clock_sources.attr, + &dev_attr_utc_tai_offset.attr, + &dev_attr_ts_window_adjust.attr, + &dev_attr_sma1.attr, + &dev_attr_sma2.attr, + &dev_attr_sma3.attr, + &dev_attr_sma4.attr, + &dev_attr_available_sma_inputs.attr, + &dev_attr_available_sma_outputs.attr, + NULL, +}; + +static const struct bin_attribute *const bin_art_timecard_attrs[] = { + &bin_attr_disciplining_config, + &bin_attr_temperature_table, + NULL, +}; + +static const struct attribute_group art_timecard_group = { + .attrs = art_timecard_attrs, + .bin_attrs = bin_art_timecard_attrs, +}; + +static const struct ocp_attr_group art_timecard_groups[] = { + { .cap = OCP_CAP_BASIC, .group = &art_timecard_group }, + { .cap = OCP_CAP_BASIC, .group = &ptp_ocp_timecard_tty_group }, + { }, +}; + +static struct attribute *adva_timecard_attrs[] = { + &dev_attr_serialnum.attr, + &dev_attr_gnss_sync.attr, + &dev_attr_clock_source.attr, + &dev_attr_available_clock_sources.attr, + &dev_attr_sma1.attr, + &dev_attr_sma2.attr, + &dev_attr_sma3.attr, + &dev_attr_sma4.attr, + &dev_attr_available_sma_inputs.attr, + &dev_attr_available_sma_outputs.attr, + &dev_attr_clock_status_drift.attr, + &dev_attr_clock_status_offset.attr, + &dev_attr_ts_window_adjust.attr, + &dev_attr_tod_correction.attr, + NULL, +}; + +static const struct attribute_group adva_timecard_group = { + .attrs = adva_timecard_attrs, +}; + +static const struct ocp_attr_group adva_timecard_groups[] = { + { .cap = OCP_CAP_BASIC, .group = &adva_timecard_group }, + { .cap = OCP_CAP_BASIC, .group = &ptp_ocp_timecard_tty_group }, + { .cap = OCP_CAP_SIGNAL, .group = &fb_timecard_signal0_group }, + { .cap = OCP_CAP_SIGNAL, .group = &fb_timecard_signal1_group }, + { .cap = OCP_CAP_FREQ, .group = &fb_timecard_freq0_group }, + { .cap = OCP_CAP_FREQ, .group = &fb_timecard_freq1_group }, + { }, +}; + +static struct attribute *adva_timecard_x1_attrs[] = { + &dev_attr_serialnum.attr, + &dev_attr_gnss_sync.attr, + &dev_attr_clock_source.attr, + &dev_attr_available_clock_sources.attr, + &dev_attr_sma1.attr, + &dev_attr_sma2.attr, + &dev_attr_sma3.attr, + &dev_attr_sma4.attr, + &dev_attr_available_sma_inputs.attr, + &dev_attr_available_sma_outputs.attr, + &dev_attr_clock_status_drift.attr, + &dev_attr_clock_status_offset.attr, + &dev_attr_ts_window_adjust.attr, + &dev_attr_utc_tai_offset.attr, + &dev_attr_tod_correction.attr, + NULL, +}; + +static const struct attribute_group adva_timecard_x1_group = { + .attrs = adva_timecard_x1_attrs, +}; + +static const struct ocp_attr_group adva_timecard_x1_groups[] = { + { .cap = OCP_CAP_BASIC, .group = &adva_timecard_x1_group }, + { .cap = OCP_CAP_BASIC, .group = &ptp_ocp_timecard_tty_group }, + { .cap = OCP_CAP_SIGNAL, .group = &fb_timecard_signal0_group }, + { .cap = OCP_CAP_SIGNAL, .group = &fb_timecard_signal1_group }, + { .cap = OCP_CAP_SIGNAL, .group = &fb_timecard_signal2_group }, + { .cap = OCP_CAP_SIGNAL, .group = &fb_timecard_signal3_group }, + { .cap = OCP_CAP_FREQ, .group = &fb_timecard_freq0_group }, + { .cap = OCP_CAP_FREQ, .group = &fb_timecard_freq1_group }, + { .cap = OCP_CAP_FREQ, .group = &fb_timecard_freq2_group }, + { .cap = OCP_CAP_FREQ, .group = &fb_timecard_freq3_group }, + { }, +}; + +static void +gpio_input_map(char *buf, struct ptp_ocp *bp, u16 map[][2], u16 bit, + const char *def) +{ + int i; + + for (i = 0; i < 4; i++) { + if (bp->sma[i].mode != SMA_MODE_IN) + continue; + if (map[i][0] & (1 << bit)) { + sprintf(buf, "sma%d", i + 1); + return; + } + } + if (!def) + def = "----"; + strcpy(buf, def); +} + +static void +gpio_output_map(char *buf, struct ptp_ocp *bp, u16 map[][2], u16 bit) +{ + char *ans = buf; + int i; + + strcpy(ans, "----"); + for (i = 0; i < 4; i++) { + if (bp->sma[i].mode != SMA_MODE_OUT) + continue; + if (map[i][1] & (1 << bit)) + ans += sprintf(ans, "sma%d ", i + 1); + } +} + +static void +_signal_summary_show(struct seq_file *s, struct ptp_ocp *bp, int nr) +{ + struct signal_reg __iomem *reg = bp->signal_out[nr]->mem; + struct ptp_ocp_signal *signal = &bp->signal[nr]; + char label[16]; + bool on; + u32 val; + + on = signal->running; + sprintf(label, "GEN%d", nr + 1); + seq_printf(s, "%7s: %s, period:%llu duty:%d%% phase:%llu pol:%d", + label, on ? " ON" : "OFF", + signal->period, signal->duty, signal->phase, + signal->polarity); + + val = ioread32(®->enable); + seq_printf(s, " [%x", val); + val = ioread32(®->status); + seq_printf(s, " %x]", val); + + seq_printf(s, " start:%llu\n", signal->start); +} + +static void +_frequency_summary_show(struct seq_file *s, int nr, + struct frequency_reg __iomem *reg) +{ + char label[16]; + bool on; + u32 val; + + if (!reg) + return; + + sprintf(label, "FREQ%d", nr + 1); + val = ioread32(®->ctrl); + on = val & 1; + val = (val >> 8) & 0xff; + seq_printf(s, "%7s: %s, sec:%u", + label, + on ? " ON" : "OFF", + val); + + val = ioread32(®->status); + if (val & FREQ_STATUS_ERROR) + seq_printf(s, ", error"); + if (val & FREQ_STATUS_OVERRUN) + seq_printf(s, ", overrun"); + if (val & FREQ_STATUS_VALID) + seq_printf(s, ", freq %lu Hz", val & FREQ_STATUS_MASK); + seq_printf(s, " reg:%x\n", val); +} + +static int +ptp_ocp_summary_show(struct seq_file *s, void *data) +{ + struct device *dev = s->private; + struct ptp_system_timestamp sts; + struct ts_reg __iomem *ts_reg; + char *buf, *src, *mac_src; + struct timespec64 ts; + struct ptp_ocp *bp; + u16 sma_val[4][2]; + u32 ctrl, val; + bool on, map; + int i; + + buf = (char *)__get_free_page(GFP_KERNEL); + if (!buf) + return -ENOMEM; + + bp = dev_get_drvdata(dev); + + seq_printf(s, "%7s: /dev/ptp%d\n", "PTP", ptp_clock_index(bp->ptp)); + for (i = 0; i < __PORT_COUNT; i++) { + if (bp->port[i].line != -1) + seq_printf(s, "%7s: /dev/ttyS%d\n", ptp_ocp_tty_port_name(i), + bp->port[i].line); + } + + memset(sma_val, 0xff, sizeof(sma_val)); + if (bp->sma_map1) { + u32 reg; + + reg = ioread32(&bp->sma_map1->gpio1); + sma_val[0][0] = reg & 0xffff; + sma_val[1][0] = reg >> 16; + + reg = ioread32(&bp->sma_map1->gpio2); + sma_val[2][1] = reg & 0xffff; + sma_val[3][1] = reg >> 16; + + reg = ioread32(&bp->sma_map2->gpio1); + sma_val[2][0] = reg & 0xffff; + sma_val[3][0] = reg >> 16; + + reg = ioread32(&bp->sma_map2->gpio2); + sma_val[0][1] = reg & 0xffff; + sma_val[1][1] = reg >> 16; + } + + sma1_show(dev, NULL, buf); + seq_printf(s, " sma1: %04x,%04x %s", + sma_val[0][0], sma_val[0][1], buf); + + sma2_show(dev, NULL, buf); + seq_printf(s, " sma2: %04x,%04x %s", + sma_val[1][0], sma_val[1][1], buf); + + sma3_show(dev, NULL, buf); + seq_printf(s, " sma3: %04x,%04x %s", + sma_val[2][0], sma_val[2][1], buf); + + sma4_show(dev, NULL, buf); + seq_printf(s, " sma4: %04x,%04x %s", + sma_val[3][0], sma_val[3][1], buf); + + if (bp->ts0) { + ts_reg = bp->ts0->mem; + on = ioread32(&ts_reg->enable); + src = "GNSS1"; + seq_printf(s, "%7s: %s, src: %s\n", "TS0", + on ? " ON" : "OFF", src); + } + + if (bp->ts1) { + ts_reg = bp->ts1->mem; + on = ioread32(&ts_reg->enable); + gpio_input_map(buf, bp, sma_val, 2, NULL); + seq_printf(s, "%7s: %s, src: %s\n", "TS1", + on ? " ON" : "OFF", buf); + } + + if (bp->ts2) { + ts_reg = bp->ts2->mem; + on = ioread32(&ts_reg->enable); + gpio_input_map(buf, bp, sma_val, 3, NULL); + seq_printf(s, "%7s: %s, src: %s\n", "TS2", + on ? " ON" : "OFF", buf); + } + + if (bp->ts3) { + ts_reg = bp->ts3->mem; + on = ioread32(&ts_reg->enable); + gpio_input_map(buf, bp, sma_val, 6, NULL); + seq_printf(s, "%7s: %s, src: %s\n", "TS3", + on ? " ON" : "OFF", buf); + } + + if (bp->ts4) { + ts_reg = bp->ts4->mem; + on = ioread32(&ts_reg->enable); + gpio_input_map(buf, bp, sma_val, 7, NULL); + seq_printf(s, "%7s: %s, src: %s\n", "TS4", + on ? " ON" : "OFF", buf); + } + + if (bp->pps) { + ts_reg = bp->pps->mem; + src = "PHC"; + on = ioread32(&ts_reg->enable); + map = !!(bp->pps_req_map & OCP_REQ_TIMESTAMP); + seq_printf(s, "%7s: %s, src: %s\n", "TS5", + on && map ? " ON" : "OFF", src); + + map = !!(bp->pps_req_map & OCP_REQ_PPS); + seq_printf(s, "%7s: %s, src: %s\n", "PPS", + on && map ? " ON" : "OFF", src); + } + + if (bp->fw_cap & OCP_CAP_SIGNAL) + for (i = 0; i < bp->signals_nr; i++) + _signal_summary_show(s, bp, i); + + if (bp->fw_cap & OCP_CAP_FREQ) + for (i = 0; i < bp->freq_in_nr; i++) + _frequency_summary_show(s, i, bp->freq_in[i]); + + if (bp->irig_out) { + ctrl = ioread32(&bp->irig_out->ctrl); + on = ctrl & IRIG_M_CTRL_ENABLE; + val = ioread32(&bp->irig_out->status); + gpio_output_map(buf, bp, sma_val, 4); + seq_printf(s, "%7s: %s, error: %d, mode %d, out: %s\n", "IRIG", + on ? " ON" : "OFF", val, (ctrl >> 16), buf); + } + + if (bp->irig_in) { + on = ioread32(&bp->irig_in->ctrl) & IRIG_S_CTRL_ENABLE; + val = ioread32(&bp->irig_in->status); + gpio_input_map(buf, bp, sma_val, 4, NULL); + seq_printf(s, "%7s: %s, error: %d, src: %s\n", "IRIG in", + on ? " ON" : "OFF", val, buf); + } + + if (bp->dcf_out) { + on = ioread32(&bp->dcf_out->ctrl) & DCF_M_CTRL_ENABLE; + val = ioread32(&bp->dcf_out->status); + gpio_output_map(buf, bp, sma_val, 5); + seq_printf(s, "%7s: %s, error: %d, out: %s\n", "DCF", + on ? " ON" : "OFF", val, buf); + } + + if (bp->dcf_in) { + on = ioread32(&bp->dcf_in->ctrl) & DCF_S_CTRL_ENABLE; + val = ioread32(&bp->dcf_in->status); + gpio_input_map(buf, bp, sma_val, 5, NULL); + seq_printf(s, "%7s: %s, error: %d, src: %s\n", "DCF in", + on ? " ON" : "OFF", val, buf); + } + + if (bp->nmea_out) { + on = ioread32(&bp->nmea_out->ctrl) & 1; + val = ioread32(&bp->nmea_out->status); + seq_printf(s, "%7s: %s, error: %d\n", "NMEA", + on ? " ON" : "OFF", val); + } + + /* compute src for PPS1, used below. */ + if (bp->pps_select) { + val = ioread32(&bp->pps_select->gpio1); + src = &buf[80]; + mac_src = "GNSS1"; + if (val & 0x01) { + gpio_input_map(src, bp, sma_val, 0, NULL); + mac_src = src; + } else if (val & 0x02) { + src = "MAC"; + } else if (val & 0x04) { + src = "GNSS1"; + } else { + src = "----"; + mac_src = src; + } + } else { + src = "?"; + mac_src = src; + } + seq_printf(s, "MAC PPS1 src: %s\n", mac_src); + + gpio_input_map(buf, bp, sma_val, 1, "GNSS2"); + seq_printf(s, "MAC PPS2 src: %s\n", buf); + + /* assumes automatic switchover/selection */ + val = ioread32(&bp->reg->select); + switch (val >> 16) { + case 0: + sprintf(buf, "----"); + break; + case 2: + sprintf(buf, "IRIG"); + break; + case 3: + sprintf(buf, "%s via PPS1", src); + break; + case 6: + sprintf(buf, "DCF"); + break; + default: + strcpy(buf, "unknown"); + break; + } + seq_printf(s, "%7s: %s, state: %s\n", "PHC src", buf, + bp->sync ? "sync" : "unsynced"); + + if (!ptp_ocp_gettimex(&bp->ptp_info, &ts, &sts)) { + struct timespec64 sys_ts; + s64 pre_ns, post_ns, ns; + + pre_ns = ktime_to_ns(sts.pre_sts.systime); + post_ns = ktime_to_ns(sts.post_sts.systime); + ns = (pre_ns + post_ns) / 2; + ns += (s64)bp->utc_tai_offset * NSEC_PER_SEC; + sys_ts = ns_to_timespec64(ns); + + seq_printf(s, "%7s: %ptSp == %ptS TAI\n", "PHC", &ts, &ts); + seq_printf(s, "%7s: %ptSp == %ptS UTC offset %d\n", "SYS", + &sys_ts, &sys_ts, bp->utc_tai_offset); + seq_printf(s, "%7s: PHC:SYS offset: %lld window: %lld\n", "", + timespec64_to_ns(&ts) - ns, + post_ns - pre_ns); + } + + free_page((unsigned long)buf); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ptp_ocp_summary); + +static int +ptp_ocp_tod_status_show(struct seq_file *s, void *data) +{ + struct device *dev = s->private; + struct ptp_ocp *bp; + u32 val; + int idx; + + bp = dev_get_drvdata(dev); + + val = ioread32(&bp->tod->ctrl); + if (!(val & TOD_CTRL_ENABLE)) { + seq_printf(s, "TOD Slave disabled\n"); + return 0; + } + seq_printf(s, "TOD Slave enabled, Control Register 0x%08X\n", val); + + idx = val & TOD_CTRL_PROTOCOL ? 4 : 0; + idx += (val >> 16) & 3; + seq_printf(s, "Protocol %s\n", ptp_ocp_tod_proto_name(idx)); + + idx = (val >> TOD_CTRL_GNSS_SHIFT) & TOD_CTRL_GNSS_MASK; + seq_printf(s, "GNSS %s\n", ptp_ocp_tod_gnss_name(idx)); + + val = ioread32(&bp->tod->version); + seq_printf(s, "TOD Version %d.%d.%d\n", + val >> 24, (val >> 16) & 0xff, val & 0xffff); + + val = ioread32(&bp->tod->status); + seq_printf(s, "Status register: 0x%08X\n", val); + + val = ioread32(&bp->tod->adj_sec); + idx = (val & ~INT_MAX) ? -1 : 1; + idx *= (val & INT_MAX); + seq_printf(s, "Correction seconds: %d\n", idx); + + val = ioread32(&bp->tod->utc_status); + seq_printf(s, "UTC status register: 0x%08X\n", val); + seq_printf(s, "UTC offset: %ld valid:%d\n", + val & TOD_STATUS_UTC_MASK, val & TOD_STATUS_UTC_VALID ? 1 : 0); + seq_printf(s, "Leap second info valid:%d, Leap second announce %d\n", + val & TOD_STATUS_LEAP_VALID ? 1 : 0, + val & TOD_STATUS_LEAP_ANNOUNCE ? 1 : 0); + + val = ioread32(&bp->tod->leap); + seq_printf(s, "Time to next leap second (in sec): %d\n", (s32) val); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ptp_ocp_tod_status); + +static struct dentry *ptp_ocp_debugfs_root; + +static void +ptp_ocp_debugfs_add_device(struct ptp_ocp *bp) +{ + struct dentry *d; + + d = debugfs_create_dir(dev_name(&bp->dev), ptp_ocp_debugfs_root); + bp->debug_root = d; + debugfs_create_file("summary", 0444, bp->debug_root, + &bp->dev, &ptp_ocp_summary_fops); + if (bp->tod) + debugfs_create_file("tod_status", 0444, bp->debug_root, + &bp->dev, &ptp_ocp_tod_status_fops); +} + +static void +ptp_ocp_debugfs_remove_device(struct ptp_ocp *bp) +{ + debugfs_remove_recursive(bp->debug_root); +} + +static void +ptp_ocp_debugfs_init(void) +{ + ptp_ocp_debugfs_root = debugfs_create_dir("timecard", NULL); +} + +static void +ptp_ocp_debugfs_fini(void) +{ + debugfs_remove_recursive(ptp_ocp_debugfs_root); +} + +static void +ptp_ocp_dev_release(struct device *dev) +{ + struct ptp_ocp *bp = dev_get_drvdata(dev); + + mutex_lock(&ptp_ocp_lock); + idr_remove(&ptp_ocp_idr, bp->id); + mutex_unlock(&ptp_ocp_lock); +} + +static int +ptp_ocp_device_init(struct ptp_ocp *bp, struct pci_dev *pdev) +{ + int i, err; + + mutex_lock(&ptp_ocp_lock); + err = idr_alloc(&ptp_ocp_idr, bp, 0, 0, GFP_KERNEL); + mutex_unlock(&ptp_ocp_lock); + if (err < 0) { + dev_err(&pdev->dev, "idr_alloc failed: %d\n", err); + return err; + } + bp->id = err; + + bp->ptp_info = ptp_ocp_clock_info; + spin_lock_init(&bp->lock); + + for (i = 0; i < __PORT_COUNT; i++) + bp->port[i].line = -1; + + bp->pdev = pdev; + + device_initialize(&bp->dev); + dev_set_name(&bp->dev, "ocp%d", bp->id); + bp->dev.class = &timecard_class; + bp->dev.parent = &pdev->dev; + bp->dev.release = ptp_ocp_dev_release; + dev_set_drvdata(&bp->dev, bp); + + err = device_add(&bp->dev); + if (err) { + dev_err(&bp->dev, "device add failed: %d\n", err); + goto out; + } + + pci_set_drvdata(pdev, bp); + + return 0; + +out: + put_device(&bp->dev); + return err; +} + +static void +ptp_ocp_symlink(struct ptp_ocp *bp, struct device *child, const char *link) +{ + struct device *dev = &bp->dev; + + if (sysfs_create_link(&dev->kobj, &child->kobj, link)) + dev_err(dev, "%s symlink failed\n", link); +} + +static void +ptp_ocp_link_child(struct ptp_ocp *bp, const char *name, const char *link) +{ + struct device *dev, *child; + + dev = &bp->pdev->dev; + + child = device_find_child_by_name(dev, name); + if (!child) { + dev_err(dev, "Could not find device %s\n", name); + return; + } + + ptp_ocp_symlink(bp, child, link); + put_device(child); +} + +static int +ptp_ocp_complete(struct ptp_ocp *bp) +{ + struct pps_device *pps; + char buf[32]; + + sprintf(buf, "ptp%d", ptp_clock_index(bp->ptp)); + ptp_ocp_link_child(bp, buf, "ptp"); + + pps = pps_lookup_dev(bp->ptp); + if (pps) + ptp_ocp_symlink(bp, &pps->dev, "pps"); + + ptp_ocp_debugfs_add_device(bp); + + return 0; +} + +static void +ptp_ocp_phc_info(struct ptp_ocp *bp) +{ + struct timespec64 ts; + u32 version, select; + + version = ioread32(&bp->reg->version); + select = ioread32(&bp->reg->select); + dev_info(&bp->pdev->dev, "Version %d.%d.%d, clock %s, device ptp%d\n", + version >> 24, (version >> 16) & 0xff, version & 0xffff, + ptp_ocp_select_name_from_val(ptp_ocp_clock, select >> 16), + ptp_clock_index(bp->ptp)); + + if (!ptp_ocp_gettimex(&bp->ptp_info, &ts, NULL)) + dev_info(&bp->pdev->dev, "Time: %ptSp, %s\n", + &ts, bp->sync ? "in-sync" : "UNSYNCED"); +} + +static void +ptp_ocp_serial_info(struct device *dev, const char *name, int port, int baud) +{ + if (port != -1) + dev_info(dev, "%5s: /dev/ttyS%-2d @ %6d\n", name, port, baud); +} + +static void +ptp_ocp_info(struct ptp_ocp *bp) +{ + static int nmea_baud[] = { + 1200, 2400, 4800, 9600, 19200, 38400, + 57600, 115200, 230400, 460800, 921600, + 1000000, 2000000 + }; + struct device *dev = &bp->pdev->dev; + u32 reg; + int i; + + ptp_ocp_phc_info(bp); + + for (i = 0; i < __PORT_COUNT; i++) { + if (i == PORT_NMEA && bp->nmea_out && bp->port[PORT_NMEA].line != -1) { + bp->port[PORT_NMEA].baud = -1; + + reg = ioread32(&bp->nmea_out->uart_baud); + if (reg < ARRAY_SIZE(nmea_baud)) + bp->port[PORT_NMEA].baud = nmea_baud[reg]; + } + ptp_ocp_serial_info(dev, ptp_ocp_tty_port_name(i), bp->port[i].line, + bp->port[i].baud); + } +} + +static void +ptp_ocp_detach_sysfs(struct ptp_ocp *bp) +{ + struct device *dev = &bp->dev; + + sysfs_remove_link(&dev->kobj, "ptp"); + sysfs_remove_link(&dev->kobj, "pps"); +} + +static void +ptp_ocp_detach(struct ptp_ocp *bp) +{ + int i; + + ptp_ocp_debugfs_remove_device(bp); + ptp_ocp_detach_sysfs(bp); + ptp_ocp_attr_group_del(bp); + timer_delete_sync(&bp->watchdog); + /* Disable interrupts on all timestampers */ + if (bp->ts0) + ptp_ocp_ts_enable(bp->ts0, 0, false); + if (bp->ts1) + ptp_ocp_ts_enable(bp->ts1, 0, false); + if (bp->ts2) + ptp_ocp_ts_enable(bp->ts2, 0, false); + if (bp->ts3) + ptp_ocp_ts_enable(bp->ts3, 0, false); + if (bp->ts4) + ptp_ocp_ts_enable(bp->ts4, 0, false); + if (bp->pps) + ptp_ocp_ts_enable(bp->pps, ~0, false); + if (bp->ptp) + ptp_clock_unregister(bp->ptp); + kfree(bp->ptp_info.pin_config); + ptp_ocp_unregister_ext(bp->ts0); + ptp_ocp_unregister_ext(bp->ts1); + ptp_ocp_unregister_ext(bp->ts2); + ptp_ocp_unregister_ext(bp->ts3); + ptp_ocp_unregister_ext(bp->ts4); + ptp_ocp_unregister_ext(bp->pps); + for (i = 0; i < 4; i++) + ptp_ocp_unregister_ext(bp->signal_out[i]); + for (i = 0; i < __PORT_COUNT; i++) + if (bp->port[i].line != -1) + serial8250_unregister_port(bp->port[i].line); + platform_device_unregister(bp->spi_flash); + platform_device_unregister(bp->i2c_ctrl); + if (bp->i2c_clk) + clk_hw_unregister_fixed_rate(bp->i2c_clk); + if (bp->n_irqs) + pci_free_irq_vectors(bp->pdev); + device_unregister(&bp->dev); +} + +static int +ptp_ocp_dpll_lock_status_get(const struct dpll_device *dpll, void *priv, + enum dpll_lock_status *status, + enum dpll_lock_status_error *status_error, + struct netlink_ext_ack *extack) +{ + struct ptp_ocp *bp = priv; + + *status = bp->sync ? DPLL_LOCK_STATUS_LOCKED : DPLL_LOCK_STATUS_UNLOCKED; + + return 0; +} + +static int ptp_ocp_dpll_state_get(const struct dpll_pin *pin, void *pin_priv, + const struct dpll_device *dpll, void *priv, + enum dpll_pin_state *state, + struct netlink_ext_ack *extack) +{ + struct ptp_ocp *bp = priv; + int idx; + + if (bp->pps_select) { + idx = ioread32(&bp->pps_select->gpio1); + *state = (&bp->sma[idx] == pin_priv) ? DPLL_PIN_STATE_CONNECTED : + DPLL_PIN_STATE_SELECTABLE; + return 0; + } + NL_SET_ERR_MSG(extack, "pin selection is not supported on current HW"); + return -EINVAL; +} + +static int ptp_ocp_dpll_mode_get(const struct dpll_device *dpll, void *priv, + enum dpll_mode *mode, struct netlink_ext_ack *extack) +{ + *mode = DPLL_MODE_AUTOMATIC; + return 0; +} + +static int ptp_ocp_dpll_direction_get(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *priv, + enum dpll_pin_direction *direction, + struct netlink_ext_ack *extack) +{ + struct ptp_ocp_sma_connector *sma = pin_priv; + + *direction = sma->mode == SMA_MODE_IN ? + DPLL_PIN_DIRECTION_INPUT : + DPLL_PIN_DIRECTION_OUTPUT; + return 0; +} + +static int ptp_ocp_dpll_direction_set(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_direction direction, + struct netlink_ext_ack *extack) +{ + struct ptp_ocp_sma_connector *sma = pin_priv; + struct ptp_ocp *bp = dpll_priv; + enum ptp_ocp_sma_mode mode; + int sma_nr = (sma - bp->sma); + + if (sma->fixed_dir) + return -EOPNOTSUPP; + mode = direction == DPLL_PIN_DIRECTION_INPUT ? + SMA_MODE_IN : SMA_MODE_OUT; + return ptp_ocp_sma_store_val(bp, 0, mode, sma_nr + 1); +} + +static int ptp_ocp_dpll_frequency_set(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, u64 frequency, + struct netlink_ext_ack *extack) +{ + struct ptp_ocp_sma_connector *sma = pin_priv; + struct ptp_ocp *bp = dpll_priv; + const struct ocp_selector *tbl; + int sma_nr = (sma - bp->sma); + int i; + + if (sma->fixed_fcn) + return -EOPNOTSUPP; + + tbl = bp->sma_op->tbl[sma->mode]; + for (i = 0; tbl[i].name; i++) + if (tbl[i].frequency == frequency) + return ptp_ocp_sma_store_val(bp, i, sma->mode, sma_nr + 1); + return -EINVAL; +} + +static int ptp_ocp_dpll_frequency_get(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, u64 *frequency, + struct netlink_ext_ack *extack) +{ + struct ptp_ocp_sma_connector *sma = pin_priv; + struct ptp_ocp *bp = dpll_priv; + const struct ocp_selector *tbl; + int sma_nr = (sma - bp->sma); + u32 val; + int i; + + val = bp->sma_op->get(bp, sma_nr + 1); + tbl = bp->sma_op->tbl[sma->mode]; + for (i = 0; tbl[i].name; i++) + if (val == tbl[i].value) { + *frequency = tbl[i].frequency; + return 0; + } + + return -EINVAL; +} + +static const struct dpll_device_ops dpll_ops = { + .lock_status_get = ptp_ocp_dpll_lock_status_get, + .mode_get = ptp_ocp_dpll_mode_get, +}; + +static const struct dpll_pin_ops dpll_pins_ops = { + .frequency_get = ptp_ocp_dpll_frequency_get, + .frequency_set = ptp_ocp_dpll_frequency_set, + .direction_get = ptp_ocp_dpll_direction_get, + .direction_set = ptp_ocp_dpll_direction_set, + .state_on_dpll_get = ptp_ocp_dpll_state_get, +}; + +static void +ptp_ocp_sync_work(struct work_struct *work) +{ + struct ptp_ocp *bp; + bool sync; + + bp = container_of(work, struct ptp_ocp, sync_work.work); + sync = !!(ioread32(&bp->reg->status) & OCP_STATUS_IN_SYNC); + + if (bp->sync != sync) + dpll_device_change_ntf(bp->dpll); + + bp->sync = sync; + + queue_delayed_work(system_power_efficient_wq, &bp->sync_work, HZ); +} + +static int +ptp_ocp_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct devlink *devlink; + struct ptp_ocp *bp; + int err, i; + u64 clkid; + + devlink = devlink_alloc(&ptp_ocp_devlink_ops, sizeof(*bp), &pdev->dev); + if (!devlink) { + dev_err(&pdev->dev, "devlink_alloc failed\n"); + return -ENOMEM; + } + + err = pci_enable_device(pdev); + if (err) { + dev_err(&pdev->dev, "pci_enable_device\n"); + goto out_free; + } + + bp = devlink_priv(devlink); + err = ptp_ocp_device_init(bp, pdev); + if (err) + goto out_disable; + + INIT_DELAYED_WORK(&bp->sync_work, ptp_ocp_sync_work); + + /* compat mode. + * Older FPGA firmware only returns 2 irq's. + * allow this - if not all of the IRQ's are returned, skip the + * extra devices and just register the clock. + */ + err = pci_alloc_irq_vectors(pdev, 1, 17, PCI_IRQ_MSI | PCI_IRQ_MSIX); + if (err < 0) { + dev_err(&pdev->dev, "alloc_irq_vectors err: %d\n", err); + goto out; + } + bp->n_irqs = err; + pci_set_master(pdev); + + err = ptp_ocp_register_resources(bp, id->driver_data); + if (err) + goto out; + + bp->ptp = ptp_clock_register(&bp->ptp_info, &pdev->dev); + if (IS_ERR(bp->ptp)) { + err = PTR_ERR(bp->ptp); + dev_err(&pdev->dev, "ptp_clock_register: %d\n", err); + bp->ptp = NULL; + goto out; + } + + err = ptp_ocp_complete(bp); + if (err) + goto out; + + ptp_ocp_info(bp); + devlink_register(devlink); + + clkid = pci_get_dsn(pdev); + bp->dpll = dpll_device_get(clkid, 0, THIS_MODULE, &bp->tracker); + if (IS_ERR(bp->dpll)) { + err = PTR_ERR(bp->dpll); + dev_err(&pdev->dev, "dpll_device_alloc failed\n"); + goto out; + } + + err = dpll_device_register(bp->dpll, DPLL_TYPE_PPS, &dpll_ops, bp); + if (err) + goto out; + + for (i = 0; i < OCP_SMA_NUM; i++) { + bp->sma[i].dpll_pin = dpll_pin_get(clkid, i, THIS_MODULE, + &bp->sma[i].dpll_prop, + &bp->sma[i].tracker); + if (IS_ERR(bp->sma[i].dpll_pin)) { + err = PTR_ERR(bp->sma[i].dpll_pin); + goto out_dpll; + } + + err = dpll_pin_register(bp->dpll, bp->sma[i].dpll_pin, &dpll_pins_ops, + &bp->sma[i]); + if (err) { + dpll_pin_put(bp->sma[i].dpll_pin, &bp->sma[i].tracker); + goto out_dpll; + } + } + queue_delayed_work(system_power_efficient_wq, &bp->sync_work, HZ); + + return 0; +out_dpll: + while (i--) { + dpll_pin_unregister(bp->dpll, bp->sma[i].dpll_pin, &dpll_pins_ops, &bp->sma[i]); + dpll_pin_put(bp->sma[i].dpll_pin, &bp->sma[i].tracker); + } + dpll_device_put(bp->dpll, &bp->tracker); +out: + ptp_ocp_detach(bp); +out_disable: + pci_disable_device(pdev); +out_free: + devlink_free(devlink); + return err; +} + +static void +ptp_ocp_remove(struct pci_dev *pdev) +{ + struct ptp_ocp *bp = pci_get_drvdata(pdev); + struct devlink *devlink = priv_to_devlink(bp); + int i; + + cancel_delayed_work_sync(&bp->sync_work); + for (i = 0; i < OCP_SMA_NUM; i++) { + if (bp->sma[i].dpll_pin) { + dpll_pin_unregister(bp->dpll, bp->sma[i].dpll_pin, &dpll_pins_ops, &bp->sma[i]); + dpll_pin_put(bp->sma[i].dpll_pin, &bp->sma[i].tracker); + } + } + dpll_device_unregister(bp->dpll, &dpll_ops, bp); + dpll_device_put(bp->dpll, &bp->tracker); + devlink_unregister(devlink); + ptp_ocp_detach(bp); + pci_disable_device(pdev); + + devlink_free(devlink); +} + +static struct pci_driver ptp_ocp_driver = { + .name = KBUILD_MODNAME, + .id_table = ptp_ocp_pcidev_id, + .probe = ptp_ocp_probe, + .remove = ptp_ocp_remove, + .shutdown = ptp_ocp_remove, +}; + +static int +ptp_ocp_i2c_notifier_call(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct device *dev, *child = data; + struct ptp_ocp *bp; + bool add; + + switch (action) { + case BUS_NOTIFY_ADD_DEVICE: + case BUS_NOTIFY_DEL_DEVICE: + add = action == BUS_NOTIFY_ADD_DEVICE; + break; + default: + return 0; + } + + if (!i2c_verify_adapter(child)) + return 0; + + dev = child; + while ((dev = dev->parent)) + if (dev->driver && !strcmp(dev->driver->name, KBUILD_MODNAME)) + goto found; + return 0; + +found: + bp = dev_get_drvdata(dev); + if (add) + ptp_ocp_symlink(bp, child, "i2c"); + else + sysfs_remove_link(&bp->dev.kobj, "i2c"); + + return 0; +} + +static struct notifier_block ptp_ocp_i2c_notifier = { + .notifier_call = ptp_ocp_i2c_notifier_call, +}; + +static int __init +ptp_ocp_init(void) +{ + const char *what; + int err; + + ptp_ocp_debugfs_init(); + + what = "timecard class"; + err = class_register(&timecard_class); + if (err) + goto out; + + what = "i2c notifier"; + err = bus_register_notifier(&i2c_bus_type, &ptp_ocp_i2c_notifier); + if (err) + goto out_notifier; + + what = "ptp_ocp driver"; + err = pci_register_driver(&ptp_ocp_driver); + if (err) + goto out_register; + + return 0; + +out_register: + bus_unregister_notifier(&i2c_bus_type, &ptp_ocp_i2c_notifier); +out_notifier: + class_unregister(&timecard_class); +out: + ptp_ocp_debugfs_fini(); + pr_err(KBUILD_MODNAME ": failed to register %s: %d\n", what, err); + return err; +} + +static void __exit +ptp_ocp_fini(void) +{ + bus_unregister_notifier(&i2c_bus_type, &ptp_ocp_i2c_notifier); + pci_unregister_driver(&ptp_ocp_driver); + class_unregister(&timecard_class); + ptp_ocp_debugfs_fini(); +} + +module_init(ptp_ocp_init); +module_exit(ptp_ocp_fini); + +MODULE_DESCRIPTION("OpenCompute TimeCard driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/ptp/ptp_pch.c b/drivers/ptp/ptp_pch.c new file mode 100644 index 000000000..f854da2fd --- /dev/null +++ b/drivers/ptp/ptp_pch.c @@ -0,0 +1,545 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PTP 1588 clock using the EG20T PCH + * + * Copyright (C) 2010 OMICRON electronics GmbH + * Copyright (C) 2011-2012 LAPIS SEMICONDUCTOR Co., LTD. + * + * This code was derived from the IXP46X driver. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define STATION_ADDR_LEN 20 +#define PCI_DEVICE_ID_PCH_1588 0x8819 +#define IO_MEM_BAR 1 + +#define DEFAULT_ADDEND 0xA0000000 +#define TICKS_NS_SHIFT 5 +#define N_EXT_TS 2 + +enum pch_status { + PCH_SUCCESS, + PCH_INVALIDPARAM, + PCH_NOTIMESTAMP, + PCH_INTERRUPTMODEINUSE, + PCH_FAILED, + PCH_UNSUPPORTED, +}; + +/* + * struct pch_ts_regs - IEEE 1588 registers + */ +struct pch_ts_regs { + u32 control; + u32 event; + u32 addend; + u32 accum; + u32 test; + u32 ts_compare; + u32 rsystime_lo; + u32 rsystime_hi; + u32 systime_lo; + u32 systime_hi; + u32 trgt_lo; + u32 trgt_hi; + u32 asms_lo; + u32 asms_hi; + u32 amms_lo; + u32 amms_hi; + u32 ch_control; + u32 ch_event; + u32 tx_snap_lo; + u32 tx_snap_hi; + u32 rx_snap_lo; + u32 rx_snap_hi; + u32 src_uuid_lo; + u32 src_uuid_hi; + u32 can_status; + u32 can_snap_lo; + u32 can_snap_hi; + u32 ts_sel; + u32 ts_st[6]; + u32 reserve1[14]; + u32 stl_max_set_en; + u32 stl_max_set; + u32 reserve2[13]; + u32 srst; +}; + +#define PCH_TSC_RESET (1 << 0) +#define PCH_TSC_TTM_MASK (1 << 1) +#define PCH_TSC_ASMS_MASK (1 << 2) +#define PCH_TSC_AMMS_MASK (1 << 3) +#define PCH_TSC_PPSM_MASK (1 << 4) +#define PCH_TSE_TTIPEND (1 << 1) +#define PCH_TSE_SNS (1 << 2) +#define PCH_TSE_SNM (1 << 3) +#define PCH_TSE_PPS (1 << 4) +#define PCH_CC_MM (1 << 0) +#define PCH_CC_TA (1 << 1) + +#define PCH_CC_MODE_SHIFT 16 +#define PCH_CC_MODE_MASK 0x001F0000 +#define PCH_CC_VERSION (1 << 31) +#define PCH_CE_TXS (1 << 0) +#define PCH_CE_RXS (1 << 1) +#define PCH_CE_OVR (1 << 0) +#define PCH_CE_VAL (1 << 1) +#define PCH_ECS_ETH (1 << 0) + +#define PCH_ECS_CAN (1 << 1) + +#define PCH_IEEE1588_ETH (1 << 0) +#define PCH_IEEE1588_CAN (1 << 1) + +/* + * struct pch_dev - Driver private data + */ +struct pch_dev { + struct pch_ts_regs __iomem *regs; + struct ptp_clock *ptp_clock; + struct ptp_clock_info caps; + int exts0_enabled; + int exts1_enabled; + + u32 irq; + struct pci_dev *pdev; + spinlock_t register_lock; +}; + +/* + * struct pch_params - 1588 module parameter + */ +struct pch_params { + u8 station[STATION_ADDR_LEN]; +}; + +/* structure to hold the module parameters */ +static struct pch_params pch_param = { + "00:00:00:00:00:00" +}; + +/* + * Register access functions + */ +static inline void pch_eth_enable_set(struct pch_dev *chip) +{ + u32 val; + /* SET the eth_enable bit */ + val = ioread32(&chip->regs->ts_sel) | (PCH_ECS_ETH); + iowrite32(val, (&chip->regs->ts_sel)); +} + +static u64 pch_systime_read(struct pch_ts_regs __iomem *regs) +{ + u64 ns; + + ns = ioread64_lo_hi(®s->systime_lo); + + return ns << TICKS_NS_SHIFT; +} + +static void pch_systime_write(struct pch_ts_regs __iomem *regs, u64 ns) +{ + iowrite64_lo_hi(ns >> TICKS_NS_SHIFT, ®s->systime_lo); +} + +static inline void pch_block_reset(struct pch_dev *chip) +{ + u32 val; + /* Reset Hardware Assist block */ + val = ioread32(&chip->regs->control) | PCH_TSC_RESET; + iowrite32(val, (&chip->regs->control)); + val = val & ~PCH_TSC_RESET; + iowrite32(val, (&chip->regs->control)); +} + +void pch_ch_control_write(struct pci_dev *pdev, u32 val) +{ + struct pch_dev *chip = pci_get_drvdata(pdev); + + iowrite32(val, (&chip->regs->ch_control)); +} +EXPORT_SYMBOL(pch_ch_control_write); + +u32 pch_ch_event_read(struct pci_dev *pdev) +{ + struct pch_dev *chip = pci_get_drvdata(pdev); + u32 val; + + val = ioread32(&chip->regs->ch_event); + + return val; +} +EXPORT_SYMBOL(pch_ch_event_read); + +void pch_ch_event_write(struct pci_dev *pdev, u32 val) +{ + struct pch_dev *chip = pci_get_drvdata(pdev); + + iowrite32(val, (&chip->regs->ch_event)); +} +EXPORT_SYMBOL(pch_ch_event_write); + +u32 pch_src_uuid_lo_read(struct pci_dev *pdev) +{ + struct pch_dev *chip = pci_get_drvdata(pdev); + u32 val; + + val = ioread32(&chip->regs->src_uuid_lo); + + return val; +} +EXPORT_SYMBOL(pch_src_uuid_lo_read); + +u32 pch_src_uuid_hi_read(struct pci_dev *pdev) +{ + struct pch_dev *chip = pci_get_drvdata(pdev); + u32 val; + + val = ioread32(&chip->regs->src_uuid_hi); + + return val; +} +EXPORT_SYMBOL(pch_src_uuid_hi_read); + +u64 pch_rx_snap_read(struct pci_dev *pdev) +{ + struct pch_dev *chip = pci_get_drvdata(pdev); + u64 ns; + + ns = ioread64_lo_hi(&chip->regs->rx_snap_lo); + + return ns << TICKS_NS_SHIFT; +} +EXPORT_SYMBOL(pch_rx_snap_read); + +u64 pch_tx_snap_read(struct pci_dev *pdev) +{ + struct pch_dev *chip = pci_get_drvdata(pdev); + u64 ns; + + ns = ioread64_lo_hi(&chip->regs->tx_snap_lo); + + return ns << TICKS_NS_SHIFT; +} +EXPORT_SYMBOL(pch_tx_snap_read); + +/* This function enables all 64 bits in system time registers [high & low]. +This is a work-around for non continuous value in the SystemTime Register*/ +static void pch_set_system_time_count(struct pch_dev *chip) +{ + iowrite32(0x01, &chip->regs->stl_max_set_en); + iowrite32(0xFFFFFFFF, &chip->regs->stl_max_set); + iowrite32(0x00, &chip->regs->stl_max_set_en); +} + +static void pch_reset(struct pch_dev *chip) +{ + /* Reset Hardware Assist */ + pch_block_reset(chip); + + /* enable all 32 bits in system time registers */ + pch_set_system_time_count(chip); +} + +/** + * pch_set_station_address() - This API sets the station address used by + * IEEE 1588 hardware when looking at PTP + * traffic on the ethernet interface + * @addr: dress which contain the column separated address to be used. + * @pdev: PCI device. + */ +int pch_set_station_address(u8 *addr, struct pci_dev *pdev) +{ + struct pch_dev *chip = pci_get_drvdata(pdev); + bool valid; + u64 mac; + + /* Verify the parameter */ + if ((chip->regs == NULL) || addr == (u8 *)NULL) { + dev_err(&pdev->dev, + "invalid params returning PCH_INVALIDPARAM\n"); + return PCH_INVALIDPARAM; + } + + valid = mac_pton(addr, (u8 *)&mac); + if (!valid) { + dev_err(&pdev->dev, "invalid params returning PCH_INVALIDPARAM\n"); + return PCH_INVALIDPARAM; + } + + dev_dbg(&pdev->dev, "invoking pch_station_set\n"); + iowrite64_lo_hi(mac, &chip->regs->ts_st); + return 0; +} +EXPORT_SYMBOL(pch_set_station_address); + +/* + * Interrupt service routine + */ +static irqreturn_t isr(int irq, void *priv) +{ + struct pch_dev *pch_dev = priv; + struct pch_ts_regs __iomem *regs = pch_dev->regs; + struct ptp_clock_event event; + u32 ack = 0, val; + + val = ioread32(®s->event); + + if (val & PCH_TSE_SNS) { + ack |= PCH_TSE_SNS; + if (pch_dev->exts0_enabled) { + event.type = PTP_CLOCK_EXTTS; + event.index = 0; + event.timestamp = ioread64_hi_lo(®s->asms_hi); + event.timestamp <<= TICKS_NS_SHIFT; + ptp_clock_event(pch_dev->ptp_clock, &event); + } + } + + if (val & PCH_TSE_SNM) { + ack |= PCH_TSE_SNM; + if (pch_dev->exts1_enabled) { + event.type = PTP_CLOCK_EXTTS; + event.index = 1; + event.timestamp = ioread64_hi_lo(®s->asms_hi); + event.timestamp <<= TICKS_NS_SHIFT; + ptp_clock_event(pch_dev->ptp_clock, &event); + } + } + + if (val & PCH_TSE_TTIPEND) + ack |= PCH_TSE_TTIPEND; /* this bit seems to be always set */ + + if (ack) { + iowrite32(ack, ®s->event); + return IRQ_HANDLED; + } else + return IRQ_NONE; +} + +/* + * PTP clock operations + */ + +static int ptp_pch_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) +{ + u32 addend; + struct pch_dev *pch_dev = container_of(ptp, struct pch_dev, caps); + struct pch_ts_regs __iomem *regs = pch_dev->regs; + + addend = adjust_by_scaled_ppm(DEFAULT_ADDEND, scaled_ppm); + + iowrite32(addend, ®s->addend); + + return 0; +} + +static int ptp_pch_adjtime(struct ptp_clock_info *ptp, s64 delta) +{ + s64 now; + unsigned long flags; + struct pch_dev *pch_dev = container_of(ptp, struct pch_dev, caps); + struct pch_ts_regs __iomem *regs = pch_dev->regs; + + spin_lock_irqsave(&pch_dev->register_lock, flags); + now = pch_systime_read(regs); + now += delta; + pch_systime_write(regs, now); + spin_unlock_irqrestore(&pch_dev->register_lock, flags); + + return 0; +} + +static int ptp_pch_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts) +{ + u64 ns; + unsigned long flags; + struct pch_dev *pch_dev = container_of(ptp, struct pch_dev, caps); + struct pch_ts_regs __iomem *regs = pch_dev->regs; + + spin_lock_irqsave(&pch_dev->register_lock, flags); + ns = pch_systime_read(regs); + spin_unlock_irqrestore(&pch_dev->register_lock, flags); + + *ts = ns_to_timespec64(ns); + return 0; +} + +static int ptp_pch_settime(struct ptp_clock_info *ptp, + const struct timespec64 *ts) +{ + u64 ns; + unsigned long flags; + struct pch_dev *pch_dev = container_of(ptp, struct pch_dev, caps); + struct pch_ts_regs __iomem *regs = pch_dev->regs; + + ns = timespec64_to_ns(ts); + + spin_lock_irqsave(&pch_dev->register_lock, flags); + pch_systime_write(regs, ns); + spin_unlock_irqrestore(&pch_dev->register_lock, flags); + + return 0; +} + +static int ptp_pch_enable(struct ptp_clock_info *ptp, + struct ptp_clock_request *rq, int on) +{ + struct pch_dev *pch_dev = container_of(ptp, struct pch_dev, caps); + + switch (rq->type) { + case PTP_CLK_REQ_EXTTS: + switch (rq->extts.index) { + case 0: + pch_dev->exts0_enabled = on ? 1 : 0; + break; + case 1: + pch_dev->exts1_enabled = on ? 1 : 0; + break; + default: + return -EINVAL; + } + return 0; + default: + break; + } + + return -EOPNOTSUPP; +} + +static const struct ptp_clock_info ptp_pch_caps = { + .owner = THIS_MODULE, + .name = "PCH timer", + .max_adj = 50000000, + .n_ext_ts = N_EXT_TS, + .n_pins = 0, + .pps = 0, + .adjfine = ptp_pch_adjfine, + .adjtime = ptp_pch_adjtime, + .gettime64 = ptp_pch_gettime, + .settime64 = ptp_pch_settime, + .enable = ptp_pch_enable, +}; + +static void pch_remove(struct pci_dev *pdev) +{ + struct pch_dev *chip = pci_get_drvdata(pdev); + + free_irq(pdev->irq, chip); + ptp_clock_unregister(chip->ptp_clock); +} + +static s32 +pch_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + s32 ret; + unsigned long flags; + struct pch_dev *chip; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + + /* enable the 1588 pci device */ + ret = pcim_enable_device(pdev); + if (ret != 0) { + dev_err(&pdev->dev, "could not enable the pci device\n"); + return ret; + } + + /* get the virtual address to the 1588 registers */ + chip->regs = pcim_iomap_region(pdev, IO_MEM_BAR, KBUILD_MODNAME); + ret = PTR_ERR_OR_ZERO(chip->regs); + if (ret) { + dev_err(&pdev->dev, "could not locate IO memory address\n"); + return ret; + } + + chip->caps = ptp_pch_caps; + chip->ptp_clock = ptp_clock_register(&chip->caps, &pdev->dev); + if (IS_ERR(chip->ptp_clock)) + return PTR_ERR(chip->ptp_clock); + + spin_lock_init(&chip->register_lock); + + ret = request_irq(pdev->irq, &isr, IRQF_SHARED, KBUILD_MODNAME, chip); + if (ret != 0) { + dev_err(&pdev->dev, "failed to get irq %d\n", pdev->irq); + goto err_req_irq; + } + + /* indicate success */ + chip->irq = pdev->irq; + chip->pdev = pdev; + pci_set_drvdata(pdev, chip); + + spin_lock_irqsave(&chip->register_lock, flags); + /* reset the ieee1588 h/w */ + pch_reset(chip); + + iowrite32(DEFAULT_ADDEND, &chip->regs->addend); + iowrite64_lo_hi(1, &chip->regs->trgt_lo); + iowrite32(PCH_TSE_TTIPEND, &chip->regs->event); + + pch_eth_enable_set(chip); + + if (strcmp(pch_param.station, "00:00:00:00:00:00") != 0) { + if (pch_set_station_address(pch_param.station, pdev) != 0) { + dev_err(&pdev->dev, + "Invalid station address parameter\n" + "Module loaded but station address not set correctly\n" + ); + } + } + spin_unlock_irqrestore(&chip->register_lock, flags); + return 0; + +err_req_irq: + ptp_clock_unregister(chip->ptp_clock); + + dev_err(&pdev->dev, "probe failed(ret=0x%x)\n", ret); + + return ret; +} + +static const struct pci_device_id pch_ieee1588_pcidev_id[] = { + { + .vendor = PCI_VENDOR_ID_INTEL, + .device = PCI_DEVICE_ID_PCH_1588 + }, + {0} +}; +MODULE_DEVICE_TABLE(pci, pch_ieee1588_pcidev_id); + +static struct pci_driver pch_driver = { + .name = KBUILD_MODNAME, + .id_table = pch_ieee1588_pcidev_id, + .probe = pch_probe, + .remove = pch_remove, +}; +module_pci_driver(pch_driver); + +module_param_string(station, + pch_param.station, sizeof(pch_param.station), 0444); +MODULE_PARM_DESC(station, + "IEEE 1588 station address to use - colon separated hex values"); + +MODULE_AUTHOR("LAPIS SEMICONDUCTOR, "); +MODULE_DESCRIPTION("PTP clock using the EG20T timer"); +MODULE_LICENSE("GPL"); diff --git a/drivers/ptp/ptp_private.h b/drivers/ptp/ptp_private.h new file mode 100644 index 000000000..db4039d64 --- /dev/null +++ b/drivers/ptp/ptp_private.h @@ -0,0 +1,175 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * PTP 1588 clock support - private declarations for the core module. + * + * Copyright (C) 2010 OMICRON electronics GmbH + */ +#ifndef _PTP_PRIVATE_H_ +#define _PTP_PRIVATE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; + int tail; + spinlock_t lock; + struct list_head qlist; + unsigned long *mask; + struct dentry *debugfs_instance; + struct debugfs_u32_array dfs_bitmap; +}; + +struct ptp_clock { + struct posix_clock clock; + struct device dev; + struct ptp_clock_info *info; + dev_t devid; + int index; /* index into clocks.map */ + struct pps_device *pps_source; + long dialed_frequency; /* remembers the frequency adjustment */ + struct list_head tsevqs; /* timestamp fifo list */ + spinlock_t tsevqs_lock; /* protects tsevqs from concurrent access */ + struct mutex pincfg_mux; /* protect concurrent info->pin_config access */ + wait_queue_head_t tsev_wq; + int defunct; /* tells readers to go away when clock is being removed */ + struct device_attribute *pin_dev_attr; + struct attribute **pin_attr; + struct attribute_group pin_attr_group; + /* 1st entry is a pointer to the real group, 2nd is NULL terminator */ + const struct attribute_group *pin_attr_groups[2]; + struct kthread_worker *kworker; + struct kthread_delayed_work aux_work; + unsigned int max_vclocks; + unsigned int n_vclocks; + int *vclock_index; + struct mutex n_vclocks_mux; /* protect concurrent n_vclocks access */ + bool is_virtual_clock; + bool has_cycles; + struct dentry *debugfs_root; +}; + +#define info_to_vclock(d) container_of((d), struct ptp_vclock, info) +#define cc_to_vclock(d) container_of((d), struct ptp_vclock, cc) +#define dw_to_vclock(d) container_of((d), struct ptp_vclock, refresh_work) + +struct ptp_vclock { + struct ptp_clock *pclock; + struct ptp_clock_info info; + struct ptp_clock *clock; + struct hlist_node vclock_hash_node; + struct cyclecounter cc; + struct timecounter tc; + struct mutex lock; /* protects tc/cc */ +}; + +/* + * The function queue_cnt() is safe for readers to call without + * holding q->lock. Readers use this function to verify that the queue + * is nonempty before proceeding with a dequeue operation. The fact + * that a writer might concurrently increment the tail does not + * matter, since the queue remains nonempty nonetheless. + */ +static inline int queue_cnt(const struct timestamp_event_queue *q) +{ + /* + * Paired with WRITE_ONCE() in enqueue_external_timestamp(), + * ptp_read(), extts_fifo_show(). + */ + int cnt = READ_ONCE(q->tail) - READ_ONCE(q->head); + return cnt < 0 ? PTP_MAX_TIMESTAMPS + cnt : cnt; +} + +/* Check if ptp virtual clock is in use */ +static inline bool ptp_vclock_in_use(struct ptp_clock *ptp) +{ + bool in_use = false; + + /* Virtual clocks can't be stacked on top of virtual clocks. + * Avoid acquiring the n_vclocks_mux on virtual clocks, to allow this + * function to be called from code paths where the n_vclocks_mux of the + * parent physical clock is already held. Functionally that's not an + * issue, but lockdep would complain, because they have the same lock + * class. + */ + if (ptp->is_virtual_clock) + return false; + + if (mutex_lock_interruptible(&ptp->n_vclocks_mux)) + return true; + + if (ptp->n_vclocks) + in_use = true; + + mutex_unlock(&ptp->n_vclocks_mux); + + return in_use; +} + +/* Check if ptp clock shall be free running */ +static inline bool ptp_clock_freerun(struct ptp_clock *ptp) +{ + if (ptp->has_cycles) + return false; + + return ptp_vclock_in_use(ptp); +} + +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); + +long ptp_ioctl(struct posix_clock_context *pccontext, unsigned int cmd, + unsigned long arg); + +int ptp_open(struct posix_clock_context *pccontext, fmode_t fmode); + +int ptp_release(struct posix_clock_context *pccontext); + +ssize_t ptp_read(struct posix_clock_context *pccontext, uint flags, char __user *buf, + size_t cnt); + +__poll_t ptp_poll(struct posix_clock_context *pccontext, struct file *fp, + poll_table *wait); + +/* + * see ptp_sysfs.c + */ + +extern const struct attribute_group *ptp_groups[]; + +int ptp_populate_pin_groups(struct ptp_clock *ptp); +void ptp_cleanup_pin_groups(struct ptp_clock *ptp); + +struct ptp_vclock *ptp_vclock_register(struct ptp_clock *pclock); +void ptp_vclock_unregister(struct ptp_vclock *vclock); +#endif diff --git a/drivers/ptp/ptp_qoriq.c b/drivers/ptp/ptp_qoriq.c new file mode 100644 index 000000000..76272e5ac --- /dev/null +++ b/drivers/ptp/ptp_qoriq.c @@ -0,0 +1,700 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PTP 1588 clock for Freescale QorIQ 1588 timer + * + * Copyright (C) 2010 OMICRON electronics GmbH + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* + * Register access functions + */ + +/* Caller must hold ptp_qoriq->lock. */ +static u64 tmr_cnt_read(struct ptp_qoriq *ptp_qoriq) +{ + struct ptp_qoriq_registers *regs = &ptp_qoriq->regs; + u64 ns; + u32 lo, hi; + + lo = ptp_qoriq->read(®s->ctrl_regs->tmr_cnt_l); + hi = ptp_qoriq->read(®s->ctrl_regs->tmr_cnt_h); + ns = ((u64) hi) << 32; + ns |= lo; + return ns; +} + +/* Caller must hold ptp_qoriq->lock. */ +static void tmr_cnt_write(struct ptp_qoriq *ptp_qoriq, u64 ns) +{ + struct ptp_qoriq_registers *regs = &ptp_qoriq->regs; + u32 hi = ns >> 32; + u32 lo = ns & 0xffffffff; + + ptp_qoriq->write(®s->ctrl_regs->tmr_cnt_l, lo); + ptp_qoriq->write(®s->ctrl_regs->tmr_cnt_h, hi); +} + +static u64 tmr_offset_read(struct ptp_qoriq *ptp_qoriq) +{ + struct ptp_qoriq_registers *regs = &ptp_qoriq->regs; + u32 lo, hi; + u64 ns; + + lo = ptp_qoriq->read(®s->ctrl_regs->tmroff_l); + hi = ptp_qoriq->read(®s->ctrl_regs->tmroff_h); + ns = ((u64) hi) << 32; + ns |= lo; + return ns; +} + +static void tmr_offset_write(struct ptp_qoriq *ptp_qoriq, u64 delta_ns) +{ + struct ptp_qoriq_registers *regs = &ptp_qoriq->regs; + u32 lo = delta_ns & 0xffffffff; + u32 hi = delta_ns >> 32; + + ptp_qoriq->write(®s->ctrl_regs->tmroff_l, lo); + ptp_qoriq->write(®s->ctrl_regs->tmroff_h, hi); +} + +/* Caller must hold ptp_qoriq->lock. */ +static void set_alarm(struct ptp_qoriq *ptp_qoriq) +{ + struct ptp_qoriq_registers *regs = &ptp_qoriq->regs; + u64 ns; + u32 lo, hi; + + ns = tmr_cnt_read(ptp_qoriq) + tmr_offset_read(ptp_qoriq) + + 1500000000ULL; + + ns = div_u64(ns, 1000000000UL) * 1000000000ULL; + ns -= ptp_qoriq->tclk_period; + hi = ns >> 32; + lo = ns & 0xffffffff; + ptp_qoriq->write(®s->alarm_regs->tmr_alarm1_l, lo); + ptp_qoriq->write(®s->alarm_regs->tmr_alarm1_h, hi); +} + +/* Caller must hold ptp_qoriq->lock. */ +static void set_fipers(struct ptp_qoriq *ptp_qoriq) +{ + struct ptp_qoriq_registers *regs = &ptp_qoriq->regs; + + set_alarm(ptp_qoriq); + ptp_qoriq->write(®s->fiper_regs->tmr_fiper1, ptp_qoriq->tmr_fiper1); + ptp_qoriq->write(®s->fiper_regs->tmr_fiper2, ptp_qoriq->tmr_fiper2); + + if (ptp_qoriq->fiper3_support) + ptp_qoriq->write(®s->fiper_regs->tmr_fiper3, + ptp_qoriq->tmr_fiper3); +} + +int extts_clean_up(struct ptp_qoriq *ptp_qoriq, int index, bool update_event) +{ + struct ptp_qoriq_registers *regs = &ptp_qoriq->regs; + struct ptp_clock_event event; + void __iomem *reg_etts_l; + void __iomem *reg_etts_h; + u32 valid, lo, hi; + + switch (index) { + case 0: + valid = ETS1_VLD; + reg_etts_l = ®s->etts_regs->tmr_etts1_l; + reg_etts_h = ®s->etts_regs->tmr_etts1_h; + break; + case 1: + valid = ETS2_VLD; + reg_etts_l = ®s->etts_regs->tmr_etts2_l; + reg_etts_h = ®s->etts_regs->tmr_etts2_h; + break; + default: + return -EINVAL; + } + + event.type = PTP_CLOCK_EXTTS; + event.index = index; + + if (ptp_qoriq->extts_fifo_support) + if (!(ptp_qoriq->read(®s->ctrl_regs->tmr_stat) & valid)) + return 0; + + do { + lo = ptp_qoriq->read(reg_etts_l); + hi = ptp_qoriq->read(reg_etts_h); + + if (update_event) { + event.timestamp = ((u64) hi) << 32; + event.timestamp |= lo; + ptp_clock_event(ptp_qoriq->clock, &event); + } + + if (!ptp_qoriq->extts_fifo_support) + break; + } while (ptp_qoriq->read(®s->ctrl_regs->tmr_stat) & valid); + + return 0; +} +EXPORT_SYMBOL_GPL(extts_clean_up); + +/* + * Interrupt service routine + */ + +irqreturn_t ptp_qoriq_isr(int irq, void *priv) +{ + struct ptp_qoriq *ptp_qoriq = priv; + struct ptp_qoriq_registers *regs = &ptp_qoriq->regs; + struct ptp_clock_event event; + u32 ack = 0, mask, val, irqs; + + spin_lock(&ptp_qoriq->lock); + + val = ptp_qoriq->read(®s->ctrl_regs->tmr_tevent); + mask = ptp_qoriq->read(®s->ctrl_regs->tmr_temask); + + spin_unlock(&ptp_qoriq->lock); + + irqs = val & mask; + + if (irqs & ETS1) { + ack |= ETS1; + extts_clean_up(ptp_qoriq, 0, true); + } + + if (irqs & ETS2) { + ack |= ETS2; + extts_clean_up(ptp_qoriq, 1, true); + } + + if (irqs & PP1) { + ack |= PP1; + event.type = PTP_CLOCK_PPS; + ptp_clock_event(ptp_qoriq->clock, &event); + } + + if (ack) { + ptp_qoriq->write(®s->ctrl_regs->tmr_tevent, ack); + return IRQ_HANDLED; + } else + return IRQ_NONE; +} +EXPORT_SYMBOL_GPL(ptp_qoriq_isr); + +/* + * PTP clock operations + */ + +int ptp_qoriq_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) +{ + u64 adj, diff; + u32 tmr_add; + int neg_adj = 0; + struct ptp_qoriq *ptp_qoriq = container_of(ptp, struct ptp_qoriq, caps); + struct ptp_qoriq_registers *regs = &ptp_qoriq->regs; + + if (scaled_ppm < 0) { + neg_adj = 1; + scaled_ppm = -scaled_ppm; + } + tmr_add = ptp_qoriq->tmr_add; + adj = tmr_add; + + /* + * Calculate diff and round() to the nearest integer + * + * diff = adj * (ppb / 1000000000) + * = adj * scaled_ppm / 65536000000 + */ + diff = mul_u64_u64_div_u64(adj, scaled_ppm, 32768000000); + diff = DIV64_U64_ROUND_UP(diff, 2); + + tmr_add = neg_adj ? tmr_add - diff : tmr_add + diff; + ptp_qoriq->write(®s->ctrl_regs->tmr_add, tmr_add); + + return 0; +} +EXPORT_SYMBOL_GPL(ptp_qoriq_adjfine); + +int ptp_qoriq_adjtime(struct ptp_clock_info *ptp, s64 delta) +{ + struct ptp_qoriq *ptp_qoriq = container_of(ptp, struct ptp_qoriq, caps); + s64 now, curr_delta; + unsigned long flags; + + spin_lock_irqsave(&ptp_qoriq->lock, flags); + + /* On LS1021A, eTSEC2 and eTSEC3 do not take into account the TMR_OFF + * adjustment + */ + if (ptp_qoriq->etsec) { + now = tmr_cnt_read(ptp_qoriq); + now += delta; + tmr_cnt_write(ptp_qoriq, now); + } else { + curr_delta = tmr_offset_read(ptp_qoriq); + curr_delta += delta; + tmr_offset_write(ptp_qoriq, curr_delta); + } + set_fipers(ptp_qoriq); + + spin_unlock_irqrestore(&ptp_qoriq->lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(ptp_qoriq_adjtime); + +int ptp_qoriq_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts) +{ + u64 ns; + unsigned long flags; + struct ptp_qoriq *ptp_qoriq = container_of(ptp, struct ptp_qoriq, caps); + + spin_lock_irqsave(&ptp_qoriq->lock, flags); + + ns = tmr_cnt_read(ptp_qoriq) + tmr_offset_read(ptp_qoriq); + + spin_unlock_irqrestore(&ptp_qoriq->lock, flags); + + *ts = ns_to_timespec64(ns); + + return 0; +} +EXPORT_SYMBOL_GPL(ptp_qoriq_gettime); + +int ptp_qoriq_settime(struct ptp_clock_info *ptp, + const struct timespec64 *ts) +{ + u64 ns; + unsigned long flags; + struct ptp_qoriq *ptp_qoriq = container_of(ptp, struct ptp_qoriq, caps); + + ns = timespec64_to_ns(ts); + + spin_lock_irqsave(&ptp_qoriq->lock, flags); + + tmr_offset_write(ptp_qoriq, 0); + tmr_cnt_write(ptp_qoriq, ns); + set_fipers(ptp_qoriq); + + spin_unlock_irqrestore(&ptp_qoriq->lock, flags); + + return 0; +} +EXPORT_SYMBOL_GPL(ptp_qoriq_settime); + +int ptp_qoriq_enable(struct ptp_clock_info *ptp, + struct ptp_clock_request *rq, int on) +{ + struct ptp_qoriq *ptp_qoriq = container_of(ptp, struct ptp_qoriq, caps); + struct ptp_qoriq_registers *regs = &ptp_qoriq->regs; + unsigned long flags; + u32 bit, mask = 0; + + switch (rq->type) { + case PTP_CLK_REQ_EXTTS: + switch (rq->extts.index) { + case 0: + bit = ETS1EN; + break; + case 1: + bit = ETS2EN; + break; + default: + return -EINVAL; + } + + if (on) + extts_clean_up(ptp_qoriq, rq->extts.index, false); + + break; + case PTP_CLK_REQ_PPS: + bit = PP1EN; + break; + default: + return -EOPNOTSUPP; + } + + spin_lock_irqsave(&ptp_qoriq->lock, flags); + + mask = ptp_qoriq->read(®s->ctrl_regs->tmr_temask); + if (on) { + mask |= bit; + ptp_qoriq->write(®s->ctrl_regs->tmr_tevent, bit); + } else { + mask &= ~bit; + } + + ptp_qoriq->write(®s->ctrl_regs->tmr_temask, mask); + + spin_unlock_irqrestore(&ptp_qoriq->lock, flags); + return 0; +} +EXPORT_SYMBOL_GPL(ptp_qoriq_enable); + +static const struct ptp_clock_info ptp_qoriq_caps = { + .owner = THIS_MODULE, + .name = "qoriq ptp clock", + .max_adj = 512000, + .n_alarm = 0, + .n_ext_ts = N_EXT_TS, + .n_per_out = 0, + .n_pins = 0, + .pps = 1, + .adjfine = ptp_qoriq_adjfine, + .adjtime = ptp_qoriq_adjtime, + .gettime64 = ptp_qoriq_gettime, + .settime64 = ptp_qoriq_settime, + .enable = ptp_qoriq_enable, +}; + +/** + * ptp_qoriq_nominal_freq - calculate nominal frequency according to + * reference clock frequency + * + * @clk_src: reference clock frequency + * + * The nominal frequency is the desired clock frequency. + * It should be less than the reference clock frequency. + * It should be a factor of 1000MHz. + * + * Return the nominal frequency + */ +static u32 ptp_qoriq_nominal_freq(u32 clk_src) +{ + u32 remainder = 0; + + clk_src /= 1000000; + remainder = clk_src % 100; + if (remainder) { + clk_src -= remainder; + clk_src += 100; + } + + do { + clk_src -= 100; + + } while (1000 % clk_src); + + return clk_src * 1000000; +} + +/** + * ptp_qoriq_auto_config - calculate a set of default configurations + * + * @ptp_qoriq: pointer to ptp_qoriq + * @node: pointer to device_node + * + * If below dts properties are not provided, this function will be + * called to calculate a set of default configurations for them. + * "fsl,tclk-period" + * "fsl,tmr-prsc" + * "fsl,tmr-add" + * "fsl,tmr-fiper1" + * "fsl,tmr-fiper2" + * "fsl,tmr-fiper3" (required only for DPAA2 and ENETC hardware) + * "fsl,max-adj" + * + * Return 0 if success + */ +static int ptp_qoriq_auto_config(struct ptp_qoriq *ptp_qoriq, + struct device_node *node) +{ + struct clk *clk; + u64 freq_comp; + u64 max_adj; + u32 nominal_freq; + u32 remainder = 0; + u32 clk_src = 0; + + ptp_qoriq->cksel = DEFAULT_CKSEL; + + clk = of_clk_get(node, 0); + if (!IS_ERR(clk)) { + clk_src = clk_get_rate(clk); + clk_put(clk); + } + + if (clk_src <= 100000000UL) { + pr_err("error reference clock value, or lower than 100MHz\n"); + return -EINVAL; + } + + nominal_freq = ptp_qoriq_nominal_freq(clk_src); + if (!nominal_freq) + return -EINVAL; + + ptp_qoriq->tclk_period = 1000000000UL / nominal_freq; + ptp_qoriq->tmr_prsc = DEFAULT_TMR_PRSC; + + /* Calculate initial frequency compensation value for TMR_ADD register. + * freq_comp = ceil(2^32 / freq_ratio) + * freq_ratio = reference_clock_freq / nominal_freq + */ + freq_comp = ((u64)1 << 32) * nominal_freq; + freq_comp = div_u64_rem(freq_comp, clk_src, &remainder); + if (remainder) + freq_comp++; + + ptp_qoriq->tmr_add = freq_comp; + ptp_qoriq->tmr_fiper1 = DEFAULT_FIPER1_PERIOD - ptp_qoriq->tclk_period; + ptp_qoriq->tmr_fiper2 = DEFAULT_FIPER2_PERIOD - ptp_qoriq->tclk_period; + ptp_qoriq->tmr_fiper3 = DEFAULT_FIPER3_PERIOD - ptp_qoriq->tclk_period; + + /* max_adj = 1000000000 * (freq_ratio - 1.0) - 1 + * freq_ratio = reference_clock_freq / nominal_freq + */ + max_adj = 1000000000ULL * (clk_src - nominal_freq); + max_adj = div_u64(max_adj, nominal_freq) - 1; + ptp_qoriq->caps.max_adj = max_adj; + + 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(®s->ctrl_regs->tmr_ctrl); + if (on) + tmr_ctrl |= loopback_bit; + else + tmr_ctrl &= ~loopback_bit; + + ptp_qoriq->write(®s->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) +{ + struct device_node *node = ptp_qoriq->dev->of_node; + struct ptp_qoriq_registers *regs; + struct timespec64 now; + unsigned long flags; + u32 tmr_ctrl; + + if (!node) + return -ENODEV; + + 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; + + if (of_property_read_bool(node, "fsl,extts-fifo")) + ptp_qoriq->extts_fifo_support = true; + else + ptp_qoriq->extts_fifo_support = false; + + if (of_device_is_compatible(node, "fsl,dpaa2-ptp") || + of_device_is_compatible(node, "fsl,enetc-ptp")) + ptp_qoriq->fiper3_support = true; + + if (of_property_read_u32(node, + "fsl,tclk-period", &ptp_qoriq->tclk_period) || + of_property_read_u32(node, + "fsl,tmr-prsc", &ptp_qoriq->tmr_prsc) || + of_property_read_u32(node, + "fsl,tmr-add", &ptp_qoriq->tmr_add) || + of_property_read_u32(node, + "fsl,tmr-fiper1", &ptp_qoriq->tmr_fiper1) || + of_property_read_u32(node, + "fsl,tmr-fiper2", &ptp_qoriq->tmr_fiper2) || + of_property_read_u32(node, + "fsl,max-adj", &ptp_qoriq->caps.max_adj) || + (ptp_qoriq->fiper3_support && + of_property_read_u32(node, "fsl,tmr-fiper3", + &ptp_qoriq->tmr_fiper3))) { + pr_warn("device tree node missing required elements, try automatic configuration\n"); + + if (ptp_qoriq_auto_config(ptp_qoriq, node)) + return -ENODEV; + } + + if (of_property_read_bool(node, "little-endian")) { + ptp_qoriq->read = qoriq_read_le; + ptp_qoriq->write = qoriq_write_le; + } else { + ptp_qoriq->read = qoriq_read_be; + ptp_qoriq->write = qoriq_write_be; + } + + /* The eTSEC uses differnt memory map with DPAA/ENETC */ + if (of_device_is_compatible(node, "fsl,etsec-ptp")) { + ptp_qoriq->etsec = true; + ptp_qoriq->regs.ctrl_regs = base + ETSEC_CTRL_REGS_OFFSET; + ptp_qoriq->regs.alarm_regs = base + ETSEC_ALARM_REGS_OFFSET; + ptp_qoriq->regs.fiper_regs = base + ETSEC_FIPER_REGS_OFFSET; + ptp_qoriq->regs.etts_regs = base + ETSEC_ETTS_REGS_OFFSET; + } else { + ptp_qoriq->regs.ctrl_regs = base + CTRL_REGS_OFFSET; + ptp_qoriq->regs.alarm_regs = base + ALARM_REGS_OFFSET; + ptp_qoriq->regs.fiper_regs = base + FIPER_REGS_OFFSET; + ptp_qoriq->regs.etts_regs = base + ETTS_REGS_OFFSET; + } + + spin_lock_init(&ptp_qoriq->lock); + + ktime_get_real_ts64(&now); + ptp_qoriq_settime(&ptp_qoriq->caps, &now); + + tmr_ctrl = + (ptp_qoriq->tclk_period & TCLK_PERIOD_MASK) << TCLK_PERIOD_SHIFT | + (ptp_qoriq->cksel & CKSEL_MASK) << CKSEL_SHIFT; + + spin_lock_irqsave(&ptp_qoriq->lock, flags); + + regs = &ptp_qoriq->regs; + ptp_qoriq->write(®s->ctrl_regs->tmr_ctrl, tmr_ctrl); + ptp_qoriq->write(®s->ctrl_regs->tmr_add, ptp_qoriq->tmr_add); + ptp_qoriq->write(®s->ctrl_regs->tmr_prsc, ptp_qoriq->tmr_prsc); + ptp_qoriq->write(®s->fiper_regs->tmr_fiper1, ptp_qoriq->tmr_fiper1); + ptp_qoriq->write(®s->fiper_regs->tmr_fiper2, ptp_qoriq->tmr_fiper2); + + if (ptp_qoriq->fiper3_support) + ptp_qoriq->write(®s->fiper_regs->tmr_fiper3, + ptp_qoriq->tmr_fiper3); + + set_alarm(ptp_qoriq); + ptp_qoriq->write(®s->ctrl_regs->tmr_ctrl, + tmr_ctrl|FIPERST|RTPE|TE|FRD); + + spin_unlock_irqrestore(&ptp_qoriq->lock, flags); + + ptp_qoriq->clock = ptp_clock_register(&ptp_qoriq->caps, ptp_qoriq->dev); + if (IS_ERR(ptp_qoriq->clock)) + return PTR_ERR(ptp_qoriq->clock); + + ptp_qoriq->phc_index = ptp_clock_index(ptp_qoriq->clock); + + return 0; +} +EXPORT_SYMBOL_GPL(ptp_qoriq_init); + +void ptp_qoriq_free(struct ptp_qoriq *ptp_qoriq) +{ + struct ptp_qoriq_registers *regs = &ptp_qoriq->regs; + + ptp_qoriq->write(®s->ctrl_regs->tmr_temask, 0); + ptp_qoriq->write(®s->ctrl_regs->tmr_ctrl, 0); + + ptp_clock_unregister(ptp_qoriq->clock); + iounmap(ptp_qoriq->base); + free_irq(ptp_qoriq->irq, ptp_qoriq); +} +EXPORT_SYMBOL_GPL(ptp_qoriq_free); + +static int ptp_qoriq_probe(struct platform_device *dev) +{ + struct ptp_qoriq *ptp_qoriq; + int err = -ENOMEM; + void __iomem *base; + + ptp_qoriq = kzalloc_obj(*ptp_qoriq); + if (!ptp_qoriq) + goto no_memory; + + ptp_qoriq->dev = &dev->dev; + + err = -ENODEV; + + ptp_qoriq->irq = platform_get_irq(dev, 0); + if (ptp_qoriq->irq < 0) { + pr_err("irq not in device tree\n"); + goto no_node; + } + if (request_irq(ptp_qoriq->irq, ptp_qoriq_isr, IRQF_SHARED, + DRIVER, ptp_qoriq)) { + pr_err("request_irq failed\n"); + goto no_node; + } + + ptp_qoriq->rsrc = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!ptp_qoriq->rsrc) { + pr_err("no resource\n"); + goto no_resource; + } + if (request_resource(&iomem_resource, ptp_qoriq->rsrc)) { + pr_err("resource busy\n"); + goto no_resource; + } + + base = ioremap(ptp_qoriq->rsrc->start, + resource_size(ptp_qoriq->rsrc)); + if (!base) { + pr_err("ioremap ptp registers failed\n"); + goto no_ioremap; + } + + err = ptp_qoriq_init(ptp_qoriq, base, &ptp_qoriq_caps); + if (err) + goto no_clock; + + platform_set_drvdata(dev, ptp_qoriq); + return 0; + +no_clock: + iounmap(base); +no_ioremap: + release_resource(ptp_qoriq->rsrc); +no_resource: + free_irq(ptp_qoriq->irq, ptp_qoriq); +no_node: + kfree(ptp_qoriq); +no_memory: + return err; +} + +static void ptp_qoriq_remove(struct platform_device *dev) +{ + struct ptp_qoriq *ptp_qoriq = platform_get_drvdata(dev); + + ptp_qoriq_free(ptp_qoriq); + release_resource(ptp_qoriq->rsrc); + kfree(ptp_qoriq); +} + +static const struct of_device_id match_table[] = { + { .compatible = "fsl,etsec-ptp" }, + { .compatible = "fsl,fman-ptp-timer" }, + {}, +}; +MODULE_DEVICE_TABLE(of, match_table); + +static struct platform_driver ptp_qoriq_driver = { + .driver = { + .name = "ptp_qoriq", + .of_match_table = match_table, + }, + .probe = ptp_qoriq_probe, + .remove = ptp_qoriq_remove, +}; + +module_platform_driver(ptp_qoriq_driver); + +MODULE_AUTHOR("Richard Cochran "); +MODULE_DESCRIPTION("PTP clock for Freescale QorIQ 1588 timer"); +MODULE_LICENSE("GPL"); diff --git a/drivers/ptp/ptp_s390.c b/drivers/ptp/ptp_s390.c new file mode 100644 index 000000000..02d624d89 --- /dev/null +++ b/drivers/ptp/ptp_s390.c @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * s390 PTP clock driver + * + */ + +#include "ptp_private.h" +#include +#include + +static struct ptp_clock *ptp_stcke_clock, *ptp_qpt_clock; + +static int ptp_s390_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) +{ + return -EOPNOTSUPP; +} + +static int ptp_s390_adjtime(struct ptp_clock_info *ptp, s64 delta) +{ + return -EOPNOTSUPP; +} + +static struct timespec64 eitod_to_timespec64(union tod_clock *clk) +{ + return ns_to_timespec64(eitod_to_ns(clk->eitod - TOD_UNIX_EPOCH)); +} + +static struct timespec64 tod_to_timespec64(unsigned long tod) +{ + return ns_to_timespec64(tod_to_ns(tod - TOD_UNIX_EPOCH)); +} + +static int ptp_s390_stcke_gettime(struct ptp_clock_info *ptp, + struct timespec64 *ts) +{ + union tod_clock tod; + + if (!stp_enabled()) + return -EOPNOTSUPP; + + store_tod_clock_ext(&tod); + *ts = eitod_to_timespec64(&tod); + return 0; +} + +static int ptp_s390_qpt_gettime(struct ptp_clock_info *ptp, + struct timespec64 *ts) +{ + unsigned long tod; + + ptff(&tod, sizeof(tod), PTFF_QPT); + *ts = tod_to_timespec64(tod); + return 0; +} + +static int ptp_s390_settime(struct ptp_clock_info *ptp, + const struct timespec64 *ts) +{ + return -EOPNOTSUPP; +} + +static int s390_arch_ptp_get_crosststamp(ktime_t *device_time, + struct system_counterval_t *system_counter, + void *ctx) +{ + union tod_clock clk; + + store_tod_clock_ext(&clk); + *device_time = ns_to_ktime(tod_to_ns(clk.tod - TOD_UNIX_EPOCH)); + system_counter->cycles = clk.tod; + system_counter->cs_id = CSID_S390_TOD; + return 0; +} + +static int ptp_s390_getcrosststamp(struct ptp_clock_info *ptp, + struct system_device_crosststamp *xtstamp) +{ + if (!stp_enabled()) + return -EOPNOTSUPP; + return get_device_system_crosststamp(s390_arch_ptp_get_crosststamp, NULL, NULL, xtstamp); +} + +static struct ptp_clock_info ptp_s390_stcke_info = { + .owner = THIS_MODULE, + .name = "s390 STCKE Clock", + .max_adj = 0, + .adjfine = ptp_s390_adjfine, + .adjtime = ptp_s390_adjtime, + .gettime64 = ptp_s390_stcke_gettime, + .settime64 = ptp_s390_settime, + .getcrosststamp = ptp_s390_getcrosststamp, +}; + +static struct ptp_clock_info ptp_s390_qpt_info = { + .owner = THIS_MODULE, + .name = "s390 Physical Clock", + .max_adj = 0, + .adjfine = ptp_s390_adjfine, + .adjtime = ptp_s390_adjtime, + .gettime64 = ptp_s390_qpt_gettime, + .settime64 = ptp_s390_settime, +}; + +static __init int ptp_s390_init(void) +{ + ptp_stcke_clock = ptp_clock_register(&ptp_s390_stcke_info, NULL); + if (IS_ERR(ptp_stcke_clock)) + return PTR_ERR(ptp_stcke_clock); + + if (!test_facility(28) || !ptff_query(PTFF_QPT)) + return 0; + + ptp_qpt_clock = ptp_clock_register(&ptp_s390_qpt_info, NULL); + if (IS_ERR(ptp_qpt_clock)) { + ptp_clock_unregister(ptp_stcke_clock); + return PTR_ERR(ptp_qpt_clock); + } + return 0; +} + +static __exit void ptp_s390_exit(void) +{ + if (ptp_qpt_clock) + ptp_clock_unregister(ptp_qpt_clock); + ptp_clock_unregister(ptp_stcke_clock); +} + +module_init(ptp_s390_init); +module_exit(ptp_s390_exit); + +MODULE_AUTHOR("Sven Schnelle "); +MODULE_DESCRIPTION("s390 Physical/STCKE Clock PtP Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/ptp/ptp_sysfs.c b/drivers/ptp/ptp_sysfs.c new file mode 100644 index 000000000..dc398c6b7 --- /dev/null +++ b/drivers/ptp/ptp_sysfs.c @@ -0,0 +1,481 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PTP 1588 clock support - sysfs interface. + * + * Copyright (C) 2010 OMICRON electronics GmbH + * Copyright 2021 NXP + */ +#include +#include + +#include "ptp_private.h" + +static ssize_t clock_name_show(struct device *dev, + struct device_attribute *attr, char *page) +{ + struct ptp_clock *ptp = dev_get_drvdata(dev); + return sysfs_emit(page, "%s\n", ptp->info->name); +} +static DEVICE_ATTR_RO(clock_name); + +static ssize_t max_phase_adjustment_show(struct device *dev, + struct device_attribute *attr, + char *page) +{ + struct ptp_clock *ptp = dev_get_drvdata(dev); + + return sysfs_emit(page, "%d\n", ptp->info->getmaxphase(ptp->info)); +} +static DEVICE_ATTR_RO(max_phase_adjustment); + +#define PTP_SHOW_INT(name, var) \ +static ssize_t var##_show(struct device *dev, \ + struct device_attribute *attr, char *page) \ +{ \ + struct ptp_clock *ptp = dev_get_drvdata(dev); \ + return sysfs_emit(page, "%d\n", ptp->info->var); \ +} \ +static DEVICE_ATTR(name, 0444, var##_show, NULL); + +PTP_SHOW_INT(max_adjustment, max_adj); +PTP_SHOW_INT(n_alarms, n_alarm); +PTP_SHOW_INT(n_external_timestamps, n_ext_ts); +PTP_SHOW_INT(n_periodic_outputs, n_per_out); +PTP_SHOW_INT(n_programmable_pins, n_pins); +PTP_SHOW_INT(pps_available, pps); + +static ssize_t extts_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ptp_clock *ptp = dev_get_drvdata(dev); + struct ptp_clock_info *ops = ptp->info; + struct ptp_clock_request req = { .type = PTP_CLK_REQ_EXTTS }; + int cnt, enable; + int err = -EINVAL; + + cnt = sscanf(buf, "%u %d", &req.extts.index, &enable); + if (cnt != 2) + goto out; + if (req.extts.index >= ops->n_ext_ts) + goto out; + + err = ops->enable(ops, &req, enable ? 1 : 0); + if (err) + goto out; + + return count; +out: + return err; +} +static DEVICE_ATTR(extts_enable, 0220, NULL, extts_enable_store); + +static ssize_t extts_fifo_show(struct device *dev, + struct device_attribute *attr, char *page) +{ + struct ptp_clock *ptp = dev_get_drvdata(dev); + struct timestamp_event_queue *queue; + struct ptp_extts_event event; + unsigned long flags; + size_t qcnt; + int cnt = 0; + + cnt = list_count_nodes(&ptp->tsevqs); + if (cnt <= 0) + goto out; + + /* The sysfs fifo will always draw from the fist queue */ + queue = list_first_entry(&ptp->tsevqs, struct timestamp_event_queue, + qlist); + + memset(&event, 0, sizeof(event)); + spin_lock_irqsave(&queue->lock, flags); + qcnt = queue_cnt(queue); + if (qcnt) { + event = queue->buf[queue->head]; + /* Paired with READ_ONCE() in queue_cnt() */ + WRITE_ONCE(queue->head, (queue->head + 1) % PTP_MAX_TIMESTAMPS); + } + spin_unlock_irqrestore(&queue->lock, flags); + + if (!qcnt) + goto out; + + cnt = sysfs_emit(page, "%u %lld %u\n", + event.index, event.t.sec, event.t.nsec); +out: + return cnt; +} +static DEVICE_ATTR(fifo, 0444, extts_fifo_show, NULL); + +static ssize_t period_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ptp_clock *ptp = dev_get_drvdata(dev); + struct ptp_clock_info *ops = ptp->info; + struct ptp_clock_request req = { .type = PTP_CLK_REQ_PEROUT }; + int cnt, enable, err = -EINVAL; + + cnt = sscanf(buf, "%u %lld %u %lld %u", &req.perout.index, + &req.perout.start.sec, &req.perout.start.nsec, + &req.perout.period.sec, &req.perout.period.nsec); + if (cnt != 5) + goto out; + if (req.perout.index >= ops->n_per_out) + goto out; + + enable = req.perout.period.sec || req.perout.period.nsec; + err = ops->enable(ops, &req, enable); + if (err) + goto out; + + return count; +out: + return err; +} +static DEVICE_ATTR(period, 0220, NULL, period_store); + +static ssize_t pps_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ptp_clock *ptp = dev_get_drvdata(dev); + struct ptp_clock_info *ops = ptp->info; + struct ptp_clock_request req = { .type = PTP_CLK_REQ_PPS }; + int cnt, enable; + int err = -EINVAL; + + if (!capable(CAP_SYS_TIME)) + return -EPERM; + + cnt = sscanf(buf, "%d", &enable); + if (cnt != 1) + goto out; + + err = ops->enable(ops, &req, enable ? 1 : 0); + if (err) + goto out; + + return count; +out: + return err; +} +static DEVICE_ATTR(pps_enable, 0220, NULL, pps_enable_store); + +static int unregister_vclock(struct device *dev, void *data) +{ + struct ptp_clock *ptp = dev_get_drvdata(dev); + struct ptp_clock_info *info = ptp->info; + struct ptp_vclock *vclock; + u32 *num = data; + + vclock = info_to_vclock(info); + dev_info(dev->parent, "delete virtual clock ptp%d\n", + vclock->clock->index); + + ptp_vclock_unregister(vclock); + (*num)--; + + /* For break. Not error. */ + if (*num == 0) + return -EINVAL; + + return 0; +} + +static ssize_t n_vclocks_show(struct device *dev, + struct device_attribute *attr, char *page) +{ + struct ptp_clock *ptp = dev_get_drvdata(dev); + ssize_t size; + + if (mutex_lock_interruptible(&ptp->n_vclocks_mux)) + return -ERESTARTSYS; + + size = sysfs_emit(page, "%u\n", ptp->n_vclocks); + + mutex_unlock(&ptp->n_vclocks_mux); + + return size; +} + +static ssize_t n_vclocks_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ptp_clock *ptp = dev_get_drvdata(dev); + struct ptp_vclock *vclock; + int err = -EINVAL; + u32 num, i; + + if (kstrtou32(buf, 0, &num)) + return err; + + if (mutex_lock_interruptible(&ptp->n_vclocks_mux)) + return -ERESTARTSYS; + + if (num > ptp->max_vclocks) { + dev_err(dev, "max value is %d\n", ptp->max_vclocks); + goto out; + } + + /* Need to create more vclocks */ + if (num > ptp->n_vclocks) { + for (i = 0; i < num - ptp->n_vclocks; i++) { + vclock = ptp_vclock_register(ptp); + if (!vclock) + goto out; + + *(ptp->vclock_index + ptp->n_vclocks + i) = + vclock->clock->index; + + dev_info(dev, "new virtual clock ptp%d\n", + vclock->clock->index); + } + } + + /* Need to delete vclocks */ + if (num < ptp->n_vclocks) { + i = ptp->n_vclocks - num; + device_for_each_child_reverse(dev, &i, + unregister_vclock); + + for (i = 1; i <= ptp->n_vclocks - num; i++) + *(ptp->vclock_index + ptp->n_vclocks - i) = -1; + } + + /* Need to inform about changed physical clock behavior */ + if (!ptp->has_cycles) { + if (num == 0) + dev_info(dev, "only physical clock in use now\n"); + else + dev_info(dev, "guarantee physical clock free running\n"); + } + + ptp->n_vclocks = num; + mutex_unlock(&ptp->n_vclocks_mux); + + return count; +out: + mutex_unlock(&ptp->n_vclocks_mux); + return err; +} +static DEVICE_ATTR_RW(n_vclocks); + +static ssize_t max_vclocks_show(struct device *dev, + struct device_attribute *attr, char *page) +{ + struct ptp_clock *ptp = dev_get_drvdata(dev); + ssize_t size; + + size = sysfs_emit(page, "%u\n", ptp->max_vclocks); + + return size; +} + +static ssize_t max_vclocks_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ptp_clock *ptp = dev_get_drvdata(dev); + unsigned int *vclock_index; + int err = -EINVAL; + size_t size; + u32 max; + + if (kstrtou32(buf, 0, &max) || max == 0 || max > PTP_MAX_VCLOCKS_LIMIT) + return -EINVAL; + + if (max == ptp->max_vclocks) + return count; + + if (mutex_lock_interruptible(&ptp->n_vclocks_mux)) + return -ERESTARTSYS; + + if (max < ptp->n_vclocks) + goto out; + + vclock_index = kcalloc(max, sizeof(int), GFP_KERNEL); + if (!vclock_index) { + err = -ENOMEM; + goto out; + } + + size = sizeof(int) * ptp->n_vclocks; + memcpy(vclock_index, ptp->vclock_index, size); + + kfree(ptp->vclock_index); + ptp->vclock_index = vclock_index; + ptp->max_vclocks = max; + + mutex_unlock(&ptp->n_vclocks_mux); + + return count; +out: + mutex_unlock(&ptp->n_vclocks_mux); + return err; +} +static DEVICE_ATTR_RW(max_vclocks); + +static struct attribute *ptp_attrs[] = { + &dev_attr_clock_name.attr, + + &dev_attr_max_adjustment.attr, + &dev_attr_max_phase_adjustment.attr, + &dev_attr_n_alarms.attr, + &dev_attr_n_external_timestamps.attr, + &dev_attr_n_periodic_outputs.attr, + &dev_attr_n_programmable_pins.attr, + &dev_attr_pps_available.attr, + + &dev_attr_extts_enable.attr, + &dev_attr_fifo.attr, + &dev_attr_period.attr, + &dev_attr_pps_enable.attr, + &dev_attr_n_vclocks.attr, + &dev_attr_max_vclocks.attr, + NULL +}; + +static umode_t ptp_is_attribute_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct device *dev = kobj_to_dev(kobj); + struct ptp_clock *ptp = dev_get_drvdata(dev); + struct ptp_clock_info *info = ptp->info; + umode_t mode = attr->mode; + + if (attr == &dev_attr_extts_enable.attr || + attr == &dev_attr_fifo.attr) { + if (!info->n_ext_ts) + mode = 0; + } else if (attr == &dev_attr_period.attr) { + if (!info->n_per_out) + mode = 0; + } else if (attr == &dev_attr_pps_enable.attr) { + if (!info->pps) + mode = 0; + } else if (attr == &dev_attr_n_vclocks.attr || + attr == &dev_attr_max_vclocks.attr) { + if (ptp->is_virtual_clock) + mode = 0; + } else if (attr == &dev_attr_max_phase_adjustment.attr) { + if (!info->adjphase || !info->getmaxphase) + mode = 0; + } + + return mode; +} + +static const struct attribute_group ptp_group = { + .is_visible = ptp_is_attribute_visible, + .attrs = ptp_attrs, +}; + +const struct attribute_group *ptp_groups[] = { + &ptp_group, + NULL +}; + +static int ptp_pin_name2index(struct ptp_clock *ptp, const char *name) +{ + int i; + for (i = 0; i < ptp->info->n_pins; i++) { + if (!strcmp(ptp->info->pin_config[i].name, name)) + return i; + } + return -1; +} + +static ssize_t ptp_pin_show(struct device *dev, struct device_attribute *attr, + char *page) +{ + struct ptp_clock *ptp = dev_get_drvdata(dev); + unsigned int func, chan; + int index; + + index = ptp_pin_name2index(ptp, attr->attr.name); + if (index < 0) + return -EINVAL; + + if (mutex_lock_interruptible(&ptp->pincfg_mux)) + return -ERESTARTSYS; + + func = ptp->info->pin_config[index].func; + chan = ptp->info->pin_config[index].chan; + + mutex_unlock(&ptp->pincfg_mux); + + return sysfs_emit(page, "%u %u\n", func, chan); +} + +static ssize_t ptp_pin_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ptp_clock *ptp = dev_get_drvdata(dev); + unsigned int func, chan; + int cnt, err, index; + + cnt = sscanf(buf, "%u %u", &func, &chan); + if (cnt != 2) + return -EINVAL; + + index = ptp_pin_name2index(ptp, attr->attr.name); + if (index < 0) + return -EINVAL; + + if (mutex_lock_interruptible(&ptp->pincfg_mux)) + return -ERESTARTSYS; + err = ptp_set_pinfunc(ptp, index, func, chan); + mutex_unlock(&ptp->pincfg_mux); + if (err) + return err; + + return count; +} + +int ptp_populate_pin_groups(struct ptp_clock *ptp) +{ + struct ptp_clock_info *info = ptp->info; + int err = -ENOMEM, i, n_pins = info->n_pins; + + if (!n_pins) + return 0; + + ptp->pin_dev_attr = kzalloc_objs(*ptp->pin_dev_attr, n_pins); + if (!ptp->pin_dev_attr) + goto no_dev_attr; + + ptp->pin_attr = kzalloc_objs(*ptp->pin_attr, 1 + n_pins); + if (!ptp->pin_attr) + goto no_pin_attr; + + for (i = 0; i < n_pins; i++) { + struct device_attribute *da = &ptp->pin_dev_attr[i]; + sysfs_attr_init(&da->attr); + da->attr.name = info->pin_config[i].name; + da->attr.mode = 0644; + da->show = ptp_pin_show; + da->store = ptp_pin_store; + ptp->pin_attr[i] = &da->attr; + } + + ptp->pin_attr_group.name = "pins"; + ptp->pin_attr_group.attrs = ptp->pin_attr; + + ptp->pin_attr_groups[0] = &ptp->pin_attr_group; + + return 0; + +no_pin_attr: + kfree(ptp->pin_dev_attr); +no_dev_attr: + return err; +} + +void ptp_cleanup_pin_groups(struct ptp_clock *ptp) +{ + kfree(ptp->pin_attr); + kfree(ptp->pin_dev_attr); +} diff --git a/drivers/ptp/ptp_vclock.c b/drivers/ptp/ptp_vclock.c new file mode 100644 index 000000000..84cb527f5 --- /dev/null +++ b/drivers/ptp/ptp_vclock.c @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PTP virtual clock driver + * + * Copyright 2021 NXP + */ +#include +#include +#include "ptp_private.h" + +#define PTP_VCLOCK_CC_SHIFT 31 +#define PTP_VCLOCK_CC_MULT (1 << PTP_VCLOCK_CC_SHIFT) +#define PTP_VCLOCK_FADJ_SHIFT 9 +#define PTP_VCLOCK_FADJ_DENOMINATOR 15625ULL +#define PTP_VCLOCK_REFRESH_INTERVAL (HZ * 2) + +/* protects vclock_hash addition/deletion */ +static DEFINE_SPINLOCK(vclock_hash_lock); + +static DEFINE_READ_MOSTLY_HASHTABLE(vclock_hash, 8); + +DEFINE_STATIC_SRCU(vclock_srcu); + +static void ptp_vclock_hash_add(struct ptp_vclock *vclock) +{ + spin_lock(&vclock_hash_lock); + + hlist_add_head_rcu(&vclock->vclock_hash_node, + &vclock_hash[vclock->clock->index % HASH_SIZE(vclock_hash)]); + + spin_unlock(&vclock_hash_lock); +} + +static void ptp_vclock_hash_del(struct ptp_vclock *vclock) +{ + spin_lock(&vclock_hash_lock); + + hlist_del_init_rcu(&vclock->vclock_hash_node); + + spin_unlock(&vclock_hash_lock); + + synchronize_srcu(&vclock_srcu); +} + +static int ptp_vclock_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) +{ + struct ptp_vclock *vclock = info_to_vclock(ptp); + s64 adj; + + adj = (s64)scaled_ppm << PTP_VCLOCK_FADJ_SHIFT; + adj = div_s64(adj, PTP_VCLOCK_FADJ_DENOMINATOR); + + if (mutex_lock_interruptible(&vclock->lock)) + return -EINTR; + timecounter_read(&vclock->tc); + vclock->cc.mult = PTP_VCLOCK_CC_MULT + adj; + mutex_unlock(&vclock->lock); + + return 0; +} + +static int ptp_vclock_adjtime(struct ptp_clock_info *ptp, s64 delta) +{ + struct ptp_vclock *vclock = info_to_vclock(ptp); + + if (mutex_lock_interruptible(&vclock->lock)) + return -EINTR; + timecounter_adjtime(&vclock->tc, delta); + mutex_unlock(&vclock->lock); + + return 0; +} + +static int ptp_vclock_gettime(struct ptp_clock_info *ptp, + struct timespec64 *ts) +{ + struct ptp_vclock *vclock = info_to_vclock(ptp); + u64 ns; + + if (mutex_lock_interruptible(&vclock->lock)) + return -EINTR; + ns = timecounter_read(&vclock->tc); + mutex_unlock(&vclock->lock); + *ts = ns_to_timespec64(ns); + + return 0; +} + +static int ptp_vclock_gettimex(struct ptp_clock_info *ptp, + struct timespec64 *ts, + struct ptp_system_timestamp *sts) +{ + struct ptp_vclock *vclock = info_to_vclock(ptp); + struct ptp_clock *pptp = vclock->pclock; + struct timespec64 pts; + int err; + u64 ns; + + err = pptp->info->getcyclesx64(pptp->info, &pts, sts); + if (err) + return err; + + if (mutex_lock_interruptible(&vclock->lock)) + return -EINTR; + ns = timecounter_cyc2time(&vclock->tc, timespec64_to_ns(&pts)); + mutex_unlock(&vclock->lock); + + *ts = ns_to_timespec64(ns); + + return 0; +} + +static int ptp_vclock_settime(struct ptp_clock_info *ptp, + const struct timespec64 *ts) +{ + struct ptp_vclock *vclock = info_to_vclock(ptp); + u64 ns = timespec64_to_ns(ts); + + if (mutex_lock_interruptible(&vclock->lock)) + return -EINTR; + timecounter_init(&vclock->tc, &vclock->cc, ns); + mutex_unlock(&vclock->lock); + + return 0; +} + +static int ptp_vclock_getcrosststamp(struct ptp_clock_info *ptp, + struct system_device_crosststamp *xtstamp) +{ + struct ptp_vclock *vclock = info_to_vclock(ptp); + struct ptp_clock *pptp = vclock->pclock; + int err; + u64 ns; + + err = pptp->info->getcrosscycles(pptp->info, xtstamp); + if (err) + return err; + + if (mutex_lock_interruptible(&vclock->lock)) + return -EINTR; + ns = timecounter_cyc2time(&vclock->tc, ktime_to_ns(xtstamp->device)); + mutex_unlock(&vclock->lock); + + xtstamp->device = ns_to_ktime(ns); + + return 0; +} + +static long ptp_vclock_refresh(struct ptp_clock_info *ptp) +{ + struct ptp_vclock *vclock = info_to_vclock(ptp); + struct timespec64 ts; + + ptp_vclock_gettime(&vclock->info, &ts); + + 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", + .max_adj = 500000000, + .adjfine = ptp_vclock_adjfine, + .adjtime = ptp_vclock_adjtime, + .settime64 = ptp_vclock_settime, + .do_aux_work = ptp_vclock_refresh, +}; + +static u64 ptp_vclock_read(struct cyclecounter *cc) +{ + struct ptp_vclock *vclock = cc_to_vclock(cc); + struct ptp_clock *ptp = vclock->pclock; + struct timespec64 ts = {}; + + ptp->info->getcycles64(ptp->info, &ts); + + return timespec64_to_ns(&ts); +} + +static const struct cyclecounter ptp_vclock_cc = { + .read = ptp_vclock_read, + .mask = CYCLECOUNTER_MASK(32), + .mult = PTP_VCLOCK_CC_MULT, + .shift = PTP_VCLOCK_CC_SHIFT, +}; + +struct ptp_vclock *ptp_vclock_register(struct ptp_clock *pclock) +{ + struct ptp_vclock *vclock; + + vclock = kzalloc_obj(*vclock); + if (!vclock) + return NULL; + + vclock->pclock = pclock; + vclock->info = ptp_vclock_info; + if (pclock->info->getcyclesx64) + vclock->info.gettimex64 = ptp_vclock_gettimex; + else + vclock->info.gettime64 = ptp_vclock_gettime; + if (pclock->info->getcrosscycles) + vclock->info.getcrosststamp = ptp_vclock_getcrosststamp; + vclock->cc = ptp_vclock_cc; + + snprintf(vclock->info.name, PTP_CLOCK_NAME_LEN, "ptp%d_virt", + pclock->index); + + INIT_HLIST_NODE(&vclock->vclock_hash_node); + + mutex_init(&vclock->lock); + + vclock->clock = ptp_clock_register(&vclock->info, &pclock->dev); + if (IS_ERR_OR_NULL(vclock->clock)) { + kfree(vclock); + return NULL; + } + + ptp_vclock_set_subclass(vclock->clock); + + timecounter_init(&vclock->tc, &vclock->cc, 0); + ptp_schedule_worker(vclock->clock, PTP_VCLOCK_REFRESH_INTERVAL); + + ptp_vclock_hash_add(vclock); + + return vclock; +} + +void ptp_vclock_unregister(struct ptp_vclock *vclock) +{ + ptp_vclock_hash_del(vclock); + + ptp_clock_unregister(vclock->clock); + kfree(vclock); +} + +#if IS_BUILTIN(CONFIG_PTP_1588_CLOCK) +int ptp_get_vclocks_index(int pclock_index, int **vclock_index) +{ + char name[PTP_CLOCK_NAME_LEN] = ""; + struct ptp_clock *ptp; + struct device *dev; + int num = 0; + + if (pclock_index < 0) + return num; + + snprintf(name, PTP_CLOCK_NAME_LEN, "ptp%d", pclock_index); + dev = class_find_device_by_name(&ptp_class, name); + if (!dev) + return num; + + ptp = dev_get_drvdata(dev); + + if (mutex_lock_interruptible(&ptp->n_vclocks_mux)) { + put_device(dev); + return num; + } + + *vclock_index = kzalloc(sizeof(int) * ptp->n_vclocks, GFP_KERNEL); + if (!(*vclock_index)) + goto out; + + memcpy(*vclock_index, ptp->vclock_index, sizeof(int) * ptp->n_vclocks); + num = ptp->n_vclocks; +out: + mutex_unlock(&ptp->n_vclocks_mux); + put_device(dev); + return num; +} +EXPORT_SYMBOL(ptp_get_vclocks_index); + +ktime_t ptp_convert_timestamp(const ktime_t *hwtstamp, int vclock_index) +{ + unsigned int hash = vclock_index % HASH_SIZE(vclock_hash); + struct ptp_vclock *vclock; + u64 vclock_ns = 0; + int srcu_idx; + u64 ns; + + ns = ktime_to_ns(*hwtstamp); + + srcu_idx = srcu_read_lock(&vclock_srcu); + + hlist_for_each_entry_srcu(vclock, &vclock_hash[hash], vclock_hash_node, + srcu_read_lock_held(&vclock_srcu)) { + if (vclock->clock->index != vclock_index) + continue; + + if (mutex_lock_interruptible(&vclock->lock)) + break; + vclock_ns = timecounter_cyc2time(&vclock->tc, ns); + mutex_unlock(&vclock->lock); + break; + } + + srcu_read_unlock(&vclock_srcu, srcu_idx); + + return ns_to_ktime(vclock_ns); +} +EXPORT_SYMBOL(ptp_convert_timestamp); +#endif diff --git a/drivers/ptp/ptp_vmclock.c b/drivers/ptp/ptp_vmclock.c new file mode 100644 index 000000000..bb0e14bac --- /dev/null +++ b/drivers/ptp/ptp_vmclock.c @@ -0,0 +1,797 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Virtual PTP 1588 clock for use with LM-safe VMclock device. + * + * Copyright © 2024 Amazon.com, Inc. or its affiliates. + */ + +#include "linux/poll.h" +#include "linux/types.h" +#include "linux/wait.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#ifdef CONFIG_X86 +#include +#include +#endif + +#ifdef CONFIG_KVM_GUEST +#define SUPPORT_KVMCLOCK +#endif + +static DEFINE_IDA(vmclock_ida); + +ACPI_MODULE_NAME("vmclock"); + +struct vmclock_state { + struct resource res; + struct vmclock_abi *clk; + struct miscdevice miscdev; + wait_queue_head_t disrupt_wait; + struct ptp_clock_info ptp_clock_info; + struct ptp_clock *ptp_clock; + enum clocksource_ids cs_id, sys_cs_id; + int index; + char *name; +}; + +#define VMCLOCK_MAX_WAIT ms_to_ktime(100) + +/* Require at least the flags field to be present. All else can be optional. */ +#define VMCLOCK_MIN_SIZE offsetof(struct vmclock_abi, pad) + +#define VMCLOCK_FIELD_PRESENT(_c, _f) \ + (le32_to_cpu((_c)->size) >= (offsetof(struct vmclock_abi, _f) + \ + sizeof((_c)->_f))) + +/* + * Multiply a 64-bit count by a 64-bit tick 'period' in units of seconds >> 64 + * and add the fractional second part of the reference time. + * + * The result is a 128-bit value, the top 64 bits of which are seconds, and + * the low 64 bits are (seconds >> 64). + */ +static uint64_t mul_u64_u64_shr_add_u64(uint64_t *res_hi, uint64_t delta, + uint64_t period, uint8_t shift, + uint64_t frac_sec) +{ + unsigned __int128 res = (unsigned __int128)delta * period; + + res >>= shift; + res += frac_sec; + *res_hi = res >> 64; + return (uint64_t)res; +} + +static bool tai_adjust(struct vmclock_abi *clk, uint64_t *sec) +{ + if (clk->time_type == VMCLOCK_TIME_TAI) + return true; + + if (clk->time_type == VMCLOCK_TIME_UTC && + (le64_to_cpu(clk->flags) & VMCLOCK_FLAG_TAI_OFFSET_VALID)) { + if (sec) + *sec -= (int16_t)le16_to_cpu(clk->tai_offset_sec); + return true; + } + return false; +} + +static int vmclock_get_crosststamp(struct vmclock_state *st, + struct ptp_system_timestamp *sts, + struct system_counterval_t *system_counter, + struct timespec64 *tspec) +{ + ktime_t deadline = ktime_add(ktime_get(), VMCLOCK_MAX_WAIT); + uint64_t cycle, delta, seq, frac_sec; + +#ifdef CONFIG_X86 + /* + * We'd expect the hypervisor to know this and to report the clock + * status as VMCLOCK_STATUS_UNRELIABLE. But be paranoid. + */ + if (check_tsc_unstable()) + return -EINVAL; +#endif + + while (1) { + seq = le32_to_cpu(st->clk->seq_count) & ~1ULL; + + /* + * This pairs with a write barrier in the hypervisor + * which populates this structure. + */ + virt_rmb(); + + if (st->clk->clock_status == VMCLOCK_STATUS_UNRELIABLE) + return -EINVAL; + + /* + * When invoked for gettimex64(), fill in the pre/post system + * times. The simple case is when system time is based on the + * same counter as st->cs_id, in which case all three times + * will be derived from the *same* counter value. + * + * If the system isn't using the same counter, then the value + * from ptp_read_system_prets() will still be used as pre_ts, + * and ptp_read_system_postts() is called to populate postts + * after calling get_cycles(). + */ + if (sts) { + ptp_read_system_prets(sts); + if (sts->pre_sts.cs_id == st->cs_id) { + cycle = sts->pre_sts.cycles; + sts->post_sts = sts->pre_sts; + } else if (sts->pre_sts.hw_csid == st->cs_id && + sts->pre_sts.hw_cycles) { + cycle = sts->pre_sts.hw_cycles; + sts->post_sts = sts->pre_sts; + } else { + cycle = get_cycles(); + ptp_read_system_postts(sts); + } + } else { + cycle = get_cycles(); + } + + delta = cycle - le64_to_cpu(st->clk->counter_value); + + frac_sec = mul_u64_u64_shr_add_u64(&tspec->tv_sec, delta, + le64_to_cpu(st->clk->counter_period_frac_sec), + st->clk->counter_period_shift, + le64_to_cpu(st->clk->time_frac_sec)); + tspec->tv_nsec = mul_u64_u64_shr(frac_sec, NSEC_PER_SEC, 64); + tspec->tv_sec += le64_to_cpu(st->clk->time_sec); + + if (!tai_adjust(st->clk, &tspec->tv_sec)) + return -EINVAL; + + /* + * This pairs with a write barrier in the hypervisor + * which populates this structure. + */ + virt_rmb(); + if (seq == le32_to_cpu(st->clk->seq_count)) + break; + + if (ktime_after(ktime_get(), deadline)) + return -ETIMEDOUT; + } + + if (system_counter) { + system_counter->cycles = cycle; + system_counter->cs_id = st->cs_id; + } + + return 0; +} + +#ifdef SUPPORT_KVMCLOCK +/* + * In the case where the system is using the KVM clock for timekeeping, convert + * the TSC value into a KVM clock time in order to return a paired reading that + * get_device_system_crosststamp() can cope with. + */ +static int vmclock_get_crosststamp_kvmclock(struct vmclock_state *st, + struct ptp_system_timestamp *sts, + struct system_counterval_t *system_counter, + struct timespec64 *tspec) +{ + struct pvclock_vcpu_time_info *pvti = this_cpu_pvti(); + unsigned int pvti_ver; + int ret; + + preempt_disable_notrace(); + + do { + pvti_ver = pvclock_read_begin(pvti); + + ret = vmclock_get_crosststamp(st, sts, system_counter, tspec); + if (ret) + break; + + system_counter->cycles = __pvclock_read_cycles(pvti, + system_counter->cycles); + system_counter->cs_id = CSID_X86_KVM_CLK; + + /* + * This retry should never really happen; if the TSC is + * stable and reliable enough across vCPUS that it is sane + * for the hypervisor to expose a VMCLOCK device which uses + * it as the reference counter, then the KVM clock sohuld be + * in 'master clock mode' and basically never changed. But + * the KVM clock is a fickle and often broken thing, so do + * it "properly" just in case. + */ + } while (pvclock_read_retry(pvti, pvti_ver)); + + preempt_enable_notrace(); + + return ret; +} +#endif + +static int ptp_vmclock_get_time_fn(ktime_t *device_time, + struct system_counterval_t *system_counter, + void *ctx) +{ + struct vmclock_state *st = ctx; + struct timespec64 tspec; + int ret; + +#ifdef SUPPORT_KVMCLOCK + if (READ_ONCE(st->sys_cs_id) == CSID_X86_KVM_CLK) + ret = vmclock_get_crosststamp_kvmclock(st, NULL, system_counter, + &tspec); + else +#endif + ret = vmclock_get_crosststamp(st, NULL, system_counter, &tspec); + + if (!ret) + *device_time = timespec64_to_ktime(tspec); + + return ret; +} + +static int ptp_vmclock_getcrosststamp(struct ptp_clock_info *ptp, + struct system_device_crosststamp *xtstamp) +{ + struct vmclock_state *st = container_of(ptp, struct vmclock_state, + ptp_clock_info); + int ret = get_device_system_crosststamp(ptp_vmclock_get_time_fn, st, + NULL, xtstamp); +#ifdef SUPPORT_KVMCLOCK + /* + * On x86, the KVM clock may be used for the system time. We can + * actually convert a TSC reading to that, and return a paired + * timestamp that get_device_system_crosststamp() *can* handle. + */ + if (ret == -ENODEV) { + struct system_time_snapshot systime_snapshot; + + ktime_get_snapshot_id(CLOCK_REALTIME, &systime_snapshot); + + if (systime_snapshot.cs_id == CSID_X86_TSC || + systime_snapshot.cs_id == CSID_X86_KVM_CLK) { + WRITE_ONCE(st->sys_cs_id, systime_snapshot.cs_id); + ret = get_device_system_crosststamp(ptp_vmclock_get_time_fn, + st, NULL, xtstamp); + } + } +#endif + return ret; +} + +/* + * PTP clock operations + */ + +static int ptp_vmclock_adjfine(struct ptp_clock_info *ptp, long delta) +{ + return -EOPNOTSUPP; +} + +static int ptp_vmclock_adjtime(struct ptp_clock_info *ptp, s64 delta) +{ + return -EOPNOTSUPP; +} + +static int ptp_vmclock_settime(struct ptp_clock_info *ptp, + const struct timespec64 *ts) +{ + return -EOPNOTSUPP; +} + +static int ptp_vmclock_gettimex(struct ptp_clock_info *ptp, struct timespec64 *ts, + struct ptp_system_timestamp *sts) +{ + struct vmclock_state *st = container_of(ptp, struct vmclock_state, + ptp_clock_info); + + return vmclock_get_crosststamp(st, sts, NULL, ts); +} + +static int ptp_vmclock_enable(struct ptp_clock_info *ptp, + struct ptp_clock_request *rq, int on) +{ + return -EOPNOTSUPP; +} + +static const struct ptp_clock_info ptp_vmclock_info = { + .owner = THIS_MODULE, + .max_adj = 0, + .n_ext_ts = 0, + .n_pins = 0, + .pps = 0, + .adjfine = ptp_vmclock_adjfine, + .adjtime = ptp_vmclock_adjtime, + .gettimex64 = ptp_vmclock_gettimex, + .settime64 = ptp_vmclock_settime, + .enable = ptp_vmclock_enable, + .getcrosststamp = ptp_vmclock_getcrosststamp, +}; + +static struct ptp_clock *vmclock_ptp_register(struct device *dev, + struct vmclock_state *st) +{ + enum clocksource_ids cs_id; + + if (IS_ENABLED(CONFIG_ARM64) && + st->clk->counter_id == VMCLOCK_COUNTER_ARM_VCNT) { + /* Can we check it's the virtual counter? */ + cs_id = CSID_ARM_ARCH_COUNTER; + } else if (IS_ENABLED(CONFIG_X86) && + st->clk->counter_id == VMCLOCK_COUNTER_X86_TSC) { + cs_id = CSID_X86_TSC; + } else { + return NULL; + } + + /* Accept TAI directly, or UTC with valid offset for conversion to TAI */ + if (!tai_adjust(st->clk, NULL)) { + dev_info(dev, "vmclock does not provide unambiguous time\n"); + return NULL; + } + + st->sys_cs_id = cs_id; + st->cs_id = cs_id; + st->ptp_clock_info = ptp_vmclock_info; + strscpy(st->ptp_clock_info.name, st->name); + + return ptp_clock_register(&st->ptp_clock_info, dev); +} + +struct vmclock_file_state { + struct vmclock_state *st; + atomic_t seq; +}; + +static int vmclock_miscdev_mmap(struct file *fp, struct vm_area_struct *vma) +{ + struct vmclock_file_state *fst = fp->private_data; + struct vmclock_state *st = fst->st; + + if ((vma->vm_flags & (VM_READ|VM_WRITE)) != VM_READ) + return -EROFS; + + /* + * Restrict the read-only mapping so it cannot be upgraded to + * writable later with mprotect(). + */ + vm_flags_clear(vma, VM_MAYWRITE); + + if (vma->vm_end - vma->vm_start != PAGE_SIZE || vma->vm_pgoff) + return -EINVAL; + + if (io_remap_pfn_range(vma, vma->vm_start, + st->res.start >> PAGE_SHIFT, PAGE_SIZE, + vma->vm_page_prot)) + return -EAGAIN; + + return 0; +} + +static ssize_t vmclock_miscdev_read(struct file *fp, char __user *buf, + size_t count, loff_t *ppos) +{ + ktime_t deadline = ktime_add(ktime_get(), VMCLOCK_MAX_WAIT); + struct vmclock_file_state *fst = fp->private_data; + struct vmclock_state *st = fst->st; + uint32_t seq, old_seq; + size_t max_count; + + if (*ppos >= PAGE_SIZE) + return 0; + + max_count = PAGE_SIZE - *ppos; + if (count > max_count) + count = max_count; + + old_seq = atomic_read(&fst->seq); + while (1) { + seq = le32_to_cpu(st->clk->seq_count) & ~1U; + /* Pairs with hypervisor wmb */ + virt_rmb(); + + if (copy_to_user(buf, ((char *)st->clk) + *ppos, count)) + return -EFAULT; + + /* Pairs with hypervisor wmb */ + virt_rmb(); + if (seq == le32_to_cpu(st->clk->seq_count)) { + /* + * Either we updated fst->seq to seq (the latest version we observed) + * or someone else did (old_seq == seq), so we can break. + */ + if (atomic_try_cmpxchg(&fst->seq, &old_seq, seq) || + old_seq == seq) { + break; + } + } + + if (ktime_after(ktime_get(), deadline)) + return -ETIMEDOUT; + } + + *ppos += count; + return count; +} + +static __poll_t vmclock_miscdev_poll(struct file *fp, poll_table *wait) +{ + struct vmclock_file_state *fst = fp->private_data; + struct vmclock_state *st = fst->st; + uint32_t seq; + + /* + * Hypervisor will not send us any notifications, so fail immediately + * to avoid having caller sleeping for ever. + */ + if (!(le64_to_cpu(st->clk->flags) & VMCLOCK_FLAG_NOTIFICATION_PRESENT)) + return POLLHUP; + + poll_wait(fp, &st->disrupt_wait, wait); + + seq = le32_to_cpu(st->clk->seq_count); + if (atomic_read(&fst->seq) != seq) + return POLLIN | POLLRDNORM; + + return 0; +} + +static int vmclock_miscdev_open(struct inode *inode, struct file *fp) +{ + struct vmclock_state *st = container_of(fp->private_data, + struct vmclock_state, miscdev); + struct vmclock_file_state *fst = kzalloc_obj(*fst); + + if (!fst) + return -ENOMEM; + + fst->st = st; + atomic_set(&fst->seq, 0); + + fp->private_data = fst; + + return 0; +} + +static int vmclock_miscdev_release(struct inode *inode, struct file *fp) +{ + kfree(fp->private_data); + return 0; +} + +static const struct file_operations vmclock_miscdev_fops = { + .owner = THIS_MODULE, + .open = vmclock_miscdev_open, + .release = vmclock_miscdev_release, + .mmap = vmclock_miscdev_mmap, + .read = vmclock_miscdev_read, + .poll = vmclock_miscdev_poll, +}; + +/* module operations */ + +#if IS_ENABLED(CONFIG_ACPI) +static acpi_status vmclock_acpi_resources(struct acpi_resource *ares, void *data) +{ + struct vmclock_state *st = data; + struct resource_win win; + struct resource *res = &win.res; + + if (ares->type == ACPI_RESOURCE_TYPE_END_TAG) + return AE_OK; + + /* There can be only one */ + if (resource_type(&st->res) == IORESOURCE_MEM) + return AE_ERROR; + + if (acpi_dev_resource_memory(ares, res) || + acpi_dev_resource_address_space(ares, &win)) { + + if (resource_type(res) != IORESOURCE_MEM || + resource_size(res) < sizeof(st->clk)) + return AE_ERROR; + + st->res = *res; + return AE_OK; + } + + return AE_ERROR; +} + +static void +vmclock_acpi_notification_handler(acpi_handle __always_unused handle, + u32 __always_unused event, void *dev) +{ + struct device *device = dev; + struct vmclock_state *st = device->driver_data; + + wake_up_interruptible(&st->disrupt_wait); +} + +static int vmclock_setup_acpi_notification(struct device *dev) +{ + struct acpi_device *adev = ACPI_COMPANION(dev); + acpi_status status; + + /* + * This should never happen as this function is only called when + * has_acpi_companion(dev) is true, but the logic is sufficiently + * complex that Coverity can't see the tautology. + */ + if (!adev) + return -ENODEV; + + status = acpi_install_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY, + vmclock_acpi_notification_handler, + dev); + if (ACPI_FAILURE(status)) { + dev_err(dev, "failed to install notification handler"); + return -ENODEV; + } + + return 0; +} + +static int vmclock_probe_acpi(struct device *dev, struct vmclock_state *st) +{ + struct acpi_device *adev = ACPI_COMPANION(dev); + acpi_status status; + + /* + * This should never happen as this function is only called when + * has_acpi_companion(dev) is true, but the logic is sufficiently + * complex that Coverity can't see the tautology. + */ + if (!adev) + return -ENODEV; + + status = acpi_walk_resources(adev->handle, METHOD_NAME__CRS, + vmclock_acpi_resources, st); + if (ACPI_FAILURE(status) || resource_type(&st->res) != IORESOURCE_MEM) { + dev_err(dev, "failed to get resources\n"); + return -ENODEV; + } + + return 0; +} +#endif /* CONFIG_ACPI */ + +static irqreturn_t vmclock_of_irq_handler(int __always_unused irq, void *_st) +{ + struct vmclock_state *st = _st; + + wake_up_interruptible(&st->disrupt_wait); + return IRQ_HANDLED; +} + +static int vmclock_probe_dt(struct device *dev, struct vmclock_state *st) +{ + struct platform_device *pdev = to_platform_device(dev); + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + st->res = *res; + + return 0; +} + +static int vmclock_setup_of_notification(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + int irq; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + return devm_request_irq(dev, irq, vmclock_of_irq_handler, IRQF_SHARED, + "vmclock", dev->driver_data); +} + +static int vmclock_setup_notification(struct device *dev, + struct vmclock_state *st) +{ + /* The device does not support notifications. Nothing else to do */ + if (!(le64_to_cpu(st->clk->flags) & VMCLOCK_FLAG_NOTIFICATION_PRESENT)) + return 0; + +#if IS_ENABLED(CONFIG_ACPI) + if (has_acpi_companion(dev)) + return vmclock_setup_acpi_notification(dev); +#endif + return vmclock_setup_of_notification(dev); +} + +static void vmclock_remove(void *data) +{ + struct device *dev = data; + struct vmclock_state *st = dev->driver_data; + + if (!st) { + dev_err(dev, "%s called with NULL driver_data", __func__); + return; + } + +#if IS_ENABLED(CONFIG_ACPI) + if (has_acpi_companion(dev)) + acpi_remove_notify_handler(ACPI_COMPANION(dev)->handle, + ACPI_DEVICE_NOTIFY, + vmclock_acpi_notification_handler); +#endif + + if (st->ptp_clock) + ptp_clock_unregister(st->ptp_clock); + + if (st->miscdev.minor != MISC_DYNAMIC_MINOR) + misc_deregister(&st->miscdev); + + dev->driver_data = NULL; +} + +static void vmclock_put_idx(void *data) +{ + struct vmclock_state *st = data; + + ida_free(&vmclock_ida, st->index); +} + +static int vmclock_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct vmclock_state *st; + int ret; + + st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL); + if (!st) + return -ENOMEM; + +#if IS_ENABLED(CONFIG_ACPI) + if (has_acpi_companion(dev)) + ret = vmclock_probe_acpi(dev, st); + else +#endif + ret = vmclock_probe_dt(dev, st); + + if (ret) { + dev_info(dev, "Failed to obtain physical address: %d\n", ret); + return ret; + } + + if (resource_size(&st->res) < VMCLOCK_MIN_SIZE) { + dev_info(dev, "Region too small (0x%llx)\n", + resource_size(&st->res)); + return -EINVAL; + } + st->clk = devm_memremap(dev, st->res.start, resource_size(&st->res), + MEMREMAP_WB | MEMREMAP_DEC); + if (IS_ERR(st->clk)) { + ret = PTR_ERR(st->clk); + dev_info(dev, "failed to map shared memory\n"); + st->clk = NULL; + return ret; + } + + if (le32_to_cpu(st->clk->magic) != VMCLOCK_MAGIC || + le32_to_cpu(st->clk->size) > resource_size(&st->res) || + le16_to_cpu(st->clk->version) != 1) { + dev_info(dev, "vmclock magic fields invalid\n"); + return -EINVAL; + } + + ret = ida_alloc(&vmclock_ida, GFP_KERNEL); + if (ret < 0) + return ret; + + st->index = ret; + ret = devm_add_action_or_reset(&pdev->dev, vmclock_put_idx, st); + if (ret) + return ret; + + st->name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "vmclock%d", st->index); + if (!st->name) + return -ENOMEM; + + st->miscdev.minor = MISC_DYNAMIC_MINOR; + + init_waitqueue_head(&st->disrupt_wait); + dev->driver_data = st; + + ret = devm_add_action_or_reset(&pdev->dev, vmclock_remove, dev); + if (ret) + return ret; + + ret = vmclock_setup_notification(dev, st); + if (ret) + return ret; + + /* + * If the structure is big enough, it can be mapped to userspace. + * Theoretically a guest OS even using larger pages could still + * use 4KiB PTEs to map smaller MMIO regions like this, but let's + * cross that bridge if/when we come to it. + */ + if (le32_to_cpu(st->clk->size) >= PAGE_SIZE) { + st->miscdev.fops = &vmclock_miscdev_fops; + st->miscdev.name = st->name; + + ret = misc_register(&st->miscdev); + if (ret) + return ret; + } + + /* If there is valid clock information, register a PTP clock */ + if (VMCLOCK_FIELD_PRESENT(st->clk, time_frac_sec)) { + /* Can return a silent NULL, or an error. */ + st->ptp_clock = vmclock_ptp_register(dev, st); + if (IS_ERR(st->ptp_clock)) { + ret = PTR_ERR(st->ptp_clock); + st->ptp_clock = NULL; + return ret; + } + } + + if (!st->miscdev.minor && !st->ptp_clock) { + /* Neither miscdev nor PTP registered */ + dev_info(dev, "vmclock: Neither miscdev nor PTP available; not registering\n"); + return -ENODEV; + } + + dev_info(dev, "%s: registered %s%s%s\n", st->name, + st->miscdev.minor ? "miscdev" : "", + (st->miscdev.minor && st->ptp_clock) ? ", " : "", + st->ptp_clock ? "PTP" : ""); + + return 0; +} + +static const struct acpi_device_id vmclock_acpi_ids[] = { + { "AMZNC10C", 0 }, + { "VMCLOCK", 0 }, + {} +}; +MODULE_DEVICE_TABLE(acpi, vmclock_acpi_ids); + +static const struct of_device_id vmclock_of_ids[] = { + { .compatible = "amazon,vmclock", }, + { }, +}; +MODULE_DEVICE_TABLE(of, vmclock_of_ids); + +static struct platform_driver vmclock_platform_driver = { + .probe = vmclock_probe, + .driver = { + .name = "vmclock", + .acpi_match_table = vmclock_acpi_ids, + .of_match_table = vmclock_of_ids, + }, +}; + +module_platform_driver(vmclock_platform_driver) + +MODULE_AUTHOR("David Woodhouse "); +MODULE_DESCRIPTION("PTP clock using VMCLOCK"); +MODULE_LICENSE("GPL"); diff --git a/drivers/ptp/ptp_vmw.c b/drivers/ptp/ptp_vmw.c new file mode 100644 index 000000000..14fc587d0 --- /dev/null +++ b/drivers/ptp/ptp_vmw.c @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause +/* + * Copyright (C) 2020 VMware, Inc., Palo Alto, CA., USA + * + * PTP clock driver for VMware precision clock virtual device. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include + +#define VMWARE_CMD_PCLK(nr) ((nr << 16) | 97) +#define VMWARE_CMD_PCLK_GETTIME VMWARE_CMD_PCLK(0) + +static struct ptp_clock *ptp_vmw_clock; + + +static int ptp_vmw_pclk_read(u64 *ns) +{ + u32 ret, nsec_hi, nsec_lo; + + ret = vmware_hypercall3(VMWARE_CMD_PCLK_GETTIME, 0, + &nsec_hi, &nsec_lo); + if (ret == 0) + *ns = ((u64)nsec_hi << 32) | nsec_lo; + return ret; +} + +/* + * PTP clock ops. + */ + +static int ptp_vmw_adjtime(struct ptp_clock_info *info, s64 delta) +{ + return -EOPNOTSUPP; +} + +static int ptp_vmw_adjfine(struct ptp_clock_info *info, long delta) +{ + return -EOPNOTSUPP; +} + +static int ptp_vmw_gettime(struct ptp_clock_info *info, struct timespec64 *ts) +{ + u64 ns; + + if (ptp_vmw_pclk_read(&ns) != 0) + return -EIO; + *ts = ns_to_timespec64(ns); + return 0; +} + +static int ptp_vmw_settime(struct ptp_clock_info *info, + const struct timespec64 *ts) +{ + return -EOPNOTSUPP; +} + +static int ptp_vmw_enable(struct ptp_clock_info *info, + struct ptp_clock_request *request, int on) +{ + return -EOPNOTSUPP; +} + +static struct ptp_clock_info ptp_vmw_clock_info = { + .owner = THIS_MODULE, + .name = "ptp_vmw", + .max_adj = 0, + .adjtime = ptp_vmw_adjtime, + .adjfine = ptp_vmw_adjfine, + .gettime64 = ptp_vmw_gettime, + .settime64 = ptp_vmw_settime, + .enable = ptp_vmw_enable, +}; + +/* + * ACPI driver ops for VMware "precision clock" virtual device. + */ + +static int ptp_vmw_acpi_probe(struct platform_device *pdev) +{ + ptp_vmw_clock = ptp_clock_register(&ptp_vmw_clock_info, NULL); + if (IS_ERR(ptp_vmw_clock)) { + pr_err("failed to register ptp clock\n"); + return PTR_ERR(ptp_vmw_clock); + } + return 0; +} + +static void ptp_vmw_acpi_remove(struct platform_device *pdev) +{ + ptp_clock_unregister(ptp_vmw_clock); +} + +static const struct acpi_device_id ptp_vmw_acpi_device_ids[] = { + { "VMW0005", 0 }, + { "", 0 }, +}; + +MODULE_DEVICE_TABLE(acpi, ptp_vmw_acpi_device_ids); + +static struct platform_driver ptp_vmw_acpi_driver = { + .probe = ptp_vmw_acpi_probe, + .remove = ptp_vmw_acpi_remove, + .driver = { + .name = "ptp_vmw_acpi", + .acpi_match_table = ptp_vmw_acpi_device_ids, + }, +}; + +static int __init ptp_vmw_init(void) +{ + if (x86_hyper_type != X86_HYPER_VMWARE) + return -1; + return platform_driver_register(&ptp_vmw_acpi_driver); +} + +static void __exit ptp_vmw_exit(void) +{ + platform_driver_unregister(&ptp_vmw_acpi_driver); +} + +module_init(ptp_vmw_init); +module_exit(ptp_vmw_exit); + +MODULE_DESCRIPTION("VMware virtual PTP clock driver"); +MODULE_AUTHOR("VMware, Inc."); +MODULE_LICENSE("Dual BSD/GPL"); -- cgit v1.3.1