xref: /linux/drivers/pps/generators/pps_gen_tio.c (revision 16cd1c2657762c62a00ac78eecaa25868f7e601b)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Intel PPS signal Generator Driver
4  *
5  * Copyright (C) 2024 Intel Corporation
6  */
7 
8 #include <linux/bitfield.h>
9 #include <linux/bits.h>
10 #include <linux/cleanup.h>
11 #include <linux/container_of.h>
12 #include <linux/device.h>
13 #include <linux/hrtimer.h>
14 #include <linux/io-64-nonatomic-hi-lo.h>
15 #include <linux/mod_devicetable.h>
16 #include <linux/module.h>
17 #include <linux/platform_device.h>
18 #include <linux/pps_gen_kernel.h>
19 #include <linux/timekeeping.h>
20 #include <linux/types.h>
21 
22 #include <asm/cpu_device_id.h>
23 
24 #define TIOCTL			0x00
25 #define TIOCOMPV		0x10
26 #define TIOEC			0x30
27 
28 /* Control Register */
29 #define TIOCTL_EN			BIT(0)
30 #define TIOCTL_DIR			BIT(1)
31 #define TIOCTL_EP			GENMASK(3, 2)
32 #define TIOCTL_EP_RISING_EDGE		FIELD_PREP(TIOCTL_EP, 0)
33 #define TIOCTL_EP_FALLING_EDGE		FIELD_PREP(TIOCTL_EP, 1)
34 #define TIOCTL_EP_TOGGLE_EDGE		FIELD_PREP(TIOCTL_EP, 2)
35 
36 /* Safety time to set hrtimer early */
37 #define SAFE_TIME_NS			(10 * NSEC_PER_MSEC)
38 
39 #define MAGIC_CONST			(NSEC_PER_SEC - SAFE_TIME_NS)
40 #define ART_HW_DELAY_CYCLES		2
41 
42 struct pps_tio {
43 	struct pps_gen_source_info gen_info;
44 	struct pps_gen_device *pps_gen;
45 	struct hrtimer timer;
46 	void __iomem *base;
47 	u32 prev_count;
48 	spinlock_t lock;
49 	struct device *dev;
50 };
51 
pps_tio_read(u32 offset,struct pps_tio * tio)52 static inline u32 pps_tio_read(u32 offset, struct pps_tio *tio)
53 {
54 	return readl(tio->base + offset);
55 }
56 
pps_ctl_write(u32 value,struct pps_tio * tio)57 static inline void pps_ctl_write(u32 value, struct pps_tio *tio)
58 {
59 	writel(value, tio->base + TIOCTL);
60 }
61 
62 /*
63  * For COMPV register, It's safer to write
64  * higher 32-bit followed by lower 32-bit
65  */
pps_compv_write(u64 value,struct pps_tio * tio)66 static inline void pps_compv_write(u64 value, struct pps_tio *tio)
67 {
68 	hi_lo_writeq(value, tio->base + TIOCOMPV);
69 }
70 
first_event(struct pps_tio * tio)71 static inline ktime_t first_event(struct pps_tio *tio)
72 {
73 	return ktime_set(ktime_get_real_seconds() + 1, MAGIC_CONST);
74 }
75 
pps_tio_disable(struct pps_tio * tio)76 static u32 pps_tio_disable(struct pps_tio *tio)
77 {
78 	u32 ctrl;
79 
80 	ctrl = pps_tio_read(TIOCTL, tio);
81 	pps_compv_write(0, tio);
82 
83 	ctrl &= ~TIOCTL_EN;
84 	pps_ctl_write(ctrl, tio);
85 	tio->pps_gen->enabled = false;
86 	tio->prev_count = 0;
87 	return ctrl;
88 }
89 
pps_tio_enable(struct pps_tio * tio)90 static void pps_tio_enable(struct pps_tio *tio)
91 {
92 	u32 ctrl;
93 
94 	ctrl = pps_tio_read(TIOCTL, tio);
95 	ctrl |= TIOCTL_EN;
96 	pps_ctl_write(ctrl, tio);
97 	tio->pps_gen->enabled = true;
98 }
99 
pps_tio_direction_output(struct pps_tio * tio)100 static void pps_tio_direction_output(struct pps_tio *tio)
101 {
102 	u32 ctrl;
103 
104 	ctrl = pps_tio_disable(tio);
105 
106 	/*
107 	 * We enable the device, be sure that the
108 	 * 'compare' value is invalid
109 	 */
110 	pps_compv_write(0, tio);
111 
112 	ctrl &= ~(TIOCTL_DIR | TIOCTL_EP);
113 	ctrl |= TIOCTL_EP_TOGGLE_EDGE;
114 	pps_ctl_write(ctrl, tio);
115 	pps_tio_enable(tio);
116 }
117 
pps_generate_next_pulse(ktime_t expires,struct pps_tio * tio)118 static bool pps_generate_next_pulse(ktime_t expires, struct pps_tio *tio)
119 {
120 	u64 art;
121 
122 	if (!ktime_real_to_base_clock(expires, CSID_X86_ART, &art)) {
123 		pps_tio_disable(tio);
124 		return false;
125 	}
126 
127 	pps_compv_write(art - ART_HW_DELAY_CYCLES, tio);
128 	return true;
129 }
130 
hrtimer_callback(struct hrtimer * timer)131 static enum hrtimer_restart hrtimer_callback(struct hrtimer *timer)
132 {
133 	ktime_t expires, now;
134 	u32 event_count;
135 	struct pps_tio *tio = container_of(timer, struct pps_tio, timer);
136 
137 	guard(spinlock)(&tio->lock);
138 
139 	/*
140 	 * Check if any event is missed.
141 	 * If an event is missed, TIO will be disabled.
142 	 */
143 	event_count = pps_tio_read(TIOEC, tio);
144 	if (tio->prev_count && tio->prev_count == event_count)
145 		goto err;
146 	tio->prev_count = event_count;
147 
148 	expires = hrtimer_get_expires(timer);
149 
150 	now = ktime_get_real();
151 	if (now - expires >= SAFE_TIME_NS)
152 		goto err;
153 
154 	tio->pps_gen->enabled = pps_generate_next_pulse(expires + SAFE_TIME_NS, tio);
155 	if (!tio->pps_gen->enabled)
156 		return HRTIMER_NORESTART;
157 
158 	hrtimer_forward(timer, now, NSEC_PER_SEC / 2);
159 	return HRTIMER_RESTART;
160 
161 err:
162 	dev_err(tio->dev, "Event missed, Disabling Timed I/O");
163 	pps_tio_disable(tio);
164 	pps_gen_event(tio->pps_gen, PPS_GEN_EVENT_MISSEDPULSE, NULL);
165 	return HRTIMER_NORESTART;
166 }
167 
pps_tio_gen_enable(struct pps_gen_device * pps_gen,bool enable)168 static int pps_tio_gen_enable(struct pps_gen_device *pps_gen, bool enable)
169 {
170 	struct pps_tio *tio = container_of(pps_gen->info, struct pps_tio, gen_info);
171 
172 	if (!timekeeping_clocksource_has_base(CSID_X86_ART)) {
173 		dev_err_once(tio->dev, "PPS cannot be used as clock is not related to ART");
174 		return -ENODEV;
175 	}
176 
177 	guard(spinlock_irqsave)(&tio->lock);
178 	if (enable && !pps_gen->enabled) {
179 		pps_tio_direction_output(tio);
180 		hrtimer_start(&tio->timer, first_event(tio), HRTIMER_MODE_ABS);
181 	} else if (!enable && pps_gen->enabled) {
182 		hrtimer_cancel(&tio->timer);
183 		pps_tio_disable(tio);
184 	}
185 
186 	return 0;
187 }
188 
pps_tio_get_time(struct pps_gen_device * pps_gen,struct timespec64 * time)189 static int pps_tio_get_time(struct pps_gen_device *pps_gen,
190 			    struct timespec64 *time)
191 {
192 	struct system_time_snapshot snap;
193 
194 	ktime_get_snapshot(&snap);
195 	*time = ktime_to_timespec64(snap.real);
196 
197 	return 0;
198 }
199 
pps_gen_tio_probe(struct platform_device * pdev)200 static int pps_gen_tio_probe(struct platform_device *pdev)
201 {
202 	struct device *dev = &pdev->dev;
203 	struct pps_tio *tio;
204 
205 	if (!(cpu_feature_enabled(X86_FEATURE_TSC_KNOWN_FREQ) &&
206 	      cpu_feature_enabled(X86_FEATURE_ART))) {
207 		dev_warn(dev, "TSC/ART is not enabled");
208 		return -ENODEV;
209 	}
210 
211 	tio = devm_kzalloc(dev, sizeof(*tio), GFP_KERNEL);
212 	if (!tio)
213 		return -ENOMEM;
214 
215 	tio->gen_info.use_system_clock = true;
216 	tio->gen_info.enable = pps_tio_gen_enable;
217 	tio->gen_info.get_time = pps_tio_get_time;
218 	tio->gen_info.owner = THIS_MODULE;
219 
220 	tio->pps_gen = pps_gen_register_source(&tio->gen_info);
221 	if (IS_ERR(tio->pps_gen))
222 		return PTR_ERR(tio->pps_gen);
223 
224 	tio->dev = dev;
225 	tio->base = devm_platform_ioremap_resource(pdev, 0);
226 	if (IS_ERR(tio->base))
227 		return PTR_ERR(tio->base);
228 
229 	pps_tio_disable(tio);
230 	hrtimer_setup(&tio->timer, hrtimer_callback, CLOCK_REALTIME,
231 		      HRTIMER_MODE_ABS);
232 	spin_lock_init(&tio->lock);
233 	platform_set_drvdata(pdev, &tio);
234 
235 	return 0;
236 }
237 
pps_gen_tio_remove(struct platform_device * pdev)238 static void pps_gen_tio_remove(struct platform_device *pdev)
239 {
240 	struct pps_tio *tio = platform_get_drvdata(pdev);
241 
242 	hrtimer_cancel(&tio->timer);
243 	pps_tio_disable(tio);
244 	pps_gen_unregister_source(tio->pps_gen);
245 }
246 
247 static const struct acpi_device_id intel_pmc_tio_acpi_match[] = {
248 	{ "INTC1021" },
249 	{ "INTC1022" },
250 	{ "INTC1023" },
251 	{ "INTC1024" },
252 	{}
253 };
254 MODULE_DEVICE_TABLE(acpi, intel_pmc_tio_acpi_match);
255 
256 static struct platform_driver pps_gen_tio_driver = {
257 	.probe          = pps_gen_tio_probe,
258 	.remove         = pps_gen_tio_remove,
259 	.driver         = {
260 		.name                   = "intel-pps-gen-tio",
261 		.acpi_match_table       = intel_pmc_tio_acpi_match,
262 	},
263 };
264 module_platform_driver(pps_gen_tio_driver);
265 
266 MODULE_AUTHOR("Christopher Hall <christopher.s.hall@intel.com>");
267 MODULE_AUTHOR("Lakshmi Sowjanya D <lakshmi.sowjanya.d@intel.com>");
268 MODULE_AUTHOR("Pandith N <pandith.n@intel.com>");
269 MODULE_AUTHOR("Thejesh Reddy T R <thejesh.reddy.t.r@intel.com>");
270 MODULE_AUTHOR("Subramanian Mohan <subramanian.mohan@intel.com>");
271 MODULE_DESCRIPTION("Intel PMC Time-Aware IO Generator Driver");
272 MODULE_LICENSE("GPL");
273