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