File Index
Files and directories
Sign in
FILE

pps_gen_tio.c

File preview with syntax highlighting.

linux-6.19.14/drivers/pps/generators/pps_gen_tio.c C
  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
 42struct 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
 52static inline u32 pps_tio_read(u32 offset, struct pps_tio *tio)
 53{
 54	return readl(tio->base + offset);
 55}
 56
 57static 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 */
 66static inline void pps_compv_write(u64 value, struct pps_tio *tio)
 67{
 68	hi_lo_writeq(value, tio->base + TIOCOMPV);
 69}
 70
 71static inline ktime_t first_event(struct pps_tio *tio)
 72{
 73	return ktime_set(ktime_get_real_seconds() + 1, MAGIC_CONST);
 74}
 75
 76static 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
 90static 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
100static 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
118static 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
131static 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
161err:
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
168static 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
189static 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
200static 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
238static 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
247static const struct acpi_device_id intel_pmc_tio_acpi_match[] = {
248	{ "INTC1021" },
249	{ "INTC1022" },
250	{ "INTC1023" },
251	{ "INTC1024" },
252	{}
253};
254MODULE_DEVICE_TABLE(acpi, intel_pmc_tio_acpi_match);
255
256static 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};
264module_platform_driver(pps_gen_tio_driver);
265
266MODULE_AUTHOR("Christopher Hall <christopher.s.hall@intel.com>");
267MODULE_AUTHOR("Lakshmi Sowjanya D <lakshmi.sowjanya.d@intel.com>");
268MODULE_AUTHOR("Pandith N <pandith.n@intel.com>");
269MODULE_AUTHOR("Thejesh Reddy T R <thejesh.reddy.t.r@intel.com>");
270MODULE_AUTHOR("Subramanian Mohan <subramanian.mohan@intel.com>");
271MODULE_DESCRIPTION("Intel PMC Time-Aware IO Generator Driver");
272MODULE_LICENSE("GPL");
~/ Read-only file preview.