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(lock: &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(lock: &pps_kc_hardpps_lock); |
46 | dev_info(pps->dev, "unbound kernel" |
47 | " consumer\n" ); |
48 | } else { |
49 | spin_unlock_irq(lock: &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(lock: &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(lock: &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(lock: &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(lock: &pps_kc_hardpps_lock); |
86 | dev_info(pps->dev, "unbound kernel consumer" |
87 | " on device removal\n" ); |
88 | } else |
89 | spin_unlock_irq(lock: &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(lock: &pps_kc_hardpps_lock, flags); |
109 | } |
110 | |