ptp: Add clock driver for the OpenCompute TimeCard.
authorJonathan Lemon <jonathan.lemon@gmail.com>
Fri, 4 Dec 2020 03:51:28 +0000 (19:51 -0800)
committerJakub Kicinski <kuba@kernel.org>
Sat, 5 Dec 2020 21:59:41 +0000 (13:59 -0800)
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>
drivers/ptp/Kconfig
drivers/ptp/Makefile
drivers/ptp/ptp_ocp.c [new file with mode: 0644]

index 942f72d..476d7c7 100644 (file)
@@ -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
index 7aff75f..db5aef3 100644 (file)
@@ -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 (file)
index 0000000..530e5f9
--- /dev/null
@@ -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");