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 | |
52 | static inline u32 pps_tio_read(u32 offset, struct pps_tio *tio) |
53 | { |
54 | return readl(addr: tio->base + offset); |
55 | } |
56 | |
57 | static inline void pps_ctl_write(u32 value, struct pps_tio *tio) |
58 | { |
59 | writel(val: value, addr: 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 | */ |
66 | static inline void pps_compv_write(u64 value, struct pps_tio *tio) |
67 | { |
68 | hi_lo_writeq(val: value, addr: tio->base + TIOCOMPV); |
69 | } |
70 | |
71 | static inline ktime_t first_event(struct pps_tio *tio) |
72 | { |
73 | return ktime_set(secs: ktime_get_real_seconds() + 1, MAGIC_CONST); |
74 | } |
75 | |
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(value: 0, tio); |
82 | |
83 | ctrl &= ~TIOCTL_EN; |
84 | pps_ctl_write(value: ctrl, tio); |
85 | tio->pps_gen->enabled = false; |
86 | tio->prev_count = 0; |
87 | return ctrl; |
88 | } |
89 | |
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(value: ctrl, tio); |
97 | tio->pps_gen->enabled = true; |
98 | } |
99 | |
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(value: 0, tio); |
111 | |
112 | ctrl &= ~(TIOCTL_DIR | TIOCTL_EP); |
113 | ctrl |= TIOCTL_EP_TOGGLE_EDGE; |
114 | pps_ctl_write(value: ctrl, tio); |
115 | pps_tio_enable(tio); |
116 | } |
117 | |
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(treal: expires, base_id: CSID_X86_ART, cycles: &art)) { |
123 | pps_tio_disable(tio); |
124 | return false; |
125 | } |
126 | |
127 | pps_compv_write(value: art - ART_HW_DELAY_CYCLES, tio); |
128 | return true; |
129 | } |
130 | |
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)(l: &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: 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(pps_gen: tio->pps_gen, PPS_GEN_EVENT_MISSEDPULSE, NULL); |
165 | return HRTIMER_NORESTART; |
166 | } |
167 | |
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(id: 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)(l: &tio->lock); |
178 | if (enable && !pps_gen->enabled) { |
179 | pps_tio_direction_output(tio); |
180 | hrtimer_start(timer: &tio->timer, tim: first_event(tio), mode: HRTIMER_MODE_ABS); |
181 | } else if (!enable && pps_gen->enabled) { |
182 | hrtimer_cancel(timer: &tio->timer); |
183 | pps_tio_disable(tio); |
184 | } |
185 | |
186 | return 0; |
187 | } |
188 | |
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(systime_snapshot: &snap); |
195 | *time = ktime_to_timespec64(snap.real); |
196 | |
197 | return 0; |
198 | } |
199 | |
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, size: 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(info: &tio->gen_info); |
221 | if (IS_ERR(ptr: tio->pps_gen)) |
222 | return PTR_ERR(ptr: tio->pps_gen); |
223 | |
224 | tio->dev = dev; |
225 | tio->base = devm_platform_ioremap_resource(pdev, index: 0); |
226 | if (IS_ERR(ptr: tio->base)) |
227 | return PTR_ERR(ptr: tio->base); |
228 | |
229 | pps_tio_disable(tio); |
230 | hrtimer_setup(timer: &tio->timer, function: hrtimer_callback, CLOCK_REALTIME, |
231 | mode: HRTIMER_MODE_ABS); |
232 | spin_lock_init(&tio->lock); |
233 | platform_set_drvdata(pdev, data: tio); |
234 | |
235 | return 0; |
236 | } |
237 | |
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(timer: &tio->timer); |
243 | pps_tio_disable(tio); |
244 | pps_gen_unregister_source(pps_gen: 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 | |