xref: /linux/drivers/pps/generators/pps_gen.c (revision 4f9786035f9e519db41375818e1d0b5f20da2f10)
186b525beSRodolfo Giometti // SPDX-License-Identifier: GPL-2.0-or-later
286b525beSRodolfo Giometti /*
386b525beSRodolfo Giometti  * PPS generators core file
486b525beSRodolfo Giometti  *
586b525beSRodolfo Giometti  * Copyright (C) 2024 Rodolfo Giometti <giometti@enneenne.com>
686b525beSRodolfo Giometti  */
786b525beSRodolfo Giometti 
886b525beSRodolfo Giometti #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
986b525beSRodolfo Giometti 
1086b525beSRodolfo Giometti #include <linux/kernel.h>
1186b525beSRodolfo Giometti #include <linux/module.h>
1286b525beSRodolfo Giometti #include <linux/init.h>
1386b525beSRodolfo Giometti #include <linux/sched.h>
1486b525beSRodolfo Giometti #include <linux/time.h>
1586b525beSRodolfo Giometti #include <linux/timex.h>
1686b525beSRodolfo Giometti #include <linux/uaccess.h>
1786b525beSRodolfo Giometti #include <linux/idr.h>
1886b525beSRodolfo Giometti #include <linux/cdev.h>
1986b525beSRodolfo Giometti #include <linux/poll.h>
2086b525beSRodolfo Giometti #include <linux/fs.h>
2186b525beSRodolfo Giometti #include <linux/pps_gen_kernel.h>
2286b525beSRodolfo Giometti #include <linux/slab.h>
2386b525beSRodolfo Giometti 
2486b525beSRodolfo Giometti /*
2586b525beSRodolfo Giometti  * Local variables
2686b525beSRodolfo Giometti  */
2786b525beSRodolfo Giometti 
2886b525beSRodolfo Giometti static dev_t pps_gen_devt;
2986b525beSRodolfo Giometti static struct class *pps_gen_class;
3086b525beSRodolfo Giometti 
3186b525beSRodolfo Giometti static DEFINE_IDA(pps_gen_ida);
3286b525beSRodolfo Giometti 
3386b525beSRodolfo Giometti /*
3486b525beSRodolfo Giometti  * Char device methods
3586b525beSRodolfo Giometti  */
3686b525beSRodolfo Giometti 
3786b525beSRodolfo Giometti static __poll_t pps_gen_cdev_poll(struct file *file, poll_table *wait)
3886b525beSRodolfo Giometti {
3986b525beSRodolfo Giometti 	struct pps_gen_device *pps_gen = file->private_data;
4086b525beSRodolfo Giometti 
4186b525beSRodolfo Giometti 	poll_wait(file, &pps_gen->queue, wait);
4286b525beSRodolfo Giometti 	return EPOLLIN | EPOLLRDNORM;
4386b525beSRodolfo Giometti }
4486b525beSRodolfo Giometti 
4586b525beSRodolfo Giometti static int pps_gen_cdev_fasync(int fd, struct file *file, int on)
4686b525beSRodolfo Giometti {
4786b525beSRodolfo Giometti 	struct pps_gen_device *pps_gen = file->private_data;
4886b525beSRodolfo Giometti 
4986b525beSRodolfo Giometti 	return fasync_helper(fd, file, on, &pps_gen->async_queue);
5086b525beSRodolfo Giometti }
5186b525beSRodolfo Giometti 
5286b525beSRodolfo Giometti static long pps_gen_cdev_ioctl(struct file *file,
5386b525beSRodolfo Giometti 		unsigned int cmd, unsigned long arg)
5486b525beSRodolfo Giometti {
5586b525beSRodolfo Giometti 	struct pps_gen_device *pps_gen = file->private_data;
5686b525beSRodolfo Giometti 	void __user *uarg = (void __user *) arg;
5786b525beSRodolfo Giometti 	unsigned int __user *uiuarg = (unsigned int __user *) arg;
5886b525beSRodolfo Giometti 	unsigned int status;
5986b525beSRodolfo Giometti 	int ret;
6086b525beSRodolfo Giometti 
6186b525beSRodolfo Giometti 	switch (cmd) {
6286b525beSRodolfo Giometti 	case PPS_GEN_SETENABLE:
6386b525beSRodolfo Giometti 		dev_dbg(pps_gen->dev, "PPS_GEN_SETENABLE\n");
6486b525beSRodolfo Giometti 
6586b525beSRodolfo Giometti 		ret = get_user(status, uiuarg);
6686b525beSRodolfo Giometti 		if (ret)
6786b525beSRodolfo Giometti 			return -EFAULT;
6886b525beSRodolfo Giometti 
69*ac9c5170SSubramanian Mohan 		ret = pps_gen->info->enable(pps_gen, status);
7086b525beSRodolfo Giometti 		if (ret)
7186b525beSRodolfo Giometti 			return ret;
7286b525beSRodolfo Giometti 		pps_gen->enabled = status;
7386b525beSRodolfo Giometti 
7486b525beSRodolfo Giometti 		break;
7586b525beSRodolfo Giometti 
7686b525beSRodolfo Giometti 	case PPS_GEN_USESYSTEMCLOCK:
7786b525beSRodolfo Giometti 		dev_dbg(pps_gen->dev, "PPS_GEN_USESYSTEMCLOCK\n");
7886b525beSRodolfo Giometti 
79*ac9c5170SSubramanian Mohan 		ret = put_user(pps_gen->info->use_system_clock, uiuarg);
8086b525beSRodolfo Giometti 		if (ret)
8186b525beSRodolfo Giometti 			return -EFAULT;
8286b525beSRodolfo Giometti 
8386b525beSRodolfo Giometti 		break;
8486b525beSRodolfo Giometti 
8586b525beSRodolfo Giometti 	case PPS_GEN_FETCHEVENT: {
8686b525beSRodolfo Giometti 		struct pps_gen_event info;
8786b525beSRodolfo Giometti 		unsigned int ev = pps_gen->last_ev;
8886b525beSRodolfo Giometti 
8986b525beSRodolfo Giometti 		dev_dbg(pps_gen->dev, "PPS_GEN_FETCHEVENT\n");
9086b525beSRodolfo Giometti 
9186b525beSRodolfo Giometti 		ret = wait_event_interruptible(pps_gen->queue,
9286b525beSRodolfo Giometti 				ev != pps_gen->last_ev);
9386b525beSRodolfo Giometti 		if (ret == -ERESTARTSYS) {
9486b525beSRodolfo Giometti 			dev_dbg(pps_gen->dev, "pending signal caught\n");
9586b525beSRodolfo Giometti 			return -EINTR;
9686b525beSRodolfo Giometti 		}
9786b525beSRodolfo Giometti 
9886b525beSRodolfo Giometti 		spin_lock_irq(&pps_gen->lock);
9986b525beSRodolfo Giometti 		info.sequence = pps_gen->sequence;
10086b525beSRodolfo Giometti 		info.event = pps_gen->event;
10186b525beSRodolfo Giometti 		spin_unlock_irq(&pps_gen->lock);
10286b525beSRodolfo Giometti 
10386b525beSRodolfo Giometti 		ret = copy_to_user(uarg, &info, sizeof(struct pps_gen_event));
10486b525beSRodolfo Giometti 		if (ret)
10586b525beSRodolfo Giometti 			return -EFAULT;
10686b525beSRodolfo Giometti 
10786b525beSRodolfo Giometti 		break;
10886b525beSRodolfo Giometti 	}
10986b525beSRodolfo Giometti 	default:
11086b525beSRodolfo Giometti 		return -ENOTTY;
11186b525beSRodolfo Giometti 	}
11286b525beSRodolfo Giometti 
11386b525beSRodolfo Giometti 	return 0;
11486b525beSRodolfo Giometti }
11586b525beSRodolfo Giometti 
11686b525beSRodolfo Giometti static int pps_gen_cdev_open(struct inode *inode, struct file *file)
11786b525beSRodolfo Giometti {
11886b525beSRodolfo Giometti 	struct pps_gen_device *pps_gen = container_of(inode->i_cdev,
11986b525beSRodolfo Giometti 				struct pps_gen_device, cdev);
12086b525beSRodolfo Giometti 
12186b525beSRodolfo Giometti 	get_device(pps_gen->dev);
12286b525beSRodolfo Giometti 	file->private_data = pps_gen;
12386b525beSRodolfo Giometti 	return 0;
12486b525beSRodolfo Giometti }
12586b525beSRodolfo Giometti 
12686b525beSRodolfo Giometti static int pps_gen_cdev_release(struct inode *inode, struct file *file)
12786b525beSRodolfo Giometti {
12886b525beSRodolfo Giometti 	struct pps_gen_device *pps_gen = file->private_data;
12986b525beSRodolfo Giometti 
13086b525beSRodolfo Giometti 	put_device(pps_gen->dev);
13186b525beSRodolfo Giometti 	return 0;
13286b525beSRodolfo Giometti }
13386b525beSRodolfo Giometti 
13486b525beSRodolfo Giometti /*
13586b525beSRodolfo Giometti  * Char device stuff
13686b525beSRodolfo Giometti  */
13786b525beSRodolfo Giometti 
13886b525beSRodolfo Giometti static const struct file_operations pps_gen_cdev_fops = {
13986b525beSRodolfo Giometti 	.owner		= THIS_MODULE,
14086b525beSRodolfo Giometti 	.poll	   = pps_gen_cdev_poll,
14186b525beSRodolfo Giometti 	.fasync	 = pps_gen_cdev_fasync,
14286b525beSRodolfo Giometti 	.unlocked_ioctl	= pps_gen_cdev_ioctl,
14386b525beSRodolfo Giometti 	.open		= pps_gen_cdev_open,
14486b525beSRodolfo Giometti 	.release	= pps_gen_cdev_release,
14586b525beSRodolfo Giometti };
14686b525beSRodolfo Giometti 
14786b525beSRodolfo Giometti static void pps_gen_device_destruct(struct device *dev)
14886b525beSRodolfo Giometti {
14986b525beSRodolfo Giometti 	struct pps_gen_device *pps_gen = dev_get_drvdata(dev);
15086b525beSRodolfo Giometti 
15186b525beSRodolfo Giometti 	cdev_del(&pps_gen->cdev);
15286b525beSRodolfo Giometti 
15386b525beSRodolfo Giometti 	pr_debug("deallocating pps-gen%d\n", pps_gen->id);
15486b525beSRodolfo Giometti 	ida_free(&pps_gen_ida, pps_gen->id);
15586b525beSRodolfo Giometti 
15686b525beSRodolfo Giometti 	kfree(dev);
15786b525beSRodolfo Giometti 	kfree(pps_gen);
15886b525beSRodolfo Giometti }
15986b525beSRodolfo Giometti 
16086b525beSRodolfo Giometti static int pps_gen_register_cdev(struct pps_gen_device *pps_gen)
16186b525beSRodolfo Giometti {
16286b525beSRodolfo Giometti 	int err;
16386b525beSRodolfo Giometti 	dev_t devt;
16486b525beSRodolfo Giometti 
16586b525beSRodolfo Giometti 	err = ida_alloc_max(&pps_gen_ida, PPS_GEN_MAX_SOURCES - 1, GFP_KERNEL);
16686b525beSRodolfo Giometti 	if (err < 0) {
16786b525beSRodolfo Giometti 		if (err == -ENOSPC) {
16886b525beSRodolfo Giometti 			pr_err("too many PPS sources in the system\n");
16986b525beSRodolfo Giometti 			err = -EBUSY;
17086b525beSRodolfo Giometti 		}
17186b525beSRodolfo Giometti 		return err;
17286b525beSRodolfo Giometti 	}
17386b525beSRodolfo Giometti 	pps_gen->id = err;
17486b525beSRodolfo Giometti 
17586b525beSRodolfo Giometti 	devt = MKDEV(MAJOR(pps_gen_devt), pps_gen->id);
17686b525beSRodolfo Giometti 
17786b525beSRodolfo Giometti 	cdev_init(&pps_gen->cdev, &pps_gen_cdev_fops);
178*ac9c5170SSubramanian Mohan 	pps_gen->cdev.owner = pps_gen->info->owner;
17986b525beSRodolfo Giometti 
18086b525beSRodolfo Giometti 	err = cdev_add(&pps_gen->cdev, devt, 1);
18186b525beSRodolfo Giometti 	if (err) {
18286b525beSRodolfo Giometti 		pr_err("failed to add char device %d:%d\n",
18386b525beSRodolfo Giometti 				MAJOR(pps_gen_devt), pps_gen->id);
18486b525beSRodolfo Giometti 		goto free_ida;
18586b525beSRodolfo Giometti 	}
186*ac9c5170SSubramanian Mohan 	pps_gen->dev = device_create(pps_gen_class, pps_gen->info->parent, devt,
18786b525beSRodolfo Giometti 				     pps_gen, "pps-gen%d", pps_gen->id);
18886b525beSRodolfo Giometti 	if (IS_ERR(pps_gen->dev)) {
18986b525beSRodolfo Giometti 		err = PTR_ERR(pps_gen->dev);
19086b525beSRodolfo Giometti 		goto del_cdev;
19186b525beSRodolfo Giometti 	}
19286b525beSRodolfo Giometti 	pps_gen->dev->release = pps_gen_device_destruct;
19386b525beSRodolfo Giometti 	dev_set_drvdata(pps_gen->dev, pps_gen);
19486b525beSRodolfo Giometti 
19586b525beSRodolfo Giometti 	pr_debug("generator got cdev (%d:%d)\n",
19686b525beSRodolfo Giometti 			MAJOR(pps_gen_devt), pps_gen->id);
19786b525beSRodolfo Giometti 
19886b525beSRodolfo Giometti 	return 0;
19986b525beSRodolfo Giometti 
20086b525beSRodolfo Giometti del_cdev:
20186b525beSRodolfo Giometti 	cdev_del(&pps_gen->cdev);
20286b525beSRodolfo Giometti free_ida:
20386b525beSRodolfo Giometti 	ida_free(&pps_gen_ida, pps_gen->id);
20486b525beSRodolfo Giometti 	return err;
20586b525beSRodolfo Giometti }
20686b525beSRodolfo Giometti 
20786b525beSRodolfo Giometti static void pps_gen_unregister_cdev(struct pps_gen_device *pps_gen)
20886b525beSRodolfo Giometti {
20986b525beSRodolfo Giometti 	pr_debug("unregistering pps-gen%d\n", pps_gen->id);
21086b525beSRodolfo Giometti 	device_destroy(pps_gen_class, pps_gen->dev->devt);
21186b525beSRodolfo Giometti }
21286b525beSRodolfo Giometti 
21386b525beSRodolfo Giometti /*
21486b525beSRodolfo Giometti  * Exported functions
21586b525beSRodolfo Giometti  */
21686b525beSRodolfo Giometti 
21786b525beSRodolfo Giometti /**
21886b525beSRodolfo Giometti  * pps_gen_register_source() - add a PPS generator in the system
21986b525beSRodolfo Giometti  * @info: the PPS generator info struct
22086b525beSRodolfo Giometti  *
22186b525beSRodolfo Giometti  * This function is used to register a new PPS generator in the system.
22286b525beSRodolfo Giometti  * When it returns successfully the new generator is up and running, and
22386b525beSRodolfo Giometti  * it can be managed by the userspace.
22486b525beSRodolfo Giometti  *
22586b525beSRodolfo Giometti  * Return: the PPS generator device in case of success, and ERR_PTR(errno)
22686b525beSRodolfo Giometti  *	 otherwise.
22786b525beSRodolfo Giometti  */
228*ac9c5170SSubramanian Mohan struct pps_gen_device *pps_gen_register_source(const struct pps_gen_source_info *info)
22986b525beSRodolfo Giometti {
23086b525beSRodolfo Giometti 	struct pps_gen_device *pps_gen;
23186b525beSRodolfo Giometti 	int err;
23286b525beSRodolfo Giometti 
23386b525beSRodolfo Giometti 	pps_gen = kzalloc(sizeof(struct pps_gen_device), GFP_KERNEL);
23486b525beSRodolfo Giometti 	if (pps_gen == NULL) {
23586b525beSRodolfo Giometti 		err = -ENOMEM;
23686b525beSRodolfo Giometti 		goto pps_gen_register_source_exit;
23786b525beSRodolfo Giometti 	}
238*ac9c5170SSubramanian Mohan 	pps_gen->info = info;
23986b525beSRodolfo Giometti 	pps_gen->enabled = false;
24086b525beSRodolfo Giometti 
24186b525beSRodolfo Giometti 	init_waitqueue_head(&pps_gen->queue);
24286b525beSRodolfo Giometti 	spin_lock_init(&pps_gen->lock);
24386b525beSRodolfo Giometti 
24486b525beSRodolfo Giometti 	/* Create the char device */
24586b525beSRodolfo Giometti 	err = pps_gen_register_cdev(pps_gen);
24686b525beSRodolfo Giometti 	if (err < 0) {
24786b525beSRodolfo Giometti 		pr_err(" unable to create char device\n");
24886b525beSRodolfo Giometti 		goto kfree_pps_gen;
24986b525beSRodolfo Giometti 	}
25086b525beSRodolfo Giometti 
25186b525beSRodolfo Giometti 	return pps_gen;
25286b525beSRodolfo Giometti 
25386b525beSRodolfo Giometti kfree_pps_gen:
25486b525beSRodolfo Giometti 	kfree(pps_gen);
25586b525beSRodolfo Giometti 
25686b525beSRodolfo Giometti pps_gen_register_source_exit:
25786b525beSRodolfo Giometti 	pr_err("unable to register generator\n");
25886b525beSRodolfo Giometti 
25986b525beSRodolfo Giometti 	return ERR_PTR(err);
26086b525beSRodolfo Giometti }
26186b525beSRodolfo Giometti EXPORT_SYMBOL(pps_gen_register_source);
26286b525beSRodolfo Giometti 
26386b525beSRodolfo Giometti /**
26486b525beSRodolfo Giometti  * pps_gen_unregister_source() - remove a PPS generator from the system
26586b525beSRodolfo Giometti  * @pps_gen: the PPS generator device to be removed
26686b525beSRodolfo Giometti  *
26786b525beSRodolfo Giometti  * This function is used to deregister a PPS generator from the system. When
26886b525beSRodolfo Giometti  * called, it disables the generator so no pulses are generated anymore.
26986b525beSRodolfo Giometti  */
27086b525beSRodolfo Giometti void pps_gen_unregister_source(struct pps_gen_device *pps_gen)
27186b525beSRodolfo Giometti {
27286b525beSRodolfo Giometti 	pps_gen_unregister_cdev(pps_gen);
27386b525beSRodolfo Giometti }
27486b525beSRodolfo Giometti EXPORT_SYMBOL(pps_gen_unregister_source);
27586b525beSRodolfo Giometti 
27686b525beSRodolfo Giometti /* pps_gen_event - register a PPS generator event into the system
27786b525beSRodolfo Giometti  * @pps: the PPS generator device
27886b525beSRodolfo Giometti  * @event: the event type
27986b525beSRodolfo Giometti  * @data: userdef pointer
28086b525beSRodolfo Giometti  *
28186b525beSRodolfo Giometti  * This function is used by each PPS generator in order to register a new
28286b525beSRodolfo Giometti  * PPS event into the system (it's usually called inside an IRQ handler).
28386b525beSRodolfo Giometti  */
28486b525beSRodolfo Giometti void pps_gen_event(struct pps_gen_device *pps_gen,
28586b525beSRodolfo Giometti 			unsigned int event, void *data)
28686b525beSRodolfo Giometti {
28786b525beSRodolfo Giometti 	unsigned long flags;
28886b525beSRodolfo Giometti 
28986b525beSRodolfo Giometti 	dev_dbg(pps_gen->dev, "PPS generator event %u\n", event);
29086b525beSRodolfo Giometti 
29186b525beSRodolfo Giometti 	spin_lock_irqsave(&pps_gen->lock, flags);
29286b525beSRodolfo Giometti 
29386b525beSRodolfo Giometti 	pps_gen->event = event;
29486b525beSRodolfo Giometti 	pps_gen->sequence++;
29586b525beSRodolfo Giometti 
29686b525beSRodolfo Giometti 	pps_gen->last_ev++;
29786b525beSRodolfo Giometti 	wake_up_interruptible_all(&pps_gen->queue);
29886b525beSRodolfo Giometti 	kill_fasync(&pps_gen->async_queue, SIGIO, POLL_IN);
29986b525beSRodolfo Giometti 
30086b525beSRodolfo Giometti 	spin_unlock_irqrestore(&pps_gen->lock, flags);
30186b525beSRodolfo Giometti }
30286b525beSRodolfo Giometti EXPORT_SYMBOL(pps_gen_event);
30386b525beSRodolfo Giometti 
30486b525beSRodolfo Giometti /*
30586b525beSRodolfo Giometti  * Module stuff
30686b525beSRodolfo Giometti  */
30786b525beSRodolfo Giometti 
30886b525beSRodolfo Giometti static void __exit pps_gen_exit(void)
30986b525beSRodolfo Giometti {
31086b525beSRodolfo Giometti 	class_destroy(pps_gen_class);
31186b525beSRodolfo Giometti 	unregister_chrdev_region(pps_gen_devt, PPS_GEN_MAX_SOURCES);
31286b525beSRodolfo Giometti }
31386b525beSRodolfo Giometti 
31486b525beSRodolfo Giometti static int __init pps_gen_init(void)
31586b525beSRodolfo Giometti {
31686b525beSRodolfo Giometti 	int err;
31786b525beSRodolfo Giometti 
31886b525beSRodolfo Giometti 	pps_gen_class = class_create("pps-gen");
31986b525beSRodolfo Giometti 	if (IS_ERR(pps_gen_class)) {
32086b525beSRodolfo Giometti 		pr_err("failed to allocate class\n");
32186b525beSRodolfo Giometti 		return PTR_ERR(pps_gen_class);
32286b525beSRodolfo Giometti 	}
32386b525beSRodolfo Giometti 	pps_gen_class->dev_groups = pps_gen_groups;
32486b525beSRodolfo Giometti 
32586b525beSRodolfo Giometti 	err = alloc_chrdev_region(&pps_gen_devt, 0,
32686b525beSRodolfo Giometti 					PPS_GEN_MAX_SOURCES, "pps-gen");
32786b525beSRodolfo Giometti 	if (err < 0) {
32886b525beSRodolfo Giometti 		pr_err("failed to allocate char device region\n");
32986b525beSRodolfo Giometti 		goto remove_class;
33086b525beSRodolfo Giometti 	}
33186b525beSRodolfo Giometti 
33286b525beSRodolfo Giometti 	return 0;
33386b525beSRodolfo Giometti 
33486b525beSRodolfo Giometti remove_class:
33586b525beSRodolfo Giometti 	class_destroy(pps_gen_class);
33686b525beSRodolfo Giometti 	return err;
33786b525beSRodolfo Giometti }
33886b525beSRodolfo Giometti 
33986b525beSRodolfo Giometti subsys_initcall(pps_gen_init);
34086b525beSRodolfo Giometti module_exit(pps_gen_exit);
34186b525beSRodolfo Giometti 
34286b525beSRodolfo Giometti MODULE_AUTHOR("Rodolfo Giometti <giometti@enneenne.com>");
34386b525beSRodolfo Giometti MODULE_DESCRIPTION("LinuxPPS generators support");
34486b525beSRodolfo Giometti MODULE_LICENSE("GPL");
345