-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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>
- Loading branch information
Jonathan Lemon
authored and
Jakub Kicinski
committed
Dec 5, 2020
1 parent
bcd684a
commit a7e1aba
Showing
3 changed files
with
413 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"); |