110 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * PPS kernel consumer API4 *5 * Copyright (C) 2009-2010 Alexander Gordeev <lasaine@lvk.cs.msu.su>6 */7 8#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt9 10#include <linux/kernel.h>11#include <linux/module.h>12#include <linux/device.h>13#include <linux/init.h>14#include <linux/spinlock.h>15#include <linux/pps_kernel.h>16 17#include "kc.h"18 19/*20 * Global variables21 */22 23/* state variables to bind kernel consumer */24static DEFINE_SPINLOCK(pps_kc_hardpps_lock);25/* PPS API (RFC 2783): current source and mode for kernel consumer */26static struct pps_device *pps_kc_hardpps_dev; /* unique pointer to device */27static int pps_kc_hardpps_mode; /* mode bits for kernel consumer */28 29/* pps_kc_bind - control PPS kernel consumer binding30 * @pps: the PPS source31 * @bind_args: kernel consumer bind parameters32 *33 * This function is used to bind or unbind PPS kernel consumer according to34 * supplied parameters. Should not be called in interrupt context.35 */36int pps_kc_bind(struct pps_device *pps, struct pps_bind_args *bind_args)37{38 /* Check if another consumer is already bound */39 spin_lock_irq(&pps_kc_hardpps_lock);40 41 if (bind_args->edge == 0)42 if (pps_kc_hardpps_dev == pps) {43 pps_kc_hardpps_mode = 0;44 pps_kc_hardpps_dev = NULL;45 spin_unlock_irq(&pps_kc_hardpps_lock);46 dev_info(pps->dev, "unbound kernel"47 " consumer\n");48 } else {49 spin_unlock_irq(&pps_kc_hardpps_lock);50 dev_err(pps->dev, "selected kernel consumer"51 " is not bound\n");52 return -EINVAL;53 }54 else55 if (pps_kc_hardpps_dev == NULL ||56 pps_kc_hardpps_dev == pps) {57 pps_kc_hardpps_mode = bind_args->edge;58 pps_kc_hardpps_dev = pps;59 spin_unlock_irq(&pps_kc_hardpps_lock);60 dev_info(pps->dev, "bound kernel consumer: "61 "edge=0x%x\n", bind_args->edge);62 } else {63 spin_unlock_irq(&pps_kc_hardpps_lock);64 dev_err(pps->dev, "another kernel consumer"65 " is already bound\n");66 return -EINVAL;67 }68 69 return 0;70}71 72/* pps_kc_remove - unbind kernel consumer on PPS source removal73 * @pps: the PPS source74 *75 * This function is used to disable kernel consumer on PPS source removal76 * if this source was bound to PPS kernel consumer. Can be called on any77 * source safely. Should not be called in interrupt context.78 */79void pps_kc_remove(struct pps_device *pps)80{81 spin_lock_irq(&pps_kc_hardpps_lock);82 if (pps == pps_kc_hardpps_dev) {83 pps_kc_hardpps_mode = 0;84 pps_kc_hardpps_dev = NULL;85 spin_unlock_irq(&pps_kc_hardpps_lock);86 dev_info(pps->dev, "unbound kernel consumer"87 " on device removal\n");88 } else89 spin_unlock_irq(&pps_kc_hardpps_lock);90}91 92/* pps_kc_event - call hardpps() on PPS event93 * @pps: the PPS source94 * @ts: PPS event timestamp95 * @event: PPS event edge96 *97 * This function calls hardpps() when an event from bound PPS source occurs.98 */99void pps_kc_event(struct pps_device *pps, struct pps_event_time *ts,100 int event)101{102 unsigned long flags;103 104 /* Pass some events to kernel consumer if activated */105 spin_lock_irqsave(&pps_kc_hardpps_lock, flags);106 if (pps == pps_kc_hardpps_dev && event & pps_kc_hardpps_mode)107 hardpps(&ts->ts_real, &ts->ts_raw);108 spin_unlock_irqrestore(&pps_kc_hardpps_lock, flags);109}110