162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * PPS kernel consumer API
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2009-2010   Alexander Gordeev <lasaine@lvk.cs.msu.su>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/kernel.h>
1162306a36Sopenharmony_ci#include <linux/module.h>
1262306a36Sopenharmony_ci#include <linux/device.h>
1362306a36Sopenharmony_ci#include <linux/init.h>
1462306a36Sopenharmony_ci#include <linux/spinlock.h>
1562306a36Sopenharmony_ci#include <linux/pps_kernel.h>
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include "kc.h"
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci/*
2062306a36Sopenharmony_ci * Global variables
2162306a36Sopenharmony_ci */
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci/* state variables to bind kernel consumer */
2462306a36Sopenharmony_cistatic DEFINE_SPINLOCK(pps_kc_hardpps_lock);
2562306a36Sopenharmony_ci/* PPS API (RFC 2783): current source and mode for kernel consumer */
2662306a36Sopenharmony_cistatic struct pps_device *pps_kc_hardpps_dev;	/* unique pointer to device */
2762306a36Sopenharmony_cistatic int pps_kc_hardpps_mode;		/* mode bits for kernel consumer */
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci/* pps_kc_bind - control PPS kernel consumer binding
3062306a36Sopenharmony_ci * @pps: the PPS source
3162306a36Sopenharmony_ci * @bind_args: kernel consumer bind parameters
3262306a36Sopenharmony_ci *
3362306a36Sopenharmony_ci * This function is used to bind or unbind PPS kernel consumer according to
3462306a36Sopenharmony_ci * supplied parameters. Should not be called in interrupt context.
3562306a36Sopenharmony_ci */
3662306a36Sopenharmony_ciint pps_kc_bind(struct pps_device *pps, struct pps_bind_args *bind_args)
3762306a36Sopenharmony_ci{
3862306a36Sopenharmony_ci	/* Check if another consumer is already bound */
3962306a36Sopenharmony_ci	spin_lock_irq(&pps_kc_hardpps_lock);
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	if (bind_args->edge == 0)
4262306a36Sopenharmony_ci		if (pps_kc_hardpps_dev == pps) {
4362306a36Sopenharmony_ci			pps_kc_hardpps_mode = 0;
4462306a36Sopenharmony_ci			pps_kc_hardpps_dev = NULL;
4562306a36Sopenharmony_ci			spin_unlock_irq(&pps_kc_hardpps_lock);
4662306a36Sopenharmony_ci			dev_info(pps->dev, "unbound kernel"
4762306a36Sopenharmony_ci					" consumer\n");
4862306a36Sopenharmony_ci		} else {
4962306a36Sopenharmony_ci			spin_unlock_irq(&pps_kc_hardpps_lock);
5062306a36Sopenharmony_ci			dev_err(pps->dev, "selected kernel consumer"
5162306a36Sopenharmony_ci					" is not bound\n");
5262306a36Sopenharmony_ci			return -EINVAL;
5362306a36Sopenharmony_ci		}
5462306a36Sopenharmony_ci	else
5562306a36Sopenharmony_ci		if (pps_kc_hardpps_dev == NULL ||
5662306a36Sopenharmony_ci				pps_kc_hardpps_dev == pps) {
5762306a36Sopenharmony_ci			pps_kc_hardpps_mode = bind_args->edge;
5862306a36Sopenharmony_ci			pps_kc_hardpps_dev = pps;
5962306a36Sopenharmony_ci			spin_unlock_irq(&pps_kc_hardpps_lock);
6062306a36Sopenharmony_ci			dev_info(pps->dev, "bound kernel consumer: "
6162306a36Sopenharmony_ci				"edge=0x%x\n", bind_args->edge);
6262306a36Sopenharmony_ci		} else {
6362306a36Sopenharmony_ci			spin_unlock_irq(&pps_kc_hardpps_lock);
6462306a36Sopenharmony_ci			dev_err(pps->dev, "another kernel consumer"
6562306a36Sopenharmony_ci					" is already bound\n");
6662306a36Sopenharmony_ci			return -EINVAL;
6762306a36Sopenharmony_ci		}
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	return 0;
7062306a36Sopenharmony_ci}
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci/* pps_kc_remove - unbind kernel consumer on PPS source removal
7362306a36Sopenharmony_ci * @pps: the PPS source
7462306a36Sopenharmony_ci *
7562306a36Sopenharmony_ci * This function is used to disable kernel consumer on PPS source removal
7662306a36Sopenharmony_ci * if this source was bound to PPS kernel consumer. Can be called on any
7762306a36Sopenharmony_ci * source safely. Should not be called in interrupt context.
7862306a36Sopenharmony_ci */
7962306a36Sopenharmony_civoid pps_kc_remove(struct pps_device *pps)
8062306a36Sopenharmony_ci{
8162306a36Sopenharmony_ci	spin_lock_irq(&pps_kc_hardpps_lock);
8262306a36Sopenharmony_ci	if (pps == pps_kc_hardpps_dev) {
8362306a36Sopenharmony_ci		pps_kc_hardpps_mode = 0;
8462306a36Sopenharmony_ci		pps_kc_hardpps_dev = NULL;
8562306a36Sopenharmony_ci		spin_unlock_irq(&pps_kc_hardpps_lock);
8662306a36Sopenharmony_ci		dev_info(pps->dev, "unbound kernel consumer"
8762306a36Sopenharmony_ci				" on device removal\n");
8862306a36Sopenharmony_ci	} else
8962306a36Sopenharmony_ci		spin_unlock_irq(&pps_kc_hardpps_lock);
9062306a36Sopenharmony_ci}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci/* pps_kc_event - call hardpps() on PPS event
9362306a36Sopenharmony_ci * @pps: the PPS source
9462306a36Sopenharmony_ci * @ts: PPS event timestamp
9562306a36Sopenharmony_ci * @event: PPS event edge
9662306a36Sopenharmony_ci *
9762306a36Sopenharmony_ci * This function calls hardpps() when an event from bound PPS source occurs.
9862306a36Sopenharmony_ci */
9962306a36Sopenharmony_civoid pps_kc_event(struct pps_device *pps, struct pps_event_time *ts,
10062306a36Sopenharmony_ci		int event)
10162306a36Sopenharmony_ci{
10262306a36Sopenharmony_ci	unsigned long flags;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	/* Pass some events to kernel consumer if activated */
10562306a36Sopenharmony_ci	spin_lock_irqsave(&pps_kc_hardpps_lock, flags);
10662306a36Sopenharmony_ci	if (pps == pps_kc_hardpps_dev && event & pps_kc_hardpps_mode)
10762306a36Sopenharmony_ci		hardpps(&ts->ts_real, &ts->ts_raw);
10862306a36Sopenharmony_ci	spin_unlock_irqrestore(&pps_kc_hardpps_lock, flags);
10962306a36Sopenharmony_ci}
110