diff options
author | Jonathan Lemon <jonathan.lemon@gmail.com> | 2020-12-03 19:51:28 -0800 |
---|---|---|
committer | Jakub Kicinski <kuba@kernel.org> | 2020-12-05 13:59:41 -0800 |
commit | a7e1abad13f3f0366ee625831fecda2b603cdc17 (patch) | |
tree | 3f6bbfae3c2bc1b08bde95c35997960d589f776c /drivers | |
parent | bcd684aace34fedbd473fbd9b21ed06b0c2d2212 (diff) | |
download | linux-stable-a7e1abad13f3f0366ee625831fecda2b603cdc17.tar.gz linux-stable-a7e1abad13f3f0366ee625831fecda2b603cdc17.tar.bz2 linux-stable-a7e1abad13f3f0366ee625831fecda2b603cdc17.zip |
ptp: Add clock driver for the OpenCompute TimeCard.
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/
Signed-off-by: Jonathan Lemon <jonathan.lemon@gmail.com>
Acked-by: Richard Cochran <richardcochran@gmail.com>
Link: https://lore.kernel.org/r/20201204035128.2219252-2-jonathan.lemon@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/ptp/Kconfig | 14 | ||||
-rw-r--r-- | drivers/ptp/Makefile | 1 | ||||
-rw-r--r-- | drivers/ptp/ptp_ocp.c | 398 |
3 files changed, 413 insertions, 0 deletions
diff --git a/drivers/ptp/Kconfig b/drivers/ptp/Kconfig index 942f72d8151d..476d7c7fe70a 100644 --- a/drivers/ptp/Kconfig +++ b/drivers/ptp/Kconfig @@ -151,4 +151,18 @@ config PTP_1588_CLOCK_VMW 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 + default n + 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/ + endmenu diff --git a/drivers/ptp/Makefile b/drivers/ptp/Makefile index 7aff75f745dc..db5aef3bddc6 100644 --- a/drivers/ptp/Makefile +++ b/drivers/ptp/Makefile @@ -15,3 +15,4 @@ ptp-qoriq-$(CONFIG_DEBUG_FS) += ptp_qoriq_debugfs.o obj-$(CONFIG_PTP_1588_CLOCK_IDTCM) += ptp_clockmatrix.o obj-$(CONFIG_PTP_1588_CLOCK_IDT82P33) += ptp_idt82p33.o obj-$(CONFIG_PTP_1588_CLOCK_VMW) += ptp_vmw.o +obj-$(CONFIG_PTP_1588_CLOCK_OCP) += ptp_ocp.o diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c new file mode 100644 index 000000000000..530e5f90095e --- /dev/null +++ b/drivers/ptp/ptp_ocp.c @@ -0,0 +1,398 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2020 Facebook */ + +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/ptp_clock_kernel.h> + +static const struct pci_device_id ptp_ocp_pcidev_id[] = { + { PCI_DEVICE(0x1d9b, 0x0400) }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, ptp_ocp_pcidev_id); + +#define OCP_REGISTER_OFFSET 0x01000000 + +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; +}; + +#define OCP_CTRL_ENABLE BIT(0) +#define OCP_CTRL_ADJUST_TIME BIT(1) +#define OCP_CTRL_ADJUST_OFFSET BIT(2) +#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_SELECT_CLK_NONE 0 +#define OCP_SELECT_CLK_REG 6 + +struct tod_reg { + u32 ctrl; + u32 status; + u32 uart_polarity; + u32 version; + u32 correction_sec; + u32 __pad0[3]; + u32 uart_baud; + u32 __pad1[3]; + u32 utc_status; + u32 leap; +}; + +#define TOD_REGISTER_OFFSET 0x01050000 + +#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 ((1U << 4) - 1) +#define TOD_CTRL_GNSS_SHIFT 24 + +#define TOD_STATUS_UTC_MASK 0xff +#define TOD_STATUS_UTC_VALID BIT(8) +#define TOD_STATUS_LEAP_VALID BIT(16) + +struct ptp_ocp { + struct pci_dev *pdev; + spinlock_t lock; + void __iomem *base; + struct ocp_reg __iomem *reg; + struct tod_reg __iomem *tod; + struct ptp_clock *ptp; + struct ptp_clock_info ptp_info; +}; + +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; + + ctrl = ioread32(&bp->reg->ctrl); + ctrl |= OCP_CTRL_READ_TIME_REQ; + + ptp_read_system_prets(sts); + 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); + + 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 = ioread32(&bp->reg->ctrl); + ctrl |= OCP_CTRL_ADJUST_TIME; + 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; + + if (ioread32(&bp->reg->status) & OCP_STATUS_IN_SYNC) + return 0; + + spin_lock_irqsave(&bp->lock, flags); + __ptp_ocp_settime_locked(bp, ts); + spin_unlock_irqrestore(&bp->lock, flags); + + return 0; +} + +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); + struct timespec64 ts; + unsigned long flags; + int err; + + if (ioread32(&bp->reg->status) & OCP_STATUS_IN_SYNC) + return 0; + + spin_lock_irqsave(&bp->lock, flags); + err = __ptp_ocp_gettime_locked(bp, &ts, NULL); + if (likely(!err)) { + timespec64_add_ns(&ts, delta_ns); + __ptp_ocp_settime_locked(bp, &ts); + } + spin_unlock_irqrestore(&bp->lock, flags); + + return err; +} + +static int +ptp_ocp_null_adjfine(struct ptp_clock_info *ptp_info, long scaled_ppm) +{ + if (scaled_ppm == 0) + return 0; + + return -EOPNOTSUPP; +} + +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, +}; + +static int +ptp_ocp_check_clock(struct ptp_ocp *bp) +{ + struct timespec64 ts; + bool sync; + u32 ctrl; + + /* make sure clock is enabled */ + ctrl = ioread32(&bp->reg->ctrl); + ctrl |= OCP_CTRL_ENABLE; + 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; + } + + sync = ioread32(&bp->reg->status) & OCP_STATUS_IN_SYNC; + if (!sync) { + ktime_get_real_ts64(&ts); + ptp_ocp_settime(&bp->ptp_info, &ts); + } + if (!ptp_ocp_gettimex(&bp->ptp_info, &ts, NULL)) + dev_info(&bp->pdev->dev, "Time: %lld.%ld, %s\n", + ts.tv_sec, ts.tv_nsec, + sync ? "in-sync" : "UNSYNCED"); + + return 0; +} + +static void +ptp_ocp_tod_info(struct ptp_ocp *bp) +{ + static const char * const proto_name[] = { + "NMEA", "NMEA_ZDA", "NMEA_RMC", "NMEA_none", + "UBX", "UBX_UTC", "UBX_LS", "UBX_none" + }; + static const char * const gnss_name[] = { + "ALL", "COMBINED", "GPS", "GLONASS", "GALILEO", "BEIDOU", + }; + u32 version, ctrl, reg; + int idx; + + version = ioread32(&bp->tod->version); + dev_info(&bp->pdev->dev, "TOD Version %d.%d.%d\n", + version >> 24, (version >> 16) & 0xff, version & 0xffff); + + 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); + + ctrl = ioread32(&bp->tod->ctrl); + idx = ctrl & TOD_CTRL_PROTOCOL ? 4 : 0; + idx += (ctrl >> 16) & 3; + dev_info(&bp->pdev->dev, "control: %x\n", ctrl); + dev_info(&bp->pdev->dev, "TOD Protocol %s %s\n", proto_name[idx], + ctrl & TOD_CTRL_ENABLE ? "enabled" : ""); + + idx = (ctrl >> TOD_CTRL_GNSS_SHIFT) & TOD_CTRL_GNSS_MASK; + if (idx < ARRAY_SIZE(gnss_name)) + dev_info(&bp->pdev->dev, "GNSS %s\n", gnss_name[idx]); + + reg = ioread32(&bp->tod->status); + dev_info(&bp->pdev->dev, "status: %x\n", reg); + + reg = ioread32(&bp->tod->correction_sec); + dev_info(&bp->pdev->dev, "correction: %d\n", reg); + + reg = ioread32(&bp->tod->utc_status); + dev_info(&bp->pdev->dev, "utc_status: %x\n", reg); + dev_info(&bp->pdev->dev, "utc_offset: %d valid:%d leap_valid:%d\n", + reg & TOD_STATUS_UTC_MASK, reg & TOD_STATUS_UTC_VALID ? 1 : 0, + reg & TOD_STATUS_LEAP_VALID ? 1 : 0); +} + +static void +ptp_ocp_info(struct ptp_ocp *bp) +{ + static const char * const clock_name[] = { + "NO", "TOD", "IRIG", "PPS", "PTP", "RTC", "REGS", "EXT" + }; + 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, + clock_name[select & 7], + ptp_clock_index(bp->ptp)); + + ptp_ocp_tod_info(bp); +} + +static int +ptp_ocp_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct ptp_ocp *bp; + int err; + + bp = kzalloc(sizeof(*bp), GFP_KERNEL); + if (!bp) + return -ENOMEM; + bp->pdev = pdev; + pci_set_drvdata(pdev, bp); + + err = pci_enable_device(pdev); + if (err) { + dev_err(&pdev->dev, "pci_enable_device\n"); + goto out_free; + } + + err = pci_request_regions(pdev, KBUILD_MODNAME); + if (err) { + dev_err(&pdev->dev, "pci_request_region\n"); + goto out_disable; + } + + bp->base = pci_ioremap_bar(pdev, 0); + if (!bp->base) { + dev_err(&pdev->dev, "io_remap bar0\n"); + err = -ENOMEM; + goto out; + } + bp->reg = bp->base + OCP_REGISTER_OFFSET; + bp->tod = bp->base + TOD_REGISTER_OFFSET; + bp->ptp_info = ptp_ocp_clock_info; + spin_lock_init(&bp->lock); + + err = ptp_ocp_check_clock(bp); + if (err) + goto out; + + bp->ptp = ptp_clock_register(&bp->ptp_info, &pdev->dev); + if (IS_ERR(bp->ptp)) { + dev_err(&pdev->dev, "ptp_clock_register\n"); + err = PTR_ERR(bp->ptp); + goto out; + } + + ptp_ocp_info(bp); + + return 0; + +out: + pci_release_regions(pdev); +out_disable: + pci_disable_device(pdev); +out_free: + kfree(bp); + + return err; +} + +static void +ptp_ocp_remove(struct pci_dev *pdev) +{ + struct ptp_ocp *bp = pci_get_drvdata(pdev); + + ptp_clock_unregister(bp->ptp); + pci_iounmap(pdev, bp->base); + pci_release_regions(pdev); + pci_disable_device(pdev); + pci_set_drvdata(pdev, NULL); + kfree(bp); +} + +static struct pci_driver ptp_ocp_driver = { + .name = KBUILD_MODNAME, + .id_table = ptp_ocp_pcidev_id, + .probe = ptp_ocp_probe, + .remove = ptp_ocp_remove, +}; + +static int __init +ptp_ocp_init(void) +{ + int err; + + err = pci_register_driver(&ptp_ocp_driver); + return err; +} + +static void __exit +ptp_ocp_fini(void) +{ + pci_unregister_driver(&ptp_ocp_driver); +} + +module_init(ptp_ocp_init); +module_exit(ptp_ocp_fini); + +MODULE_DESCRIPTION("OpenCompute TimeCard driver"); +MODULE_LICENSE("GPL v2"); |