forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
109 lines
3.1 KiB
109 lines
3.1 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* |
|
* PPS kernel consumer API |
|
* |
|
* Copyright (C) 2009-2010 Alexander Gordeev <[email protected]> |
|
*/ |
|
|
|
#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); |
|
}
|
|
|