162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * DFL device driver for Time-of-Day (ToD) private feature
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2023 Intel Corporation
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/bitfield.h>
962306a36Sopenharmony_ci#include <linux/delay.h>
1062306a36Sopenharmony_ci#include <linux/dfl.h>
1162306a36Sopenharmony_ci#include <linux/gcd.h>
1262306a36Sopenharmony_ci#include <linux/iopoll.h>
1362306a36Sopenharmony_ci#include <linux/module.h>
1462306a36Sopenharmony_ci#include <linux/ptp_clock_kernel.h>
1562306a36Sopenharmony_ci#include <linux/spinlock.h>
1662306a36Sopenharmony_ci#include <linux/units.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#define FME_FEATURE_ID_TOD		0x22
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci/* ToD clock register space. */
2162306a36Sopenharmony_ci#define TOD_CLK_FREQ			0x038
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci/*
2462306a36Sopenharmony_ci * The read sequence of ToD timestamp registers: TOD_NANOSEC, TOD_SECONDSL and
2562306a36Sopenharmony_ci * TOD_SECONDSH, because there is a hardware snapshot whenever the TOD_NANOSEC
2662306a36Sopenharmony_ci * register is read.
2762306a36Sopenharmony_ci *
2862306a36Sopenharmony_ci * The ToD IP requires writing registers in the reverse order to the read sequence.
2962306a36Sopenharmony_ci * The timestamp is corrected when the TOD_NANOSEC register is written, so the
3062306a36Sopenharmony_ci * sequence of write TOD registers: TOD_SECONDSH, TOD_SECONDSL and TOD_NANOSEC.
3162306a36Sopenharmony_ci */
3262306a36Sopenharmony_ci#define TOD_SECONDSH			0x100
3362306a36Sopenharmony_ci#define TOD_SECONDSL			0x104
3462306a36Sopenharmony_ci#define TOD_NANOSEC			0x108
3562306a36Sopenharmony_ci#define TOD_PERIOD			0x110
3662306a36Sopenharmony_ci#define TOD_ADJUST_PERIOD		0x114
3762306a36Sopenharmony_ci#define TOD_ADJUST_COUNT		0x118
3862306a36Sopenharmony_ci#define TOD_DRIFT_ADJUST		0x11c
3962306a36Sopenharmony_ci#define TOD_DRIFT_ADJUST_RATE		0x120
4062306a36Sopenharmony_ci#define PERIOD_FRAC_OFFSET		16
4162306a36Sopenharmony_ci#define SECONDS_MSB			GENMASK_ULL(47, 32)
4262306a36Sopenharmony_ci#define SECONDS_LSB			GENMASK_ULL(31, 0)
4362306a36Sopenharmony_ci#define TOD_SECONDSH_SEC_MSB		GENMASK_ULL(15, 0)
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci#define CAL_SECONDS(m, l)		((FIELD_GET(TOD_SECONDSH_SEC_MSB, (m)) << 32) | (l))
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci#define TOD_PERIOD_MASK		GENMASK_ULL(19, 0)
4862306a36Sopenharmony_ci#define TOD_PERIOD_MAX			FIELD_MAX(TOD_PERIOD_MASK)
4962306a36Sopenharmony_ci#define TOD_PERIOD_MIN			0
5062306a36Sopenharmony_ci#define TOD_DRIFT_ADJUST_MASK		GENMASK_ULL(15, 0)
5162306a36Sopenharmony_ci#define TOD_DRIFT_ADJUST_FNS_MAX	FIELD_MAX(TOD_DRIFT_ADJUST_MASK)
5262306a36Sopenharmony_ci#define TOD_DRIFT_ADJUST_RATE_MAX	TOD_DRIFT_ADJUST_FNS_MAX
5362306a36Sopenharmony_ci#define TOD_ADJUST_COUNT_MASK		GENMASK_ULL(19, 0)
5462306a36Sopenharmony_ci#define TOD_ADJUST_COUNT_MAX		FIELD_MAX(TOD_ADJUST_COUNT_MASK)
5562306a36Sopenharmony_ci#define TOD_ADJUST_INTERVAL_US		10
5662306a36Sopenharmony_ci#define TOD_ADJUST_MS			\
5762306a36Sopenharmony_ci		(((TOD_PERIOD_MAX >> 16) + 1) * (TOD_ADJUST_COUNT_MAX + 1))
5862306a36Sopenharmony_ci#define TOD_ADJUST_MS_MAX		(TOD_ADJUST_MS / MICRO)
5962306a36Sopenharmony_ci#define TOD_ADJUST_MAX_US		(TOD_ADJUST_MS_MAX * USEC_PER_MSEC)
6062306a36Sopenharmony_ci#define TOD_MAX_ADJ			(500 * MEGA)
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_cistruct dfl_tod {
6362306a36Sopenharmony_ci	struct ptp_clock_info ptp_clock_ops;
6462306a36Sopenharmony_ci	struct device *dev;
6562306a36Sopenharmony_ci	struct ptp_clock *ptp_clock;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	/* ToD Clock address space */
6862306a36Sopenharmony_ci	void __iomem *tod_ctrl;
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	/* ToD clock registers protection */
7162306a36Sopenharmony_ci	spinlock_t tod_lock;
7262306a36Sopenharmony_ci};
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci/*
7562306a36Sopenharmony_ci * A fine ToD HW clock offset adjustment. To perform the fine offset adjustment, the
7662306a36Sopenharmony_ci * adjust_period and adjust_count argument are used to update the TOD_ADJUST_PERIOD
7762306a36Sopenharmony_ci * and TOD_ADJUST_COUNT register for in hardware. The dt->tod_lock spinlock must be
7862306a36Sopenharmony_ci * held when calling this function.
7962306a36Sopenharmony_ci */
8062306a36Sopenharmony_cistatic int fine_adjust_tod_clock(struct dfl_tod *dt, u32 adjust_period,
8162306a36Sopenharmony_ci				 u32 adjust_count)
8262306a36Sopenharmony_ci{
8362306a36Sopenharmony_ci	void __iomem *base = dt->tod_ctrl;
8462306a36Sopenharmony_ci	u32 val;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	writel(adjust_period, base + TOD_ADJUST_PERIOD);
8762306a36Sopenharmony_ci	writel(adjust_count, base + TOD_ADJUST_COUNT);
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	/* Wait for present offset adjustment update to complete */
9062306a36Sopenharmony_ci	return readl_poll_timeout_atomic(base + TOD_ADJUST_COUNT, val, !val, TOD_ADJUST_INTERVAL_US,
9162306a36Sopenharmony_ci				  TOD_ADJUST_MAX_US);
9262306a36Sopenharmony_ci}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci/*
9562306a36Sopenharmony_ci * A coarse ToD HW clock offset adjustment. The coarse time adjustment performs by
9662306a36Sopenharmony_ci * adding or subtracting the delta value from the current ToD HW clock time.
9762306a36Sopenharmony_ci */
9862306a36Sopenharmony_cistatic int coarse_adjust_tod_clock(struct dfl_tod *dt, s64 delta)
9962306a36Sopenharmony_ci{
10062306a36Sopenharmony_ci	u32 seconds_msb, seconds_lsb, nanosec;
10162306a36Sopenharmony_ci	void __iomem *base = dt->tod_ctrl;
10262306a36Sopenharmony_ci	u64 seconds, now;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	if (delta == 0)
10562306a36Sopenharmony_ci		return 0;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	nanosec = readl(base + TOD_NANOSEC);
10862306a36Sopenharmony_ci	seconds_lsb = readl(base + TOD_SECONDSL);
10962306a36Sopenharmony_ci	seconds_msb = readl(base + TOD_SECONDSH);
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	/* Calculate new time */
11262306a36Sopenharmony_ci	seconds = CAL_SECONDS(seconds_msb, seconds_lsb);
11362306a36Sopenharmony_ci	now = seconds * NSEC_PER_SEC + nanosec + delta;
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	seconds = div_u64_rem(now, NSEC_PER_SEC, &nanosec);
11662306a36Sopenharmony_ci	seconds_msb = FIELD_GET(SECONDS_MSB, seconds);
11762306a36Sopenharmony_ci	seconds_lsb = FIELD_GET(SECONDS_LSB, seconds);
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	writel(seconds_msb, base + TOD_SECONDSH);
12062306a36Sopenharmony_ci	writel(seconds_lsb, base + TOD_SECONDSL);
12162306a36Sopenharmony_ci	writel(nanosec, base + TOD_NANOSEC);
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	return 0;
12462306a36Sopenharmony_ci}
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_cistatic int dfl_tod_adjust_fine(struct ptp_clock_info *ptp, long scaled_ppm)
12762306a36Sopenharmony_ci{
12862306a36Sopenharmony_ci	struct dfl_tod *dt = container_of(ptp, struct dfl_tod, ptp_clock_ops);
12962306a36Sopenharmony_ci	u32 tod_period, tod_rem, tod_drift_adjust_fns, tod_drift_adjust_rate;
13062306a36Sopenharmony_ci	void __iomem *base = dt->tod_ctrl;
13162306a36Sopenharmony_ci	unsigned long flags, rate;
13262306a36Sopenharmony_ci	u64 ppb;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	/* Get the clock rate from clock frequency register offset */
13562306a36Sopenharmony_ci	rate = readl(base + TOD_CLK_FREQ);
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	/* add GIGA as nominal ppb */
13862306a36Sopenharmony_ci	ppb = scaled_ppm_to_ppb(scaled_ppm) + GIGA;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	tod_period = div_u64_rem(ppb << PERIOD_FRAC_OFFSET, rate, &tod_rem);
14162306a36Sopenharmony_ci	if (tod_period > TOD_PERIOD_MAX)
14262306a36Sopenharmony_ci		return -ERANGE;
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	/*
14562306a36Sopenharmony_ci	 * The drift of ToD adjusted periodically by adding a drift_adjust_fns
14662306a36Sopenharmony_ci	 * correction value every drift_adjust_rate count of clock cycles.
14762306a36Sopenharmony_ci	 */
14862306a36Sopenharmony_ci	tod_drift_adjust_fns = tod_rem / gcd(tod_rem, rate);
14962306a36Sopenharmony_ci	tod_drift_adjust_rate = rate / gcd(tod_rem, rate);
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	while ((tod_drift_adjust_fns > TOD_DRIFT_ADJUST_FNS_MAX) ||
15262306a36Sopenharmony_ci	       (tod_drift_adjust_rate > TOD_DRIFT_ADJUST_RATE_MAX)) {
15362306a36Sopenharmony_ci		tod_drift_adjust_fns >>= 1;
15462306a36Sopenharmony_ci		tod_drift_adjust_rate >>= 1;
15562306a36Sopenharmony_ci	}
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	if (tod_drift_adjust_fns == 0)
15862306a36Sopenharmony_ci		tod_drift_adjust_rate = 0;
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	spin_lock_irqsave(&dt->tod_lock, flags);
16162306a36Sopenharmony_ci	writel(tod_period, base + TOD_PERIOD);
16262306a36Sopenharmony_ci	writel(0, base + TOD_ADJUST_PERIOD);
16362306a36Sopenharmony_ci	writel(0, base + TOD_ADJUST_COUNT);
16462306a36Sopenharmony_ci	writel(tod_drift_adjust_fns, base + TOD_DRIFT_ADJUST);
16562306a36Sopenharmony_ci	writel(tod_drift_adjust_rate, base + TOD_DRIFT_ADJUST_RATE);
16662306a36Sopenharmony_ci	spin_unlock_irqrestore(&dt->tod_lock, flags);
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	return 0;
16962306a36Sopenharmony_ci}
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_cistatic int dfl_tod_adjust_time(struct ptp_clock_info *ptp, s64 delta)
17262306a36Sopenharmony_ci{
17362306a36Sopenharmony_ci	struct dfl_tod *dt = container_of(ptp, struct dfl_tod, ptp_clock_ops);
17462306a36Sopenharmony_ci	u32 period, diff, rem, rem_period, adj_period;
17562306a36Sopenharmony_ci	void __iomem *base = dt->tod_ctrl;
17662306a36Sopenharmony_ci	unsigned long flags;
17762306a36Sopenharmony_ci	bool neg_adj;
17862306a36Sopenharmony_ci	u64 count;
17962306a36Sopenharmony_ci	int ret;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	neg_adj = delta < 0;
18262306a36Sopenharmony_ci	if (neg_adj)
18362306a36Sopenharmony_ci		delta = -delta;
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	spin_lock_irqsave(&dt->tod_lock, flags);
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	/*
18862306a36Sopenharmony_ci	 * Get the maximum possible value of the Period register offset
18962306a36Sopenharmony_ci	 * adjustment in nanoseconds scale. This depends on the current
19062306a36Sopenharmony_ci	 * Period register setting and the maximum and minimum possible
19162306a36Sopenharmony_ci	 * values of the Period register.
19262306a36Sopenharmony_ci	 */
19362306a36Sopenharmony_ci	period = readl(base + TOD_PERIOD);
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	if (neg_adj) {
19662306a36Sopenharmony_ci		diff = (period - TOD_PERIOD_MIN) >> PERIOD_FRAC_OFFSET;
19762306a36Sopenharmony_ci		adj_period = period - (diff << PERIOD_FRAC_OFFSET);
19862306a36Sopenharmony_ci		count = div_u64_rem(delta, diff, &rem);
19962306a36Sopenharmony_ci		rem_period = period - (rem << PERIOD_FRAC_OFFSET);
20062306a36Sopenharmony_ci	} else {
20162306a36Sopenharmony_ci		diff = (TOD_PERIOD_MAX - period) >> PERIOD_FRAC_OFFSET;
20262306a36Sopenharmony_ci		adj_period = period + (diff << PERIOD_FRAC_OFFSET);
20362306a36Sopenharmony_ci		count = div_u64_rem(delta, diff, &rem);
20462306a36Sopenharmony_ci		rem_period = period + (rem << PERIOD_FRAC_OFFSET);
20562306a36Sopenharmony_ci	}
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	ret = 0;
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	if (count > TOD_ADJUST_COUNT_MAX) {
21062306a36Sopenharmony_ci		ret = coarse_adjust_tod_clock(dt, delta);
21162306a36Sopenharmony_ci	} else {
21262306a36Sopenharmony_ci		/* Adjust the period by count cycles to adjust the time */
21362306a36Sopenharmony_ci		if (count)
21462306a36Sopenharmony_ci			ret = fine_adjust_tod_clock(dt, adj_period, count);
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci		/* If there is a remainder, adjust the period for an additional cycle */
21762306a36Sopenharmony_ci		if (rem)
21862306a36Sopenharmony_ci			ret = fine_adjust_tod_clock(dt, rem_period, 1);
21962306a36Sopenharmony_ci	}
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	spin_unlock_irqrestore(&dt->tod_lock, flags);
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	return ret;
22462306a36Sopenharmony_ci}
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_cistatic int dfl_tod_get_timex(struct ptp_clock_info *ptp, struct timespec64 *ts,
22762306a36Sopenharmony_ci			     struct ptp_system_timestamp *sts)
22862306a36Sopenharmony_ci{
22962306a36Sopenharmony_ci	struct dfl_tod *dt = container_of(ptp, struct dfl_tod, ptp_clock_ops);
23062306a36Sopenharmony_ci	u32 seconds_msb, seconds_lsb, nanosec;
23162306a36Sopenharmony_ci	void __iomem *base = dt->tod_ctrl;
23262306a36Sopenharmony_ci	unsigned long flags;
23362306a36Sopenharmony_ci	u64 seconds;
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	spin_lock_irqsave(&dt->tod_lock, flags);
23662306a36Sopenharmony_ci	ptp_read_system_prets(sts);
23762306a36Sopenharmony_ci	nanosec = readl(base + TOD_NANOSEC);
23862306a36Sopenharmony_ci	seconds_lsb = readl(base + TOD_SECONDSL);
23962306a36Sopenharmony_ci	seconds_msb = readl(base + TOD_SECONDSH);
24062306a36Sopenharmony_ci	ptp_read_system_postts(sts);
24162306a36Sopenharmony_ci	spin_unlock_irqrestore(&dt->tod_lock, flags);
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	seconds = CAL_SECONDS(seconds_msb, seconds_lsb);
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	ts->tv_nsec = nanosec;
24662306a36Sopenharmony_ci	ts->tv_sec = seconds;
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	return 0;
24962306a36Sopenharmony_ci}
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_cistatic int dfl_tod_set_time(struct ptp_clock_info *ptp,
25262306a36Sopenharmony_ci			    const struct timespec64 *ts)
25362306a36Sopenharmony_ci{
25462306a36Sopenharmony_ci	struct dfl_tod *dt = container_of(ptp, struct dfl_tod, ptp_clock_ops);
25562306a36Sopenharmony_ci	u32 seconds_msb = FIELD_GET(SECONDS_MSB, ts->tv_sec);
25662306a36Sopenharmony_ci	u32 seconds_lsb = FIELD_GET(SECONDS_LSB, ts->tv_sec);
25762306a36Sopenharmony_ci	u32 nanosec = FIELD_GET(SECONDS_LSB, ts->tv_nsec);
25862306a36Sopenharmony_ci	void __iomem *base = dt->tod_ctrl;
25962306a36Sopenharmony_ci	unsigned long flags;
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	spin_lock_irqsave(&dt->tod_lock, flags);
26262306a36Sopenharmony_ci	writel(seconds_msb, base + TOD_SECONDSH);
26362306a36Sopenharmony_ci	writel(seconds_lsb, base + TOD_SECONDSL);
26462306a36Sopenharmony_ci	writel(nanosec, base + TOD_NANOSEC);
26562306a36Sopenharmony_ci	spin_unlock_irqrestore(&dt->tod_lock, flags);
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	return 0;
26862306a36Sopenharmony_ci}
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_cistatic struct ptp_clock_info dfl_tod_clock_ops = {
27162306a36Sopenharmony_ci	.owner = THIS_MODULE,
27262306a36Sopenharmony_ci	.name = "dfl_tod",
27362306a36Sopenharmony_ci	.max_adj = TOD_MAX_ADJ,
27462306a36Sopenharmony_ci	.adjfine = dfl_tod_adjust_fine,
27562306a36Sopenharmony_ci	.adjtime = dfl_tod_adjust_time,
27662306a36Sopenharmony_ci	.gettimex64 = dfl_tod_get_timex,
27762306a36Sopenharmony_ci	.settime64 = dfl_tod_set_time,
27862306a36Sopenharmony_ci};
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_cistatic int dfl_tod_probe(struct dfl_device *ddev)
28162306a36Sopenharmony_ci{
28262306a36Sopenharmony_ci	struct device *dev = &ddev->dev;
28362306a36Sopenharmony_ci	struct dfl_tod *dt;
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci	dt = devm_kzalloc(dev, sizeof(*dt), GFP_KERNEL);
28662306a36Sopenharmony_ci	if (!dt)
28762306a36Sopenharmony_ci		return -ENOMEM;
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci	dt->tod_ctrl = devm_ioremap_resource(dev, &ddev->mmio_res);
29062306a36Sopenharmony_ci	if (IS_ERR(dt->tod_ctrl))
29162306a36Sopenharmony_ci		return PTR_ERR(dt->tod_ctrl);
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	dt->dev = dev;
29462306a36Sopenharmony_ci	spin_lock_init(&dt->tod_lock);
29562306a36Sopenharmony_ci	dev_set_drvdata(dev, dt);
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci	dt->ptp_clock_ops = dfl_tod_clock_ops;
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	dt->ptp_clock = ptp_clock_register(&dt->ptp_clock_ops, dev);
30062306a36Sopenharmony_ci	if (IS_ERR(dt->ptp_clock))
30162306a36Sopenharmony_ci		return dev_err_probe(dt->dev, PTR_ERR(dt->ptp_clock),
30262306a36Sopenharmony_ci				     "Unable to register PTP clock\n");
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ci	return 0;
30562306a36Sopenharmony_ci}
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_cistatic void dfl_tod_remove(struct dfl_device *ddev)
30862306a36Sopenharmony_ci{
30962306a36Sopenharmony_ci	struct dfl_tod *dt = dev_get_drvdata(&ddev->dev);
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci	ptp_clock_unregister(dt->ptp_clock);
31262306a36Sopenharmony_ci}
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_cistatic const struct dfl_device_id dfl_tod_ids[] = {
31562306a36Sopenharmony_ci	{ FME_ID, FME_FEATURE_ID_TOD },
31662306a36Sopenharmony_ci	{ }
31762306a36Sopenharmony_ci};
31862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(dfl, dfl_tod_ids);
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_cistatic struct dfl_driver dfl_tod_driver = {
32162306a36Sopenharmony_ci	.drv = {
32262306a36Sopenharmony_ci		.name = "dfl-tod",
32362306a36Sopenharmony_ci	},
32462306a36Sopenharmony_ci	.id_table = dfl_tod_ids,
32562306a36Sopenharmony_ci	.probe = dfl_tod_probe,
32662306a36Sopenharmony_ci	.remove = dfl_tod_remove,
32762306a36Sopenharmony_ci};
32862306a36Sopenharmony_cimodule_dfl_driver(dfl_tod_driver);
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_ciMODULE_DESCRIPTION("FPGA DFL ToD driver");
33162306a36Sopenharmony_ciMODULE_AUTHOR("Intel Corporation");
33262306a36Sopenharmony_ciMODULE_LICENSE("GPL");
333