18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * PPS kernel consumer API 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2009-2010 Alexander Gordeev <lasaine@lvk.cs.msu.su> 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/device.h> 138c2ecf20Sopenharmony_ci#include <linux/init.h> 148c2ecf20Sopenharmony_ci#include <linux/spinlock.h> 158c2ecf20Sopenharmony_ci#include <linux/pps_kernel.h> 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci#include "kc.h" 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci/* 208c2ecf20Sopenharmony_ci * Global variables 218c2ecf20Sopenharmony_ci */ 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci/* state variables to bind kernel consumer */ 248c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(pps_kc_hardpps_lock); 258c2ecf20Sopenharmony_ci/* PPS API (RFC 2783): current source and mode for kernel consumer */ 268c2ecf20Sopenharmony_cistatic struct pps_device *pps_kc_hardpps_dev; /* unique pointer to device */ 278c2ecf20Sopenharmony_cistatic int pps_kc_hardpps_mode; /* mode bits for kernel consumer */ 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci/* pps_kc_bind - control PPS kernel consumer binding 308c2ecf20Sopenharmony_ci * @pps: the PPS source 318c2ecf20Sopenharmony_ci * @bind_args: kernel consumer bind parameters 328c2ecf20Sopenharmony_ci * 338c2ecf20Sopenharmony_ci * This function is used to bind or unbind PPS kernel consumer according to 348c2ecf20Sopenharmony_ci * supplied parameters. Should not be called in interrupt context. 358c2ecf20Sopenharmony_ci */ 368c2ecf20Sopenharmony_ciint pps_kc_bind(struct pps_device *pps, struct pps_bind_args *bind_args) 378c2ecf20Sopenharmony_ci{ 388c2ecf20Sopenharmony_ci /* Check if another consumer is already bound */ 398c2ecf20Sopenharmony_ci spin_lock_irq(&pps_kc_hardpps_lock); 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci if (bind_args->edge == 0) 428c2ecf20Sopenharmony_ci if (pps_kc_hardpps_dev == pps) { 438c2ecf20Sopenharmony_ci pps_kc_hardpps_mode = 0; 448c2ecf20Sopenharmony_ci pps_kc_hardpps_dev = NULL; 458c2ecf20Sopenharmony_ci spin_unlock_irq(&pps_kc_hardpps_lock); 468c2ecf20Sopenharmony_ci dev_info(pps->dev, "unbound kernel" 478c2ecf20Sopenharmony_ci " consumer\n"); 488c2ecf20Sopenharmony_ci } else { 498c2ecf20Sopenharmony_ci spin_unlock_irq(&pps_kc_hardpps_lock); 508c2ecf20Sopenharmony_ci dev_err(pps->dev, "selected kernel consumer" 518c2ecf20Sopenharmony_ci " is not bound\n"); 528c2ecf20Sopenharmony_ci return -EINVAL; 538c2ecf20Sopenharmony_ci } 548c2ecf20Sopenharmony_ci else 558c2ecf20Sopenharmony_ci if (pps_kc_hardpps_dev == NULL || 568c2ecf20Sopenharmony_ci pps_kc_hardpps_dev == pps) { 578c2ecf20Sopenharmony_ci pps_kc_hardpps_mode = bind_args->edge; 588c2ecf20Sopenharmony_ci pps_kc_hardpps_dev = pps; 598c2ecf20Sopenharmony_ci spin_unlock_irq(&pps_kc_hardpps_lock); 608c2ecf20Sopenharmony_ci dev_info(pps->dev, "bound kernel consumer: " 618c2ecf20Sopenharmony_ci "edge=0x%x\n", bind_args->edge); 628c2ecf20Sopenharmony_ci } else { 638c2ecf20Sopenharmony_ci spin_unlock_irq(&pps_kc_hardpps_lock); 648c2ecf20Sopenharmony_ci dev_err(pps->dev, "another kernel consumer" 658c2ecf20Sopenharmony_ci " is already bound\n"); 668c2ecf20Sopenharmony_ci return -EINVAL; 678c2ecf20Sopenharmony_ci } 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci return 0; 708c2ecf20Sopenharmony_ci} 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci/* pps_kc_remove - unbind kernel consumer on PPS source removal 738c2ecf20Sopenharmony_ci * @pps: the PPS source 748c2ecf20Sopenharmony_ci * 758c2ecf20Sopenharmony_ci * This function is used to disable kernel consumer on PPS source removal 768c2ecf20Sopenharmony_ci * if this source was bound to PPS kernel consumer. Can be called on any 778c2ecf20Sopenharmony_ci * source safely. Should not be called in interrupt context. 788c2ecf20Sopenharmony_ci */ 798c2ecf20Sopenharmony_civoid pps_kc_remove(struct pps_device *pps) 808c2ecf20Sopenharmony_ci{ 818c2ecf20Sopenharmony_ci spin_lock_irq(&pps_kc_hardpps_lock); 828c2ecf20Sopenharmony_ci if (pps == pps_kc_hardpps_dev) { 838c2ecf20Sopenharmony_ci pps_kc_hardpps_mode = 0; 848c2ecf20Sopenharmony_ci pps_kc_hardpps_dev = NULL; 858c2ecf20Sopenharmony_ci spin_unlock_irq(&pps_kc_hardpps_lock); 868c2ecf20Sopenharmony_ci dev_info(pps->dev, "unbound kernel consumer" 878c2ecf20Sopenharmony_ci " on device removal\n"); 888c2ecf20Sopenharmony_ci } else 898c2ecf20Sopenharmony_ci spin_unlock_irq(&pps_kc_hardpps_lock); 908c2ecf20Sopenharmony_ci} 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci/* pps_kc_event - call hardpps() on PPS event 938c2ecf20Sopenharmony_ci * @pps: the PPS source 948c2ecf20Sopenharmony_ci * @ts: PPS event timestamp 958c2ecf20Sopenharmony_ci * @event: PPS event edge 968c2ecf20Sopenharmony_ci * 978c2ecf20Sopenharmony_ci * This function calls hardpps() when an event from bound PPS source occurs. 988c2ecf20Sopenharmony_ci */ 998c2ecf20Sopenharmony_civoid pps_kc_event(struct pps_device *pps, struct pps_event_time *ts, 1008c2ecf20Sopenharmony_ci int event) 1018c2ecf20Sopenharmony_ci{ 1028c2ecf20Sopenharmony_ci unsigned long flags; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci /* Pass some events to kernel consumer if activated */ 1058c2ecf20Sopenharmony_ci spin_lock_irqsave(&pps_kc_hardpps_lock, flags); 1068c2ecf20Sopenharmony_ci if (pps == pps_kc_hardpps_dev && event & pps_kc_hardpps_mode) 1078c2ecf20Sopenharmony_ci hardpps(&ts->ts_real, &ts->ts_raw); 1088c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&pps_kc_hardpps_lock, flags); 1098c2ecf20Sopenharmony_ci} 110