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