|
| 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(tio->base + offset); |
| 55 | +} |
| 56 | + |
| 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 | + */ |
| 66 | +static inline void pps_compv_write(u64 value, struct pps_tio *tio) |
| 67 | +{ |
| 68 | + hi_lo_writeq(value, tio->base + TIOCOMPV); |
| 69 | +} |
| 70 | + |
| 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 | + |
| 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 | + |
| 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 | + |
| 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 | + |
| 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 | + |
| 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 | + |
| 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 | + |
| 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 | + |
| 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_init(&tio->timer, CLOCK_REALTIME, HRTIMER_MODE_ABS); |
| 231 | + tio->timer.function = hrtimer_callback; |
| 232 | + spin_lock_init(&tio->lock); |
| 233 | + platform_set_drvdata(pdev, &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(&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 <[email protected]>"); |
| 267 | +MODULE_AUTHOR( "Lakshmi Sowjanya D <[email protected]>"); |
| 268 | +MODULE_AUTHOR( "Pandith N <[email protected]>"); |
| 269 | +MODULE_AUTHOR( "Thejesh Reddy T R <[email protected]>"); |
| 270 | +MODULE_AUTHOR( "Subramanian Mohan <[email protected]>"); |
| 271 | +MODULE_DESCRIPTION("Intel PMC Time-Aware IO Generator Driver"); |
| 272 | +MODULE_LICENSE("GPL"); |
0 commit comments