1*74ba9207SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2717c0336SAlexander Gordeev /*
3717c0336SAlexander Gordeev * PPS kernel consumer API
4717c0336SAlexander Gordeev *
5717c0336SAlexander Gordeev * Copyright (C) 2009-2010 Alexander Gordeev <lasaine@lvk.cs.msu.su>
6717c0336SAlexander Gordeev */
7717c0336SAlexander Gordeev
8717c0336SAlexander Gordeev #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
9717c0336SAlexander Gordeev
10717c0336SAlexander Gordeev #include <linux/kernel.h>
11717c0336SAlexander Gordeev #include <linux/module.h>
12717c0336SAlexander Gordeev #include <linux/device.h>
13717c0336SAlexander Gordeev #include <linux/init.h>
14717c0336SAlexander Gordeev #include <linux/spinlock.h>
15717c0336SAlexander Gordeev #include <linux/pps_kernel.h>
16717c0336SAlexander Gordeev
17717c0336SAlexander Gordeev #include "kc.h"
18717c0336SAlexander Gordeev
19717c0336SAlexander Gordeev /*
20717c0336SAlexander Gordeev * Global variables
21717c0336SAlexander Gordeev */
22717c0336SAlexander Gordeev
23717c0336SAlexander Gordeev /* state variables to bind kernel consumer */
2497439d0fSFengguang Wu static DEFINE_SPINLOCK(pps_kc_hardpps_lock);
25717c0336SAlexander Gordeev /* PPS API (RFC 2783): current source and mode for kernel consumer */
2697439d0fSFengguang Wu static struct pps_device *pps_kc_hardpps_dev; /* unique pointer to device */
2797439d0fSFengguang Wu static int pps_kc_hardpps_mode; /* mode bits for kernel consumer */
28717c0336SAlexander Gordeev
29717c0336SAlexander Gordeev /* pps_kc_bind - control PPS kernel consumer binding
30717c0336SAlexander Gordeev * @pps: the PPS source
31717c0336SAlexander Gordeev * @bind_args: kernel consumer bind parameters
32717c0336SAlexander Gordeev *
33717c0336SAlexander Gordeev * This function is used to bind or unbind PPS kernel consumer according to
34717c0336SAlexander Gordeev * supplied parameters. Should not be called in interrupt context.
35717c0336SAlexander Gordeev */
pps_kc_bind(struct pps_device * pps,struct pps_bind_args * bind_args)36717c0336SAlexander Gordeev int pps_kc_bind(struct pps_device *pps, struct pps_bind_args *bind_args)
37717c0336SAlexander Gordeev {
38717c0336SAlexander Gordeev /* Check if another consumer is already bound */
39717c0336SAlexander Gordeev spin_lock_irq(&pps_kc_hardpps_lock);
40717c0336SAlexander Gordeev
41717c0336SAlexander Gordeev if (bind_args->edge == 0)
42717c0336SAlexander Gordeev if (pps_kc_hardpps_dev == pps) {
43717c0336SAlexander Gordeev pps_kc_hardpps_mode = 0;
44717c0336SAlexander Gordeev pps_kc_hardpps_dev = NULL;
45717c0336SAlexander Gordeev spin_unlock_irq(&pps_kc_hardpps_lock);
46717c0336SAlexander Gordeev dev_info(pps->dev, "unbound kernel"
47717c0336SAlexander Gordeev " consumer\n");
48717c0336SAlexander Gordeev } else {
49717c0336SAlexander Gordeev spin_unlock_irq(&pps_kc_hardpps_lock);
50717c0336SAlexander Gordeev dev_err(pps->dev, "selected kernel consumer"
51717c0336SAlexander Gordeev " is not bound\n");
52717c0336SAlexander Gordeev return -EINVAL;
53717c0336SAlexander Gordeev }
54717c0336SAlexander Gordeev else
55717c0336SAlexander Gordeev if (pps_kc_hardpps_dev == NULL ||
56717c0336SAlexander Gordeev pps_kc_hardpps_dev == pps) {
57717c0336SAlexander Gordeev pps_kc_hardpps_mode = bind_args->edge;
58717c0336SAlexander Gordeev pps_kc_hardpps_dev = pps;
59717c0336SAlexander Gordeev spin_unlock_irq(&pps_kc_hardpps_lock);
60717c0336SAlexander Gordeev dev_info(pps->dev, "bound kernel consumer: "
61717c0336SAlexander Gordeev "edge=0x%x\n", bind_args->edge);
62717c0336SAlexander Gordeev } else {
63717c0336SAlexander Gordeev spin_unlock_irq(&pps_kc_hardpps_lock);
64717c0336SAlexander Gordeev dev_err(pps->dev, "another kernel consumer"
65717c0336SAlexander Gordeev " is already bound\n");
66717c0336SAlexander Gordeev return -EINVAL;
67717c0336SAlexander Gordeev }
68717c0336SAlexander Gordeev
69717c0336SAlexander Gordeev return 0;
70717c0336SAlexander Gordeev }
71717c0336SAlexander Gordeev
72717c0336SAlexander Gordeev /* pps_kc_remove - unbind kernel consumer on PPS source removal
73717c0336SAlexander Gordeev * @pps: the PPS source
74717c0336SAlexander Gordeev *
75717c0336SAlexander Gordeev * This function is used to disable kernel consumer on PPS source removal
76717c0336SAlexander Gordeev * if this source was bound to PPS kernel consumer. Can be called on any
77717c0336SAlexander Gordeev * source safely. Should not be called in interrupt context.
78717c0336SAlexander Gordeev */
pps_kc_remove(struct pps_device * pps)79717c0336SAlexander Gordeev void pps_kc_remove(struct pps_device *pps)
80717c0336SAlexander Gordeev {
81717c0336SAlexander Gordeev spin_lock_irq(&pps_kc_hardpps_lock);
82717c0336SAlexander Gordeev if (pps == pps_kc_hardpps_dev) {
83717c0336SAlexander Gordeev pps_kc_hardpps_mode = 0;
84717c0336SAlexander Gordeev pps_kc_hardpps_dev = NULL;
85717c0336SAlexander Gordeev spin_unlock_irq(&pps_kc_hardpps_lock);
86717c0336SAlexander Gordeev dev_info(pps->dev, "unbound kernel consumer"
87717c0336SAlexander Gordeev " on device removal\n");
88717c0336SAlexander Gordeev } else
89717c0336SAlexander Gordeev spin_unlock_irq(&pps_kc_hardpps_lock);
90717c0336SAlexander Gordeev }
91717c0336SAlexander Gordeev
92717c0336SAlexander Gordeev /* pps_kc_event - call hardpps() on PPS event
93717c0336SAlexander Gordeev * @pps: the PPS source
94717c0336SAlexander Gordeev * @ts: PPS event timestamp
95717c0336SAlexander Gordeev * @event: PPS event edge
96717c0336SAlexander Gordeev *
97717c0336SAlexander Gordeev * This function calls hardpps() when an event from bound PPS source occurs.
98717c0336SAlexander Gordeev */
pps_kc_event(struct pps_device * pps,struct pps_event_time * ts,int event)99717c0336SAlexander Gordeev void pps_kc_event(struct pps_device *pps, struct pps_event_time *ts,
100717c0336SAlexander Gordeev int event)
101717c0336SAlexander Gordeev {
102717c0336SAlexander Gordeev unsigned long flags;
103717c0336SAlexander Gordeev
104717c0336SAlexander Gordeev /* Pass some events to kernel consumer if activated */
105717c0336SAlexander Gordeev spin_lock_irqsave(&pps_kc_hardpps_lock, flags);
106717c0336SAlexander Gordeev if (pps == pps_kc_hardpps_dev && event & pps_kc_hardpps_mode)
107717c0336SAlexander Gordeev hardpps(&ts->ts_real, &ts->ts_raw);
108717c0336SAlexander Gordeev spin_unlock_irqrestore(&pps_kc_hardpps_lock, flags);
109717c0336SAlexander Gordeev }
110