// SPDX-License-Identifier: GPL-2.0-only /* Copyright (c) 2020 Facebook */ #include #include #include #include #include #include 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_release_regions; } 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_iounmap(pdev, bp->base); out_release_regions: 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");