diff options
| author | Niklas Cassel <cassel@kernel.org> | 2026-09-16 15:00:32 +0200 |
|---|---|---|
| committer | Niklas Cassel <cassel@kernel.org> | 2026-09-16 16:18:00 +0200 |
| commit | 8d836581f9b1f57ceaa2b47654754ef1260b410b (patch) | |
| tree | 1dbd4d0dfb99c2e238356cbe947e5b0be3e3a571 /drivers/pps/generators | |
| download | linux-stable-8d836581f9b1f57ceaa2b47654754ef1260b410b.tar.gz linux-stable-8d836581f9b1f57ceaa2b47654754ef1260b410b.zip | |
ata: libata-scsi: fix ata_dsm_trim_pages() kernel-docgrafted
make htmldocs fails with:
Documentation/driver-api/libata:604: ./drivers/ata/libata-scsi.c:2301:
ERROR: Unexpected indentation. [docutils]
The Return: section of ata_dsm_trim_pages() ends its first sentence with
a colon and continues with an indented bullet list. reStructuredText
requires a blank line before an indented block, so docutils chokes on
the list.
Simply adding the missing blank line does not work either: Return: is a
kernel-doc "special section", which is terminated by the first blank
line, so the bullet list would end up in the Description section,
detached from the sentence introducing it.
Spell the two bounds out as prose instead, so that the Return: section
stays self-contained. Documentation-only change.
Fixes: e64e6b5dc867 ("ata: libata-scsi: scale DSM TRIM payload by MAX PAGES PER DSM COMMAND")
Reported-by: Thomas Huth <thuth@redhat.com>
Closes: https://lore.kernel.org/linux-ide/b0a0b8e8-cc4f-4c7b-8bc5-fee0712d405a@redhat.com/
Reviewed-by: Damien Le Moal <dlemoal@kernel.org>
Reviewed-by: Thomas Huth <thuth@redhat.com>
Link: https://lore.kernel.org/r/20260916130031.29990-2-cassel@kernel.org
Signed-off-by: Niklas Cassel <cassel@kernel.org>
Diffstat (limited to 'drivers/pps/generators')
| -rw-r--r-- | drivers/pps/generators/Kconfig | 42 | ||||
| -rw-r--r-- | drivers/pps/generators/Makefile | 12 | ||||
| -rw-r--r-- | drivers/pps/generators/pps_gen-dummy.c | 92 | ||||
| -rw-r--r-- | drivers/pps/generators/pps_gen.c | 346 | ||||
| -rw-r--r-- | drivers/pps/generators/pps_gen_tio.c | 267 | ||||
| -rw-r--r-- | drivers/pps/generators/sysfs.c | 75 |
6 files changed, 834 insertions, 0 deletions
diff --git a/drivers/pps/generators/Kconfig b/drivers/pps/generators/Kconfig new file mode 100644 index 000000000..4ef02b3f2 --- /dev/null +++ b/drivers/pps/generators/Kconfig @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# PPS generators configuration +# + +menuconfig PPS_GENERATOR + tristate "PPS generators support" + help + PPS generators are special hardware which are able to produce PPS + (Pulse Per Second) signals. + + To compile this driver as a module, choose M here: the module + will be called pps_gen_core. + +if PPS_GENERATOR + +config PPS_GENERATOR_DUMMY + tristate "Dummy PPS generator (Testing generator, use for debug)" + help + If you say yes here you get support for a PPS debugging generator + (which generates no PPS signal at all). + + This driver can also be built as a module. If so, the module + will be called pps_gen-dummy. + +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 new file mode 100644 index 000000000..5d38774b4 --- /dev/null +++ b/drivers/pps/generators/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for PPS generators. +# + +pps_gen_core-y := pps_gen.o sysfs.o +obj-$(CONFIG_PPS_GENERATOR) := pps_gen_core.o + +obj-$(CONFIG_PPS_GENERATOR_DUMMY) += pps_gen-dummy.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 new file mode 100644 index 000000000..a4395543c --- /dev/null +++ b/drivers/pps/generators/pps_gen-dummy.c @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PPS dummy generator + * + * Copyright (C) 2024 Rodolfo Giometti <giometti@enneenne.com> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/time.h> +#include <linux/timer.h> +#include <linux/random.h> +#include <linux/pps_gen_kernel.h> + +static struct pps_gen_device *pps_gen; +static struct timer_list ktimer; + +static unsigned int get_random_delay(void) +{ + unsigned int delay = get_random_u8() & 0x0f; + + return (delay + 1) * HZ; +} + +/* + * The kernel timer + */ + +static void pps_gen_ktimer_event(struct timer_list *unused) +{ + pps_gen_event(pps_gen, PPS_GEN_EVENT_MISSEDPULSE, NULL); +} + +/* + * PPS Generator methods + */ + +static int pps_gen_dummy_get_time(struct pps_gen_device *pps_gen, + struct timespec64 *time) +{ + ktime_get_real_ts64(time); + return 0; +} + +static int pps_gen_dummy_enable(struct pps_gen_device *pps_gen, bool enable) +{ + if (enable) + mod_timer(&ktimer, jiffies + get_random_delay()); + else + timer_delete_sync(&ktimer); + + return 0; +} + +/* + * The PPS info struct + */ + +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, +}; + +/* + * Module staff + */ + +static void __exit pps_gen_dummy_exit(void) +{ + timer_delete_sync(&ktimer); + pps_gen_unregister_source(pps_gen); +} + +static int __init pps_gen_dummy_init(void) +{ + pps_gen = pps_gen_register_source(&pps_gen_dummy_info); + if (IS_ERR(pps_gen)) + return PTR_ERR(pps_gen); + + timer_setup(&ktimer, pps_gen_ktimer_event, 0); + + return 0; +} + +module_init(pps_gen_dummy_init); +module_exit(pps_gen_dummy_exit); + +MODULE_AUTHOR("Rodolfo Giometti <giometti@enneenne.com>"); +MODULE_DESCRIPTION("LinuxPPS dummy generator"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pps/generators/pps_gen.c b/drivers/pps/generators/pps_gen.c new file mode 100644 index 000000000..5e207c75e --- /dev/null +++ b/drivers/pps/generators/pps_gen.c @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PPS generators core file + * + * Copyright (C) 2024 Rodolfo Giometti <giometti@enneenne.com> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/time.h> +#include <linux/timex.h> +#include <linux/uaccess.h> +#include <linux/idr.h> +#include <linux/cdev.h> +#include <linux/poll.h> +#include <linux/fs.h> +#include <linux/pps_gen_kernel.h> +#include <linux/slab.h> + +/* + * Local variables + */ + +static dev_t pps_gen_devt; +static const struct class pps_gen_class = { + .name = "pps-gen", + .dev_groups = pps_gen_groups +}; + +static DEFINE_IDA(pps_gen_ida); + +/* + * Char device methods + */ + +static __poll_t pps_gen_cdev_poll(struct file *file, poll_table *wait) +{ + struct pps_gen_device *pps_gen = file->private_data; + + poll_wait(file, &pps_gen->queue, wait); + return EPOLLIN | EPOLLRDNORM; +} + +static int pps_gen_cdev_fasync(int fd, struct file *file, int on) +{ + struct pps_gen_device *pps_gen = file->private_data; + + return fasync_helper(fd, file, on, &pps_gen->async_queue); +} + +static long pps_gen_cdev_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct pps_gen_device *pps_gen = file->private_data; + void __user *uarg = (void __user *) arg; + unsigned int __user *uiuarg = (unsigned int __user *) arg; + unsigned int status; + int ret; + + switch (cmd) { + case PPS_GEN_SETENABLE: + dev_dbg(pps_gen->dev, "PPS_GEN_SETENABLE\n"); + + ret = get_user(status, uiuarg); + if (ret) + return -EFAULT; + + ret = pps_gen->info->enable(pps_gen, status); + if (ret) + return ret; + pps_gen->enabled = status; + + break; + + case PPS_GEN_USESYSTEMCLOCK: + dev_dbg(pps_gen->dev, "PPS_GEN_USESYSTEMCLOCK\n"); + + ret = put_user(pps_gen->info->use_system_clock, uiuarg); + if (ret) + return -EFAULT; + + break; + + case PPS_GEN_FETCHEVENT: { + struct pps_gen_event info; + unsigned int ev = pps_gen->last_ev; + + dev_dbg(pps_gen->dev, "PPS_GEN_FETCHEVENT\n"); + + ret = wait_event_interruptible(pps_gen->queue, + ev != pps_gen->last_ev); + if (ret == -ERESTARTSYS) { + dev_dbg(pps_gen->dev, "pending signal caught\n"); + return -EINTR; + } + + spin_lock_irq(&pps_gen->lock); + info.sequence = pps_gen->sequence; + info.event = pps_gen->event; + spin_unlock_irq(&pps_gen->lock); + + ret = copy_to_user(uarg, &info, sizeof(struct pps_gen_event)); + if (ret) + return -EFAULT; + + break; + } + default: + return -ENOTTY; + } + + return 0; +} + +static int pps_gen_cdev_open(struct inode *inode, struct file *file) +{ + struct pps_gen_device *pps_gen = container_of(inode->i_cdev, + struct pps_gen_device, cdev); + + get_device(pps_gen->dev); + file->private_data = pps_gen; + return 0; +} + +static int pps_gen_cdev_release(struct inode *inode, struct file *file) +{ + struct pps_gen_device *pps_gen = file->private_data; + + put_device(pps_gen->dev); + return 0; +} + +/* + * Char device stuff + */ + +static const struct file_operations pps_gen_cdev_fops = { + .owner = THIS_MODULE, + .poll = pps_gen_cdev_poll, + .fasync = pps_gen_cdev_fasync, + .unlocked_ioctl = pps_gen_cdev_ioctl, + .open = pps_gen_cdev_open, + .release = pps_gen_cdev_release, +}; + +static void pps_gen_device_destruct(struct device *dev) +{ + struct pps_gen_device *pps_gen = dev_get_drvdata(dev); + + cdev_del(&pps_gen->cdev); + + pr_debug("deallocating pps-gen%d\n", pps_gen->id); + ida_free(&pps_gen_ida, pps_gen->id); + + kfree(dev); + kfree(pps_gen); +} + +static int pps_gen_register_cdev(struct pps_gen_device *pps_gen) +{ + int err; + dev_t devt; + + err = ida_alloc_max(&pps_gen_ida, PPS_GEN_MAX_SOURCES - 1, GFP_KERNEL); + if (err < 0) { + if (err == -ENOSPC) { + pr_err("too many PPS sources in the system\n"); + err = -EBUSY; + } + return err; + } + pps_gen->id = err; + + 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; + + err = cdev_add(&pps_gen->cdev, devt, 1); + if (err) { + pr_err("failed to add char device %d:%d\n", + 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); + if (IS_ERR(pps_gen->dev)) { + err = PTR_ERR(pps_gen->dev); + goto del_cdev; + } + pps_gen->dev->release = pps_gen_device_destruct; + dev_set_drvdata(pps_gen->dev, pps_gen); + + pr_debug("generator got cdev (%d:%d)\n", + MAJOR(pps_gen_devt), pps_gen->id); + + return 0; + +del_cdev: + cdev_del(&pps_gen->cdev); +free_ida: + ida_free(&pps_gen_ida, pps_gen->id); + return err; +} + +static void pps_gen_unregister_cdev(struct pps_gen_device *pps_gen) +{ + pr_debug("unregistering pps-gen%d\n", pps_gen->id); + device_destroy(&pps_gen_class, pps_gen->dev->devt); +} + +/* + * Exported functions + */ + +/** + * pps_gen_register_source() - add a PPS generator in the system + * @info: the PPS generator info struct + * + * This function is used to register a new PPS generator in the system. + * When it returns successfully the new generator is up and running, and + * it can be managed by the userspace. + * + * Return: the PPS generator device in case of success, and ERR_PTR(errno) + * otherwise. + */ +struct pps_gen_device *pps_gen_register_source(const struct pps_gen_source_info *info) +{ + struct pps_gen_device *pps_gen; + int err; + + pps_gen = kzalloc_obj(struct pps_gen_device); + if (pps_gen == NULL) { + err = -ENOMEM; + goto pps_gen_register_source_exit; + } + pps_gen->info = info; + pps_gen->enabled = false; + + init_waitqueue_head(&pps_gen->queue); + spin_lock_init(&pps_gen->lock); + + /* Create the char device */ + err = pps_gen_register_cdev(pps_gen); + if (err < 0) { + pr_err(" unable to create char device\n"); + goto kfree_pps_gen; + } + + return pps_gen; + +kfree_pps_gen: + kfree(pps_gen); + +pps_gen_register_source_exit: + pr_err("unable to register generator\n"); + + return ERR_PTR(err); +} +EXPORT_SYMBOL(pps_gen_register_source); + +/** + * pps_gen_unregister_source() - remove a PPS generator from the system + * @pps_gen: the PPS generator device to be removed + * + * This function is used to deregister a PPS generator from the system. When + * called, it disables the generator so no pulses are generated anymore. + */ +void pps_gen_unregister_source(struct pps_gen_device *pps_gen) +{ + pps_gen_unregister_cdev(pps_gen); +} +EXPORT_SYMBOL(pps_gen_unregister_source); + +/* pps_gen_event - register a PPS generator event into the system + * @pps: the PPS generator device + * @event: the event type + * @data: userdef pointer + * + * This function is used by each PPS generator in order to register a new + * PPS event into the system (it's usually called inside an IRQ handler). + */ +void pps_gen_event(struct pps_gen_device *pps_gen, + unsigned int event, void *data) +{ + unsigned long flags; + + dev_dbg(pps_gen->dev, "PPS generator event %u\n", event); + + spin_lock_irqsave(&pps_gen->lock, flags); + + pps_gen->event = event; + pps_gen->sequence++; + + pps_gen->last_ev++; + wake_up_interruptible_all(&pps_gen->queue); + kill_fasync(&pps_gen->async_queue, SIGIO, POLL_IN); + + spin_unlock_irqrestore(&pps_gen->lock, flags); +} +EXPORT_SYMBOL(pps_gen_event); + +/* + * Module stuff + */ + +static void __exit pps_gen_exit(void) +{ + class_unregister(&pps_gen_class); + unregister_chrdev_region(pps_gen_devt, PPS_GEN_MAX_SOURCES); +} + +static int __init pps_gen_init(void) +{ + int err; + + err = class_register(&pps_gen_class); + if (err) { + pr_err("failed to register class\n"); + return err; + } + + err = alloc_chrdev_region(&pps_gen_devt, 0, + PPS_GEN_MAX_SOURCES, "pps-gen"); + if (err < 0) { + pr_err("failed to allocate char device region\n"); + goto remove_class; + } + + return 0; + +remove_class: + class_unregister(&pps_gen_class); + return err; +} + +subsys_initcall(pps_gen_init); +module_exit(pps_gen_exit); + +MODULE_AUTHOR("Rodolfo Giometti <giometti@enneenne.com>"); +MODULE_DESCRIPTION("LinuxPPS generators support"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pps/generators/pps_gen_tio.c b/drivers/pps/generators/pps_gen_tio.c new file mode 100644 index 000000000..5088aaf1a --- /dev/null +++ b/drivers/pps/generators/pps_gen_tio.c @@ -0,0 +1,267 @@ +// 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/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) +{ + ktime_get_real_ts64(time); + 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 new file mode 100644 index 000000000..6d6bc0006 --- /dev/null +++ b/drivers/pps/generators/sysfs.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PPS generators sysfs support + * + * Copyright (C) 2024 Rodolfo Giometti <giometti@enneenne.com> + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/pps_gen_kernel.h> + +/* + * Attribute functions + */ + +static ssize_t system_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct pps_gen_device *pps_gen = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", pps_gen->info->use_system_clock); +} +static DEVICE_ATTR_RO(system); + +static ssize_t time_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct pps_gen_device *pps_gen = dev_get_drvdata(dev); + struct timespec64 time; + int ret; + + ret = pps_gen->info->get_time(pps_gen, &time); + if (ret) + return ret; + + return sysfs_emit(buf, "%llu %09lu\n", time.tv_sec, time.tv_nsec); +} +static DEVICE_ATTR_RO(time); + +static ssize_t enable_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct pps_gen_device *pps_gen = dev_get_drvdata(dev); + bool status; + int ret; + + ret = kstrtobool(buf, &status); + if (ret) + return ret; + + ret = pps_gen->info->enable(pps_gen, status); + if (ret) + return ret; + pps_gen->enabled = status; + + return count; +} +static DEVICE_ATTR_WO(enable); + +static struct attribute *pps_gen_attrs[] = { + &dev_attr_enable.attr, + &dev_attr_time.attr, + &dev_attr_system.attr, + NULL, +}; + +static const struct attribute_group pps_gen_group = { + .attrs = pps_gen_attrs, +}; + +const struct attribute_group *pps_gen_groups[] = { + &pps_gen_group, + NULL, +}; |
