xref: /linux/drivers/pps/kc.c (revision 58e16d792a6a8c6b750f637a4649967fcac853dc)
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