1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * PPS kernel consumer API
4 *
5 * Copyright (C) 2009-2010   Alexander Gordeev <lasaine@lvk.cs.msu.su>
6 */
7
8#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
9
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 variables
21 */
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 binding
30 * @pps: the PPS source
31 * @bind_args: kernel consumer bind parameters
32 *
33 * This function is used to bind or unbind PPS kernel consumer according to
34 * 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	else
55		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 removal
73 * @pps: the PPS source
74 *
75 * This function is used to disable kernel consumer on PPS source removal
76 * if this source was bound to PPS kernel consumer. Can be called on any
77 * 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	} else
89		spin_unlock_irq(&pps_kc_hardpps_lock);
90}
91
92/* pps_kc_event - call hardpps() on PPS event
93 * @pps: the PPS source
94 * @ts: PPS event timestamp
95 * @event: PPS event edge
96 *
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