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