xref: /linux/drivers/pps/kc.c (revision 7db28abbea0f7dc1ec4fdfdc149db5fbd9e4c994)
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 	/*
42 	 * Don't allow PPS_KC_BIND on a removed device.
43 	 */
44 	if (pps->kc_removed) {
45 		spin_unlock_irq(&pps_kc_hardpps_lock);
46 		return -ENODEV;
47 	}
48 
49 	if (bind_args->edge == 0)
50 		if (pps_kc_hardpps_dev == pps) {
51 			pps_kc_hardpps_mode = 0;
52 			pps_kc_hardpps_dev = NULL;
53 			spin_unlock_irq(&pps_kc_hardpps_lock);
54 			dev_info(&pps->dev, "unbound kernel"
55 					" consumer\n");
56 		} else {
57 			spin_unlock_irq(&pps_kc_hardpps_lock);
58 			dev_err(&pps->dev, "selected kernel consumer"
59 					" is not bound\n");
60 			return -EINVAL;
61 		}
62 	else
63 		if (pps_kc_hardpps_dev == NULL ||
64 				pps_kc_hardpps_dev == pps) {
65 			pps_kc_hardpps_mode = bind_args->edge;
66 			pps_kc_hardpps_dev = pps;
67 			spin_unlock_irq(&pps_kc_hardpps_lock);
68 			dev_info(&pps->dev, "bound kernel consumer: "
69 				"edge=0x%x\n", bind_args->edge);
70 		} else {
71 			spin_unlock_irq(&pps_kc_hardpps_lock);
72 			dev_err(&pps->dev, "another kernel consumer"
73 					" is already bound\n");
74 			return -EINVAL;
75 		}
76 
77 	return 0;
78 }
79 
80 /* pps_kc_remove - unbind kernel consumer on PPS source removal
81  * @pps: the PPS source
82  *
83  * This function is used to disable kernel consumer on PPS source removal
84  * if this source was bound to PPS kernel consumer. Can be called on any
85  * source safely. Should not be called in interrupt context.
86  */
87 void pps_kc_remove(struct pps_device *pps)
88 {
89 	spin_lock_irq(&pps_kc_hardpps_lock);
90 
91 	pps->kc_removed = true;
92 	if (pps == pps_kc_hardpps_dev) {
93 		pps_kc_hardpps_mode = 0;
94 		pps_kc_hardpps_dev = NULL;
95 		spin_unlock_irq(&pps_kc_hardpps_lock);
96 		dev_info(&pps->dev, "unbound kernel consumer"
97 				" on device removal\n");
98 	} else
99 		spin_unlock_irq(&pps_kc_hardpps_lock);
100 }
101 
102 /* pps_kc_event - call hardpps() on PPS event
103  * @pps: the PPS source
104  * @ts: PPS event timestamp
105  * @event: PPS event edge
106  *
107  * This function calls hardpps() when an event from bound PPS source occurs.
108  */
109 void pps_kc_event(struct pps_device *pps, struct pps_event_time *ts,
110 		int event)
111 {
112 	unsigned long flags;
113 
114 	/* Pass some events to kernel consumer if activated */
115 	spin_lock_irqsave(&pps_kc_hardpps_lock, flags);
116 	if (pps == pps_kc_hardpps_dev && event & pps_kc_hardpps_mode)
117 		hardpps(&ts->ts_real, &ts->ts_raw);
118 	spin_unlock_irqrestore(&pps_kc_hardpps_lock, flags);
119 }
120