diff options
Diffstat (limited to 'drivers/pps')
-rw-r--r-- | drivers/pps/clients/pps-gpio.c | 2 | ||||
-rw-r--r-- | drivers/pps/clients/pps-ktimer.c | 2 | ||||
-rw-r--r-- | drivers/pps/generators/Kconfig | 16 | ||||
-rw-r--r-- | drivers/pps/generators/Makefile | 1 | ||||
-rw-r--r-- | drivers/pps/generators/pps_gen-dummy.c | 6 | ||||
-rw-r--r-- | drivers/pps/generators/pps_gen.c | 14 | ||||
-rw-r--r-- | drivers/pps/generators/pps_gen_parport.c | 3 | ||||
-rw-r--r-- | drivers/pps/generators/pps_gen_tio.c | 272 | ||||
-rw-r--r-- | drivers/pps/generators/sysfs.c | 6 |
9 files changed, 305 insertions, 17 deletions
diff --git a/drivers/pps/clients/pps-gpio.c b/drivers/pps/clients/pps-gpio.c index 75c1bae30a7c..374ceefd6f2a 100644 --- a/drivers/pps/clients/pps-gpio.c +++ b/drivers/pps/clients/pps-gpio.c @@ -229,7 +229,7 @@ static void pps_gpio_remove(struct platform_device *pdev) struct pps_gpio_device_data *data = platform_get_drvdata(pdev); pps_unregister_source(data->pps); - del_timer_sync(&data->echo_timer); + timer_delete_sync(&data->echo_timer); /* reset echo pin in any case */ gpiod_set_value(data->echo_pin, 0); dev_info(&pdev->dev, "removed IRQ %d as PPS source\n", data->irq); diff --git a/drivers/pps/clients/pps-ktimer.c b/drivers/pps/clients/pps-ktimer.c index 2f465549b843..121bd29d863d 100644 --- a/drivers/pps/clients/pps-ktimer.c +++ b/drivers/pps/clients/pps-ktimer.c @@ -58,7 +58,7 @@ static void __exit pps_ktimer_exit(void) { dev_dbg(&pps->dev, "ktimer PPS source unregistered\n"); - del_timer_sync(&ktimer); + timer_delete_sync(&ktimer); pps_unregister_source(pps); } diff --git a/drivers/pps/generators/Kconfig b/drivers/pps/generators/Kconfig index cd94bf3bfaf2..b3f340ed3163 100644 --- a/drivers/pps/generators/Kconfig +++ b/drivers/pps/generators/Kconfig @@ -31,4 +31,20 @@ config PPS_GENERATOR_PARPORT utilizes STROBE pin of a parallel port to send PPS signals. It uses parport abstraction layer and hrtimers to precisely control the signal. +config PPS_GENERATOR_TIO + tristate "TIO PPS signal generator" + depends on X86 && CPU_SUP_INTEL + help + If you say yes here you get support for a PPS TIO signal generator + which generates a pulse at a prescribed time based on the system clock. + It uses time translation and hrtimers to precisely generate a pulse. + This hardware is present on 2019 and newer Intel CPUs. However, this + driver is not useful without adding highly specialized hardware outside + the Linux system to observe these pulses. + + To compile this driver as a module, choose M here: the module + will be called pps_gen_tio. + + If unsure, say N. + endif # PPS_GENERATOR diff --git a/drivers/pps/generators/Makefile b/drivers/pps/generators/Makefile index dc1aa5a4688b..e109920e8a2d 100644 --- a/drivers/pps/generators/Makefile +++ b/drivers/pps/generators/Makefile @@ -8,5 +8,6 @@ obj-$(CONFIG_PPS_GENERATOR) := pps_gen_core.o obj-$(CONFIG_PPS_GENERATOR_DUMMY) += pps_gen-dummy.o obj-$(CONFIG_PPS_GENERATOR_PARPORT) += pps_gen_parport.o +obj-$(CONFIG_PPS_GENERATOR_TIO) += pps_gen_tio.o ccflags-$(CONFIG_PPS_DEBUG) := -DDEBUG diff --git a/drivers/pps/generators/pps_gen-dummy.c b/drivers/pps/generators/pps_gen-dummy.c index b284c200cbe5..547fa7fe29f4 100644 --- a/drivers/pps/generators/pps_gen-dummy.c +++ b/drivers/pps/generators/pps_gen-dummy.c @@ -52,7 +52,7 @@ static int pps_gen_dummy_enable(struct pps_gen_device *pps_gen, bool enable) if (enable) mod_timer(&ktimer, jiffies + get_random_delay()); else - del_timer_sync(&ktimer); + timer_delete_sync(&ktimer); return 0; } @@ -61,7 +61,7 @@ static int pps_gen_dummy_enable(struct pps_gen_device *pps_gen, bool enable) * The PPS info struct */ -static struct pps_gen_source_info pps_gen_dummy_info = { +static const struct pps_gen_source_info pps_gen_dummy_info = { .use_system_clock = true, .get_time = pps_gen_dummy_get_time, .enable = pps_gen_dummy_enable, @@ -73,7 +73,7 @@ static struct pps_gen_source_info pps_gen_dummy_info = { static void __exit pps_gen_dummy_exit(void) { - del_timer_sync(&ktimer); + timer_delete_sync(&ktimer); pps_gen_unregister_source(pps_gen); } diff --git a/drivers/pps/generators/pps_gen.c b/drivers/pps/generators/pps_gen.c index ca592f1736f4..5b8bb454913c 100644 --- a/drivers/pps/generators/pps_gen.c +++ b/drivers/pps/generators/pps_gen.c @@ -66,7 +66,7 @@ static long pps_gen_cdev_ioctl(struct file *file, if (ret) return -EFAULT; - ret = pps_gen->info.enable(pps_gen, status); + ret = pps_gen->info->enable(pps_gen, status); if (ret) return ret; pps_gen->enabled = status; @@ -76,7 +76,7 @@ static long pps_gen_cdev_ioctl(struct file *file, case PPS_GEN_USESYSTEMCLOCK: dev_dbg(pps_gen->dev, "PPS_GEN_USESYSTEMCLOCK\n"); - ret = put_user(pps_gen->info.use_system_clock, uiuarg); + ret = put_user(pps_gen->info->use_system_clock, uiuarg); if (ret) return -EFAULT; @@ -175,7 +175,7 @@ static int pps_gen_register_cdev(struct pps_gen_device *pps_gen) devt = MKDEV(MAJOR(pps_gen_devt), pps_gen->id); cdev_init(&pps_gen->cdev, &pps_gen_cdev_fops); - pps_gen->cdev.owner = pps_gen->info.owner; + pps_gen->cdev.owner = pps_gen->info->owner; err = cdev_add(&pps_gen->cdev, devt, 1); if (err) { @@ -183,8 +183,8 @@ static int pps_gen_register_cdev(struct pps_gen_device *pps_gen) MAJOR(pps_gen_devt), pps_gen->id); goto free_ida; } - pps_gen->dev = device_create(pps_gen_class, pps_gen->info.parent, devt, - pps_gen, "pps-gen%d", pps_gen->id); + pps_gen->dev = device_create(pps_gen_class, pps_gen->info->parent, devt, + pps_gen, "pps-gen%d", pps_gen->id); if (IS_ERR(pps_gen->dev)) { err = PTR_ERR(pps_gen->dev); goto del_cdev; @@ -225,7 +225,7 @@ static void pps_gen_unregister_cdev(struct pps_gen_device *pps_gen) * Return: the PPS generator device in case of success, and ERR_PTR(errno) * otherwise. */ -struct pps_gen_device *pps_gen_register_source(struct pps_gen_source_info *info) +struct pps_gen_device *pps_gen_register_source(const struct pps_gen_source_info *info) { struct pps_gen_device *pps_gen; int err; @@ -235,7 +235,7 @@ struct pps_gen_device *pps_gen_register_source(struct pps_gen_source_info *info) err = -ENOMEM; goto pps_gen_register_source_exit; } - pps_gen->info = *info; + pps_gen->info = info; pps_gen->enabled = false; init_waitqueue_head(&pps_gen->queue); diff --git a/drivers/pps/generators/pps_gen_parport.c b/drivers/pps/generators/pps_gen_parport.c index d46eed159495..f5eeb4dd01ad 100644 --- a/drivers/pps/generators/pps_gen_parport.c +++ b/drivers/pps/generators/pps_gen_parport.c @@ -208,8 +208,7 @@ static void parport_attach(struct parport *port) calibrate_port(&device); - hrtimer_init(&device.timer, CLOCK_REALTIME, HRTIMER_MODE_ABS); - device.timer.function = hrtimer_event; + hrtimer_setup(&device.timer, hrtimer_event, CLOCK_REALTIME, HRTIMER_MODE_ABS); hrtimer_start(&device.timer, next_intr_time(&device), HRTIMER_MODE_ABS); return; diff --git a/drivers/pps/generators/pps_gen_tio.c b/drivers/pps/generators/pps_gen_tio.c new file mode 100644 index 000000000000..1d5ffe055463 --- /dev/null +++ b/drivers/pps/generators/pps_gen_tio.c @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Intel PPS signal Generator Driver + * + * Copyright (C) 2024 Intel Corporation + */ + +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/cleanup.h> +#include <linux/container_of.h> +#include <linux/device.h> +#include <linux/hrtimer.h> +#include <linux/io-64-nonatomic-hi-lo.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pps_gen_kernel.h> +#include <linux/timekeeping.h> +#include <linux/types.h> + +#include <asm/cpu_device_id.h> + +#define TIOCTL 0x00 +#define TIOCOMPV 0x10 +#define TIOEC 0x30 + +/* Control Register */ +#define TIOCTL_EN BIT(0) +#define TIOCTL_DIR BIT(1) +#define TIOCTL_EP GENMASK(3, 2) +#define TIOCTL_EP_RISING_EDGE FIELD_PREP(TIOCTL_EP, 0) +#define TIOCTL_EP_FALLING_EDGE FIELD_PREP(TIOCTL_EP, 1) +#define TIOCTL_EP_TOGGLE_EDGE FIELD_PREP(TIOCTL_EP, 2) + +/* Safety time to set hrtimer early */ +#define SAFE_TIME_NS (10 * NSEC_PER_MSEC) + +#define MAGIC_CONST (NSEC_PER_SEC - SAFE_TIME_NS) +#define ART_HW_DELAY_CYCLES 2 + +struct pps_tio { + struct pps_gen_source_info gen_info; + struct pps_gen_device *pps_gen; + struct hrtimer timer; + void __iomem *base; + u32 prev_count; + spinlock_t lock; + struct device *dev; +}; + +static inline u32 pps_tio_read(u32 offset, struct pps_tio *tio) +{ + return readl(tio->base + offset); +} + +static inline void pps_ctl_write(u32 value, struct pps_tio *tio) +{ + writel(value, tio->base + TIOCTL); +} + +/* + * For COMPV register, It's safer to write + * higher 32-bit followed by lower 32-bit + */ +static inline void pps_compv_write(u64 value, struct pps_tio *tio) +{ + hi_lo_writeq(value, tio->base + TIOCOMPV); +} + +static inline ktime_t first_event(struct pps_tio *tio) +{ + return ktime_set(ktime_get_real_seconds() + 1, MAGIC_CONST); +} + +static u32 pps_tio_disable(struct pps_tio *tio) +{ + u32 ctrl; + + ctrl = pps_tio_read(TIOCTL, tio); + pps_compv_write(0, tio); + + ctrl &= ~TIOCTL_EN; + pps_ctl_write(ctrl, tio); + tio->pps_gen->enabled = false; + tio->prev_count = 0; + return ctrl; +} + +static void pps_tio_enable(struct pps_tio *tio) +{ + u32 ctrl; + + ctrl = pps_tio_read(TIOCTL, tio); + ctrl |= TIOCTL_EN; + pps_ctl_write(ctrl, tio); + tio->pps_gen->enabled = true; +} + +static void pps_tio_direction_output(struct pps_tio *tio) +{ + u32 ctrl; + + ctrl = pps_tio_disable(tio); + + /* + * We enable the device, be sure that the + * 'compare' value is invalid + */ + pps_compv_write(0, tio); + + ctrl &= ~(TIOCTL_DIR | TIOCTL_EP); + ctrl |= TIOCTL_EP_TOGGLE_EDGE; + pps_ctl_write(ctrl, tio); + pps_tio_enable(tio); +} + +static bool pps_generate_next_pulse(ktime_t expires, struct pps_tio *tio) +{ + u64 art; + + if (!ktime_real_to_base_clock(expires, CSID_X86_ART, &art)) { + pps_tio_disable(tio); + return false; + } + + pps_compv_write(art - ART_HW_DELAY_CYCLES, tio); + return true; +} + +static enum hrtimer_restart hrtimer_callback(struct hrtimer *timer) +{ + ktime_t expires, now; + u32 event_count; + struct pps_tio *tio = container_of(timer, struct pps_tio, timer); + + guard(spinlock)(&tio->lock); + + /* + * Check if any event is missed. + * If an event is missed, TIO will be disabled. + */ + event_count = pps_tio_read(TIOEC, tio); + if (tio->prev_count && tio->prev_count == event_count) + goto err; + tio->prev_count = event_count; + + expires = hrtimer_get_expires(timer); + + now = ktime_get_real(); + if (now - expires >= SAFE_TIME_NS) + goto err; + + tio->pps_gen->enabled = pps_generate_next_pulse(expires + SAFE_TIME_NS, tio); + if (!tio->pps_gen->enabled) + return HRTIMER_NORESTART; + + hrtimer_forward(timer, now, NSEC_PER_SEC / 2); + return HRTIMER_RESTART; + +err: + dev_err(tio->dev, "Event missed, Disabling Timed I/O"); + pps_tio_disable(tio); + pps_gen_event(tio->pps_gen, PPS_GEN_EVENT_MISSEDPULSE, NULL); + return HRTIMER_NORESTART; +} + +static int pps_tio_gen_enable(struct pps_gen_device *pps_gen, bool enable) +{ + struct pps_tio *tio = container_of(pps_gen->info, struct pps_tio, gen_info); + + if (!timekeeping_clocksource_has_base(CSID_X86_ART)) { + dev_err_once(tio->dev, "PPS cannot be used as clock is not related to ART"); + return -ENODEV; + } + + guard(spinlock_irqsave)(&tio->lock); + if (enable && !pps_gen->enabled) { + pps_tio_direction_output(tio); + hrtimer_start(&tio->timer, first_event(tio), HRTIMER_MODE_ABS); + } else if (!enable && pps_gen->enabled) { + hrtimer_cancel(&tio->timer); + pps_tio_disable(tio); + } + + return 0; +} + +static int pps_tio_get_time(struct pps_gen_device *pps_gen, + struct timespec64 *time) +{ + struct system_time_snapshot snap; + + ktime_get_snapshot(&snap); + *time = ktime_to_timespec64(snap.real); + + return 0; +} + +static int pps_gen_tio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pps_tio *tio; + + if (!(cpu_feature_enabled(X86_FEATURE_TSC_KNOWN_FREQ) && + cpu_feature_enabled(X86_FEATURE_ART))) { + dev_warn(dev, "TSC/ART is not enabled"); + return -ENODEV; + } + + tio = devm_kzalloc(dev, sizeof(*tio), GFP_KERNEL); + if (!tio) + return -ENOMEM; + + tio->gen_info.use_system_clock = true; + tio->gen_info.enable = pps_tio_gen_enable; + tio->gen_info.get_time = pps_tio_get_time; + tio->gen_info.owner = THIS_MODULE; + + tio->pps_gen = pps_gen_register_source(&tio->gen_info); + if (IS_ERR(tio->pps_gen)) + return PTR_ERR(tio->pps_gen); + + tio->dev = dev; + tio->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(tio->base)) + return PTR_ERR(tio->base); + + pps_tio_disable(tio); + hrtimer_setup(&tio->timer, hrtimer_callback, CLOCK_REALTIME, + HRTIMER_MODE_ABS); + spin_lock_init(&tio->lock); + platform_set_drvdata(pdev, &tio); + + return 0; +} + +static void pps_gen_tio_remove(struct platform_device *pdev) +{ + struct pps_tio *tio = platform_get_drvdata(pdev); + + hrtimer_cancel(&tio->timer); + pps_tio_disable(tio); + pps_gen_unregister_source(tio->pps_gen); +} + +static const struct acpi_device_id intel_pmc_tio_acpi_match[] = { + { "INTC1021" }, + { "INTC1022" }, + { "INTC1023" }, + { "INTC1024" }, + {} +}; +MODULE_DEVICE_TABLE(acpi, intel_pmc_tio_acpi_match); + +static struct platform_driver pps_gen_tio_driver = { + .probe = pps_gen_tio_probe, + .remove = pps_gen_tio_remove, + .driver = { + .name = "intel-pps-gen-tio", + .acpi_match_table = intel_pmc_tio_acpi_match, + }, +}; +module_platform_driver(pps_gen_tio_driver); + +MODULE_AUTHOR("Christopher Hall <christopher.s.hall@intel.com>"); +MODULE_AUTHOR("Lakshmi Sowjanya D <lakshmi.sowjanya.d@intel.com>"); +MODULE_AUTHOR("Pandith N <pandith.n@intel.com>"); +MODULE_AUTHOR("Thejesh Reddy T R <thejesh.reddy.t.r@intel.com>"); +MODULE_AUTHOR("Subramanian Mohan <subramanian.mohan@intel.com>"); +MODULE_DESCRIPTION("Intel PMC Time-Aware IO Generator Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pps/generators/sysfs.c b/drivers/pps/generators/sysfs.c index faf8b1c6d202..6d6bc0006fea 100644 --- a/drivers/pps/generators/sysfs.c +++ b/drivers/pps/generators/sysfs.c @@ -19,7 +19,7 @@ static ssize_t system_show(struct device *dev, struct device_attribute *attr, { struct pps_gen_device *pps_gen = dev_get_drvdata(dev); - return sysfs_emit(buf, "%d\n", pps_gen->info.use_system_clock); + return sysfs_emit(buf, "%d\n", pps_gen->info->use_system_clock); } static DEVICE_ATTR_RO(system); @@ -30,7 +30,7 @@ static ssize_t time_show(struct device *dev, struct device_attribute *attr, struct timespec64 time; int ret; - ret = pps_gen->info.get_time(pps_gen, &time); + ret = pps_gen->info->get_time(pps_gen, &time); if (ret) return ret; @@ -49,7 +49,7 @@ static ssize_t enable_store(struct device *dev, struct device_attribute *attr, if (ret) return ret; - ret = pps_gen->info.enable(pps_gen, status); + ret = pps_gen->info->enable(pps_gen, status); if (ret) return ret; pps_gen->enabled = status; |