162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * PPS kernel consumer API 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2009-2010 Alexander Gordeev <lasaine@lvk.cs.msu.su> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/kernel.h> 1162306a36Sopenharmony_ci#include <linux/module.h> 1262306a36Sopenharmony_ci#include <linux/device.h> 1362306a36Sopenharmony_ci#include <linux/init.h> 1462306a36Sopenharmony_ci#include <linux/spinlock.h> 1562306a36Sopenharmony_ci#include <linux/pps_kernel.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#include "kc.h" 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci/* 2062306a36Sopenharmony_ci * Global variables 2162306a36Sopenharmony_ci */ 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci/* state variables to bind kernel consumer */ 2462306a36Sopenharmony_cistatic DEFINE_SPINLOCK(pps_kc_hardpps_lock); 2562306a36Sopenharmony_ci/* PPS API (RFC 2783): current source and mode for kernel consumer */ 2662306a36Sopenharmony_cistatic struct pps_device *pps_kc_hardpps_dev; /* unique pointer to device */ 2762306a36Sopenharmony_cistatic int pps_kc_hardpps_mode; /* mode bits for kernel consumer */ 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci/* pps_kc_bind - control PPS kernel consumer binding 3062306a36Sopenharmony_ci * @pps: the PPS source 3162306a36Sopenharmony_ci * @bind_args: kernel consumer bind parameters 3262306a36Sopenharmony_ci * 3362306a36Sopenharmony_ci * This function is used to bind or unbind PPS kernel consumer according to 3462306a36Sopenharmony_ci * supplied parameters. Should not be called in interrupt context. 3562306a36Sopenharmony_ci */ 3662306a36Sopenharmony_ciint pps_kc_bind(struct pps_device *pps, struct pps_bind_args *bind_args) 3762306a36Sopenharmony_ci{ 3862306a36Sopenharmony_ci /* Check if another consumer is already bound */ 3962306a36Sopenharmony_ci spin_lock_irq(&pps_kc_hardpps_lock); 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci if (bind_args->edge == 0) 4262306a36Sopenharmony_ci if (pps_kc_hardpps_dev == pps) { 4362306a36Sopenharmony_ci pps_kc_hardpps_mode = 0; 4462306a36Sopenharmony_ci pps_kc_hardpps_dev = NULL; 4562306a36Sopenharmony_ci spin_unlock_irq(&pps_kc_hardpps_lock); 4662306a36Sopenharmony_ci dev_info(pps->dev, "unbound kernel" 4762306a36Sopenharmony_ci " consumer\n"); 4862306a36Sopenharmony_ci } else { 4962306a36Sopenharmony_ci spin_unlock_irq(&pps_kc_hardpps_lock); 5062306a36Sopenharmony_ci dev_err(pps->dev, "selected kernel consumer" 5162306a36Sopenharmony_ci " is not bound\n"); 5262306a36Sopenharmony_ci return -EINVAL; 5362306a36Sopenharmony_ci } 5462306a36Sopenharmony_ci else 5562306a36Sopenharmony_ci if (pps_kc_hardpps_dev == NULL || 5662306a36Sopenharmony_ci pps_kc_hardpps_dev == pps) { 5762306a36Sopenharmony_ci pps_kc_hardpps_mode = bind_args->edge; 5862306a36Sopenharmony_ci pps_kc_hardpps_dev = pps; 5962306a36Sopenharmony_ci spin_unlock_irq(&pps_kc_hardpps_lock); 6062306a36Sopenharmony_ci dev_info(pps->dev, "bound kernel consumer: " 6162306a36Sopenharmony_ci "edge=0x%x\n", bind_args->edge); 6262306a36Sopenharmony_ci } else { 6362306a36Sopenharmony_ci spin_unlock_irq(&pps_kc_hardpps_lock); 6462306a36Sopenharmony_ci dev_err(pps->dev, "another kernel consumer" 6562306a36Sopenharmony_ci " is already bound\n"); 6662306a36Sopenharmony_ci return -EINVAL; 6762306a36Sopenharmony_ci } 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci return 0; 7062306a36Sopenharmony_ci} 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci/* pps_kc_remove - unbind kernel consumer on PPS source removal 7362306a36Sopenharmony_ci * @pps: the PPS source 7462306a36Sopenharmony_ci * 7562306a36Sopenharmony_ci * This function is used to disable kernel consumer on PPS source removal 7662306a36Sopenharmony_ci * if this source was bound to PPS kernel consumer. Can be called on any 7762306a36Sopenharmony_ci * source safely. Should not be called in interrupt context. 7862306a36Sopenharmony_ci */ 7962306a36Sopenharmony_civoid pps_kc_remove(struct pps_device *pps) 8062306a36Sopenharmony_ci{ 8162306a36Sopenharmony_ci spin_lock_irq(&pps_kc_hardpps_lock); 8262306a36Sopenharmony_ci if (pps == pps_kc_hardpps_dev) { 8362306a36Sopenharmony_ci pps_kc_hardpps_mode = 0; 8462306a36Sopenharmony_ci pps_kc_hardpps_dev = NULL; 8562306a36Sopenharmony_ci spin_unlock_irq(&pps_kc_hardpps_lock); 8662306a36Sopenharmony_ci dev_info(pps->dev, "unbound kernel consumer" 8762306a36Sopenharmony_ci " on device removal\n"); 8862306a36Sopenharmony_ci } else 8962306a36Sopenharmony_ci spin_unlock_irq(&pps_kc_hardpps_lock); 9062306a36Sopenharmony_ci} 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci/* pps_kc_event - call hardpps() on PPS event 9362306a36Sopenharmony_ci * @pps: the PPS source 9462306a36Sopenharmony_ci * @ts: PPS event timestamp 9562306a36Sopenharmony_ci * @event: PPS event edge 9662306a36Sopenharmony_ci * 9762306a36Sopenharmony_ci * This function calls hardpps() when an event from bound PPS source occurs. 9862306a36Sopenharmony_ci */ 9962306a36Sopenharmony_civoid pps_kc_event(struct pps_device *pps, struct pps_event_time *ts, 10062306a36Sopenharmony_ci int event) 10162306a36Sopenharmony_ci{ 10262306a36Sopenharmony_ci unsigned long flags; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci /* Pass some events to kernel consumer if activated */ 10562306a36Sopenharmony_ci spin_lock_irqsave(&pps_kc_hardpps_lock, flags); 10662306a36Sopenharmony_ci if (pps == pps_kc_hardpps_dev && event & pps_kc_hardpps_mode) 10762306a36Sopenharmony_ci hardpps(&ts->ts_real, &ts->ts_raw); 10862306a36Sopenharmony_ci spin_unlock_irqrestore(&pps_kc_hardpps_lock, flags); 10962306a36Sopenharmony_ci} 110