| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * DFL device driver for Time-of-Day (ToD) private feature
- *
- * Copyright (C) 2023 Intel Corporation
- */
- #include <linux/bitfield.h>
- #include <linux/delay.h>
- #include <linux/dfl.h>
- #include <linux/gcd.h>
- #include <linux/iopoll.h>
- #include <linux/module.h>
- #include <linux/ptp_clock_kernel.h>
- #include <linux/spinlock.h>
- #include <linux/units.h>
- #define FME_FEATURE_ID_TOD 0x22
- /* ToD clock register space. */
- #define TOD_CLK_FREQ 0x038
- /*
- * The read sequence of ToD timestamp registers: TOD_NANOSEC, TOD_SECONDSL and
- * TOD_SECONDSH, because there is a hardware snapshot whenever the TOD_NANOSEC
- * register is read.
- *
- * The ToD IP requires writing registers in the reverse order to the read sequence.
- * The timestamp is corrected when the TOD_NANOSEC register is written, so the
- * sequence of write TOD registers: TOD_SECONDSH, TOD_SECONDSL and TOD_NANOSEC.
- */
- #define TOD_SECONDSH 0x100
- #define TOD_SECONDSL 0x104
- #define TOD_NANOSEC 0x108
- #define TOD_PERIOD 0x110
- #define TOD_ADJUST_PERIOD 0x114
- #define TOD_ADJUST_COUNT 0x118
- #define TOD_DRIFT_ADJUST 0x11c
- #define TOD_DRIFT_ADJUST_RATE 0x120
- #define PERIOD_FRAC_OFFSET 16
- #define SECONDS_MSB GENMASK_ULL(47, 32)
- #define SECONDS_LSB GENMASK_ULL(31, 0)
- #define TOD_SECONDSH_SEC_MSB GENMASK_ULL(15, 0)
- #define CAL_SECONDS(m, l) ((FIELD_GET(TOD_SECONDSH_SEC_MSB, (m)) << 32) | (l))
- #define TOD_PERIOD_MASK GENMASK_ULL(19, 0)
- #define TOD_PERIOD_MAX FIELD_MAX(TOD_PERIOD_MASK)
- #define TOD_PERIOD_MIN 0
- #define TOD_DRIFT_ADJUST_MASK GENMASK_ULL(15, 0)
- #define TOD_DRIFT_ADJUST_FNS_MAX FIELD_MAX(TOD_DRIFT_ADJUST_MASK)
- #define TOD_DRIFT_ADJUST_RATE_MAX TOD_DRIFT_ADJUST_FNS_MAX
- #define TOD_ADJUST_COUNT_MASK GENMASK_ULL(19, 0)
- #define TOD_ADJUST_COUNT_MAX FIELD_MAX(TOD_ADJUST_COUNT_MASK)
- #define TOD_ADJUST_INTERVAL_US 10
- #define TOD_ADJUST_MS \
- (((TOD_PERIOD_MAX >> 16) + 1) * (TOD_ADJUST_COUNT_MAX + 1))
- #define TOD_ADJUST_MS_MAX (TOD_ADJUST_MS / MICRO)
- #define TOD_ADJUST_MAX_US (TOD_ADJUST_MS_MAX * USEC_PER_MSEC)
- #define TOD_MAX_ADJ (500 * MEGA)
- struct dfl_tod {
- struct ptp_clock_info ptp_clock_ops;
- struct device *dev;
- struct ptp_clock *ptp_clock;
- /* ToD Clock address space */
- void __iomem *tod_ctrl;
- /* ToD clock registers protection */
- spinlock_t tod_lock;
- };
- /*
- * A fine ToD HW clock offset adjustment. To perform the fine offset adjustment, the
- * adjust_period and adjust_count argument are used to update the TOD_ADJUST_PERIOD
- * and TOD_ADJUST_COUNT register for in hardware. The dt->tod_lock spinlock must be
- * held when calling this function.
- */
- static int fine_adjust_tod_clock(struct dfl_tod *dt, u32 adjust_period,
- u32 adjust_count)
- {
- void __iomem *base = dt->tod_ctrl;
- u32 val;
- writel(adjust_period, base + TOD_ADJUST_PERIOD);
- writel(adjust_count, base + TOD_ADJUST_COUNT);
- /* Wait for present offset adjustment update to complete */
- return readl_poll_timeout_atomic(base + TOD_ADJUST_COUNT, val, !val, TOD_ADJUST_INTERVAL_US,
- TOD_ADJUST_MAX_US);
- }
- /*
- * A coarse ToD HW clock offset adjustment. The coarse time adjustment performs by
- * adding or subtracting the delta value from the current ToD HW clock time.
- */
- static int coarse_adjust_tod_clock(struct dfl_tod *dt, s64 delta)
- {
- u32 seconds_msb, seconds_lsb, nanosec;
- void __iomem *base = dt->tod_ctrl;
- u64 seconds, now;
- if (delta == 0)
- return 0;
- nanosec = readl(base + TOD_NANOSEC);
- seconds_lsb = readl(base + TOD_SECONDSL);
- seconds_msb = readl(base + TOD_SECONDSH);
- /* Calculate new time */
- seconds = CAL_SECONDS(seconds_msb, seconds_lsb);
- now = seconds * NSEC_PER_SEC + nanosec + delta;
- seconds = div_u64_rem(now, NSEC_PER_SEC, &nanosec);
- seconds_msb = FIELD_GET(SECONDS_MSB, seconds);
- seconds_lsb = FIELD_GET(SECONDS_LSB, seconds);
- writel(seconds_msb, base + TOD_SECONDSH);
- writel(seconds_lsb, base + TOD_SECONDSL);
- writel(nanosec, base + TOD_NANOSEC);
- return 0;
- }
- static int dfl_tod_adjust_fine(struct ptp_clock_info *ptp, long scaled_ppm)
- {
- struct dfl_tod *dt = container_of(ptp, struct dfl_tod, ptp_clock_ops);
- u32 tod_period, tod_rem, tod_drift_adjust_fns, tod_drift_adjust_rate;
- void __iomem *base = dt->tod_ctrl;
- unsigned long flags, rate;
- u64 ppb;
- /* Get the clock rate from clock frequency register offset */
- rate = readl(base + TOD_CLK_FREQ);
- /* add GIGA as nominal ppb */
- ppb = scaled_ppm_to_ppb(scaled_ppm) + GIGA;
- tod_period = div_u64_rem(ppb << PERIOD_FRAC_OFFSET, rate, &tod_rem);
- if (tod_period > TOD_PERIOD_MAX)
- return -ERANGE;
- /*
- * The drift of ToD adjusted periodically by adding a drift_adjust_fns
- * correction value every drift_adjust_rate count of clock cycles.
- */
- tod_drift_adjust_fns = tod_rem / gcd(tod_rem, rate);
- tod_drift_adjust_rate = rate / gcd(tod_rem, rate);
- while ((tod_drift_adjust_fns > TOD_DRIFT_ADJUST_FNS_MAX) ||
- (tod_drift_adjust_rate > TOD_DRIFT_ADJUST_RATE_MAX)) {
- tod_drift_adjust_fns >>= 1;
- tod_drift_adjust_rate >>= 1;
- }
- if (tod_drift_adjust_fns == 0)
- tod_drift_adjust_rate = 0;
- spin_lock_irqsave(&dt->tod_lock, flags);
- writel(tod_period, base + TOD_PERIOD);
- writel(0, base + TOD_ADJUST_PERIOD);
- writel(0, base + TOD_ADJUST_COUNT);
- writel(tod_drift_adjust_fns, base + TOD_DRIFT_ADJUST);
- writel(tod_drift_adjust_rate, base + TOD_DRIFT_ADJUST_RATE);
- spin_unlock_irqrestore(&dt->tod_lock, flags);
- return 0;
- }
- static int dfl_tod_adjust_time(struct ptp_clock_info *ptp, s64 delta)
- {
- struct dfl_tod *dt = container_of(ptp, struct dfl_tod, ptp_clock_ops);
- u32 period, diff, rem, rem_period, adj_period;
- void __iomem *base = dt->tod_ctrl;
- unsigned long flags;
- bool neg_adj;
- u64 count;
- int ret;
- neg_adj = delta < 0;
- if (neg_adj)
- delta = -delta;
- spin_lock_irqsave(&dt->tod_lock, flags);
- /*
- * Get the maximum possible value of the Period register offset
- * adjustment in nanoseconds scale. This depends on the current
- * Period register setting and the maximum and minimum possible
- * values of the Period register.
- */
- period = readl(base + TOD_PERIOD);
- if (neg_adj) {
- diff = (period - TOD_PERIOD_MIN) >> PERIOD_FRAC_OFFSET;
- adj_period = period - (diff << PERIOD_FRAC_OFFSET);
- count = div_u64_rem(delta, diff, &rem);
- rem_period = period - (rem << PERIOD_FRAC_OFFSET);
- } else {
- diff = (TOD_PERIOD_MAX - period) >> PERIOD_FRAC_OFFSET;
- adj_period = period + (diff << PERIOD_FRAC_OFFSET);
- count = div_u64_rem(delta, diff, &rem);
- rem_period = period + (rem << PERIOD_FRAC_OFFSET);
- }
- ret = 0;
- if (count > TOD_ADJUST_COUNT_MAX) {
- ret = coarse_adjust_tod_clock(dt, delta);
- } else {
- /* Adjust the period by count cycles to adjust the time */
- if (count)
- ret = fine_adjust_tod_clock(dt, adj_period, count);
- /* If there is a remainder, adjust the period for an additional cycle */
- if (rem)
- ret = fine_adjust_tod_clock(dt, rem_period, 1);
- }
- spin_unlock_irqrestore(&dt->tod_lock, flags);
- return ret;
- }
- static int dfl_tod_get_timex(struct ptp_clock_info *ptp, struct timespec64 *ts,
- struct ptp_system_timestamp *sts)
- {
- struct dfl_tod *dt = container_of(ptp, struct dfl_tod, ptp_clock_ops);
- u32 seconds_msb, seconds_lsb, nanosec;
- void __iomem *base = dt->tod_ctrl;
- unsigned long flags;
- u64 seconds;
- spin_lock_irqsave(&dt->tod_lock, flags);
- ptp_read_system_prets(sts);
- nanosec = readl(base + TOD_NANOSEC);
- seconds_lsb = readl(base + TOD_SECONDSL);
- seconds_msb = readl(base + TOD_SECONDSH);
- ptp_read_system_postts(sts);
- spin_unlock_irqrestore(&dt->tod_lock, flags);
- seconds = CAL_SECONDS(seconds_msb, seconds_lsb);
- ts->tv_nsec = nanosec;
- ts->tv_sec = seconds;
- return 0;
- }
- static int dfl_tod_set_time(struct ptp_clock_info *ptp,
- const struct timespec64 *ts)
- {
- struct dfl_tod *dt = container_of(ptp, struct dfl_tod, ptp_clock_ops);
- u32 seconds_msb = FIELD_GET(SECONDS_MSB, ts->tv_sec);
- u32 seconds_lsb = FIELD_GET(SECONDS_LSB, ts->tv_sec);
- u32 nanosec = FIELD_GET(SECONDS_LSB, ts->tv_nsec);
- void __iomem *base = dt->tod_ctrl;
- unsigned long flags;
- spin_lock_irqsave(&dt->tod_lock, flags);
- writel(seconds_msb, base + TOD_SECONDSH);
- writel(seconds_lsb, base + TOD_SECONDSL);
- writel(nanosec, base + TOD_NANOSEC);
- spin_unlock_irqrestore(&dt->tod_lock, flags);
- return 0;
- }
- static struct ptp_clock_info dfl_tod_clock_ops = {
- .owner = THIS_MODULE,
- .name = "dfl_tod",
- .max_adj = TOD_MAX_ADJ,
- .adjfine = dfl_tod_adjust_fine,
- .adjtime = dfl_tod_adjust_time,
- .gettimex64 = dfl_tod_get_timex,
- .settime64 = dfl_tod_set_time,
- };
- static int dfl_tod_probe(struct dfl_device *ddev)
- {
- struct device *dev = &ddev->dev;
- struct dfl_tod *dt;
- dt = devm_kzalloc(dev, sizeof(*dt), GFP_KERNEL);
- if (!dt)
- return -ENOMEM;
- dt->tod_ctrl = devm_ioremap_resource(dev, &ddev->mmio_res);
- if (IS_ERR(dt->tod_ctrl))
- return PTR_ERR(dt->tod_ctrl);
- dt->dev = dev;
- spin_lock_init(&dt->tod_lock);
- dev_set_drvdata(dev, dt);
- dt->ptp_clock_ops = dfl_tod_clock_ops;
- dt->ptp_clock = ptp_clock_register(&dt->ptp_clock_ops, dev);
- if (IS_ERR(dt->ptp_clock))
- return dev_err_probe(dt->dev, PTR_ERR(dt->ptp_clock),
- "Unable to register PTP clock\n");
- return 0;
- }
- static void dfl_tod_remove(struct dfl_device *ddev)
- {
- struct dfl_tod *dt = dev_get_drvdata(&ddev->dev);
- ptp_clock_unregister(dt->ptp_clock);
- }
- static const struct dfl_device_id dfl_tod_ids[] = {
- { FME_ID, FME_FEATURE_ID_TOD },
- { }
- };
- MODULE_DEVICE_TABLE(dfl, dfl_tod_ids);
- static struct dfl_driver dfl_tod_driver = {
- .drv = {
- .name = "dfl-tod",
- },
- .id_table = dfl_tod_ids,
- .probe = dfl_tod_probe,
- .remove = dfl_tod_remove,
- };
- module_dfl_driver(dfl_tod_driver);
- MODULE_DESCRIPTION("FPGA DFL ToD driver");
- MODULE_AUTHOR("Intel Corporation");
- MODULE_LICENSE("GPL");
|