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