hc
2024-08-16 62c46c9150c4afde7e5b25436263fddf79d66f0b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * PPS kernel consumer API
 *
 * Copyright (C) 2009-2010   Alexander Gordeev <lasaine@lvk.cs.msu.su>
 */
 
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/spinlock.h>
#include <linux/pps_kernel.h>
 
#include "kc.h"
 
/*
 * Global variables
 */
 
/* state variables to bind kernel consumer */
static DEFINE_SPINLOCK(pps_kc_hardpps_lock);
/* PPS API (RFC 2783): current source and mode for kernel consumer */
static struct pps_device *pps_kc_hardpps_dev;    /* unique pointer to device */
static int pps_kc_hardpps_mode;        /* mode bits for kernel consumer */
 
/* pps_kc_bind - control PPS kernel consumer binding
 * @pps: the PPS source
 * @bind_args: kernel consumer bind parameters
 *
 * This function is used to bind or unbind PPS kernel consumer according to
 * supplied parameters. Should not be called in interrupt context.
 */
int pps_kc_bind(struct pps_device *pps, struct pps_bind_args *bind_args)
{
   /* Check if another consumer is already bound */
   spin_lock_irq(&pps_kc_hardpps_lock);
 
   if (bind_args->edge == 0)
       if (pps_kc_hardpps_dev == pps) {
           pps_kc_hardpps_mode = 0;
           pps_kc_hardpps_dev = NULL;
           spin_unlock_irq(&pps_kc_hardpps_lock);
           dev_info(pps->dev, "unbound kernel"
                   " consumer\n");
       } else {
           spin_unlock_irq(&pps_kc_hardpps_lock);
           dev_err(pps->dev, "selected kernel consumer"
                   " is not bound\n");
           return -EINVAL;
       }
   else
       if (pps_kc_hardpps_dev == NULL ||
               pps_kc_hardpps_dev == pps) {
           pps_kc_hardpps_mode = bind_args->edge;
           pps_kc_hardpps_dev = pps;
           spin_unlock_irq(&pps_kc_hardpps_lock);
           dev_info(pps->dev, "bound kernel consumer: "
               "edge=0x%x\n", bind_args->edge);
       } else {
           spin_unlock_irq(&pps_kc_hardpps_lock);
           dev_err(pps->dev, "another kernel consumer"
                   " is already bound\n");
           return -EINVAL;
       }
 
   return 0;
}
 
/* pps_kc_remove - unbind kernel consumer on PPS source removal
 * @pps: the PPS source
 *
 * This function is used to disable kernel consumer on PPS source removal
 * if this source was bound to PPS kernel consumer. Can be called on any
 * source safely. Should not be called in interrupt context.
 */
void pps_kc_remove(struct pps_device *pps)
{
   spin_lock_irq(&pps_kc_hardpps_lock);
   if (pps == pps_kc_hardpps_dev) {
       pps_kc_hardpps_mode = 0;
       pps_kc_hardpps_dev = NULL;
       spin_unlock_irq(&pps_kc_hardpps_lock);
       dev_info(pps->dev, "unbound kernel consumer"
               " on device removal\n");
   } else
       spin_unlock_irq(&pps_kc_hardpps_lock);
}
 
/* pps_kc_event - call hardpps() on PPS event
 * @pps: the PPS source
 * @ts: PPS event timestamp
 * @event: PPS event edge
 *
 * This function calls hardpps() when an event from bound PPS source occurs.
 */
void pps_kc_event(struct pps_device *pps, struct pps_event_time *ts,
       int event)
{
   unsigned long flags;
 
   /* Pass some events to kernel consumer if activated */
   spin_lock_irqsave(&pps_kc_hardpps_lock, flags);
   if (pps == pps_kc_hardpps_dev && event & pps_kc_hardpps_mode)
       hardpps(&ts->ts_real, &ts->ts_raw);
   spin_unlock_irqrestore(&pps_kc_hardpps_lock, flags);
}