projects/linux·blob master · refs

119 lines · 3.3 KiB

1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * PPS kernel consumer API
4 *
5 * Copyright (C) 2009-2010 Alexander Gordeev <[email protected]>
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 */
24static DEFINE_SPINLOCK(pps_kc_hardpps_lock);
25/* PPS API (RFC 2783): current source and mode for kernel consumer */
26static struct pps_device *pps_kc_hardpps_dev; /* unique pointer to device */
27static 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 */
36int 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 */
87void 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 */
109void 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}