Linux-libre 5.4.49-gnu
[librecmc/linux-libre.git] / drivers / pps / kc.c
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 */
24 static DEFINE_SPINLOCK(pps_kc_hardpps_lock);
25 /* PPS API (RFC 2783): current source and mode for kernel consumer */
26 static struct pps_device *pps_kc_hardpps_dev;   /* unique pointer to device */
27 static 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  */
36 int 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  */
79 void 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  */
99 void 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 }