diff options
Diffstat (limited to 'drivers/ptp')
-rw-r--r-- | drivers/ptp/Kconfig | 40 | ||||
-rw-r--r-- | drivers/ptp/Makefile | 3 | ||||
-rw-r--r-- | drivers/ptp/ptp_chardev.c | 15 | ||||
-rw-r--r-- | drivers/ptp/ptp_clock.c | 79 | ||||
-rw-r--r-- | drivers/ptp/ptp_clockmatrix.c | 8 | ||||
-rw-r--r-- | drivers/ptp/ptp_dte.c | 4 | ||||
-rw-r--r-- | drivers/ptp/ptp_fc3.c | 1009 | ||||
-rw-r--r-- | drivers/ptp/ptp_fc3.h | 45 | ||||
-rw-r--r-- | drivers/ptp/ptp_idt82p33.c | 14 | ||||
-rw-r--r-- | drivers/ptp/ptp_ines.c | 9 | ||||
-rw-r--r-- | drivers/ptp/ptp_kvm_common.c | 10 | ||||
-rw-r--r-- | drivers/ptp/ptp_kvm_x86.c | 10 | ||||
-rw-r--r-- | drivers/ptp/ptp_ocp.c | 519 | ||||
-rw-r--r-- | drivers/ptp/ptp_pch.c | 6 | ||||
-rw-r--r-- | drivers/ptp/ptp_private.h | 2 | ||||
-rw-r--r-- | drivers/ptp/ptp_qoriq.c | 3 | ||||
-rw-r--r-- | drivers/ptp/ptp_s390.c | 129 | ||||
-rw-r--r-- | drivers/ptp/ptp_sysfs.c | 16 | ||||
-rw-r--r-- | drivers/ptp/ptp_vclock.c | 2 | ||||
-rw-r--r-- | drivers/ptp/ptp_vmclock.c | 610 | ||||
-rw-r--r-- | drivers/ptp/ptp_vmw.c | 13 |
21 files changed, 2360 insertions, 186 deletions
diff --git a/drivers/ptp/Kconfig b/drivers/ptp/Kconfig index 5dd5f188e14f..07bf7f9aae01 100644 --- a/drivers/ptp/Kconfig +++ b/drivers/ptp/Kconfig @@ -131,6 +131,23 @@ config PTP_1588_CLOCK_KVM 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 && ACPI && 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 @@ -155,6 +172,18 @@ config PTP_1588_CLOCK_IDTCM 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 @@ -212,4 +241,15 @@ config PTP_DFL_TOD 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. endmenu diff --git a/drivers/ptp/Makefile b/drivers/ptp/Makefile index dea0cebd2303..25f846fe48c9 100644 --- a/drivers/ptp/Makefile +++ b/drivers/ptp/Makefile @@ -11,12 +11,15 @@ 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 ptp-qoriq-y += ptp_qoriq.o ptp-qoriq-$(CONFIG_DEBUG_FS) += ptp_qoriq_debugfs.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 diff --git a/drivers/ptp/ptp_chardev.c b/drivers/ptp/ptp_chardev.c index 7513018c9f9a..bf6468c56419 100644 --- a/drivers/ptp/ptp_chardev.c +++ b/drivers/ptp/ptp_chardev.c @@ -4,6 +4,7 @@ * * Copyright (C) 2010 OMICRON electronics GmbH */ +#include <linux/compat.h> #include <linux/module.h> #include <linux/posix-clock.h> #include <linux/poll.h> @@ -85,7 +86,8 @@ int ptp_set_pinfunc(struct ptp_clock *ptp, unsigned int pin, } if (info->verify(info, pin, func, chan)) { - pr_err("driver cannot use function %u on pin %u\n", func, chan); + pr_err("driver cannot use function %u and channel %u on pin %u\n", + func, chan, pin); return -EOPNOTSUPP; } @@ -175,6 +177,9 @@ long ptp_ioctl(struct posix_clock_context *pccontext, unsigned int cmd, struct timespec64 ts; int enable, err = 0; + if (in_compat_syscall() && cmd != PTP_ENABLE_PPS && cmd != PTP_ENABLE_PPS2) + arg = (unsigned long)compat_ptr(arg); + tsevq = pccontext->private_clkdata; switch (cmd) { @@ -358,11 +363,15 @@ long ptp_ioctl(struct posix_clock_context *pccontext, unsigned int cmd, extoff = NULL; break; } - if (extoff->n_samples > PTP_MAX_SAMPLES - || extoff->rsv[0] || extoff->rsv[1] || extoff->rsv[2]) { + if (extoff->n_samples > PTP_MAX_SAMPLES || + extoff->rsv[0] || extoff->rsv[1] || + (extoff->clockid != CLOCK_REALTIME && + extoff->clockid != CLOCK_MONOTONIC && + extoff->clockid != CLOCK_MONOTONIC_RAW)) { err = -EINVAL; break; } + sts.clockid = extoff->clockid; for (i = 0; i < extoff->n_samples; i++) { err = ptp->info->gettimex64(ptp->info, &ts, &sts); if (err) diff --git a/drivers/ptp/ptp_clock.c b/drivers/ptp/ptp_clock.c index 15b804ba4868..35a5994bf64f 100644 --- a/drivers/ptp/ptp_clock.c +++ b/drivers/ptp/ptp_clock.c @@ -4,7 +4,6 @@ * * Copyright (C) 2010 OMICRON electronics GmbH */ -#include <linux/idr.h> #include <linux/device.h> #include <linux/err.h> #include <linux/init.h> @@ -16,6 +15,7 @@ #include <linux/syscalls.h> #include <linux/uaccess.h> #include <linux/debugfs.h> +#include <linux/xarray.h> #include <uapi/linux/sched/types.h> #include "ptp_private.h" @@ -25,13 +25,16 @@ #define PTP_PPS_EVENT PPS_CAPTUREASSERT #define PTP_PPS_MODE (PTP_PPS_DEFAULTS | PPS_CANWAIT | PPS_TSFMT_TSPEC) -struct class *ptp_class; +const struct class ptp_class = { + .name = "ptp", + .dev_groups = ptp_groups +}; /* private globals */ static dev_t ptp_devt; -static DEFINE_IDA(ptp_clocks_map); +static DEFINE_XARRAY_ALLOC(ptp_clocks_map); /* time stamp event queue operations */ @@ -44,18 +47,31 @@ 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; - seconds = div_u64_rem(src->timestamp, 1000000000, &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)) @@ -134,7 +150,8 @@ static int ptp_clock_adjtime(struct posix_clock *pc, struct __kernel_timex *tx) if (ppb > ops->max_adj || ppb < -ops->max_adj) return -ERANGE; err = ops->adjfine(ops, tx->freq); - ptp->dialed_frequency = 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); @@ -188,7 +205,7 @@ static void ptp_clock_release(struct device *dev) bitmap_free(tsevq->mask); kfree(tsevq); debugfs_remove(ptp->debugfs_root); - ida_free(&ptp_clocks_map, ptp->index); + xa_erase(&ptp_clocks_map, ptp->index); kfree(ptp); } @@ -200,6 +217,11 @@ static int ptp_getcycles64(struct ptp_clock_info *info, struct timespec64 *ts) 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, @@ -220,7 +242,7 @@ struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info, { struct ptp_clock *ptp; struct timestamp_event_queue *queue = NULL; - int err = 0, index, major = MAJOR(ptp_devt); + int err, index, major = MAJOR(ptp_devt); char debugfsname[16]; size_t size; @@ -228,16 +250,16 @@ struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info, return ERR_PTR(-EINVAL); /* Initialize a clock structure. */ - err = -ENOMEM; ptp = kzalloc(sizeof(struct ptp_clock), GFP_KERNEL); - if (ptp == NULL) + if (!ptp) { + err = -ENOMEM; goto no_memory; + } - index = ida_alloc_max(&ptp_clocks_map, MINORMASK, GFP_KERNEL); - if (index < 0) { - err = index; + 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; @@ -245,13 +267,17 @@ struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info, ptp->index = index; INIT_LIST_HEAD(&ptp->tsevqs); queue = kzalloc(sizeof(*queue), GFP_KERNEL); - if (!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) + 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); @@ -273,9 +299,12 @@ struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info, 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_create_worker(0, "ptp%d", ptp->index); + 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); @@ -322,7 +351,7 @@ struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info, /* 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.class = &ptp_class; ptp->dev.parent = parent; ptp->dev.groups = ptp->pin_attr_groups; ptp->dev.release = ptp_clock_release; @@ -365,7 +394,7 @@ no_memory_bitmap: list_del(&queue->qlist); kfree(queue); no_memory_queue: - ida_free(&ptp_clocks_map, index); + xa_erase(&ptp_clocks_map, index); no_slot: kfree(ptp); no_memory: @@ -417,6 +446,7 @@ void ptp_clock_event(struct ptp_clock *ptp, struct ptp_clock_event *event) 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) { @@ -495,19 +525,19 @@ EXPORT_SYMBOL(ptp_cancel_worker_sync); static void __exit ptp_exit(void) { - class_destroy(ptp_class); + class_unregister(&ptp_class); unregister_chrdev_region(ptp_devt, MINORMASK + 1); - ida_destroy(&ptp_clocks_map); + xa_destroy(&ptp_clocks_map); } static int __init ptp_init(void) { int err; - ptp_class = class_create("ptp"); - if (IS_ERR(ptp_class)) { + err = class_register(&ptp_class); + if (err) { pr_err("ptp: failed to allocate class\n"); - return PTR_ERR(ptp_class); + return err; } err = alloc_chrdev_region(&ptp_devt, 0, MINORMASK + 1, "ptp"); @@ -516,12 +546,11 @@ static int __init ptp_init(void) goto no_region; } - ptp_class->dev_groups = ptp_groups; pr_info("PTP clock support registered\n"); return 0; no_region: - class_destroy(ptp_class); + class_unregister(&ptp_class); return err; } diff --git a/drivers/ptp/ptp_clockmatrix.c b/drivers/ptp/ptp_clockmatrix.c index f6f9d4adce04..fbb3fa8fc60b 100644 --- a/drivers/ptp/ptp_clockmatrix.c +++ b/drivers/ptp/ptp_clockmatrix.c @@ -17,7 +17,7 @@ #include <linux/of.h> #include <linux/mfd/rsmu.h> #include <linux/mfd/idt8a340_reg.h> -#include <asm/unaligned.h> +#include <linux/unaligned.h> #include "ptp_private.h" #include "ptp_clockmatrix.h" @@ -2457,15 +2457,13 @@ static int idtcm_probe(struct platform_device *pdev) return 0; } -static int idtcm_remove(struct platform_device *pdev) +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); - - return 0; } static struct platform_driver idtcm_driver = { @@ -2473,7 +2471,7 @@ static struct platform_driver idtcm_driver = { .name = "8a3400x-phc", }, .probe = idtcm_probe, - .remove = idtcm_remove, + .remove = idtcm_remove, }; module_platform_driver(idtcm_driver); diff --git a/drivers/ptp/ptp_dte.c b/drivers/ptp/ptp_dte.c index 7cc5a00e625b..847276c69008 100644 --- a/drivers/ptp/ptp_dte.c +++ b/drivers/ptp/ptp_dte.c @@ -258,7 +258,7 @@ static int ptp_dte_probe(struct platform_device *pdev) return 0; } -static int ptp_dte_remove(struct platform_device *pdev) +static void ptp_dte_remove(struct platform_device *pdev) { struct ptp_dte *ptp_dte = platform_get_drvdata(pdev); u8 i; @@ -267,8 +267,6 @@ static int ptp_dte_remove(struct platform_device *pdev) for (i = 0; i < DTE_NUM_REGS_TO_RESTORE; i++) writel(0, ptp_dte->regs + (i * sizeof(u32))); - - return 0; } #ifdef CONFIG_PM_SLEEP diff --git a/drivers/ptp/ptp_fc3.c b/drivers/ptp/ptp_fc3.c new file mode 100644 index 000000000000..cfced36c70bc --- /dev/null +++ b/drivers/ptp/ptp_fc3.c @@ -0,0 +1,1009 @@ +// 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 <linux/firmware.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/ptp_clock_kernel.h> +#include <linux/delay.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/timekeeping.h> +#include <linux/string.h> +#include <linux/of.h> +#include <linux/bitfield.h> +#include <linux/mfd/rsmu.h> +#include <linux/mfd/idtRC38xxx_reg.h> +#include <linux/unaligned.h> + +#include "ptp_private.h" +#include "ptp_fc3.h" + +MODULE_DESCRIPTION("Driver for IDT FemtoClock3(TM) family"); +MODULE_AUTHOR("IDT support-1588 <IDT-support-1588@lm.renesas.com>"); +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 = sign_extend64(FIELD_GET(FINE_MEAS_MASK, meas_read), 12); + coarse = sign_extend64(FIELD_GET(COARSE_MEAS_MASK, meas_read), (39 - 13)); + + 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, + .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; + + ptp_schedule_worker(idtfc3->ptp_clock, idtfc3->tc_update_period); + + 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"); + 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; + } + + err = idtfc3_set_overhead(idtfc3); + if (err) + return err; + + err = idtfc3_init_timecounter(idtfc3); + if (err) + return err; + + 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 000000000000..897101579207 --- /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 <linux/ktime.h> +#include <linux/ptp_clock.h> +#include <linux/regmap.h> + +#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 index 057190b9cd3d..b2fd94d4f863 100644 --- a/drivers/ptp/ptp_idt82p33.c +++ b/drivers/ptp/ptp_idt82p33.c @@ -1171,10 +1171,10 @@ static void idt82p33_caps_init(u32 index, struct ptp_clock_info *caps, 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->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; @@ -1447,15 +1447,13 @@ static int idt82p33_probe(struct platform_device *pdev) return 0; } -static int idt82p33_remove(struct platform_device *pdev) +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); - - return 0; } static struct platform_driver idt82p33_driver = { @@ -1463,7 +1461,7 @@ static struct platform_driver idt82p33_driver = { .name = "82p33x1x-phc", }, .probe = idt82p33_probe, - .remove = idt82p33_remove, + .remove = idt82p33_remove, }; module_platform_driver(idt82p33_driver); diff --git a/drivers/ptp/ptp_ines.c b/drivers/ptp/ptp_ines.c index 1d2940a78455..68f1f7fdaa9d 100644 --- a/drivers/ptp/ptp_ines.c +++ b/drivers/ptp/ptp_ines.c @@ -556,18 +556,14 @@ static bool ines_timestamp_expired(struct ines_timestamp *ts) } static int ines_ts_info(struct mii_timestamper *mii_ts, - struct ethtool_ts_info *info) + struct kernel_ethtool_ts_info *info) { info->so_timestamping = SOF_TIMESTAMPING_TX_HARDWARE | SOF_TIMESTAMPING_TX_SOFTWARE | SOF_TIMESTAMPING_RX_HARDWARE | - SOF_TIMESTAMPING_RX_SOFTWARE | - SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_RAW_HARDWARE; - info->phc_index = -1; - info->tx_types = (1 << HWTSTAMP_TX_OFF) | (1 << HWTSTAMP_TX_ON) | @@ -765,7 +761,7 @@ out: return err; } -static int ines_ptp_ctrl_remove(struct platform_device *pld) +static void ines_ptp_ctrl_remove(struct platform_device *pld) { struct ines_clock *clock = dev_get_drvdata(&pld->dev); @@ -775,7 +771,6 @@ static int ines_ptp_ctrl_remove(struct platform_device *pld) mutex_unlock(&ines_clocks_lock); ines_clock_cleanup(clock); kfree(clock); - return 0; } static const struct of_device_id ines_ptp_ctrl_of_match[] = { diff --git a/drivers/ptp/ptp_kvm_common.c b/drivers/ptp/ptp_kvm_common.c index 2418977989be..15ccb7dd2ed0 100644 --- a/drivers/ptp/ptp_kvm_common.c +++ b/drivers/ptp/ptp_kvm_common.c @@ -28,15 +28,15 @@ static int ptp_kvm_get_time_fn(ktime_t *device_time, struct system_counterval_t *system_counter, void *ctx) { - long ret; - u64 cycle; + enum clocksource_ids cs_id; struct timespec64 tspec; - struct clocksource *cs; + u64 cycle; + int ret; spin_lock(&kvm_ptp_lock); preempt_disable_notrace(); - ret = kvm_arch_ptp_get_crosststamp(&cycle, &tspec, &cs); + ret = kvm_arch_ptp_get_crosststamp(&cycle, &tspec, &cs_id); if (ret) { spin_unlock(&kvm_ptp_lock); preempt_enable_notrace(); @@ -46,7 +46,7 @@ static int ptp_kvm_get_time_fn(ktime_t *device_time, preempt_enable_notrace(); system_counter->cycles = cycle; - system_counter->cs = cs; + system_counter->cs_id = cs_id; *device_time = timespec64_to_ktime(tspec); diff --git a/drivers/ptp/ptp_kvm_x86.c b/drivers/ptp/ptp_kvm_x86.c index 902844cc1a17..6cea4fe39bcf 100644 --- a/drivers/ptp/ptp_kvm_x86.c +++ b/drivers/ptp/ptp_kvm_x86.c @@ -26,7 +26,7 @@ int kvm_arch_ptp_init(void) long ret; if (!kvm_para_available()) - return -ENODEV; + return -EOPNOTSUPP; if (cc_platform_has(CC_ATTR_GUEST_MEM_ENCRYPT)) { p = alloc_page(GFP_KERNEL | __GFP_ZERO); @@ -46,14 +46,14 @@ int kvm_arch_ptp_init(void) clock_pair_gpa = slow_virt_to_phys(clock_pair); if (!pvclock_get_pvti_cpu0_va()) { - ret = -ENODEV; + ret = -EOPNOTSUPP; goto err; } ret = kvm_hypercall2(KVM_HC_CLOCK_PAIRING, clock_pair_gpa, KVM_CLOCK_PAIRING_WALLCLOCK); if (ret == -KVM_ENOSYS) { - ret = -ENODEV; + ret = -EOPNOTSUPP; goto err; } @@ -93,7 +93,7 @@ int kvm_arch_ptp_get_clock(struct timespec64 *ts) } int kvm_arch_ptp_get_crosststamp(u64 *cycle, struct timespec64 *tspec, - struct clocksource **cs) + enum clocksource_ids *cs_id) { struct pvclock_vcpu_time_info *src; unsigned int version; @@ -123,7 +123,7 @@ int kvm_arch_ptp_get_crosststamp(u64 *cycle, struct timespec64 *tspec, *cycle = __pvclock_read_cycles(src, clock_pair->tsc); } while (pvclock_read_retry(src, version)); - *cs = &kvm_clock; + *cs_id = CSID_X86_KVM_CLK; return 0; } diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c index 5f858e426bbd..b651087f426f 100644 --- a/drivers/ptp/ptp_ocp.c +++ b/drivers/ptp/ptp_ocp.c @@ -34,6 +34,9 @@ #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 + static struct class timecard_class = { .name = "timecard", }; @@ -63,6 +66,13 @@ struct ocp_reg { u32 status_drift; }; +struct ptp_ocp_servo_conf { + u32 servo_offset_p; + u32 servo_offset_i; + u32 servo_drift_p; + u32 servo_drift_i; +}; + #define OCP_CTRL_ENABLE BIT(0) #define OCP_CTRL_ADJUST_TIME BIT(1) #define OCP_CTRL_ADJUST_OFFSET BIT(2) @@ -306,6 +316,15 @@ struct ptp_ocp_serial_port { #define OCP_SERIAL_LEN 6 #define OCP_SMA_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; @@ -347,10 +366,7 @@ struct ptp_ocp { struct delayed_work sync_work; int id; int n_irqs; - struct ptp_ocp_serial_port gnss_port; - struct ptp_ocp_serial_port gnss2_port; - struct ptp_ocp_serial_port mac_port; /* miniature atomic clock */ - struct ptp_ocp_serial_port nmea_port; + struct ptp_ocp_serial_port port[__PORT_COUNT]; bool fw_loader; u8 fw_tag; u16 fw_version; @@ -397,10 +413,14 @@ 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_attr_group fb_timecard_groups[]; static const struct ocp_attr_group art_timecard_groups[]; +static const struct ocp_attr_group adva_timecard_groups[]; + struct ptp_ocp_eeprom_map { u16 off; u16 len; @@ -641,28 +661,28 @@ static struct ocp_resource ocp_fb_resource[] = { }, }, { - OCP_SERIAL_RESOURCE(gnss_port), + OCP_SERIAL_RESOURCE(port[PORT_GNSS]), .offset = 0x00160000 + 0x1000, .irq_vec = 3, .extra = &(struct ptp_ocp_serial_port) { .baud = 115200, }, }, { - OCP_SERIAL_RESOURCE(gnss2_port), + OCP_SERIAL_RESOURCE(port[PORT_GNSS2]), .offset = 0x00170000 + 0x1000, .irq_vec = 4, .extra = &(struct ptp_ocp_serial_port) { .baud = 115200, }, }, { - OCP_SERIAL_RESOURCE(mac_port), + OCP_SERIAL_RESOURCE(port[PORT_MAC]), .offset = 0x00180000 + 0x1000, .irq_vec = 5, .extra = &(struct ptp_ocp_serial_port) { .baud = 57600, }, }, { - OCP_SERIAL_RESOURCE(nmea_port), + OCP_SERIAL_RESOURCE(port[PORT_NMEA]), .offset = 0x00190000 + 0x1000, .irq_vec = 10, }, { @@ -700,6 +720,12 @@ static struct ocp_resource ocp_fb_resource[] = { }, { .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, + }, }, { } }; @@ -720,7 +746,7 @@ static struct ocp_resource ocp_art_resource[] = { .offset = 0x01000000, .size = 0x10000, }, { - OCP_SERIAL_RESOURCE(gnss_port), + OCP_SERIAL_RESOURCE(port[PORT_GNSS]), .offset = 0x00160000 + 0x1000, .irq_vec = 3, .extra = &(struct ptp_ocp_serial_port) { .baud = 115200, @@ -819,7 +845,7 @@ static struct ocp_resource ocp_art_resource[] = { }, }, { - OCP_SERIAL_RESOURCE(mac_port), + OCP_SERIAL_RESOURCE(port[PORT_MAC]), .offset = 0x00190000, .irq_vec = 7, .extra = &(struct ptp_ocp_serial_port) { .baud = 9600, @@ -831,6 +857,170 @@ static struct ocp_resource ocp_art_resource[] = { }, { .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_servo_conf) { + .servo_offset_p = 0xc000, + .servo_offset_i = 0x1000, + .servo_drift_p = 0, + .servo_drift_i = 0, + }, }, { } }; @@ -839,6 +1029,7 @@ static const struct pci_device_id ptp_ocp_pcidev_id[] = { { PCI_DEVICE_DATA(FACEBOOK, 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) }, { } }; MODULE_DEVICE_TABLE(pci, ptp_ocp_pcidev_id); @@ -917,6 +1108,30 @@ static const struct ocp_selector ptp_ocp_art_sma_out[] = { { } }; +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 }, + { } +}; + struct ocp_sma_op { const struct ocp_selector *tbl[2]; void (*init)(struct ptp_ocp *bp); @@ -1343,27 +1558,29 @@ ptp_ocp_watchdog(struct timer_list *t) static void ptp_ocp_estimate_pci_timing(struct ptp_ocp *bp) { - ktime_t start, end; - ktime_t delay; + ktime_t start, end, delay = U64_MAX; u32 ctrl; + int i; - ctrl = ioread32(&bp->reg->ctrl); - ctrl = OCP_CTRL_READ_TIME_REQ | OCP_CTRL_ENABLE; + 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); + iowrite32(ctrl, &bp->reg->ctrl); - start = ktime_get_ns(); + start = ktime_get_raw_ns(); - ctrl = ioread32(&bp->reg->ctrl); + ctrl = ioread32(&bp->reg->ctrl); - end = ktime_get_ns(); + end = ktime_get_raw_ns(); - delay = end - start; + delay = min(delay, end - start); + } bp->ts_window_adjust = (delay >> 5) * 3; } static int -ptp_ocp_init_clock(struct ptp_ocp *bp) +ptp_ocp_init_clock(struct ptp_ocp *bp, struct ptp_ocp_servo_conf *servo_conf) { struct timespec64 ts; u32 ctrl; @@ -1371,12 +1588,11 @@ ptp_ocp_init_clock(struct ptp_ocp *bp) ctrl = OCP_CTRL_ENABLE; iowrite32(ctrl, &bp->reg->ctrl); - /* NO DRIFT Correction */ - /* offset_p:i 1/8, offset_i: 1/16, drift_p: 0, drift_i: 0 */ - iowrite32(0x2000, &bp->reg->servo_offset_p); - iowrite32(0x1000, &bp->reg->servo_offset_i); - iowrite32(0, &bp->reg->servo_drift_p); - iowrite32(0, &bp->reg->servo_drift_i); + /* 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; @@ -1441,6 +1657,15 @@ ptp_ocp_tod_gnss_name(int idx) 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; @@ -2348,6 +2573,14 @@ static const struct ocp_sma_op ocp_fb_sma_op = { .set_output = ptp_ocp_sma_fb_set_output, }; +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_fb_set_inputs, + .set_output = ptp_ocp_sma_fb_set_output, +}; + static int ptp_ocp_set_pins(struct ptp_ocp *bp) { @@ -2427,7 +2660,7 @@ ptp_ocp_fb_board_init(struct ptp_ocp *bp, struct ocp_resource *r) return err; ptp_ocp_sma_init(bp); - return ptp_ocp_init_clock(bp); + return ptp_ocp_init_clock(bp, r->extra); } static bool @@ -2589,7 +2822,44 @@ ptp_ocp_art_board_init(struct ptp_ocp *bp, struct ocp_resource *r) if (err) return err; - return ptp_ocp_init_clock(bp); + return ptp_ocp_init_clock(bp, r->extra); +} + +/* ADVA specific board initializers; last "resource" registered. */ +static int +ptp_ocp_adva_board_init(struct ptp_ocp *bp, struct ocp_resource *r) +{ + int err; + u32 version; + + bp->flash_start = 0xA00000; + bp->eeprom_map = fb_eeprom_map; + bp->sma_op = &ocp_adva_sma_op; + + 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, adva_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 ssize_t @@ -3094,6 +3364,54 @@ 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); + + 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); @@ -3374,7 +3692,7 @@ DEVICE_FREQ_GROUP(freq4, 3); static ssize_t disciplining_config_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, char *buf, + 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)); @@ -3409,7 +3727,7 @@ out: static ssize_t disciplining_config_write(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, char *buf, + 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)); @@ -3432,11 +3750,11 @@ disciplining_config_write(struct file *filp, struct kobject *kobj, return err; } -static BIN_ATTR_RW(disciplining_config, OCP_ART_CONFIG_SIZE); +static const BIN_ATTR_RW(disciplining_config, OCP_ART_CONFIG_SIZE); static ssize_t temperature_table_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, char *buf, + 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)); @@ -3471,7 +3789,7 @@ out: static ssize_t temperature_table_write(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, char *buf, + 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)); @@ -3494,7 +3812,7 @@ temperature_table_write(struct file *filp, struct kobject *kobj, return err; } -static BIN_ATTR_RW(temperature_table, OCP_ART_TEMP_TABLE_SIZE); +static const BIN_ATTR_RW(temperature_table, OCP_ART_TEMP_TABLE_SIZE); static struct attribute *fb_timecard_attrs[] = { &dev_attr_serialnum.attr, @@ -3522,6 +3840,7 @@ static const struct attribute_group fb_timecard_group = { 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 }, @@ -3548,7 +3867,7 @@ static struct attribute *art_timecard_attrs[] = { NULL, }; -static struct bin_attribute *bin_art_timecard_attrs[] = { +static const struct bin_attribute *const bin_art_timecard_attrs[] = { &bin_attr_disciplining_config, &bin_attr_temperature_table, NULL, @@ -3556,11 +3875,44 @@ static struct bin_attribute *bin_art_timecard_attrs[] = { static const struct attribute_group art_timecard_group = { .attrs = art_timecard_attrs, - .bin_attrs = bin_art_timecard_attrs, + .bin_attrs_new = 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 }, { }, }; @@ -3676,16 +4028,11 @@ ptp_ocp_summary_show(struct seq_file *s, void *data) bp = dev_get_drvdata(dev); seq_printf(s, "%7s: /dev/ptp%d\n", "PTP", ptp_clock_index(bp->ptp)); - if (bp->gnss_port.line != -1) - seq_printf(s, "%7s: /dev/ttyS%d\n", "GNSS1", - bp->gnss_port.line); - if (bp->gnss2_port.line != -1) - seq_printf(s, "%7s: /dev/ttyS%d\n", "GNSS2", - bp->gnss2_port.line); - if (bp->mac_port.line != -1) - seq_printf(s, "%7s: /dev/ttyS%d\n", "MAC", bp->mac_port.line); - if (bp->nmea_port.line != -1) - seq_printf(s, "%7s: /dev/ttyS%d\n", "NMEA", bp->nmea_port.line); + 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) { @@ -3995,7 +4342,7 @@ ptp_ocp_dev_release(struct device *dev) static int ptp_ocp_device_init(struct ptp_ocp *bp, struct pci_dev *pdev) { - int err; + int i, err; mutex_lock(&ptp_ocp_lock); err = idr_alloc(&ptp_ocp_idr, bp, 0, 0, GFP_KERNEL); @@ -4008,10 +4355,10 @@ ptp_ocp_device_init(struct ptp_ocp *bp, struct pci_dev *pdev) bp->ptp_info = ptp_ocp_clock_info; spin_lock_init(&bp->lock); - bp->gnss_port.line = -1; - bp->gnss2_port.line = -1; - bp->mac_port.line = -1; - bp->nmea_port.line = -1; + + for (i = 0; i < __PORT_COUNT; i++) + bp->port[i].line = -1; + bp->pdev = pdev; device_initialize(&bp->dev); @@ -4068,28 +4415,12 @@ ptp_ocp_complete(struct ptp_ocp *bp) struct pps_device *pps; char buf[32]; - if (bp->gnss_port.line != -1) { - sprintf(buf, "ttyS%d", bp->gnss_port.line); - ptp_ocp_link_child(bp, buf, "ttyGNSS"); - } - if (bp->gnss2_port.line != -1) { - sprintf(buf, "ttyS%d", bp->gnss2_port.line); - ptp_ocp_link_child(bp, buf, "ttyGNSS2"); - } - if (bp->mac_port.line != -1) { - sprintf(buf, "ttyS%d", bp->mac_port.line); - ptp_ocp_link_child(bp, buf, "ttyMAC"); - } - if (bp->nmea_port.line != -1) { - sprintf(buf, "ttyS%d", bp->nmea_port.line); - ptp_ocp_link_child(bp, buf, "ttyNMEA"); - } 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_symlink(bp, &pps->dev, "pps"); ptp_ocp_debugfs_add_device(bp); @@ -4132,23 +4463,20 @@ ptp_ocp_info(struct ptp_ocp *bp) }; struct device *dev = &bp->pdev->dev; u32 reg; + int i; ptp_ocp_phc_info(bp); - ptp_ocp_serial_info(dev, "GNSS", bp->gnss_port.line, - bp->gnss_port.baud); - ptp_ocp_serial_info(dev, "GNSS2", bp->gnss2_port.line, - bp->gnss2_port.baud); - ptp_ocp_serial_info(dev, "MAC", bp->mac_port.line, bp->mac_port.baud); - if (bp->nmea_out && bp->nmea_port.line != -1) { - bp->nmea_port.baud = -1; + 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->nmea_port.baud = nmea_baud[reg]; - - ptp_ocp_serial_info(dev, "NMEA", bp->nmea_port.line, - bp->nmea_port.baud); + 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); } } @@ -4157,9 +4485,6 @@ ptp_ocp_detach_sysfs(struct ptp_ocp *bp) { struct device *dev = &bp->dev; - sysfs_remove_link(&dev->kobj, "ttyGNSS"); - sysfs_remove_link(&dev->kobj, "ttyGNSS2"); - sysfs_remove_link(&dev->kobj, "ttyMAC"); sysfs_remove_link(&dev->kobj, "ptp"); sysfs_remove_link(&dev->kobj, "pps"); } @@ -4189,14 +4514,9 @@ ptp_ocp_detach(struct ptp_ocp *bp) for (i = 0; i < 4; i++) if (bp->signal_out[i]) ptp_ocp_unregister_ext(bp->signal_out[i]); - if (bp->gnss_port.line != -1) - serial8250_unregister_port(bp->gnss_port.line); - if (bp->gnss2_port.line != -1) - serial8250_unregister_port(bp->gnss2_port.line); - if (bp->mac_port.line != -1) - serial8250_unregister_port(bp->mac_port.line); - if (bp->nmea_port.line != -1) - serial8250_unregister_port(bp->nmea_port.line); + 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) @@ -4209,10 +4529,11 @@ ptp_ocp_detach(struct ptp_ocp *bp) device_unregister(&bp->dev); } -static int ptp_ocp_dpll_lock_status_get(const struct dpll_device *dpll, - void *priv, - enum dpll_lock_status *status, - struct netlink_ext_ack *extack) +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; @@ -4277,7 +4598,7 @@ static int ptp_ocp_dpll_direction_set(const struct dpll_pin *pin, 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); + return ptp_ocp_sma_store_val(bp, 0, mode, sma_nr + 1); } static int ptp_ocp_dpll_frequency_set(const struct dpll_pin *pin, @@ -4298,7 +4619,7 @@ static int ptp_ocp_dpll_frequency_set(const struct dpll_pin *pin, 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); + return ptp_ocp_sma_store_val(bp, i, sma->mode, sma_nr + 1); return -EINVAL; } @@ -4315,7 +4636,7 @@ static int ptp_ocp_dpll_frequency_get(const struct dpll_pin *pin, u32 val; int i; - val = bp->sma_op->get(bp, sma_nr); + 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) { diff --git a/drivers/ptp/ptp_pch.c b/drivers/ptp/ptp_pch.c index 33355d5eb033..b8a9a54a176c 100644 --- a/drivers/ptp/ptp_pch.c +++ b/drivers/ptp/ptp_pch.c @@ -462,14 +462,14 @@ pch_probe(struct pci_dev *pdev, const struct pci_device_id *id) return ret; } - ret = pcim_iomap_regions(pdev, BIT(IO_MEM_BAR), "1588_regs"); + /* 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; } - /* get the virtual address to the 1588 registers */ - chip->regs = pcim_iomap_table(pdev)[IO_MEM_BAR]; chip->caps = ptp_pch_caps; chip->ptp_clock = ptp_clock_register(&chip->caps, &pdev->dev); if (IS_ERR(chip->ptp_clock)) diff --git a/drivers/ptp/ptp_private.h b/drivers/ptp/ptp_private.h index 45f9002a5dca..18934e28469e 100644 --- a/drivers/ptp/ptp_private.h +++ b/drivers/ptp/ptp_private.h @@ -120,7 +120,7 @@ static inline bool ptp_clock_freerun(struct ptp_clock *ptp) return ptp_vclock_in_use(ptp); } -extern struct class *ptp_class; +extern const struct class ptp_class; /* * see ptp_chardev.c diff --git a/drivers/ptp/ptp_qoriq.c b/drivers/ptp/ptp_qoriq.c index a52859d024f0..4d488c1f1941 100644 --- a/drivers/ptp/ptp_qoriq.c +++ b/drivers/ptp/ptp_qoriq.c @@ -648,14 +648,13 @@ no_memory: return err; } -static int ptp_qoriq_remove(struct platform_device *dev) +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); - return 0; } static const struct of_device_id match_table[] = { diff --git a/drivers/ptp/ptp_s390.c b/drivers/ptp/ptp_s390.c new file mode 100644 index 000000000000..29618eb9bf44 --- /dev/null +++ b/drivers/ptp/ptp_s390.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * s390 PTP clock driver + * + */ + +#include "ptp_private.h" +#include <linux/time.h> +#include <asm/stp.h> + +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); + + 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) +{ + 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 <svens@linux.ibm.com>"); +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 index f7a499a1bd39..6b1b8f57cd95 100644 --- a/drivers/ptp/ptp_sysfs.c +++ b/drivers/ptp/ptp_sysfs.c @@ -24,8 +24,7 @@ static ssize_t max_phase_adjustment_show(struct device *dev, { struct ptp_clock *ptp = dev_get_drvdata(dev); - return snprintf(page, PAGE_SIZE - 1, "%d\n", - ptp->info->getmaxphase(ptp->info)); + return sysfs_emit(page, "%d\n", ptp->info->getmaxphase(ptp->info)); } static DEVICE_ATTR_RO(max_phase_adjustment); @@ -34,7 +33,7 @@ static ssize_t var##_show(struct device *dev, \ struct device_attribute *attr, char *page) \ { \ struct ptp_clock *ptp = dev_get_drvdata(dev); \ - return snprintf(page, PAGE_SIZE-1, "%d\n", ptp->info->var); \ + return sysfs_emit(page, "%d\n", ptp->info->var); \ } \ static DEVICE_ATTR(name, 0444, var##_show, NULL); @@ -102,8 +101,8 @@ static ssize_t extts_fifo_show(struct device *dev, if (!qcnt) goto out; - cnt = snprintf(page, PAGE_SIZE, "%u %lld %u\n", - event.index, event.t.sec, event.t.nsec); + cnt = sysfs_emit(page, "%u %lld %u\n", + event.index, event.t.sec, event.t.nsec); out: return cnt; } @@ -194,7 +193,7 @@ static ssize_t n_vclocks_show(struct device *dev, if (mutex_lock_interruptible(&ptp->n_vclocks_mux)) return -ERESTARTSYS; - size = snprintf(page, PAGE_SIZE - 1, "%u\n", ptp->n_vclocks); + size = sysfs_emit(page, "%u\n", ptp->n_vclocks); mutex_unlock(&ptp->n_vclocks_mux); @@ -270,7 +269,7 @@ static ssize_t max_vclocks_show(struct device *dev, struct ptp_clock *ptp = dev_get_drvdata(dev); ssize_t size; - size = snprintf(page, PAGE_SIZE - 1, "%u\n", ptp->max_vclocks); + size = sysfs_emit(page, "%u\n", ptp->max_vclocks); return size; } @@ -297,8 +296,7 @@ static ssize_t max_vclocks_store(struct device *dev, if (max < ptp->n_vclocks) goto out; - size = sizeof(int) * max; - vclock_index = kzalloc(size, GFP_KERNEL); + vclock_index = kcalloc(max, sizeof(int), GFP_KERNEL); if (!vclock_index) { err = -ENOMEM; goto out; diff --git a/drivers/ptp/ptp_vclock.c b/drivers/ptp/ptp_vclock.c index dcf752c9e045..7febfdcbde8b 100644 --- a/drivers/ptp/ptp_vclock.c +++ b/drivers/ptp/ptp_vclock.c @@ -241,7 +241,7 @@ int ptp_get_vclocks_index(int pclock_index, int **vclock_index) return num; snprintf(name, PTP_CLOCK_NAME_LEN, "ptp%d", pclock_index); - dev = class_find_device_by_name(ptp_class, name); + dev = class_find_device_by_name(&ptp_class, name); if (!dev) return num; diff --git a/drivers/ptp/ptp_vmclock.c b/drivers/ptp/ptp_vmclock.c new file mode 100644 index 000000000000..b3a83b03d9c1 --- /dev/null +++ b/drivers/ptp/ptp_vmclock.c @@ -0,0 +1,610 @@ +// 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/acpi.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/file.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/miscdevice.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <uapi/linux/vmclock-abi.h> + +#include <linux/ptp_clock_kernel.h> + +#ifdef CONFIG_X86 +#include <asm/pvclock.h> +#include <asm/kvmclock.h> +#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; + 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 (likely(clk->time_type == VMCLOCK_TIME_UTC)) + return true; + + if (clk->time_type == VMCLOCK_TIME_TAI && + (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); + struct system_time_snapshot systime_snapshot; + 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 ktime_get_snapshot() will still be used as pre_ts, and + * ptp_read_system_postts() is called to populate postts after + * calling get_cycles(). + * + * The conversion to timespec64 happens further down, outside + * the seq_count loop. + */ + if (sts) { + ktime_get_snapshot(&systime_snapshot); + if (systime_snapshot.cs_id == st->cs_id) { + cycle = systime_snapshot.cycles; + } 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; + } + + if (sts) { + sts->pre_ts = ktime_to_timespec64(systime_snapshot.real); + if (systime_snapshot.cs_id == st->cs_id) + sts->post_ts = sts->pre_ts; + } + + 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(&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; + } + + /* Only UTC, or TAI with offset */ + if (!tai_adjust(st->clk, NULL)) { + dev_info(dev, "vmclock does not provide unambiguous UTC\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); +} + +static int vmclock_miscdev_mmap(struct file *fp, struct vm_area_struct *vma) +{ + struct vmclock_state *st = container_of(fp->private_data, + struct vmclock_state, miscdev); + + if ((vma->vm_flags & (VM_READ|VM_WRITE)) != VM_READ) + return -EROFS; + + 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) +{ + struct vmclock_state *st = container_of(fp->private_data, + struct vmclock_state, miscdev); + ktime_t deadline = ktime_add(ktime_get(), VMCLOCK_MAX_WAIT); + size_t max_count; + uint32_t seq; + + if (*ppos >= PAGE_SIZE) + return 0; + + max_count = PAGE_SIZE - *ppos; + if (count > max_count) + count = max_count; + + 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)) + break; + + if (ktime_after(ktime_get(), deadline)) + return -ETIMEDOUT; + } + + *ppos += count; + return count; +} + +static const struct file_operations vmclock_miscdev_fops = { + .owner = THIS_MODULE, + .mmap = vmclock_miscdev_mmap, + .read = vmclock_miscdev_read, +}; + +/* module operations */ + +static void vmclock_remove(void *data) +{ + struct vmclock_state *st = data; + + if (st->ptp_clock) + ptp_clock_unregister(st->ptp_clock); + + if (st->miscdev.minor != MISC_DYNAMIC_MINOR) + misc_deregister(&st->miscdev); +} + +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 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; +} + +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 (has_acpi_companion(dev)) + ret = vmclock_probe_acpi(dev, st); + else + ret = -EINVAL; /* Only ACPI for now */ + + 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; + + ret = devm_add_action_or_reset(&pdev->dev, vmclock_remove, 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 }, + {} +}; +MODULE_DEVICE_TABLE(acpi, vmclock_acpi_ids); + +static struct platform_driver vmclock_platform_driver = { + .probe = vmclock_probe, + .driver = { + .name = "vmclock", + .acpi_match_table = vmclock_acpi_ids, + }, +}; + +module_platform_driver(vmclock_platform_driver) + +MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); +MODULE_DESCRIPTION("PTP clock using VMCLOCK"); +MODULE_LICENSE("GPL"); diff --git a/drivers/ptp/ptp_vmw.c b/drivers/ptp/ptp_vmw.c index 27c5547aa8a9..20ab05c4daa8 100644 --- a/drivers/ptp/ptp_vmw.c +++ b/drivers/ptp/ptp_vmw.c @@ -14,7 +14,6 @@ #include <asm/hypervisor.h> #include <asm/vmware.h> -#define VMWARE_MAGIC 0x564D5868 #define VMWARE_CMD_PCLK(nr) ((nr << 16) | 97) #define VMWARE_CMD_PCLK_GETTIME VMWARE_CMD_PCLK(0) @@ -24,15 +23,10 @@ static struct ptp_clock *ptp_vmw_clock; static int ptp_vmw_pclk_read(u64 *ns) { - u32 ret, nsec_hi, nsec_lo, unused1, unused2, unused3; - - asm volatile (VMWARE_HYPERCALL : - "=a"(ret), "=b"(nsec_hi), "=c"(nsec_lo), "=d"(unused1), - "=S"(unused2), "=D"(unused3) : - "a"(VMWARE_MAGIC), "b"(0), - "c"(VMWARE_CMD_PCLK_GETTIME), "d"(0) : - "memory"); + 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; @@ -120,7 +114,6 @@ static struct acpi_driver ptp_vmw_acpi_driver = { .add = ptp_vmw_acpi_add, .remove = ptp_vmw_acpi_remove }, - .owner = THIS_MODULE }; static int __init ptp_vmw_init(void) |