Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+37
View File
@@ -0,0 +1,37 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# PPS clients configuration
#
comment "PPS clients support"
config PPS_CLIENT_KTIMER
tristate "Kernel timer client (Testing client, use for debug)"
help
If you say yes here you get support for a PPS debugging client
which uses a kernel timer to generate the PPS signal.
This driver can also be built as a module. If so, the module
will be called pps-ktimer.
config PPS_CLIENT_LDISC
tristate "PPS line discipline"
depends on TTY
help
If you say yes here you get support for a PPS source connected
with the CD (Carrier Detect) pin of your serial port.
config PPS_CLIENT_PARPORT
tristate "Parallel port PPS client"
depends on PARPORT
help
If you say yes here you get support for a PPS source connected
with the interrupt pin of your parallel port.
config PPS_CLIENT_GPIO
tristate "PPS client using GPIO"
help
If you say yes here you get support for a PPS source using
GPIO. To be useful you must also register a platform device
specifying the GPIO pin and other options, usually in your board
setup.
+11
View File
@@ -0,0 +1,11 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Makefile for PPS clients.
#
obj-$(CONFIG_PPS_CLIENT_KTIMER) += pps-ktimer.o
obj-$(CONFIG_PPS_CLIENT_LDISC) += pps-ldisc.o
obj-$(CONFIG_PPS_CLIENT_PARPORT) += pps_parport.o
obj-$(CONFIG_PPS_CLIENT_GPIO) += pps-gpio.o
ccflags-$(CONFIG_PPS_DEBUG) := -DDEBUG
+258
View File
@@ -0,0 +1,258 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* pps-gpio.c -- PPS client driver using GPIO
*
* Copyright (C) 2010 Ricardo Martins <rasm@fe.up.pt>
* Copyright (C) 2011 James Nuss <jamesnuss@nanometrics.ca>
*/
#define PPS_GPIO_NAME "pps-gpio"
#define pr_fmt(fmt) PPS_GPIO_NAME ": " fmt
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/pps_kernel.h>
#include <linux/gpio/consumer.h>
#include <linux/list.h>
#include <linux/property.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
/* Info for each registered platform device */
struct pps_gpio_device_data {
int irq; /* IRQ used as PPS source */
struct pps_device *pps; /* PPS source device */
struct pps_source_info info; /* PPS source information */
struct gpio_desc *gpio_pin; /* GPIO port descriptors */
struct gpio_desc *echo_pin;
struct timer_list echo_timer; /* timer to reset echo active state */
bool assert_falling_edge;
bool capture_clear;
unsigned int echo_active_ms; /* PPS echo active duration */
unsigned long echo_timeout; /* timer timeout value in jiffies */
};
/*
* Report the PPS event
*/
static irqreturn_t pps_gpio_irq_handler(int irq, void *data)
{
const struct pps_gpio_device_data *info;
struct pps_event_time ts;
int rising_edge;
/* Get the time stamp first */
pps_get_ts(&ts);
info = data;
/* Small trick to bypass the check on edge's direction when capture_clear is unset */
rising_edge = info->capture_clear ?
gpiod_get_value(info->gpio_pin) : !info->assert_falling_edge;
if ((rising_edge && !info->assert_falling_edge) ||
(!rising_edge && info->assert_falling_edge))
pps_event(info->pps, &ts, PPS_CAPTUREASSERT, data);
else if (info->capture_clear &&
((rising_edge && info->assert_falling_edge) ||
(!rising_edge && !info->assert_falling_edge)))
pps_event(info->pps, &ts, PPS_CAPTURECLEAR, data);
else
dev_warn_ratelimited(&info->pps->dev, "IRQ did not trigger any PPS event\n");
return IRQ_HANDLED;
}
/* This function will only be called when an ECHO GPIO is defined */
static void pps_gpio_echo(struct pps_device *pps, int event, void *data)
{
/* add_timer() needs to write into info->echo_timer */
struct pps_gpio_device_data *info = data;
switch (event) {
case PPS_CAPTUREASSERT:
if (pps->params.mode & PPS_ECHOASSERT)
gpiod_set_value(info->echo_pin, 1);
break;
case PPS_CAPTURECLEAR:
if (pps->params.mode & PPS_ECHOCLEAR)
gpiod_set_value(info->echo_pin, 1);
break;
}
/* fire the timer */
if (info->pps->params.mode & (PPS_ECHOASSERT | PPS_ECHOCLEAR)) {
info->echo_timer.expires = jiffies + info->echo_timeout;
add_timer(&info->echo_timer);
}
}
/* Timer callback to reset the echo pin to the inactive state */
static void pps_gpio_echo_timer_callback(struct timer_list *t)
{
const struct pps_gpio_device_data *info;
info = timer_container_of(info, t, echo_timer);
gpiod_set_value(info->echo_pin, 0);
}
static int pps_gpio_setup(struct device *dev)
{
struct pps_gpio_device_data *data = dev_get_drvdata(dev);
int ret;
u32 value;
data->gpio_pin = devm_gpiod_get(dev, NULL, GPIOD_IN);
if (IS_ERR(data->gpio_pin))
return dev_err_probe(dev, PTR_ERR(data->gpio_pin),
"failed to request PPS GPIO\n");
data->assert_falling_edge =
device_property_read_bool(dev, "assert-falling-edge");
data->echo_pin = devm_gpiod_get_optional(dev, "echo", GPIOD_OUT_LOW);
if (IS_ERR(data->echo_pin))
return dev_err_probe(dev, PTR_ERR(data->echo_pin),
"failed to request ECHO GPIO\n");
if (!data->echo_pin)
return 0;
ret = device_property_read_u32(dev, "echo-active-ms", &value);
if (ret) {
dev_err(dev, "failed to get echo-active-ms from FW\n");
return ret;
}
/* sanity check on echo_active_ms */
if (!value || value > 999) {
dev_err(dev, "echo-active-ms: %u - bad value from FW\n", value);
return -EINVAL;
}
data->echo_active_ms = value;
return 0;
}
static unsigned long
get_irqf_trigger_flags(const struct pps_gpio_device_data *data)
{
unsigned long flags = data->assert_falling_edge ?
IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING;
if (data->capture_clear) {
flags |= ((flags & IRQF_TRIGGER_RISING) ?
IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING);
}
return flags;
}
static int pps_gpio_probe(struct platform_device *pdev)
{
struct pps_gpio_device_data *data;
struct device *dev = &pdev->dev;
int ret;
int pps_default_params;
/* allocate space for device info */
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
dev_set_drvdata(dev, data);
/* GPIO setup */
ret = pps_gpio_setup(dev);
if (ret)
return ret;
/* IRQ setup */
ret = gpiod_to_irq(data->gpio_pin);
if (ret < 0) {
dev_err(dev, "failed to map GPIO to IRQ: %d\n", ret);
return -EINVAL;
}
data->irq = ret;
/* initialize PPS specific parts of the bookkeeping data structure. */
data->info.mode = PPS_CAPTUREASSERT | PPS_OFFSETASSERT |
PPS_ECHOASSERT | PPS_CANWAIT | PPS_TSFMT_TSPEC;
if (data->capture_clear)
data->info.mode |= PPS_CAPTURECLEAR | PPS_OFFSETCLEAR |
PPS_ECHOCLEAR;
data->info.owner = THIS_MODULE;
snprintf(data->info.name, PPS_MAX_NAME_LEN - 1, "%s.%d",
pdev->name, pdev->id);
if (data->echo_pin) {
data->info.echo = pps_gpio_echo;
data->echo_timeout = msecs_to_jiffies(data->echo_active_ms);
timer_setup(&data->echo_timer, pps_gpio_echo_timer_callback, 0);
}
/* register PPS source */
pps_default_params = PPS_CAPTUREASSERT | PPS_OFFSETASSERT;
if (data->capture_clear)
pps_default_params |= PPS_CAPTURECLEAR | PPS_OFFSETCLEAR;
data->pps = pps_register_source(&data->info, pps_default_params);
if (IS_ERR(data->pps)) {
dev_err(dev, "failed to register IRQ %d as PPS source\n",
data->irq);
return PTR_ERR(data->pps);
}
/* register IRQ interrupt handler */
ret = request_irq(data->irq, pps_gpio_irq_handler,
get_irqf_trigger_flags(data), data->info.name, data);
if (ret) {
pps_unregister_source(data->pps);
dev_err(dev, "failed to acquire IRQ %d\n", data->irq);
return -EINVAL;
}
dev_dbg(&data->pps->dev, "Registered IRQ %d as PPS source\n",
data->irq);
return 0;
}
static void pps_gpio_remove(struct platform_device *pdev)
{
struct pps_gpio_device_data *data = platform_get_drvdata(pdev);
free_irq(data->irq, data);
pps_unregister_source(data->pps);
timer_delete_sync(&data->echo_timer);
/* reset echo pin in any case */
gpiod_set_value(data->echo_pin, 0);
dev_info(&pdev->dev, "removed IRQ %d as PPS source\n", data->irq);
}
static const struct of_device_id pps_gpio_dt_ids[] = {
{ .compatible = "pps-gpio", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, pps_gpio_dt_ids);
static struct platform_driver pps_gpio_driver = {
.probe = pps_gpio_probe,
.remove = pps_gpio_remove,
.driver = {
.name = PPS_GPIO_NAME,
.of_match_table = pps_gpio_dt_ids,
},
};
module_platform_driver(pps_gpio_driver);
MODULE_AUTHOR("Ricardo Martins <rasm@fe.up.pt>");
MODULE_AUTHOR("James Nuss <jamesnuss@nanometrics.ca>");
MODULE_DESCRIPTION("Use GPIO pin as PPS source");
MODULE_LICENSE("GPL");
MODULE_VERSION("1.2.0");
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* pps-ktimer.c -- kernel timer test client
*
* Copyright (C) 2005-2006 Rodolfo Giometti <giometti@linux.it>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/time.h>
#include <linux/timer.h>
#include <linux/pps_kernel.h>
/*
* Global variables
*/
static struct pps_device *pps;
static struct timer_list ktimer;
/*
* The kernel timer
*/
static void pps_ktimer_event(struct timer_list *unused)
{
struct pps_event_time ts;
/* First of all we get the time stamp... */
pps_get_ts(&ts);
pps_event(pps, &ts, PPS_CAPTUREASSERT, NULL);
mod_timer(&ktimer, jiffies + HZ);
}
/*
* The PPS info struct
*/
static struct pps_source_info pps_ktimer_info = {
.name = "ktimer",
.path = "",
.mode = PPS_CAPTUREASSERT | PPS_OFFSETASSERT |
PPS_ECHOASSERT |
PPS_CANWAIT | PPS_TSFMT_TSPEC,
.owner = THIS_MODULE,
};
/*
* Module staff
*/
static void __exit pps_ktimer_exit(void)
{
dev_dbg(&pps->dev, "ktimer PPS source unregistered\n");
timer_delete_sync(&ktimer);
pps_unregister_source(pps);
}
static int __init pps_ktimer_init(void)
{
pps = pps_register_source(&pps_ktimer_info,
PPS_CAPTUREASSERT | PPS_OFFSETASSERT);
if (IS_ERR(pps)) {
pr_err("cannot register PPS source\n");
return PTR_ERR(pps);
}
timer_setup(&ktimer, pps_ktimer_event, 0);
mod_timer(&ktimer, jiffies + HZ);
dev_dbg(&pps->dev, "ktimer PPS source registered\n");
return 0;
}
module_init(pps_ktimer_init);
module_exit(pps_ktimer_exit);
MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>");
MODULE_DESCRIPTION("dummy PPS source by using a kernel timer (just for debug)");
MODULE_LICENSE("GPL");
+141
View File
@@ -0,0 +1,141 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* pps-ldisc.c -- PPS line discipline
*
* Copyright (C) 2008 Rodolfo Giometti <giometti@linux.it>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/serial_core.h>
#include <linux/tty.h>
#include <linux/pps_kernel.h>
#include <linux/bug.h>
static void pps_tty_dcd_change(struct tty_struct *tty, bool active)
{
struct pps_device *pps;
struct pps_event_time ts;
pps_get_ts(&ts);
pps = pps_lookup_dev(tty);
/*
* This should never fail, but the ldisc locking is very
* convoluted, so don't crash just in case.
*/
if (WARN_ON_ONCE(pps == NULL))
return;
/* Now do the PPS event report */
pps_event(pps, &ts, active ? PPS_CAPTUREASSERT :
PPS_CAPTURECLEAR, NULL);
dev_dbg(&pps->dev, "PPS %s at %lu\n",
active ? "assert" : "clear", jiffies);
}
static int (*alias_n_tty_open)(struct tty_struct *tty);
static int pps_tty_open(struct tty_struct *tty)
{
struct pps_source_info info;
struct tty_driver *drv = tty->driver;
int index = tty->index + drv->name_base;
struct pps_device *pps;
int ret;
info.owner = THIS_MODULE;
info.dev = NULL;
snprintf(info.name, PPS_MAX_NAME_LEN, "%s%d", drv->driver_name, index);
snprintf(info.path, PPS_MAX_NAME_LEN, "/dev/%s%d", drv->name, index);
info.mode = PPS_CAPTUREBOTH | \
PPS_OFFSETASSERT | PPS_OFFSETCLEAR | \
PPS_CANWAIT | PPS_TSFMT_TSPEC;
pps = pps_register_source(&info, PPS_CAPTUREBOTH | \
PPS_OFFSETASSERT | PPS_OFFSETCLEAR);
if (IS_ERR(pps)) {
pr_err("cannot register PPS source \"%s\"\n", info.path);
return PTR_ERR(pps);
}
pps->lookup_cookie = tty;
/* Now open the base class N_TTY ldisc */
ret = alias_n_tty_open(tty);
if (ret < 0) {
pr_err("cannot open tty ldisc \"%s\"\n", info.path);
goto err_unregister;
}
dev_dbg(&pps->dev, "source \"%s\" added\n", info.path);
return 0;
err_unregister:
pps_unregister_source(pps);
return ret;
}
static void (*alias_n_tty_close)(struct tty_struct *tty);
static void pps_tty_close(struct tty_struct *tty)
{
struct pps_device *pps = pps_lookup_dev(tty);
alias_n_tty_close(tty);
if (WARN_ON(!pps))
return;
dev_info(&pps->dev, "removed\n");
pps_unregister_source(pps);
}
static struct tty_ldisc_ops pps_ldisc_ops;
/*
* Module stuff
*/
static int __init pps_tty_init(void)
{
int err;
/* Inherit the N_TTY's ops */
n_tty_inherit_ops(&pps_ldisc_ops);
/* Save N_TTY's open()/close() methods */
alias_n_tty_open = pps_ldisc_ops.open;
alias_n_tty_close = pps_ldisc_ops.close;
/* Init PPS_TTY data */
pps_ldisc_ops.owner = THIS_MODULE;
pps_ldisc_ops.num = N_PPS;
pps_ldisc_ops.name = "pps_tty";
pps_ldisc_ops.dcd_change = pps_tty_dcd_change;
pps_ldisc_ops.open = pps_tty_open;
pps_ldisc_ops.close = pps_tty_close;
err = tty_register_ldisc(&pps_ldisc_ops);
if (err)
pr_err("can't register PPS line discipline\n");
else
pr_info("PPS line discipline registered\n");
return err;
}
static void __exit pps_tty_cleanup(void)
{
tty_unregister_ldisc(&pps_ldisc_ops);
}
module_init(pps_tty_init);
module_exit(pps_tty_cleanup);
MODULE_ALIAS_LDISC(N_PPS);
MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>");
MODULE_DESCRIPTION("PPS TTY device driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,228 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* pps_parport.c -- kernel parallel port PPS client
*
* Copyright (C) 2009 Alexander Gordeev <lasaine@lvk.cs.msu.su>
*/
/*
* TODO:
* implement echo over SEL pin
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/irqnr.h>
#include <linux/time.h>
#include <linux/slab.h>
#include <linux/parport.h>
#include <linux/pps_kernel.h>
/* module parameters */
#define CLEAR_WAIT_MAX 100
#define CLEAR_WAIT_MAX_ERRORS 5
static unsigned int clear_wait = 100;
MODULE_PARM_DESC(clear_wait,
"Maximum number of port reads when polling for signal clear,"
" zero turns clear edge capture off entirely");
module_param(clear_wait, uint, 0);
static DEFINE_IDA(pps_client_index);
/* internal per port structure */
struct pps_client_pp {
struct pardevice *pardev; /* parport device */
struct pps_device *pps; /* PPS device */
unsigned int cw; /* port clear timeout */
unsigned int cw_err; /* number of timeouts */
int index; /* device number */
};
static inline int signal_is_set(struct parport *port)
{
return (port->ops->read_status(port) & PARPORT_STATUS_ACK) != 0;
}
/* parport interrupt handler */
static void parport_irq(void *handle)
{
struct pps_event_time ts_assert, ts_clear;
struct pps_client_pp *dev = handle;
struct parport *port = dev->pardev->port;
unsigned int i;
unsigned long flags;
/* first of all we get the time stamp... */
pps_get_ts(&ts_assert);
if (dev->cw == 0)
/* clear edge capture disabled */
goto out_assert;
/* try capture the clear edge */
/* We have to disable interrupts here. The idea is to prevent
* other interrupts on the same processor to introduce random
* lags while polling the port. Reading from IO port is known
* to take approximately 1us while other interrupt handlers can
* take much more potentially.
*
* Interrupts won't be disabled for a long time because the
* number of polls is limited by clear_wait parameter which is
* kept rather low. So it should never be an issue.
*/
local_irq_save(flags);
/* check the signal (no signal means the pulse is lost this time) */
if (!signal_is_set(port)) {
local_irq_restore(flags);
dev_err(&dev->pps->dev, "lost the signal\n");
goto out_assert;
}
/* poll the port until the signal is unset */
for (i = dev->cw; i; i--)
if (!signal_is_set(port)) {
pps_get_ts(&ts_clear);
local_irq_restore(flags);
dev->cw_err = 0;
goto out_both;
}
local_irq_restore(flags);
/* timeout */
dev->cw_err++;
if (dev->cw_err >= CLEAR_WAIT_MAX_ERRORS) {
dev_err(&dev->pps->dev, "disabled clear edge capture after %d"
" timeouts\n", dev->cw_err);
dev->cw = 0;
dev->cw_err = 0;
}
out_assert:
/* fire assert event */
pps_event(dev->pps, &ts_assert,
PPS_CAPTUREASSERT, NULL);
return;
out_both:
/* fire assert event */
pps_event(dev->pps, &ts_assert,
PPS_CAPTUREASSERT, NULL);
/* fire clear event */
pps_event(dev->pps, &ts_clear,
PPS_CAPTURECLEAR, NULL);
return;
}
static void parport_attach(struct parport *port)
{
struct pardev_cb pps_client_cb;
int index;
struct pps_client_pp *device;
struct pps_source_info info = {
.name = KBUILD_MODNAME,
.path = "",
.mode = PPS_CAPTUREBOTH | \
PPS_OFFSETASSERT | PPS_OFFSETCLEAR | \
PPS_ECHOASSERT | PPS_ECHOCLEAR | \
PPS_CANWAIT | PPS_TSFMT_TSPEC,
.owner = THIS_MODULE,
.dev = NULL
};
if (clear_wait > CLEAR_WAIT_MAX) {
pr_err("clear_wait value should be not greater then %d\n",
CLEAR_WAIT_MAX);
return;
}
device = kzalloc_obj(struct pps_client_pp);
if (!device) {
pr_err("memory allocation failed, not attaching\n");
return;
}
index = ida_alloc(&pps_client_index, GFP_KERNEL);
if (index < 0)
goto err_free_device;
memset(&pps_client_cb, 0, sizeof(pps_client_cb));
pps_client_cb.private = device;
pps_client_cb.irq_func = parport_irq;
pps_client_cb.flags = PARPORT_FLAG_EXCL;
device->pardev = parport_register_dev_model(port,
KBUILD_MODNAME,
&pps_client_cb,
index);
if (!device->pardev) {
pr_err("couldn't register with %s\n", port->name);
goto err_free_ida;
}
if (parport_claim_or_block(device->pardev) < 0) {
pr_err("couldn't claim %s\n", port->name);
goto err_unregister_dev;
}
device->pps = pps_register_source(&info,
PPS_CAPTUREBOTH | PPS_OFFSETASSERT | PPS_OFFSETCLEAR);
if (IS_ERR(device->pps)) {
pr_err("couldn't register PPS source\n");
goto err_release_dev;
}
device->cw = clear_wait;
port->ops->enable_irq(port);
device->index = index;
pr_info("attached to %s\n", port->name);
return;
err_release_dev:
parport_release(device->pardev);
err_unregister_dev:
parport_unregister_device(device->pardev);
err_free_ida:
ida_free(&pps_client_index, index);
err_free_device:
kfree(device);
}
static void parport_detach(struct parport *port)
{
struct pardevice *pardev = port->cad;
struct pps_client_pp *device;
/* FIXME: oooh, this is ugly! */
if (!pardev || strcmp(pardev->name, KBUILD_MODNAME))
/* not our port */
return;
device = pardev->private;
port->ops->disable_irq(port);
pps_unregister_source(device->pps);
parport_release(pardev);
parport_unregister_device(pardev);
ida_free(&pps_client_index, device->index);
kfree(device);
}
static struct parport_driver pps_parport_driver = {
.name = KBUILD_MODNAME,
.match_port = parport_attach,
.detach = parport_detach,
};
module_parport_driver(pps_parport_driver);
MODULE_AUTHOR("Alexander Gordeev <lasaine@lvk.cs.msu.su>");
MODULE_DESCRIPTION("parallel port PPS client");
MODULE_LICENSE("GPL");