summaryrefslogtreecommitdiff
path: root/drivers/pps
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/pps')
-rw-r--r--drivers/pps/Makefile3
-rw-r--r--drivers/pps/clients/pps-gpio.c19
-rw-r--r--drivers/pps/clients/pps-ktimer.c6
-rw-r--r--drivers/pps/clients/pps-ldisc.c6
-rw-r--r--drivers/pps/clients/pps_parport.c6
-rw-r--r--drivers/pps/generators/Kconfig42
-rw-r--r--drivers/pps/generators/Makefile6
-rw-r--r--drivers/pps/generators/pps_gen-dummy.c96
-rw-r--r--drivers/pps/generators/pps_gen.c346
-rw-r--r--drivers/pps/generators/pps_gen_parport.c240
-rw-r--r--drivers/pps/generators/pps_gen_tio.c272
-rw-r--r--drivers/pps/generators/sysfs.c75
-rw-r--r--drivers/pps/kapi.c18
-rw-r--r--drivers/pps/kc.c10
-rw-r--r--drivers/pps/pps.c157
15 files changed, 950 insertions, 352 deletions
diff --git a/drivers/pps/Makefile b/drivers/pps/Makefile
index ceaf65cc1f1d..0aea394d4e4d 100644
--- a/drivers/pps/Makefile
+++ b/drivers/pps/Makefile
@@ -6,6 +6,7 @@
pps_core-y := pps.o kapi.o sysfs.o
pps_core-$(CONFIG_NTP_PPS) += kc.o
obj-$(CONFIG_PPS) := pps_core.o
-obj-y += clients/ generators/
+obj-y += clients/
+obj-$(CONFIG_PPS_GENERATOR) += generators/
ccflags-$(CONFIG_PPS_DEBUG) := -DDEBUG
diff --git a/drivers/pps/clients/pps-gpio.c b/drivers/pps/clients/pps-gpio.c
index 634c3b2f8c26..935da68610c7 100644
--- a/drivers/pps/clients/pps-gpio.c
+++ b/drivers/pps/clients/pps-gpio.c
@@ -52,7 +52,9 @@ static irqreturn_t pps_gpio_irq_handler(int irq, void *data)
info = data;
- rising_edge = gpiod_get_value(info->gpio_pin);
+ /* Small trick to bypass the check on edge's direction when capture_clear is unset */
+ rising_edge = info->capture_clear ?
+ gpiod_get_value(info->gpio_pin) : !info->assert_falling_edge;
if ((rising_edge && !info->assert_falling_edge) ||
(!rising_edge && info->assert_falling_edge))
pps_event(info->pps, &ts, PPS_CAPTUREASSERT, data);
@@ -60,6 +62,8 @@ static irqreturn_t pps_gpio_irq_handler(int irq, void *data)
((rising_edge && info->assert_falling_edge) ||
(!rising_edge && !info->assert_falling_edge)))
pps_event(info->pps, &ts, PPS_CAPTURECLEAR, data);
+ else
+ dev_warn_ratelimited(&info->pps->dev, "IRQ did not trigger any PPS event\n");
return IRQ_HANDLED;
}
@@ -94,7 +98,7 @@ static void pps_gpio_echo_timer_callback(struct timer_list *t)
{
const struct pps_gpio_device_data *info;
- info = from_timer(info, t, echo_timer);
+ info = timer_container_of(info, t, echo_timer);
gpiod_set_value(info->echo_pin, 0);
}
@@ -206,16 +210,16 @@ static int pps_gpio_probe(struct platform_device *pdev)
}
/* register IRQ interrupt handler */
- ret = devm_request_irq(dev, data->irq, pps_gpio_irq_handler,
- get_irqf_trigger_flags(data), data->info.name, data);
+ ret = request_irq(data->irq, pps_gpio_irq_handler,
+ get_irqf_trigger_flags(data), data->info.name, data);
if (ret) {
pps_unregister_source(data->pps);
dev_err(dev, "failed to acquire IRQ %d\n", data->irq);
return -EINVAL;
}
- dev_info(data->pps->dev, "Registered IRQ %d as PPS source\n",
- data->irq);
+ dev_dbg(&data->pps->dev, "Registered IRQ %d as PPS source\n",
+ data->irq);
return 0;
}
@@ -224,8 +228,9 @@ static void pps_gpio_remove(struct platform_device *pdev)
{
struct pps_gpio_device_data *data = platform_get_drvdata(pdev);
+ free_irq(data->irq, data);
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 d33106bd7a29..121bd29d863d 100644
--- a/drivers/pps/clients/pps-ktimer.c
+++ b/drivers/pps/clients/pps-ktimer.c
@@ -56,9 +56,9 @@ static struct pps_source_info pps_ktimer_info = {
static void __exit pps_ktimer_exit(void)
{
- dev_info(pps->dev, "ktimer PPS source unregistered\n");
+ dev_dbg(&pps->dev, "ktimer PPS source unregistered\n");
- del_timer_sync(&ktimer);
+ timer_delete_sync(&ktimer);
pps_unregister_source(pps);
}
@@ -74,7 +74,7 @@ static int __init pps_ktimer_init(void)
timer_setup(&ktimer, pps_ktimer_event, 0);
mod_timer(&ktimer, jiffies + HZ);
- dev_info(pps->dev, "ktimer PPS source registered\n");
+ dev_dbg(&pps->dev, "ktimer PPS source registered\n");
return 0;
}
diff --git a/drivers/pps/clients/pps-ldisc.c b/drivers/pps/clients/pps-ldisc.c
index 443d6bae19d1..fa5660f3c4b7 100644
--- a/drivers/pps/clients/pps-ldisc.c
+++ b/drivers/pps/clients/pps-ldisc.c
@@ -32,7 +32,7 @@ static void pps_tty_dcd_change(struct tty_struct *tty, bool active)
pps_event(pps, &ts, active ? PPS_CAPTUREASSERT :
PPS_CAPTURECLEAR, NULL);
- dev_dbg(pps->dev, "PPS %s at %lu\n",
+ dev_dbg(&pps->dev, "PPS %s at %lu\n",
active ? "assert" : "clear", jiffies);
}
@@ -69,7 +69,7 @@ static int pps_tty_open(struct tty_struct *tty)
goto err_unregister;
}
- dev_info(pps->dev, "source \"%s\" added\n", info.path);
+ dev_dbg(&pps->dev, "source \"%s\" added\n", info.path);
return 0;
@@ -89,7 +89,7 @@ static void pps_tty_close(struct tty_struct *tty)
if (WARN_ON(!pps))
return;
- dev_info(pps->dev, "removed\n");
+ dev_info(&pps->dev, "removed\n");
pps_unregister_source(pps);
}
diff --git a/drivers/pps/clients/pps_parport.c b/drivers/pps/clients/pps_parport.c
index abaffb4e1c1c..f5af18bc2971 100644
--- a/drivers/pps/clients/pps_parport.c
+++ b/drivers/pps/clients/pps_parport.c
@@ -81,7 +81,7 @@ static void parport_irq(void *handle)
/* check the signal (no signal means the pulse is lost this time) */
if (!signal_is_set(port)) {
local_irq_restore(flags);
- dev_err(dev->pps->dev, "lost the signal\n");
+ dev_err(&dev->pps->dev, "lost the signal\n");
goto out_assert;
}
@@ -98,7 +98,7 @@ static void parport_irq(void *handle)
/* timeout */
dev->cw_err++;
if (dev->cw_err >= CLEAR_WAIT_MAX_ERRORS) {
- dev_err(dev->pps->dev, "disabled clear edge capture after %d"
+ dev_err(&dev->pps->dev, "disabled clear edge capture after %d"
" timeouts\n", dev->cw_err);
dev->cw = 0;
dev->cw_err = 0;
@@ -142,7 +142,7 @@ static void parport_attach(struct parport *port)
return;
}
- device = kzalloc(sizeof(struct pps_client_pp), GFP_KERNEL);
+ device = kzalloc_obj(struct pps_client_pp);
if (!device) {
pr_err("memory allocation failed, not attaching\n");
return;
diff --git a/drivers/pps/generators/Kconfig b/drivers/pps/generators/Kconfig
index d615e640fcad..4ef02b3f2576 100644
--- a/drivers/pps/generators/Kconfig
+++ b/drivers/pps/generators/Kconfig
@@ -3,12 +3,40 @@
# PPS generators configuration
#
-comment "PPS generators support"
+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).
-config PPS_GENERATOR_PARPORT
- tristate "Parallel port PPS signal generator"
- depends on PARPORT && BROKEN
+ 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 signal generator which
- utilizes STROBE pin of a parallel port to send PPS signals. It uses
- parport abstraction layer and hrtimers to precisely control the signal.
+ 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 2589fd0f2481..5d38774b4a56 100644
--- a/drivers/pps/generators/Makefile
+++ b/drivers/pps/generators/Makefile
@@ -3,6 +3,10 @@
# Makefile for PPS generators.
#
-obj-$(CONFIG_PPS_GENERATOR_PARPORT) += pps_gen_parport.o
+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 000000000000..547fa7fe29f4
--- /dev/null
+++ b/drivers/pps/generators/pps_gen-dummy.c
@@ -0,0 +1,96 @@
+// 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)
+{
+ struct system_time_snapshot snap;
+
+ ktime_get_snapshot(&snap);
+ *time = ktime_to_timespec64(snap.real);
+
+ 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 000000000000..5e207c75e340
--- /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_parport.c b/drivers/pps/generators/pps_gen_parport.c
deleted file mode 100644
index d46eed159495..000000000000
--- a/drivers/pps/generators/pps_gen_parport.c
+++ /dev/null
@@ -1,240 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- * pps_gen_parport.c -- kernel parallel port PPS signal generator
- *
- * Copyright (C) 2009 Alexander Gordeev <lasaine@lvk.cs.msu.su>
- */
-
-
-/*
- * TODO:
- * fix issues when realtime clock is adjusted in a leap
- */
-
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/time.h>
-#include <linux/hrtimer.h>
-#include <linux/parport.h>
-
-#define SIGNAL 0
-#define NO_SIGNAL PARPORT_CONTROL_STROBE
-
-/* module parameters */
-
-#define SEND_DELAY_MAX 100000
-
-static unsigned int send_delay = 30000;
-MODULE_PARM_DESC(delay,
- "Delay between setting and dropping the signal (ns)");
-module_param_named(delay, send_delay, uint, 0);
-
-
-#define SAFETY_INTERVAL 3000 /* set the hrtimer earlier for safety (ns) */
-
-/* internal per port structure */
-struct pps_generator_pp {
- struct pardevice *pardev; /* parport device */
- struct hrtimer timer;
- long port_write_time; /* calibrated port write time (ns) */
-};
-
-static struct pps_generator_pp device = {
- .pardev = NULL,
-};
-
-static int attached;
-
-/* calibrated time between a hrtimer event and the reaction */
-static long hrtimer_error = SAFETY_INTERVAL;
-
-/* the kernel hrtimer event */
-static enum hrtimer_restart hrtimer_event(struct hrtimer *timer)
-{
- struct timespec64 expire_time, ts1, ts2, ts3, dts;
- struct pps_generator_pp *dev;
- struct parport *port;
- long lim, delta;
- unsigned long flags;
-
- /* We have to disable interrupts here. The idea is to prevent
- * other interrupts on the same processor to introduce random
- * lags while polling the clock. ktime_get_real_ts64() takes <1us on
- * most machines while other interrupt handlers can take much
- * more potentially.
- *
- * NB: approx time with blocked interrupts =
- * send_delay + 3 * SAFETY_INTERVAL
- */
- local_irq_save(flags);
-
- /* first of all we get the time stamp... */
- ktime_get_real_ts64(&ts1);
- expire_time = ktime_to_timespec64(hrtimer_get_softexpires(timer));
- dev = container_of(timer, struct pps_generator_pp, timer);
- lim = NSEC_PER_SEC - send_delay - dev->port_write_time;
-
- /* check if we are late */
- if (expire_time.tv_sec != ts1.tv_sec || ts1.tv_nsec > lim) {
- local_irq_restore(flags);
- pr_err("we are late this time %lld.%09ld\n",
- (s64)ts1.tv_sec, ts1.tv_nsec);
- goto done;
- }
-
- /* busy loop until the time is right for an assert edge */
- do {
- ktime_get_real_ts64(&ts2);
- } while (expire_time.tv_sec == ts2.tv_sec && ts2.tv_nsec < lim);
-
- /* set the signal */
- port = dev->pardev->port;
- port->ops->write_control(port, SIGNAL);
-
- /* busy loop until the time is right for a clear edge */
- lim = NSEC_PER_SEC - dev->port_write_time;
- do {
- ktime_get_real_ts64(&ts2);
- } while (expire_time.tv_sec == ts2.tv_sec && ts2.tv_nsec < lim);
-
- /* unset the signal */
- port->ops->write_control(port, NO_SIGNAL);
-
- ktime_get_real_ts64(&ts3);
-
- local_irq_restore(flags);
-
- /* update calibrated port write time */
- dts = timespec64_sub(ts3, ts2);
- dev->port_write_time =
- (dev->port_write_time + timespec64_to_ns(&dts)) >> 1;
-
-done:
- /* update calibrated hrtimer error */
- dts = timespec64_sub(ts1, expire_time);
- delta = timespec64_to_ns(&dts);
- /* If the new error value is bigger then the old, use the new
- * value, if not then slowly move towards the new value. This
- * way it should be safe in bad conditions and efficient in
- * good conditions.
- */
- if (delta >= hrtimer_error)
- hrtimer_error = delta;
- else
- hrtimer_error = (3 * hrtimer_error + delta) >> 2;
-
- /* update the hrtimer expire time */
- hrtimer_set_expires(timer,
- ktime_set(expire_time.tv_sec + 1,
- NSEC_PER_SEC - (send_delay +
- dev->port_write_time + SAFETY_INTERVAL +
- 2 * hrtimer_error)));
-
- return HRTIMER_RESTART;
-}
-
-/* calibrate port write time */
-#define PORT_NTESTS_SHIFT 5
-static void calibrate_port(struct pps_generator_pp *dev)
-{
- struct parport *port = dev->pardev->port;
- int i;
- long acc = 0;
-
- for (i = 0; i < (1 << PORT_NTESTS_SHIFT); i++) {
- struct timespec64 a, b;
- unsigned long irq_flags;
-
- local_irq_save(irq_flags);
- ktime_get_real_ts64(&a);
- port->ops->write_control(port, NO_SIGNAL);
- ktime_get_real_ts64(&b);
- local_irq_restore(irq_flags);
-
- b = timespec64_sub(b, a);
- acc += timespec64_to_ns(&b);
- }
-
- dev->port_write_time = acc >> PORT_NTESTS_SHIFT;
- pr_info("port write takes %ldns\n", dev->port_write_time);
-}
-
-static inline ktime_t next_intr_time(struct pps_generator_pp *dev)
-{
- struct timespec64 ts;
-
- ktime_get_real_ts64(&ts);
-
- return ktime_set(ts.tv_sec +
- ((ts.tv_nsec > 990 * NSEC_PER_MSEC) ? 1 : 0),
- NSEC_PER_SEC - (send_delay +
- dev->port_write_time + 3 * SAFETY_INTERVAL));
-}
-
-static void parport_attach(struct parport *port)
-{
- struct pardev_cb pps_cb;
-
- if (send_delay > SEND_DELAY_MAX) {
- pr_err("delay value should be not greater then %d\n", SEND_DELAY_MAX);
- return;
- }
-
- if (attached) {
- /* we already have a port */
- return;
- }
-
- memset(&pps_cb, 0, sizeof(pps_cb));
- pps_cb.private = &device;
- pps_cb.flags = PARPORT_FLAG_EXCL;
- device.pardev = parport_register_dev_model(port, KBUILD_MODNAME,
- &pps_cb, 0);
- if (!device.pardev) {
- pr_err("couldn't register with %s\n", port->name);
- return;
- }
-
- if (parport_claim_or_block(device.pardev) < 0) {
- pr_err("couldn't claim %s\n", port->name);
- goto err_unregister_dev;
- }
-
- pr_info("attached to %s\n", port->name);
- attached = 1;
-
- calibrate_port(&device);
-
- hrtimer_init(&device.timer, CLOCK_REALTIME, HRTIMER_MODE_ABS);
- device.timer.function = hrtimer_event;
- hrtimer_start(&device.timer, next_intr_time(&device), HRTIMER_MODE_ABS);
-
- return;
-
-err_unregister_dev:
- parport_unregister_device(device.pardev);
-}
-
-static void parport_detach(struct parport *port)
-{
- if (port->cad != device.pardev)
- return; /* not our port */
-
- hrtimer_cancel(&device.timer);
- parport_release(device.pardev);
- parport_unregister_device(device.pardev);
-}
-
-static struct parport_driver pps_gen_parport_driver = {
- .name = KBUILD_MODNAME,
- .match_port = parport_attach,
- .detach = parport_detach,
-};
-module_parport_driver(pps_gen_parport_driver);
-
-MODULE_AUTHOR("Alexander Gordeev <lasaine@lvk.cs.msu.su>");
-MODULE_DESCRIPTION("parallel port PPS signal generator");
-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 000000000000..de00a85bfafa
--- /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
new file mode 100644
index 000000000000..6d6bc0006fea
--- /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,
+};
diff --git a/drivers/pps/kapi.c b/drivers/pps/kapi.c
index d9d566f70ed1..1bf0335a1b41 100644
--- a/drivers/pps/kapi.c
+++ b/drivers/pps/kapi.c
@@ -41,7 +41,7 @@ static void pps_add_offset(struct pps_ktime *ts, struct pps_ktime *offset)
static void pps_echo_client_default(struct pps_device *pps, int event,
void *data)
{
- dev_info(pps->dev, "echo %s %s\n",
+ dev_info(&pps->dev, "echo %s %s\n",
event & PPS_CAPTUREASSERT ? "assert" : "",
event & PPS_CAPTURECLEAR ? "clear" : "");
}
@@ -83,7 +83,7 @@ struct pps_device *pps_register_source(struct pps_source_info *info,
}
/* Allocate memory for the new PPS source struct */
- pps = kzalloc(sizeof(struct pps_device), GFP_KERNEL);
+ pps = kzalloc_obj(struct pps_device);
if (pps == NULL) {
err = -ENOMEM;
goto pps_register_source_exit;
@@ -109,16 +109,13 @@ struct pps_device *pps_register_source(struct pps_source_info *info,
if (err < 0) {
pr_err("%s: unable to create char device\n",
info->name);
- goto kfree_pps;
+ goto pps_register_source_exit;
}
- dev_info(pps->dev, "new PPS source %s\n", info->name);
+ dev_dbg(&pps->dev, "new PPS source %s\n", info->name);
return pps;
-kfree_pps:
- kfree(pps);
-
pps_register_source_exit:
pr_err("%s: unable to register source\n", info->name);
@@ -166,8 +163,7 @@ void pps_event(struct pps_device *pps, struct pps_event_time *ts, int event,
/* check event type */
BUG_ON((event & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR)) == 0);
- dev_dbg(pps->dev, "PPS event at %lld.%09ld\n",
- (s64)ts->ts_real.tv_sec, ts->ts_real.tv_nsec);
+ dev_dbg(&pps->dev, "PPS event at %ptSp\n", &ts->ts_real);
timespec_to_pps_ktime(&ts_real, ts->ts_real);
@@ -188,7 +184,7 @@ void pps_event(struct pps_device *pps, struct pps_event_time *ts, int event,
/* Save the time stamp */
pps->assert_tu = ts_real;
pps->assert_sequence++;
- dev_dbg(pps->dev, "capture assert seq #%u\n",
+ dev_dbg(&pps->dev, "capture assert seq #%u\n",
pps->assert_sequence);
captured = ~0;
@@ -202,7 +198,7 @@ void pps_event(struct pps_device *pps, struct pps_event_time *ts, int event,
/* Save the time stamp */
pps->clear_tu = ts_real;
pps->clear_sequence++;
- dev_dbg(pps->dev, "capture clear seq #%u\n",
+ dev_dbg(&pps->dev, "capture clear seq #%u\n",
pps->clear_sequence);
captured = ~0;
diff --git a/drivers/pps/kc.c b/drivers/pps/kc.c
index 50dc59af45be..fbd23295afd7 100644
--- a/drivers/pps/kc.c
+++ b/drivers/pps/kc.c
@@ -43,11 +43,11 @@ int pps_kc_bind(struct pps_device *pps, struct pps_bind_args *bind_args)
pps_kc_hardpps_mode = 0;
pps_kc_hardpps_dev = NULL;
spin_unlock_irq(&pps_kc_hardpps_lock);
- dev_info(pps->dev, "unbound kernel"
+ dev_info(&pps->dev, "unbound kernel"
" consumer\n");
} else {
spin_unlock_irq(&pps_kc_hardpps_lock);
- dev_err(pps->dev, "selected kernel consumer"
+ dev_err(&pps->dev, "selected kernel consumer"
" is not bound\n");
return -EINVAL;
}
@@ -57,11 +57,11 @@ int pps_kc_bind(struct pps_device *pps, struct pps_bind_args *bind_args)
pps_kc_hardpps_mode = bind_args->edge;
pps_kc_hardpps_dev = pps;
spin_unlock_irq(&pps_kc_hardpps_lock);
- dev_info(pps->dev, "bound kernel consumer: "
+ dev_info(&pps->dev, "bound kernel consumer: "
"edge=0x%x\n", bind_args->edge);
} else {
spin_unlock_irq(&pps_kc_hardpps_lock);
- dev_err(pps->dev, "another kernel consumer"
+ dev_err(&pps->dev, "another kernel consumer"
" is already bound\n");
return -EINVAL;
}
@@ -83,7 +83,7 @@ void pps_kc_remove(struct pps_device *pps)
pps_kc_hardpps_mode = 0;
pps_kc_hardpps_dev = NULL;
spin_unlock_irq(&pps_kc_hardpps_lock);
- dev_info(pps->dev, "unbound kernel consumer"
+ dev_info(&pps->dev, "unbound kernel consumer"
" on device removal\n");
} else
spin_unlock_irq(&pps_kc_hardpps_lock);
diff --git a/drivers/pps/pps.c b/drivers/pps/pps.c
index 25d47907db17..de1122bb69ea 100644
--- a/drivers/pps/pps.c
+++ b/drivers/pps/pps.c
@@ -25,8 +25,11 @@
* Local variables
*/
-static dev_t pps_devt;
-static struct class *pps_class;
+static int pps_major;
+static const struct class pps_class = {
+ .name = "pps",
+ .dev_groups = pps_groups
+};
static DEFINE_MUTEX(pps_idr_lock);
static DEFINE_IDR(pps_idr);
@@ -41,6 +44,9 @@ static __poll_t pps_cdev_poll(struct file *file, poll_table *wait)
poll_wait(file, &pps->queue, wait);
+ if (pps->last_fetched_ev == pps->last_ev)
+ return 0;
+
return EPOLLIN | EPOLLRDNORM;
}
@@ -62,7 +68,7 @@ static int pps_cdev_pps_fetch(struct pps_device *pps, struct pps_fdata *fdata)
else {
unsigned long ticks;
- dev_dbg(pps->dev, "timeout %lld.%09d\n",
+ dev_dbg(&pps->dev, "timeout %lld.%09d\n",
(long long) fdata->timeout.sec,
fdata->timeout.nsec);
ticks = fdata->timeout.sec * HZ;
@@ -80,7 +86,7 @@ static int pps_cdev_pps_fetch(struct pps_device *pps, struct pps_fdata *fdata)
/* Check for pending signals */
if (err == -ERESTARTSYS) {
- dev_dbg(pps->dev, "pending signal caught\n");
+ dev_dbg(&pps->dev, "pending signal caught\n");
return -EINTR;
}
@@ -98,7 +104,7 @@ static long pps_cdev_ioctl(struct file *file,
switch (cmd) {
case PPS_GETPARAMS:
- dev_dbg(pps->dev, "PPS_GETPARAMS\n");
+ dev_dbg(&pps->dev, "PPS_GETPARAMS\n");
spin_lock_irq(&pps->lock);
@@ -114,7 +120,7 @@ static long pps_cdev_ioctl(struct file *file,
break;
case PPS_SETPARAMS:
- dev_dbg(pps->dev, "PPS_SETPARAMS\n");
+ dev_dbg(&pps->dev, "PPS_SETPARAMS\n");
/* Check the capabilities */
if (!capable(CAP_SYS_TIME))
@@ -124,14 +130,14 @@ static long pps_cdev_ioctl(struct file *file,
if (err)
return -EFAULT;
if (!(params.mode & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR))) {
- dev_dbg(pps->dev, "capture mode unspecified (%x)\n",
+ dev_dbg(&pps->dev, "capture mode unspecified (%x)\n",
params.mode);
return -EINVAL;
}
/* Check for supported capabilities */
if ((params.mode & ~pps->info.mode) != 0) {
- dev_dbg(pps->dev, "unsupported capabilities (%x)\n",
+ dev_dbg(&pps->dev, "unsupported capabilities (%x)\n",
params.mode);
return -EINVAL;
}
@@ -144,7 +150,7 @@ static long pps_cdev_ioctl(struct file *file,
/* Restore the read only parameters */
if ((params.mode & (PPS_TSFMT_TSPEC | PPS_TSFMT_NTPFP)) == 0) {
/* section 3.3 of RFC 2783 interpreted */
- dev_dbg(pps->dev, "time format unspecified (%x)\n",
+ dev_dbg(&pps->dev, "time format unspecified (%x)\n",
params.mode);
pps->params.mode |= PPS_TSFMT_TSPEC;
}
@@ -165,7 +171,7 @@ static long pps_cdev_ioctl(struct file *file,
break;
case PPS_GETCAP:
- dev_dbg(pps->dev, "PPS_GETCAP\n");
+ dev_dbg(&pps->dev, "PPS_GETCAP\n");
err = put_user(pps->info.mode, iuarg);
if (err)
@@ -176,7 +182,7 @@ static long pps_cdev_ioctl(struct file *file,
case PPS_FETCH: {
struct pps_fdata fdata;
- dev_dbg(pps->dev, "PPS_FETCH\n");
+ dev_dbg(&pps->dev, "PPS_FETCH\n");
err = copy_from_user(&fdata, uarg, sizeof(struct pps_fdata));
if (err)
@@ -186,9 +192,11 @@ static long pps_cdev_ioctl(struct file *file,
if (err)
return err;
- /* Return the fetched timestamp */
+ /* Return the fetched timestamp and save last fetched event */
spin_lock_irq(&pps->lock);
+ pps->last_fetched_ev = pps->last_ev;
+
fdata.info.assert_sequence = pps->assert_sequence;
fdata.info.clear_sequence = pps->clear_sequence;
fdata.info.assert_tu = pps->assert_tu;
@@ -206,7 +214,7 @@ static long pps_cdev_ioctl(struct file *file,
case PPS_KC_BIND: {
struct pps_bind_args bind_args;
- dev_dbg(pps->dev, "PPS_KC_BIND\n");
+ dev_dbg(&pps->dev, "PPS_KC_BIND\n");
/* Check the capabilities */
if (!capable(CAP_SYS_TIME))
@@ -218,7 +226,7 @@ static long pps_cdev_ioctl(struct file *file,
/* Check for supported capabilities */
if ((bind_args.edge & ~pps->info.mode) != 0) {
- dev_err(pps->dev, "unsupported capabilities (%x)\n",
+ dev_err(&pps->dev, "unsupported capabilities (%x)\n",
bind_args.edge);
return -EINVAL;
}
@@ -227,7 +235,7 @@ static long pps_cdev_ioctl(struct file *file,
if (bind_args.tsformat != PPS_TSFMT_TSPEC ||
(bind_args.edge & ~PPS_CAPTUREBOTH) != 0 ||
bind_args.consumer != PPS_KC_HARDPPS) {
- dev_err(pps->dev, "invalid kernel consumer bind"
+ dev_err(&pps->dev, "invalid kernel consumer bind"
" parameters (%x)\n", bind_args.edge);
return -EINVAL;
}
@@ -259,7 +267,7 @@ static long pps_cdev_compat_ioctl(struct file *file,
struct pps_fdata fdata;
int err;
- dev_dbg(pps->dev, "PPS_FETCH\n");
+ dev_dbg(&pps->dev, "PPS_FETCH\n");
err = copy_from_user(&compat, uarg, sizeof(struct pps_fdata_compat));
if (err)
@@ -272,9 +280,11 @@ static long pps_cdev_compat_ioctl(struct file *file,
if (err)
return err;
- /* Return the fetched timestamp */
+ /* Return the fetched timestamp and save last fetched event */
spin_lock_irq(&pps->lock);
+ pps->last_fetched_ev = pps->last_ev;
+
compat.info.assert_sequence = pps->assert_sequence;
compat.info.clear_sequence = pps->clear_sequence;
compat.info.current_mode = pps->current_mode;
@@ -296,20 +306,36 @@ static long pps_cdev_compat_ioctl(struct file *file,
#define pps_cdev_compat_ioctl NULL
#endif
+static struct pps_device *pps_idr_get(unsigned long id)
+{
+ struct pps_device *pps;
+
+ mutex_lock(&pps_idr_lock);
+ pps = idr_find(&pps_idr, id);
+ if (pps)
+ get_device(&pps->dev);
+
+ mutex_unlock(&pps_idr_lock);
+ return pps;
+}
+
static int pps_cdev_open(struct inode *inode, struct file *file)
{
- struct pps_device *pps = container_of(inode->i_cdev,
- struct pps_device, cdev);
+ struct pps_device *pps = pps_idr_get(iminor(inode));
+
+ if (!pps)
+ return -ENODEV;
+
file->private_data = pps;
- kobject_get(&pps->dev->kobj);
return 0;
}
static int pps_cdev_release(struct inode *inode, struct file *file)
{
- struct pps_device *pps = container_of(inode->i_cdev,
- struct pps_device, cdev);
- kobject_put(&pps->dev->kobj);
+ struct pps_device *pps = file->private_data;
+
+ WARN_ON(pps->id != iminor(inode));
+ put_device(&pps->dev);
return 0;
}
@@ -331,22 +357,13 @@ static void pps_device_destruct(struct device *dev)
{
struct pps_device *pps = dev_get_drvdata(dev);
- cdev_del(&pps->cdev);
-
- /* Now we can release the ID for re-use */
pr_debug("deallocating pps%d\n", pps->id);
- mutex_lock(&pps_idr_lock);
- idr_remove(&pps_idr, pps->id);
- mutex_unlock(&pps_idr_lock);
-
- kfree(dev);
kfree(pps);
}
int pps_register_cdev(struct pps_device *pps)
{
int err;
- dev_t devt;
mutex_lock(&pps_idr_lock);
/*
@@ -360,43 +377,31 @@ int pps_register_cdev(struct pps_device *pps)
pps->info.name);
err = -EBUSY;
}
+ kfree(pps);
goto out_unlock;
}
pps->id = err;
- mutex_unlock(&pps_idr_lock);
-
- devt = MKDEV(MAJOR(pps_devt), pps->id);
- cdev_init(&pps->cdev, &pps_cdev_fops);
- pps->cdev.owner = pps->info.owner;
-
- err = cdev_add(&pps->cdev, devt, 1);
- if (err) {
- pr_err("%s: failed to add char device %d:%d\n",
- pps->info.name, MAJOR(pps_devt), pps->id);
+ pps->dev.class = &pps_class;
+ pps->dev.parent = pps->info.dev;
+ pps->dev.devt = MKDEV(pps_major, pps->id);
+ dev_set_drvdata(&pps->dev, pps);
+ dev_set_name(&pps->dev, "pps%d", pps->id);
+ pps->dev.release = pps_device_destruct;
+ err = device_register(&pps->dev);
+ if (err)
goto free_idr;
- }
- pps->dev = device_create(pps_class, pps->info.dev, devt, pps,
- "pps%d", pps->id);
- if (IS_ERR(pps->dev)) {
- err = PTR_ERR(pps->dev);
- goto del_cdev;
- }
-
- /* Override the release function with our own */
- pps->dev->release = pps_device_destruct;
- pr_debug("source %s got cdev (%d:%d)\n", pps->info.name,
- MAJOR(pps_devt), pps->id);
+ pr_debug("source %s got cdev (%d:%d)\n", pps->info.name, pps_major,
+ pps->id);
+ get_device(&pps->dev);
+ mutex_unlock(&pps_idr_lock);
return 0;
-del_cdev:
- cdev_del(&pps->cdev);
-
free_idr:
- mutex_lock(&pps_idr_lock);
idr_remove(&pps_idr, pps->id);
+ put_device(&pps->dev);
out_unlock:
mutex_unlock(&pps_idr_lock);
return err;
@@ -406,7 +411,13 @@ void pps_unregister_cdev(struct pps_device *pps)
{
pr_debug("unregistering pps%d\n", pps->id);
pps->lookup_cookie = NULL;
- device_destroy(pps_class, pps->dev->devt);
+ device_destroy(&pps_class, pps->dev.devt);
+
+ /* Now we can release the ID for re-use */
+ mutex_lock(&pps_idr_lock);
+ idr_remove(&pps_idr, pps->id);
+ put_device(&pps->dev);
+ mutex_unlock(&pps_idr_lock);
}
/*
@@ -426,6 +437,11 @@ void pps_unregister_cdev(struct pps_device *pps)
* so that it will not be used again, even if the pps device cannot
* be removed from the idr due to pending references holding the minor
* number in use.
+ *
+ * Since pps_idr holds a reference to the device, the returned
+ * pps_device is guaranteed to be valid until pps_unregister_cdev() is
+ * called on it. But after calling pps_unregister_cdev(), it may be
+ * freed at any time.
*/
struct pps_device *pps_lookup_dev(void const *cookie)
{
@@ -447,23 +463,23 @@ EXPORT_SYMBOL(pps_lookup_dev);
static void __exit pps_exit(void)
{
- class_destroy(pps_class);
- unregister_chrdev_region(pps_devt, PPS_MAX_SOURCES);
+ class_unregister(&pps_class);
+ __unregister_chrdev(pps_major, 0, PPS_MAX_SOURCES, "pps");
}
static int __init pps_init(void)
{
int err;
- pps_class = class_create("pps");
- if (IS_ERR(pps_class)) {
- pr_err("failed to allocate class\n");
- return PTR_ERR(pps_class);
+ err = class_register(&pps_class);
+ if (err) {
+ pr_err("failed to register class\n");
+ return err;
}
- pps_class->dev_groups = pps_groups;
- err = alloc_chrdev_region(&pps_devt, 0, PPS_MAX_SOURCES, "pps");
- if (err < 0) {
+ pps_major = __register_chrdev(0, 0, PPS_MAX_SOURCES, "pps",
+ &pps_cdev_fops);
+ if (pps_major < 0) {
pr_err("failed to allocate char device region\n");
goto remove_class;
}
@@ -475,9 +491,8 @@ static int __init pps_init(void)
return 0;
remove_class:
- class_destroy(pps_class);
-
- return err;
+ class_unregister(&pps_class);
+ return pps_major;
}
subsys_initcall(pps_init);