1eae9d2baSRodolfo Giometti /* 2eae9d2baSRodolfo Giometti * kernel API 3eae9d2baSRodolfo Giometti * 4eae9d2baSRodolfo Giometti * 5eae9d2baSRodolfo Giometti * Copyright (C) 2005-2009 Rodolfo Giometti <giometti@linux.it> 6eae9d2baSRodolfo Giometti * 7eae9d2baSRodolfo Giometti * This program is free software; you can redistribute it and/or modify 8eae9d2baSRodolfo Giometti * it under the terms of the GNU General Public License as published by 9eae9d2baSRodolfo Giometti * the Free Software Foundation; either version 2 of the License, or 10eae9d2baSRodolfo Giometti * (at your option) any later version. 11eae9d2baSRodolfo Giometti * 12eae9d2baSRodolfo Giometti * This program is distributed in the hope that it will be useful, 13eae9d2baSRodolfo Giometti * but WITHOUT ANY WARRANTY; without even the implied warranty of 14eae9d2baSRodolfo Giometti * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15eae9d2baSRodolfo Giometti * GNU General Public License for more details. 16eae9d2baSRodolfo Giometti * 17eae9d2baSRodolfo Giometti * You should have received a copy of the GNU General Public License 18eae9d2baSRodolfo Giometti * along with this program; if not, write to the Free Software 19eae9d2baSRodolfo Giometti * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 20eae9d2baSRodolfo Giometti */ 21eae9d2baSRodolfo Giometti 227f7cce74SAlexander Gordeev #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 23eae9d2baSRodolfo Giometti 24eae9d2baSRodolfo Giometti #include <linux/kernel.h> 25eae9d2baSRodolfo Giometti #include <linux/module.h> 26eae9d2baSRodolfo Giometti #include <linux/init.h> 27eae9d2baSRodolfo Giometti #include <linux/sched.h> 28eae9d2baSRodolfo Giometti #include <linux/time.h> 29eae9d2baSRodolfo Giometti #include <linux/spinlock.h> 30eae9d2baSRodolfo Giometti #include <linux/fs.h> 31eae9d2baSRodolfo Giometti #include <linux/pps_kernel.h> 325a0e3ad6STejun Heo #include <linux/slab.h> 33eae9d2baSRodolfo Giometti 34eae9d2baSRodolfo Giometti /* 35eae9d2baSRodolfo Giometti * Local functions 36eae9d2baSRodolfo Giometti */ 37eae9d2baSRodolfo Giometti 38eae9d2baSRodolfo Giometti static void pps_add_offset(struct pps_ktime *ts, struct pps_ktime *offset) 39eae9d2baSRodolfo Giometti { 40eae9d2baSRodolfo Giometti ts->nsec += offset->nsec; 41eae9d2baSRodolfo Giometti while (ts->nsec >= NSEC_PER_SEC) { 42eae9d2baSRodolfo Giometti ts->nsec -= NSEC_PER_SEC; 43eae9d2baSRodolfo Giometti ts->sec++; 44eae9d2baSRodolfo Giometti } 45eae9d2baSRodolfo Giometti while (ts->nsec < 0) { 46eae9d2baSRodolfo Giometti ts->nsec += NSEC_PER_SEC; 47eae9d2baSRodolfo Giometti ts->sec--; 48eae9d2baSRodolfo Giometti } 49eae9d2baSRodolfo Giometti ts->sec += offset->sec; 50eae9d2baSRodolfo Giometti } 51eae9d2baSRodolfo Giometti 52eae9d2baSRodolfo Giometti /* 53eae9d2baSRodolfo Giometti * Exported functions 54eae9d2baSRodolfo Giometti */ 55eae9d2baSRodolfo Giometti 56eae9d2baSRodolfo Giometti /* pps_register_source - add a PPS source in the system 57eae9d2baSRodolfo Giometti * @info: the PPS info struct 58eae9d2baSRodolfo Giometti * @default_params: the default PPS parameters of the new source 59eae9d2baSRodolfo Giometti * 60eae9d2baSRodolfo Giometti * This function is used to add a new PPS source in the system. The new 61eae9d2baSRodolfo Giometti * source is described by info's fields and it will have, as default PPS 62eae9d2baSRodolfo Giometti * parameters, the ones specified into default_params. 63eae9d2baSRodolfo Giometti * 645e196d34SAlexander Gordeev * The function returns, in case of success, the PPS device. Otherwise NULL. 65eae9d2baSRodolfo Giometti */ 66eae9d2baSRodolfo Giometti 675e196d34SAlexander Gordeev struct pps_device *pps_register_source(struct pps_source_info *info, 685e196d34SAlexander Gordeev int default_params) 69eae9d2baSRodolfo Giometti { 70eae9d2baSRodolfo Giometti struct pps_device *pps; 71eae9d2baSRodolfo Giometti int err; 72eae9d2baSRodolfo Giometti 73eae9d2baSRodolfo Giometti /* Sanity checks */ 74eae9d2baSRodolfo Giometti if ((info->mode & default_params) != default_params) { 757f7cce74SAlexander Gordeev pr_err("%s: unsupported default parameters\n", 76eae9d2baSRodolfo Giometti info->name); 77eae9d2baSRodolfo Giometti err = -EINVAL; 78eae9d2baSRodolfo Giometti goto pps_register_source_exit; 79eae9d2baSRodolfo Giometti } 80eae9d2baSRodolfo Giometti if ((info->mode & (PPS_ECHOASSERT | PPS_ECHOCLEAR)) != 0 && 81eae9d2baSRodolfo Giometti info->echo == NULL) { 827f7cce74SAlexander Gordeev pr_err("%s: echo function is not defined\n", 83eae9d2baSRodolfo Giometti info->name); 84eae9d2baSRodolfo Giometti err = -EINVAL; 85eae9d2baSRodolfo Giometti goto pps_register_source_exit; 86eae9d2baSRodolfo Giometti } 87eae9d2baSRodolfo Giometti if ((info->mode & (PPS_TSFMT_TSPEC | PPS_TSFMT_NTPFP)) == 0) { 887f7cce74SAlexander Gordeev pr_err("%s: unspecified time format\n", 89eae9d2baSRodolfo Giometti info->name); 90eae9d2baSRodolfo Giometti err = -EINVAL; 91eae9d2baSRodolfo Giometti goto pps_register_source_exit; 92eae9d2baSRodolfo Giometti } 93eae9d2baSRodolfo Giometti 94eae9d2baSRodolfo Giometti /* Allocate memory for the new PPS source struct */ 95eae9d2baSRodolfo Giometti pps = kzalloc(sizeof(struct pps_device), GFP_KERNEL); 96eae9d2baSRodolfo Giometti if (pps == NULL) { 97eae9d2baSRodolfo Giometti err = -ENOMEM; 98eae9d2baSRodolfo Giometti goto pps_register_source_exit; 99eae9d2baSRodolfo Giometti } 100eae9d2baSRodolfo Giometti 101eae9d2baSRodolfo Giometti /* These initializations must be done before calling idr_get_new() 102eae9d2baSRodolfo Giometti * in order to avoid reces into pps_event(). 103eae9d2baSRodolfo Giometti */ 104eae9d2baSRodolfo Giometti pps->params.api_version = PPS_API_VERS; 105eae9d2baSRodolfo Giometti pps->params.mode = default_params; 106eae9d2baSRodolfo Giometti pps->info = *info; 107eae9d2baSRodolfo Giometti 108eae9d2baSRodolfo Giometti init_waitqueue_head(&pps->queue); 109eae9d2baSRodolfo Giometti spin_lock_init(&pps->lock); 110eae9d2baSRodolfo Giometti 111eae9d2baSRodolfo Giometti /* Create the char device */ 112eae9d2baSRodolfo Giometti err = pps_register_cdev(pps); 113eae9d2baSRodolfo Giometti if (err < 0) { 1147f7cce74SAlexander Gordeev pr_err("%s: unable to create char device\n", 115eae9d2baSRodolfo Giometti info->name); 116083e5866SAlexander Gordeev goto kfree_pps; 117eae9d2baSRodolfo Giometti } 118eae9d2baSRodolfo Giometti 1197f7cce74SAlexander Gordeev dev_info(pps->dev, "new PPS source %s\n", info->name); 120eae9d2baSRodolfo Giometti 1215e196d34SAlexander Gordeev return pps; 122eae9d2baSRodolfo Giometti 123eae9d2baSRodolfo Giometti kfree_pps: 124eae9d2baSRodolfo Giometti kfree(pps); 125eae9d2baSRodolfo Giometti 126eae9d2baSRodolfo Giometti pps_register_source_exit: 1277f7cce74SAlexander Gordeev pr_err("%s: unable to register source\n", info->name); 128eae9d2baSRodolfo Giometti 1295e196d34SAlexander Gordeev return NULL; 130eae9d2baSRodolfo Giometti } 131eae9d2baSRodolfo Giometti EXPORT_SYMBOL(pps_register_source); 132eae9d2baSRodolfo Giometti 133eae9d2baSRodolfo Giometti /* pps_unregister_source - remove a PPS source from the system 1345e196d34SAlexander Gordeev * @pps: the PPS source 135eae9d2baSRodolfo Giometti * 136eae9d2baSRodolfo Giometti * This function is used to remove a previously registered PPS source from 137eae9d2baSRodolfo Giometti * the system. 138eae9d2baSRodolfo Giometti */ 139eae9d2baSRodolfo Giometti 1405e196d34SAlexander Gordeev void pps_unregister_source(struct pps_device *pps) 141eae9d2baSRodolfo Giometti { 142eae9d2baSRodolfo Giometti pps_unregister_cdev(pps); 1435e196d34SAlexander Gordeev 144083e5866SAlexander Gordeev /* don't have to kfree(pps) here because it will be done on 145083e5866SAlexander Gordeev * device destruction */ 146eae9d2baSRodolfo Giometti } 147eae9d2baSRodolfo Giometti EXPORT_SYMBOL(pps_unregister_source); 148eae9d2baSRodolfo Giometti 149eae9d2baSRodolfo Giometti /* pps_event - register a PPS event into the system 1505e196d34SAlexander Gordeev * @pps: the PPS device 151eae9d2baSRodolfo Giometti * @ts: the event timestamp 152eae9d2baSRodolfo Giometti * @event: the event type 153eae9d2baSRodolfo Giometti * @data: userdef pointer 154eae9d2baSRodolfo Giometti * 155eae9d2baSRodolfo Giometti * This function is used by each PPS client in order to register a new 156eae9d2baSRodolfo Giometti * PPS event into the system (it's usually called inside an IRQ handler). 157eae9d2baSRodolfo Giometti * 1585e196d34SAlexander Gordeev * If an echo function is associated with the PPS device it will be called 159eae9d2baSRodolfo Giometti * as: 1605e196d34SAlexander Gordeev * pps->info.echo(pps, event, data); 161eae9d2baSRodolfo Giometti */ 1625e196d34SAlexander Gordeev void pps_event(struct pps_device *pps, struct pps_event_time *ts, int event, 1635e196d34SAlexander Gordeev void *data) 164eae9d2baSRodolfo Giometti { 165eae9d2baSRodolfo Giometti unsigned long flags; 166276b282eSRodolfo Giometti int captured = 0; 1676f4229b5SAlexander Gordeev struct pps_ktime ts_real; 168eae9d2baSRodolfo Giometti 169eae9d2baSRodolfo Giometti if ((event & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR)) == 0) { 1705e196d34SAlexander Gordeev dev_err(pps->dev, "unknown event (%x)\n", event); 171eae9d2baSRodolfo Giometti return; 172eae9d2baSRodolfo Giometti } 173eae9d2baSRodolfo Giometti 1745e196d34SAlexander Gordeev dev_dbg(pps->dev, "PPS event at %ld.%09ld\n", 1755e196d34SAlexander Gordeev ts->ts_real.tv_sec, ts->ts_real.tv_nsec); 1766f4229b5SAlexander Gordeev 1776f4229b5SAlexander Gordeev timespec_to_pps_ktime(&ts_real, ts->ts_real); 178eae9d2baSRodolfo Giometti 179eae9d2baSRodolfo Giometti spin_lock_irqsave(&pps->lock, flags); 180eae9d2baSRodolfo Giometti 181eae9d2baSRodolfo Giometti /* Must call the echo function? */ 182eae9d2baSRodolfo Giometti if ((pps->params.mode & (PPS_ECHOASSERT | PPS_ECHOCLEAR))) 1835e196d34SAlexander Gordeev pps->info.echo(pps, event, data); 184eae9d2baSRodolfo Giometti 185eae9d2baSRodolfo Giometti /* Check the event */ 186eae9d2baSRodolfo Giometti pps->current_mode = pps->params.mode; 187276b282eSRodolfo Giometti if ((event & PPS_CAPTUREASSERT) & 188276b282eSRodolfo Giometti (pps->params.mode & PPS_CAPTUREASSERT)) { 189eae9d2baSRodolfo Giometti /* We have to add an offset? */ 190eae9d2baSRodolfo Giometti if (pps->params.mode & PPS_OFFSETASSERT) 1916f4229b5SAlexander Gordeev pps_add_offset(&ts_real, 1926f4229b5SAlexander Gordeev &pps->params.assert_off_tu); 193eae9d2baSRodolfo Giometti 194eae9d2baSRodolfo Giometti /* Save the time stamp */ 1956f4229b5SAlexander Gordeev pps->assert_tu = ts_real; 196eae9d2baSRodolfo Giometti pps->assert_sequence++; 1975e196d34SAlexander Gordeev dev_dbg(pps->dev, "capture assert seq #%u\n", 1985e196d34SAlexander Gordeev pps->assert_sequence); 199276b282eSRodolfo Giometti 200276b282eSRodolfo Giometti captured = ~0; 201eae9d2baSRodolfo Giometti } 202276b282eSRodolfo Giometti if ((event & PPS_CAPTURECLEAR) & 203276b282eSRodolfo Giometti (pps->params.mode & PPS_CAPTURECLEAR)) { 204eae9d2baSRodolfo Giometti /* We have to add an offset? */ 205eae9d2baSRodolfo Giometti if (pps->params.mode & PPS_OFFSETCLEAR) 2066f4229b5SAlexander Gordeev pps_add_offset(&ts_real, 2076f4229b5SAlexander Gordeev &pps->params.clear_off_tu); 208eae9d2baSRodolfo Giometti 209eae9d2baSRodolfo Giometti /* Save the time stamp */ 2106f4229b5SAlexander Gordeev pps->clear_tu = ts_real; 211eae9d2baSRodolfo Giometti pps->clear_sequence++; 2125e196d34SAlexander Gordeev dev_dbg(pps->dev, "capture clear seq #%u\n", 2135e196d34SAlexander Gordeev pps->clear_sequence); 214276b282eSRodolfo Giometti 215276b282eSRodolfo Giometti captured = ~0; 216eae9d2baSRodolfo Giometti } 217eae9d2baSRodolfo Giometti 2187a21a3ccSAlexander Gordeev /* Wake up if captured something */ 219276b282eSRodolfo Giometti if (captured) { 2203003d55bSAlexander Gordeev pps->last_ev++; 2213003d55bSAlexander Gordeev wake_up_interruptible_all(&pps->queue); 222eae9d2baSRodolfo Giometti 223eae9d2baSRodolfo Giometti kill_fasync(&pps->async_queue, SIGIO, POLL_IN); 224276b282eSRodolfo Giometti } 225eae9d2baSRodolfo Giometti 226eae9d2baSRodolfo Giometti spin_unlock_irqrestore(&pps->lock, flags); 227eae9d2baSRodolfo Giometti } 228eae9d2baSRodolfo Giometti EXPORT_SYMBOL(pps_event); 229