Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/sage/ceph...
[platform/kernel/linux-rpi.git] / drivers / pps / kc.c
1 /*
2  * PPS kernel consumer API
3  *
4  * Copyright (C) 2009-2010   Alexander Gordeev <lasaine@lvk.cs.msu.su>
5  *
6  *   This program is free software; you can redistribute it and/or modify
7  *   it under the terms of the GNU General Public License as published by
8  *   the Free Software Foundation; either version 2 of the License, or
9  *   (at your option) any later version.
10  *
11  *   This program is distributed in the hope that it will be useful,
12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *   GNU General Public License for more details.
15  *
16  *   You should have received a copy of the GNU General Public License
17  *   along with this program; if not, write to the Free Software
18  *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19  */
20
21 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
22
23 #include <linux/kernel.h>
24 #include <linux/module.h>
25 #include <linux/device.h>
26 #include <linux/init.h>
27 #include <linux/spinlock.h>
28 #include <linux/pps_kernel.h>
29
30 #include "kc.h"
31
32 /*
33  * Global variables
34  */
35
36 /* state variables to bind kernel consumer */
37 static DEFINE_SPINLOCK(pps_kc_hardpps_lock);
38 /* PPS API (RFC 2783): current source and mode for kernel consumer */
39 static struct pps_device *pps_kc_hardpps_dev;   /* unique pointer to device */
40 static int pps_kc_hardpps_mode;         /* mode bits for kernel consumer */
41
42 /* pps_kc_bind - control PPS kernel consumer binding
43  * @pps: the PPS source
44  * @bind_args: kernel consumer bind parameters
45  *
46  * This function is used to bind or unbind PPS kernel consumer according to
47  * supplied parameters. Should not be called in interrupt context.
48  */
49 int pps_kc_bind(struct pps_device *pps, struct pps_bind_args *bind_args)
50 {
51         /* Check if another consumer is already bound */
52         spin_lock_irq(&pps_kc_hardpps_lock);
53
54         if (bind_args->edge == 0)
55                 if (pps_kc_hardpps_dev == pps) {
56                         pps_kc_hardpps_mode = 0;
57                         pps_kc_hardpps_dev = NULL;
58                         spin_unlock_irq(&pps_kc_hardpps_lock);
59                         dev_info(pps->dev, "unbound kernel"
60                                         " consumer\n");
61                 } else {
62                         spin_unlock_irq(&pps_kc_hardpps_lock);
63                         dev_err(pps->dev, "selected kernel consumer"
64                                         " is not bound\n");
65                         return -EINVAL;
66                 }
67         else
68                 if (pps_kc_hardpps_dev == NULL ||
69                                 pps_kc_hardpps_dev == pps) {
70                         pps_kc_hardpps_mode = bind_args->edge;
71                         pps_kc_hardpps_dev = pps;
72                         spin_unlock_irq(&pps_kc_hardpps_lock);
73                         dev_info(pps->dev, "bound kernel consumer: "
74                                 "edge=0x%x\n", bind_args->edge);
75                 } else {
76                         spin_unlock_irq(&pps_kc_hardpps_lock);
77                         dev_err(pps->dev, "another kernel consumer"
78                                         " is already bound\n");
79                         return -EINVAL;
80                 }
81
82         return 0;
83 }
84
85 /* pps_kc_remove - unbind kernel consumer on PPS source removal
86  * @pps: the PPS source
87  *
88  * This function is used to disable kernel consumer on PPS source removal
89  * if this source was bound to PPS kernel consumer. Can be called on any
90  * source safely. Should not be called in interrupt context.
91  */
92 void pps_kc_remove(struct pps_device *pps)
93 {
94         spin_lock_irq(&pps_kc_hardpps_lock);
95         if (pps == pps_kc_hardpps_dev) {
96                 pps_kc_hardpps_mode = 0;
97                 pps_kc_hardpps_dev = NULL;
98                 spin_unlock_irq(&pps_kc_hardpps_lock);
99                 dev_info(pps->dev, "unbound kernel consumer"
100                                 " on device removal\n");
101         } else
102                 spin_unlock_irq(&pps_kc_hardpps_lock);
103 }
104
105 /* pps_kc_event - call hardpps() on PPS event
106  * @pps: the PPS source
107  * @ts: PPS event timestamp
108  * @event: PPS event edge
109  *
110  * This function calls hardpps() when an event from bound PPS source occurs.
111  */
112 void pps_kc_event(struct pps_device *pps, struct pps_event_time *ts,
113                 int event)
114 {
115         unsigned long flags;
116
117         /* Pass some events to kernel consumer if activated */
118         spin_lock_irqsave(&pps_kc_hardpps_lock, flags);
119         if (pps == pps_kc_hardpps_dev && event & pps_kc_hardpps_mode)
120                 hardpps(&ts->ts_real, &ts->ts_raw);
121         spin_unlock_irqrestore(&pps_kc_hardpps_lock, flags);
122 }