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 */
37static DEFINE_SPINLOCK(pps_kc_hardpps_lock);
38/* PPS API (RFC 2783): current source and mode for kernel consumer */
39static struct pps_device *pps_kc_hardpps_dev;	/* unique pointer to device */
40static 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 */
49int 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 */
92void 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 */
112void 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}
123