18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * kernel API 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2005-2009 Rodolfo Giometti <giometti@linux.it> 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/kernel.h> 118c2ecf20Sopenharmony_ci#include <linux/module.h> 128c2ecf20Sopenharmony_ci#include <linux/init.h> 138c2ecf20Sopenharmony_ci#include <linux/sched.h> 148c2ecf20Sopenharmony_ci#include <linux/time.h> 158c2ecf20Sopenharmony_ci#include <linux/timex.h> 168c2ecf20Sopenharmony_ci#include <linux/spinlock.h> 178c2ecf20Sopenharmony_ci#include <linux/fs.h> 188c2ecf20Sopenharmony_ci#include <linux/pps_kernel.h> 198c2ecf20Sopenharmony_ci#include <linux/slab.h> 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#include "kc.h" 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci/* 248c2ecf20Sopenharmony_ci * Local functions 258c2ecf20Sopenharmony_ci */ 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_cistatic void pps_add_offset(struct pps_ktime *ts, struct pps_ktime *offset) 288c2ecf20Sopenharmony_ci{ 298c2ecf20Sopenharmony_ci ts->nsec += offset->nsec; 308c2ecf20Sopenharmony_ci while (ts->nsec >= NSEC_PER_SEC) { 318c2ecf20Sopenharmony_ci ts->nsec -= NSEC_PER_SEC; 328c2ecf20Sopenharmony_ci ts->sec++; 338c2ecf20Sopenharmony_ci } 348c2ecf20Sopenharmony_ci while (ts->nsec < 0) { 358c2ecf20Sopenharmony_ci ts->nsec += NSEC_PER_SEC; 368c2ecf20Sopenharmony_ci ts->sec--; 378c2ecf20Sopenharmony_ci } 388c2ecf20Sopenharmony_ci ts->sec += offset->sec; 398c2ecf20Sopenharmony_ci} 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_cistatic void pps_echo_client_default(struct pps_device *pps, int event, 428c2ecf20Sopenharmony_ci void *data) 438c2ecf20Sopenharmony_ci{ 448c2ecf20Sopenharmony_ci dev_info(pps->dev, "echo %s %s\n", 458c2ecf20Sopenharmony_ci event & PPS_CAPTUREASSERT ? "assert" : "", 468c2ecf20Sopenharmony_ci event & PPS_CAPTURECLEAR ? "clear" : ""); 478c2ecf20Sopenharmony_ci} 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci/* 508c2ecf20Sopenharmony_ci * Exported functions 518c2ecf20Sopenharmony_ci */ 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci/* pps_register_source - add a PPS source in the system 548c2ecf20Sopenharmony_ci * @info: the PPS info struct 558c2ecf20Sopenharmony_ci * @default_params: the default PPS parameters of the new source 568c2ecf20Sopenharmony_ci * 578c2ecf20Sopenharmony_ci * This function is used to add a new PPS source in the system. The new 588c2ecf20Sopenharmony_ci * source is described by info's fields and it will have, as default PPS 598c2ecf20Sopenharmony_ci * parameters, the ones specified into default_params. 608c2ecf20Sopenharmony_ci * 618c2ecf20Sopenharmony_ci * The function returns, in case of success, the PPS device. Otherwise 628c2ecf20Sopenharmony_ci * ERR_PTR(errno). 638c2ecf20Sopenharmony_ci */ 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_cistruct pps_device *pps_register_source(struct pps_source_info *info, 668c2ecf20Sopenharmony_ci int default_params) 678c2ecf20Sopenharmony_ci{ 688c2ecf20Sopenharmony_ci struct pps_device *pps; 698c2ecf20Sopenharmony_ci int err; 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci /* Sanity checks */ 728c2ecf20Sopenharmony_ci if ((info->mode & default_params) != default_params) { 738c2ecf20Sopenharmony_ci pr_err("%s: unsupported default parameters\n", 748c2ecf20Sopenharmony_ci info->name); 758c2ecf20Sopenharmony_ci err = -EINVAL; 768c2ecf20Sopenharmony_ci goto pps_register_source_exit; 778c2ecf20Sopenharmony_ci } 788c2ecf20Sopenharmony_ci if ((info->mode & (PPS_TSFMT_TSPEC | PPS_TSFMT_NTPFP)) == 0) { 798c2ecf20Sopenharmony_ci pr_err("%s: unspecified time format\n", 808c2ecf20Sopenharmony_ci info->name); 818c2ecf20Sopenharmony_ci err = -EINVAL; 828c2ecf20Sopenharmony_ci goto pps_register_source_exit; 838c2ecf20Sopenharmony_ci } 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci /* Allocate memory for the new PPS source struct */ 868c2ecf20Sopenharmony_ci pps = kzalloc(sizeof(struct pps_device), GFP_KERNEL); 878c2ecf20Sopenharmony_ci if (pps == NULL) { 888c2ecf20Sopenharmony_ci err = -ENOMEM; 898c2ecf20Sopenharmony_ci goto pps_register_source_exit; 908c2ecf20Sopenharmony_ci } 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci /* These initializations must be done before calling idr_alloc() 938c2ecf20Sopenharmony_ci * in order to avoid reces into pps_event(). 948c2ecf20Sopenharmony_ci */ 958c2ecf20Sopenharmony_ci pps->params.api_version = PPS_API_VERS; 968c2ecf20Sopenharmony_ci pps->params.mode = default_params; 978c2ecf20Sopenharmony_ci pps->info = *info; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci /* check for default echo function */ 1008c2ecf20Sopenharmony_ci if ((pps->info.mode & (PPS_ECHOASSERT | PPS_ECHOCLEAR)) && 1018c2ecf20Sopenharmony_ci pps->info.echo == NULL) 1028c2ecf20Sopenharmony_ci pps->info.echo = pps_echo_client_default; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci init_waitqueue_head(&pps->queue); 1058c2ecf20Sopenharmony_ci spin_lock_init(&pps->lock); 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci /* Create the char device */ 1088c2ecf20Sopenharmony_ci err = pps_register_cdev(pps); 1098c2ecf20Sopenharmony_ci if (err < 0) { 1108c2ecf20Sopenharmony_ci pr_err("%s: unable to create char device\n", 1118c2ecf20Sopenharmony_ci info->name); 1128c2ecf20Sopenharmony_ci goto kfree_pps; 1138c2ecf20Sopenharmony_ci } 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci dev_info(pps->dev, "new PPS source %s\n", info->name); 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci return pps; 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_cikfree_pps: 1208c2ecf20Sopenharmony_ci kfree(pps); 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_cipps_register_source_exit: 1238c2ecf20Sopenharmony_ci pr_err("%s: unable to register source\n", info->name); 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci return ERR_PTR(err); 1268c2ecf20Sopenharmony_ci} 1278c2ecf20Sopenharmony_ciEXPORT_SYMBOL(pps_register_source); 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci/* pps_unregister_source - remove a PPS source from the system 1308c2ecf20Sopenharmony_ci * @pps: the PPS source 1318c2ecf20Sopenharmony_ci * 1328c2ecf20Sopenharmony_ci * This function is used to remove a previously registered PPS source from 1338c2ecf20Sopenharmony_ci * the system. 1348c2ecf20Sopenharmony_ci */ 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_civoid pps_unregister_source(struct pps_device *pps) 1378c2ecf20Sopenharmony_ci{ 1388c2ecf20Sopenharmony_ci pps_kc_remove(pps); 1398c2ecf20Sopenharmony_ci pps_unregister_cdev(pps); 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci /* don't have to kfree(pps) here because it will be done on 1428c2ecf20Sopenharmony_ci * device destruction */ 1438c2ecf20Sopenharmony_ci} 1448c2ecf20Sopenharmony_ciEXPORT_SYMBOL(pps_unregister_source); 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci/* pps_event - register a PPS event into the system 1478c2ecf20Sopenharmony_ci * @pps: the PPS device 1488c2ecf20Sopenharmony_ci * @ts: the event timestamp 1498c2ecf20Sopenharmony_ci * @event: the event type 1508c2ecf20Sopenharmony_ci * @data: userdef pointer 1518c2ecf20Sopenharmony_ci * 1528c2ecf20Sopenharmony_ci * This function is used by each PPS client in order to register a new 1538c2ecf20Sopenharmony_ci * PPS event into the system (it's usually called inside an IRQ handler). 1548c2ecf20Sopenharmony_ci * 1558c2ecf20Sopenharmony_ci * If an echo function is associated with the PPS device it will be called 1568c2ecf20Sopenharmony_ci * as: 1578c2ecf20Sopenharmony_ci * pps->info.echo(pps, event, data); 1588c2ecf20Sopenharmony_ci */ 1598c2ecf20Sopenharmony_civoid pps_event(struct pps_device *pps, struct pps_event_time *ts, int event, 1608c2ecf20Sopenharmony_ci void *data) 1618c2ecf20Sopenharmony_ci{ 1628c2ecf20Sopenharmony_ci unsigned long flags; 1638c2ecf20Sopenharmony_ci int captured = 0; 1648c2ecf20Sopenharmony_ci struct pps_ktime ts_real = { .sec = 0, .nsec = 0, .flags = 0 }; 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci /* check event type */ 1678c2ecf20Sopenharmony_ci BUG_ON((event & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR)) == 0); 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci dev_dbg(pps->dev, "PPS event at %lld.%09ld\n", 1708c2ecf20Sopenharmony_ci (s64)ts->ts_real.tv_sec, ts->ts_real.tv_nsec); 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci timespec_to_pps_ktime(&ts_real, ts->ts_real); 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci spin_lock_irqsave(&pps->lock, flags); 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci /* Must call the echo function? */ 1778c2ecf20Sopenharmony_ci if ((pps->params.mode & (PPS_ECHOASSERT | PPS_ECHOCLEAR))) 1788c2ecf20Sopenharmony_ci pps->info.echo(pps, event, data); 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci /* Check the event */ 1818c2ecf20Sopenharmony_ci pps->current_mode = pps->params.mode; 1828c2ecf20Sopenharmony_ci if (event & pps->params.mode & PPS_CAPTUREASSERT) { 1838c2ecf20Sopenharmony_ci /* We have to add an offset? */ 1848c2ecf20Sopenharmony_ci if (pps->params.mode & PPS_OFFSETASSERT) 1858c2ecf20Sopenharmony_ci pps_add_offset(&ts_real, 1868c2ecf20Sopenharmony_ci &pps->params.assert_off_tu); 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci /* Save the time stamp */ 1898c2ecf20Sopenharmony_ci pps->assert_tu = ts_real; 1908c2ecf20Sopenharmony_ci pps->assert_sequence++; 1918c2ecf20Sopenharmony_ci dev_dbg(pps->dev, "capture assert seq #%u\n", 1928c2ecf20Sopenharmony_ci pps->assert_sequence); 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci captured = ~0; 1958c2ecf20Sopenharmony_ci } 1968c2ecf20Sopenharmony_ci if (event & pps->params.mode & PPS_CAPTURECLEAR) { 1978c2ecf20Sopenharmony_ci /* We have to add an offset? */ 1988c2ecf20Sopenharmony_ci if (pps->params.mode & PPS_OFFSETCLEAR) 1998c2ecf20Sopenharmony_ci pps_add_offset(&ts_real, 2008c2ecf20Sopenharmony_ci &pps->params.clear_off_tu); 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci /* Save the time stamp */ 2038c2ecf20Sopenharmony_ci pps->clear_tu = ts_real; 2048c2ecf20Sopenharmony_ci pps->clear_sequence++; 2058c2ecf20Sopenharmony_ci dev_dbg(pps->dev, "capture clear seq #%u\n", 2068c2ecf20Sopenharmony_ci pps->clear_sequence); 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci captured = ~0; 2098c2ecf20Sopenharmony_ci } 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci pps_kc_event(pps, ts, event); 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci /* Wake up if captured something */ 2148c2ecf20Sopenharmony_ci if (captured) { 2158c2ecf20Sopenharmony_ci pps->last_ev++; 2168c2ecf20Sopenharmony_ci wake_up_interruptible_all(&pps->queue); 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci kill_fasync(&pps->async_queue, SIGIO, POLL_IN); 2198c2ecf20Sopenharmony_ci } 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&pps->lock, flags); 2228c2ecf20Sopenharmony_ci} 2238c2ecf20Sopenharmony_ciEXPORT_SYMBOL(pps_event); 224