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>
This commit is contained in:
parent
bcd684aace
commit
a7e1abad13
@ -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
|
||||
|
@ -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
|
||||
|
398
drivers/ptp/ptp_ocp.c
Normal file
398
drivers/ptp/ptp_ocp.c
Normal file
@ -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");
|
Loading…
Reference in New Issue
Block a user