Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

47
drivers/pps/Kconfig Normal file
View file

@ -0,0 +1,47 @@
#
# PPS support configuration
#
menu "PPS support"
config PPS
tristate "PPS support"
---help---
PPS (Pulse Per Second) is a special pulse provided by some GPS
antennae. Userland can use it to get a high-precision time
reference.
Some antennae's PPS signals are connected with the CD (Carrier
Detect) pin of the serial line they use to communicate with the
host. In this case use the SERIAL_LINE client support.
Some antennae's PPS signals are connected with some special host
inputs so you have to enable the corresponding client support.
To compile this driver as a module, choose M here: the module
will be called pps_core.ko.
if PPS
config PPS_DEBUG
bool "PPS debugging messages"
help
Say Y here if you want the PPS support to produce a bunch of debug
messages to the system log. Select this if you are having a
problem with PPS support and want to see more of what is going on.
config NTP_PPS
bool "PPS kernel consumer support"
depends on !NO_HZ
help
This option adds support for direct in-kernel time
synchronization using an external PPS signal.
It doesn't work on tickless systems at the moment.
endif
source drivers/pps/clients/Kconfig
source drivers/pps/generators/Kconfig
endmenu

10
drivers/pps/Makefile Normal file
View file

@ -0,0 +1,10 @@
#
# Makefile for the PPS core.
#
pps_core-y := pps.o kapi.o sysfs.o
pps_core-$(CONFIG_NTP_PPS) += kc.o
obj-$(CONFIG_PPS) := pps_core.o
obj-y += clients/ generators/
ccflags-$(CONFIG_PPS_DEBUG) := -DDEBUG

View file

@ -0,0 +1,41 @@
#
# PPS clients configuration
#
if PPS
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 PPS && 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 PPS && 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"
depends on PPS
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.
endif

View file

@ -0,0 +1,10 @@
#
# 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

View file

@ -0,0 +1,213 @@
/*
* 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>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#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/pps-gpio.h>
#include <linux/gpio.h>
#include <linux/list.h>
#include <linux/of_device.h>
#include <linux/of_gpio.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 */
bool assert_falling_edge;
bool capture_clear;
unsigned int gpio_pin;
};
/*
* 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;
rising_edge = gpio_get_value(info->gpio_pin);
if ((rising_edge && !info->assert_falling_edge) ||
(!rising_edge && info->assert_falling_edge))
pps_event(info->pps, &ts, PPS_CAPTUREASSERT, NULL);
else if (info->capture_clear &&
((rising_edge && info->assert_falling_edge) ||
(!rising_edge && !info->assert_falling_edge)))
pps_event(info->pps, &ts, PPS_CAPTURECLEAR, NULL);
return IRQ_HANDLED;
}
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;
const char *gpio_label;
int ret;
int pps_default_params;
const struct pps_gpio_platform_data *pdata = pdev->dev.platform_data;
struct device_node *np = pdev->dev.of_node;
/* allocate space for device info */
data = devm_kzalloc(&pdev->dev, sizeof(struct pps_gpio_device_data),
GFP_KERNEL);
if (!data)
return -ENOMEM;
if (pdata) {
data->gpio_pin = pdata->gpio_pin;
gpio_label = pdata->gpio_label;
data->assert_falling_edge = pdata->assert_falling_edge;
data->capture_clear = pdata->capture_clear;
} else {
ret = of_get_gpio(np, 0);
if (ret < 0) {
dev_err(&pdev->dev, "failed to get GPIO from device tree\n");
return ret;
}
data->gpio_pin = ret;
gpio_label = PPS_GPIO_NAME;
if (of_get_property(np, "assert-falling-edge", NULL))
data->assert_falling_edge = true;
}
/* GPIO setup */
ret = devm_gpio_request(&pdev->dev, data->gpio_pin, gpio_label);
if (ret) {
dev_err(&pdev->dev, "failed to request GPIO %u\n",
data->gpio_pin);
return ret;
}
ret = gpio_direction_input(data->gpio_pin);
if (ret) {
dev_err(&pdev->dev, "failed to set pin direction\n");
return -EINVAL;
}
/* IRQ setup */
ret = gpio_to_irq(data->gpio_pin);
if (ret < 0) {
dev_err(&pdev->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);
/* 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 (data->pps == NULL) {
dev_err(&pdev->dev, "failed to register IRQ %d as PPS source\n",
data->irq);
return -EINVAL;
}
/* register IRQ interrupt handler */
ret = devm_request_irq(&pdev->dev, data->irq, pps_gpio_irq_handler,
get_irqf_trigger_flags(data), data->info.name, data);
if (ret) {
pps_unregister_source(data->pps);
dev_err(&pdev->dev, "failed to acquire IRQ %d\n", data->irq);
return -EINVAL;
}
platform_set_drvdata(pdev, data);
dev_info(data->pps->dev, "Registered IRQ %d as PPS source\n",
data->irq);
return 0;
}
static int pps_gpio_remove(struct platform_device *pdev)
{
struct pps_gpio_device_data *data = platform_get_drvdata(pdev);
pps_unregister_source(data->pps);
dev_info(&pdev->dev, "removed IRQ %d as PPS source\n", data->irq);
return 0;
}
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,
.owner = THIS_MODULE,
.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.0.0");

View file

@ -0,0 +1,101 @@
/*
* pps-ktimer.c -- kernel timer test client
*
*
* Copyright (C) 2005-2006 Rodolfo Giometti <giometti@linux.it>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#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(unsigned long ptr)
{
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_info(pps->dev, "ktimer PPS source unregistered\n");
del_timer_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 (pps == NULL) {
pr_err("cannot register PPS source\n");
return -ENOMEM;
}
setup_timer(&ktimer, pps_ktimer_event, 0);
mod_timer(&ktimer, jiffies + HZ);
dev_info(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");

View file

@ -0,0 +1,163 @@
/*
* pps-ldisc.c -- PPS line discipline
*
*
* Copyright (C) 2008 Rodolfo Giometti <giometti@linux.it>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#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>
#define PPS_TTY_MAGIC 0x0001
static void pps_tty_dcd_change(struct tty_struct *tty, unsigned int status)
{
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, status ? PPS_CAPTUREASSERT :
PPS_CAPTURECLEAR, NULL);
dev_dbg(pps->dev, "PPS %s at %lu\n",
status ? "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 (pps == NULL) {
pr_err("cannot register PPS source \"%s\"\n", info.path);
return -ENOMEM;
}
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_info(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.magic = PPS_TTY_MAGIC;
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(N_PPS, &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)
{
int err;
err = tty_unregister_ldisc(N_PPS);
if (err)
pr_err("can't unregister PPS line discipline\n");
else
pr_info("PPS line discipline removed\n");
}
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");

View file

@ -0,0 +1,250 @@
/*
* pps_parport.c -- kernel parallel port PPS client
*
*
* Copyright (C) 2009 Alexander Gordeev <lasaine@lvk.cs.msu.su>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
* 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>
#define DRVDESC "parallel port PPS client"
/* 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);
/* 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 */
};
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 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
};
device = kzalloc(sizeof(struct pps_client_pp), GFP_KERNEL);
if (!device) {
pr_err("memory allocation failed, not attaching\n");
return;
}
device->pardev = parport_register_device(port, KBUILD_MODNAME,
NULL, NULL, parport_irq, PARPORT_FLAG_EXCL, device);
if (!device->pardev) {
pr_err("couldn't register with %s\n", port->name);
goto err_free;
}
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 (device->pps == NULL) {
pr_err("couldn't register PPS source\n");
goto err_release_dev;
}
device->cw = clear_wait;
port->ops->enable_irq(port);
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:
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 (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);
kfree(device);
}
static struct parport_driver pps_parport_driver = {
.name = KBUILD_MODNAME,
.attach = parport_attach,
.detach = parport_detach,
};
/* module staff */
static int __init pps_parport_init(void)
{
int ret;
pr_info(DRVDESC "\n");
if (clear_wait > CLEAR_WAIT_MAX) {
pr_err("clear_wait value should be not greater"
" then %d\n", CLEAR_WAIT_MAX);
return -EINVAL;
}
ret = parport_register_driver(&pps_parport_driver);
if (ret) {
pr_err("unable to register with parport\n");
return ret;
}
return 0;
}
static void __exit pps_parport_exit(void)
{
parport_unregister_driver(&pps_parport_driver);
}
module_init(pps_parport_init);
module_exit(pps_parport_exit);
MODULE_AUTHOR("Alexander Gordeev <lasaine@lvk.cs.msu.su>");
MODULE_DESCRIPTION(DRVDESC);
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,13 @@
#
# PPS generators configuration
#
comment "PPS generators support"
config PPS_GENERATOR_PARPORT
tristate "Parallel port PPS signal generator"
depends on PARPORT && BROKEN
help
If you say yes here you get support for a PPS signal generator which
utilizes STROBE pin of a parallel port to send PPS signals. It uses
parport abstraction layer and hrtimers to precisely control the signal.

View file

@ -0,0 +1,9 @@
#
# Makefile for PPS generators.
#
obj-$(CONFIG_PPS_GENERATOR_PARPORT) += pps_gen_parport.o
ifeq ($(CONFIG_PPS_DEBUG),y)
EXTRA_CFLAGS += -DDEBUG
endif

View file

@ -0,0 +1,277 @@
/*
* pps_gen_parport.c -- kernel parallel port PPS signal generator
*
*
* Copyright (C) 2009 Alexander Gordeev <lasaine@lvk.cs.msu.su>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
* TODO:
* fix issues when realtime clock is adjusted in a leap
*/
#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/hrtimer.h>
#include <linux/parport.h>
#define DRVDESC "parallel port PPS signal generator"
#define SIGNAL 0
#define NO_SIGNAL PARPORT_CONTROL_STROBE
/* module parameters */
#define SEND_DELAY_MAX 100000
static unsigned int send_delay = 30000;
MODULE_PARM_DESC(delay,
"Delay between setting and dropping the signal (ns)");
module_param_named(delay, send_delay, uint, 0);
#define SAFETY_INTERVAL 3000 /* set the hrtimer earlier for safety (ns) */
/* internal per port structure */
struct pps_generator_pp {
struct pardevice *pardev; /* parport device */
struct hrtimer timer;
long port_write_time; /* calibrated port write time (ns) */
};
static struct pps_generator_pp device = {
.pardev = NULL,
};
static int attached;
/* calibrated time between a hrtimer event and the reaction */
static long hrtimer_error = SAFETY_INTERVAL;
/* the kernel hrtimer event */
static enum hrtimer_restart hrtimer_event(struct hrtimer *timer)
{
struct timespec expire_time, ts1, ts2, ts3, dts;
struct pps_generator_pp *dev;
struct parport *port;
long lim, delta;
unsigned long flags;
/* We have to disable interrupts here. The idea is to prevent
* other interrupts on the same processor to introduce random
* lags while polling the clock. getnstimeofday() takes <1us on
* most machines while other interrupt handlers can take much
* more potentially.
*
* NB: approx time with blocked interrupts =
* send_delay + 3 * SAFETY_INTERVAL
*/
local_irq_save(flags);
/* first of all we get the time stamp... */
getnstimeofday(&ts1);
expire_time = ktime_to_timespec(hrtimer_get_softexpires(timer));
dev = container_of(timer, struct pps_generator_pp, timer);
lim = NSEC_PER_SEC - send_delay - dev->port_write_time;
/* check if we are late */
if (expire_time.tv_sec != ts1.tv_sec || ts1.tv_nsec > lim) {
local_irq_restore(flags);
pr_err("we are late this time %ld.%09ld\n",
ts1.tv_sec, ts1.tv_nsec);
goto done;
}
/* busy loop until the time is right for an assert edge */
do {
getnstimeofday(&ts2);
} while (expire_time.tv_sec == ts2.tv_sec && ts2.tv_nsec < lim);
/* set the signal */
port = dev->pardev->port;
port->ops->write_control(port, SIGNAL);
/* busy loop until the time is right for a clear edge */
lim = NSEC_PER_SEC - dev->port_write_time;
do {
getnstimeofday(&ts2);
} while (expire_time.tv_sec == ts2.tv_sec && ts2.tv_nsec < lim);
/* unset the signal */
port->ops->write_control(port, NO_SIGNAL);
getnstimeofday(&ts3);
local_irq_restore(flags);
/* update calibrated port write time */
dts = timespec_sub(ts3, ts2);
dev->port_write_time =
(dev->port_write_time + timespec_to_ns(&dts)) >> 1;
done:
/* update calibrated hrtimer error */
dts = timespec_sub(ts1, expire_time);
delta = timespec_to_ns(&dts);
/* If the new error value is bigger then the old, use the new
* value, if not then slowly move towards the new value. This
* way it should be safe in bad conditions and efficient in
* good conditions.
*/
if (delta >= hrtimer_error)
hrtimer_error = delta;
else
hrtimer_error = (3 * hrtimer_error + delta) >> 2;
/* update the hrtimer expire time */
hrtimer_set_expires(timer,
ktime_set(expire_time.tv_sec + 1,
NSEC_PER_SEC - (send_delay +
dev->port_write_time + SAFETY_INTERVAL +
2 * hrtimer_error)));
return HRTIMER_RESTART;
}
/* calibrate port write time */
#define PORT_NTESTS_SHIFT 5
static void calibrate_port(struct pps_generator_pp *dev)
{
struct parport *port = dev->pardev->port;
int i;
long acc = 0;
for (i = 0; i < (1 << PORT_NTESTS_SHIFT); i++) {
struct timespec a, b;
unsigned long irq_flags;
local_irq_save(irq_flags);
getnstimeofday(&a);
port->ops->write_control(port, NO_SIGNAL);
getnstimeofday(&b);
local_irq_restore(irq_flags);
b = timespec_sub(b, a);
acc += timespec_to_ns(&b);
}
dev->port_write_time = acc >> PORT_NTESTS_SHIFT;
pr_info("port write takes %ldns\n", dev->port_write_time);
}
static inline ktime_t next_intr_time(struct pps_generator_pp *dev)
{
struct timespec ts;
getnstimeofday(&ts);
return ktime_set(ts.tv_sec +
((ts.tv_nsec > 990 * NSEC_PER_MSEC) ? 1 : 0),
NSEC_PER_SEC - (send_delay +
dev->port_write_time + 3 * SAFETY_INTERVAL));
}
static void parport_attach(struct parport *port)
{
if (attached) {
/* we already have a port */
return;
}
device.pardev = parport_register_device(port, KBUILD_MODNAME,
NULL, NULL, NULL, PARPORT_FLAG_EXCL, &device);
if (!device.pardev) {
pr_err("couldn't register with %s\n", port->name);
return;
}
if (parport_claim_or_block(device.pardev) < 0) {
pr_err("couldn't claim %s\n", port->name);
goto err_unregister_dev;
}
pr_info("attached to %s\n", port->name);
attached = 1;
calibrate_port(&device);
hrtimer_init(&device.timer, CLOCK_REALTIME, HRTIMER_MODE_ABS);
device.timer.function = hrtimer_event;
hrtimer_start(&device.timer, next_intr_time(&device), HRTIMER_MODE_ABS);
return;
err_unregister_dev:
parport_unregister_device(device.pardev);
}
static void parport_detach(struct parport *port)
{
if (port->cad != device.pardev)
return; /* not our port */
hrtimer_cancel(&device.timer);
parport_release(device.pardev);
parport_unregister_device(device.pardev);
}
static struct parport_driver pps_gen_parport_driver = {
.name = KBUILD_MODNAME,
.attach = parport_attach,
.detach = parport_detach,
};
/* module staff */
static int __init pps_gen_parport_init(void)
{
int ret;
pr_info(DRVDESC "\n");
if (send_delay > SEND_DELAY_MAX) {
pr_err("delay value should be not greater"
" then %d\n", SEND_DELAY_MAX);
return -EINVAL;
}
ret = parport_register_driver(&pps_gen_parport_driver);
if (ret) {
pr_err("unable to register with parport\n");
return ret;
}
return 0;
}
static void __exit pps_gen_parport_exit(void)
{
parport_unregister_driver(&pps_gen_parport_driver);
pr_info("hrtimer avg error is %ldns\n", hrtimer_error);
}
module_init(pps_gen_parport_init);
module_exit(pps_gen_parport_exit);
MODULE_AUTHOR("Alexander Gordeev <lasaine@lvk.cs.msu.su>");
MODULE_DESCRIPTION(DRVDESC);
MODULE_LICENSE("GPL");

236
drivers/pps/kapi.c Normal file
View file

@ -0,0 +1,236 @@
/*
* kernel API
*
*
* Copyright (C) 2005-2009 Rodolfo Giometti <giometti@linux.it>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/time.h>
#include <linux/timex.h>
#include <linux/spinlock.h>
#include <linux/fs.h>
#include <linux/pps_kernel.h>
#include <linux/slab.h>
#include "kc.h"
/*
* Local functions
*/
static void pps_add_offset(struct pps_ktime *ts, struct pps_ktime *offset)
{
ts->nsec += offset->nsec;
while (ts->nsec >= NSEC_PER_SEC) {
ts->nsec -= NSEC_PER_SEC;
ts->sec++;
}
while (ts->nsec < 0) {
ts->nsec += NSEC_PER_SEC;
ts->sec--;
}
ts->sec += offset->sec;
}
static void pps_echo_client_default(struct pps_device *pps, int event,
void *data)
{
dev_info(pps->dev, "echo %s %s\n",
event & PPS_CAPTUREASSERT ? "assert" : "",
event & PPS_CAPTURECLEAR ? "clear" : "");
}
/*
* Exported functions
*/
/* pps_register_source - add a PPS source in the system
* @info: the PPS info struct
* @default_params: the default PPS parameters of the new source
*
* This function is used to add a new PPS source in the system. The new
* source is described by info's fields and it will have, as default PPS
* parameters, the ones specified into default_params.
*
* The function returns, in case of success, the PPS device. Otherwise NULL.
*/
struct pps_device *pps_register_source(struct pps_source_info *info,
int default_params)
{
struct pps_device *pps;
int err;
/* Sanity checks */
if ((info->mode & default_params) != default_params) {
pr_err("%s: unsupported default parameters\n",
info->name);
err = -EINVAL;
goto pps_register_source_exit;
}
if ((info->mode & (PPS_TSFMT_TSPEC | PPS_TSFMT_NTPFP)) == 0) {
pr_err("%s: unspecified time format\n",
info->name);
err = -EINVAL;
goto pps_register_source_exit;
}
/* Allocate memory for the new PPS source struct */
pps = kzalloc(sizeof(struct pps_device), GFP_KERNEL);
if (pps == NULL) {
err = -ENOMEM;
goto pps_register_source_exit;
}
/* These initializations must be done before calling idr_alloc()
* in order to avoid reces into pps_event().
*/
pps->params.api_version = PPS_API_VERS;
pps->params.mode = default_params;
pps->info = *info;
/* check for default echo function */
if ((pps->info.mode & (PPS_ECHOASSERT | PPS_ECHOCLEAR)) &&
pps->info.echo == NULL)
pps->info.echo = pps_echo_client_default;
init_waitqueue_head(&pps->queue);
spin_lock_init(&pps->lock);
/* Create the char device */
err = pps_register_cdev(pps);
if (err < 0) {
pr_err("%s: unable to create char device\n",
info->name);
goto kfree_pps;
}
dev_info(pps->dev, "new PPS source %s\n", info->name);
return pps;
kfree_pps:
kfree(pps);
pps_register_source_exit:
pr_err("%s: unable to register source\n", info->name);
return NULL;
}
EXPORT_SYMBOL(pps_register_source);
/* pps_unregister_source - remove a PPS source from the system
* @pps: the PPS source
*
* This function is used to remove a previously registered PPS source from
* the system.
*/
void pps_unregister_source(struct pps_device *pps)
{
pps_kc_remove(pps);
pps_unregister_cdev(pps);
/* don't have to kfree(pps) here because it will be done on
* device destruction */
}
EXPORT_SYMBOL(pps_unregister_source);
/* pps_event - register a PPS event into the system
* @pps: the PPS device
* @ts: the event timestamp
* @event: the event type
* @data: userdef pointer
*
* This function is used by each PPS client in order to register a new
* PPS event into the system (it's usually called inside an IRQ handler).
*
* If an echo function is associated with the PPS device it will be called
* as:
* pps->info.echo(pps, event, data);
*/
void pps_event(struct pps_device *pps, struct pps_event_time *ts, int event,
void *data)
{
unsigned long flags;
int captured = 0;
struct pps_ktime ts_real = { .sec = 0, .nsec = 0, .flags = 0 };
/* check event type */
BUG_ON((event & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR)) == 0);
dev_dbg(pps->dev, "PPS event at %ld.%09ld\n",
ts->ts_real.tv_sec, ts->ts_real.tv_nsec);
timespec_to_pps_ktime(&ts_real, ts->ts_real);
spin_lock_irqsave(&pps->lock, flags);
/* Must call the echo function? */
if ((pps->params.mode & (PPS_ECHOASSERT | PPS_ECHOCLEAR)))
pps->info.echo(pps, event, data);
/* Check the event */
pps->current_mode = pps->params.mode;
if (event & pps->params.mode & PPS_CAPTUREASSERT) {
/* We have to add an offset? */
if (pps->params.mode & PPS_OFFSETASSERT)
pps_add_offset(&ts_real,
&pps->params.assert_off_tu);
/* Save the time stamp */
pps->assert_tu = ts_real;
pps->assert_sequence++;
dev_dbg(pps->dev, "capture assert seq #%u\n",
pps->assert_sequence);
captured = ~0;
}
if (event & pps->params.mode & PPS_CAPTURECLEAR) {
/* We have to add an offset? */
if (pps->params.mode & PPS_OFFSETCLEAR)
pps_add_offset(&ts_real,
&pps->params.clear_off_tu);
/* Save the time stamp */
pps->clear_tu = ts_real;
pps->clear_sequence++;
dev_dbg(pps->dev, "capture clear seq #%u\n",
pps->clear_sequence);
captured = ~0;
}
pps_kc_event(pps, ts, event);
/* Wake up if captured something */
if (captured) {
pps->last_ev++;
wake_up_interruptible_all(&pps->queue);
kill_fasync(&pps->async_queue, SIGIO, POLL_IN);
}
spin_unlock_irqrestore(&pps->lock, flags);
}
EXPORT_SYMBOL(pps_event);

122
drivers/pps/kc.c Normal file
View file

@ -0,0 +1,122 @@
/*
* PPS kernel consumer API
*
* Copyright (C) 2009-2010 Alexander Gordeev <lasaine@lvk.cs.msu.su>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#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);
}

46
drivers/pps/kc.h Normal file
View file

@ -0,0 +1,46 @@
/*
* PPS kernel consumer API header
*
* Copyright (C) 2009-2010 Alexander Gordeev <lasaine@lvk.cs.msu.su>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef LINUX_PPS_KC_H
#define LINUX_PPS_KC_H
#include <linux/errno.h>
#include <linux/pps_kernel.h>
#ifdef CONFIG_NTP_PPS
extern int pps_kc_bind(struct pps_device *pps,
struct pps_bind_args *bind_args);
extern void pps_kc_remove(struct pps_device *pps);
extern void pps_kc_event(struct pps_device *pps,
struct pps_event_time *ts, int event);
#else /* CONFIG_NTP_PPS */
static inline int pps_kc_bind(struct pps_device *pps,
struct pps_bind_args *bind_args) { return -EOPNOTSUPP; }
static inline void pps_kc_remove(struct pps_device *pps) {}
static inline void pps_kc_event(struct pps_device *pps,
struct pps_event_time *ts, int event) {}
#endif /* CONFIG_NTP_PPS */
#endif /* LINUX_PPS_KC_H */

434
drivers/pps/pps.c Normal file
View file

@ -0,0 +1,434 @@
/*
* PPS core file
*
*
* Copyright (C) 2005-2009 Rodolfo Giometti <giometti@linux.it>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
#include <linux/idr.h>
#include <linux/mutex.h>
#include <linux/cdev.h>
#include <linux/poll.h>
#include <linux/pps_kernel.h>
#include <linux/slab.h>
#include "kc.h"
/*
* Local variables
*/
static dev_t pps_devt;
static struct class *pps_class;
static DEFINE_MUTEX(pps_idr_lock);
static DEFINE_IDR(pps_idr);
/*
* Char device methods
*/
static unsigned int pps_cdev_poll(struct file *file, poll_table *wait)
{
struct pps_device *pps = file->private_data;
poll_wait(file, &pps->queue, wait);
return POLLIN | POLLRDNORM;
}
static int pps_cdev_fasync(int fd, struct file *file, int on)
{
struct pps_device *pps = file->private_data;
return fasync_helper(fd, file, on, &pps->async_queue);
}
static long pps_cdev_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
struct pps_device *pps = file->private_data;
struct pps_kparams params;
void __user *uarg = (void __user *) arg;
int __user *iuarg = (int __user *) arg;
int err;
switch (cmd) {
case PPS_GETPARAMS:
dev_dbg(pps->dev, "PPS_GETPARAMS\n");
spin_lock_irq(&pps->lock);
/* Get the current parameters */
params = pps->params;
spin_unlock_irq(&pps->lock);
err = copy_to_user(uarg, &params, sizeof(struct pps_kparams));
if (err)
return -EFAULT;
break;
case PPS_SETPARAMS:
dev_dbg(pps->dev, "PPS_SETPARAMS\n");
/* Check the capabilities */
if (!capable(CAP_SYS_TIME))
return -EPERM;
err = copy_from_user(&params, uarg, sizeof(struct pps_kparams));
if (err)
return -EFAULT;
if (!(params.mode & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR))) {
dev_dbg(pps->dev, "capture mode unspecified (%x)\n",
params.mode);
return -EINVAL;
}
/* Check for supported capabilities */
if ((params.mode & ~pps->info.mode) != 0) {
dev_dbg(pps->dev, "unsupported capabilities (%x)\n",
params.mode);
return -EINVAL;
}
spin_lock_irq(&pps->lock);
/* Save the new parameters */
pps->params = params;
/* Restore the read only parameters */
if ((params.mode & (PPS_TSFMT_TSPEC | PPS_TSFMT_NTPFP)) == 0) {
/* section 3.3 of RFC 2783 interpreted */
dev_dbg(pps->dev, "time format unspecified (%x)\n",
params.mode);
pps->params.mode |= PPS_TSFMT_TSPEC;
}
if (pps->info.mode & PPS_CANWAIT)
pps->params.mode |= PPS_CANWAIT;
pps->params.api_version = PPS_API_VERS;
spin_unlock_irq(&pps->lock);
break;
case PPS_GETCAP:
dev_dbg(pps->dev, "PPS_GETCAP\n");
err = put_user(pps->info.mode, iuarg);
if (err)
return -EFAULT;
break;
case PPS_FETCH: {
struct pps_fdata fdata;
unsigned int ev;
dev_dbg(pps->dev, "PPS_FETCH\n");
err = copy_from_user(&fdata, uarg, sizeof(struct pps_fdata));
if (err)
return -EFAULT;
ev = pps->last_ev;
/* Manage the timeout */
if (fdata.timeout.flags & PPS_TIME_INVALID)
err = wait_event_interruptible(pps->queue,
ev != pps->last_ev);
else {
unsigned long ticks;
dev_dbg(pps->dev, "timeout %lld.%09d\n",
(long long) fdata.timeout.sec,
fdata.timeout.nsec);
ticks = fdata.timeout.sec * HZ;
ticks += fdata.timeout.nsec / (NSEC_PER_SEC / HZ);
if (ticks != 0) {
err = wait_event_interruptible_timeout(
pps->queue,
ev != pps->last_ev,
ticks);
if (err == 0)
return -ETIMEDOUT;
}
}
/* Check for pending signals */
if (err == -ERESTARTSYS) {
dev_dbg(pps->dev, "pending signal caught\n");
return -EINTR;
}
/* Return the fetched timestamp */
spin_lock_irq(&pps->lock);
fdata.info.assert_sequence = pps->assert_sequence;
fdata.info.clear_sequence = pps->clear_sequence;
fdata.info.assert_tu = pps->assert_tu;
fdata.info.clear_tu = pps->clear_tu;
fdata.info.current_mode = pps->current_mode;
spin_unlock_irq(&pps->lock);
err = copy_to_user(uarg, &fdata, sizeof(struct pps_fdata));
if (err)
return -EFAULT;
break;
}
case PPS_KC_BIND: {
struct pps_bind_args bind_args;
dev_dbg(pps->dev, "PPS_KC_BIND\n");
/* Check the capabilities */
if (!capable(CAP_SYS_TIME))
return -EPERM;
if (copy_from_user(&bind_args, uarg,
sizeof(struct pps_bind_args)))
return -EFAULT;
/* Check for supported capabilities */
if ((bind_args.edge & ~pps->info.mode) != 0) {
dev_err(pps->dev, "unsupported capabilities (%x)\n",
bind_args.edge);
return -EINVAL;
}
/* Validate parameters roughly */
if (bind_args.tsformat != PPS_TSFMT_TSPEC ||
(bind_args.edge & ~PPS_CAPTUREBOTH) != 0 ||
bind_args.consumer != PPS_KC_HARDPPS) {
dev_err(pps->dev, "invalid kernel consumer bind"
" parameters (%x)\n", bind_args.edge);
return -EINVAL;
}
err = pps_kc_bind(pps, &bind_args);
if (err < 0)
return err;
break;
}
default:
return -ENOTTY;
}
return 0;
}
static int pps_cdev_open(struct inode *inode, struct file *file)
{
struct pps_device *pps = container_of(inode->i_cdev,
struct pps_device, cdev);
file->private_data = pps;
kobject_get(&pps->dev->kobj);
return 0;
}
static int pps_cdev_release(struct inode *inode, struct file *file)
{
struct pps_device *pps = container_of(inode->i_cdev,
struct pps_device, cdev);
kobject_put(&pps->dev->kobj);
return 0;
}
/*
* Char device stuff
*/
static const struct file_operations pps_cdev_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.poll = pps_cdev_poll,
.fasync = pps_cdev_fasync,
.unlocked_ioctl = pps_cdev_ioctl,
.open = pps_cdev_open,
.release = pps_cdev_release,
};
static void pps_device_destruct(struct device *dev)
{
struct pps_device *pps = dev_get_drvdata(dev);
cdev_del(&pps->cdev);
/* Now we can release the ID for re-use */
pr_debug("deallocating pps%d\n", pps->id);
mutex_lock(&pps_idr_lock);
idr_remove(&pps_idr, pps->id);
mutex_unlock(&pps_idr_lock);
kfree(dev);
kfree(pps);
}
int pps_register_cdev(struct pps_device *pps)
{
int err;
dev_t devt;
mutex_lock(&pps_idr_lock);
/*
* Get new ID for the new PPS source. After idr_alloc() calling
* the new source will be freely available into the kernel.
*/
err = idr_alloc(&pps_idr, pps, 0, PPS_MAX_SOURCES, GFP_KERNEL);
if (err < 0) {
if (err == -ENOSPC) {
pr_err("%s: too many PPS sources in the system\n",
pps->info.name);
err = -EBUSY;
}
goto out_unlock;
}
pps->id = err;
mutex_unlock(&pps_idr_lock);
devt = MKDEV(MAJOR(pps_devt), pps->id);
cdev_init(&pps->cdev, &pps_cdev_fops);
pps->cdev.owner = pps->info.owner;
err = cdev_add(&pps->cdev, devt, 1);
if (err) {
pr_err("%s: failed to add char device %d:%d\n",
pps->info.name, MAJOR(pps_devt), pps->id);
goto free_idr;
}
pps->dev = device_create(pps_class, pps->info.dev, devt, pps,
"pps%d", pps->id);
if (IS_ERR(pps->dev)) {
err = PTR_ERR(pps->dev);
goto del_cdev;
}
/* Override the release function with our own */
pps->dev->release = pps_device_destruct;
pr_debug("source %s got cdev (%d:%d)\n", pps->info.name,
MAJOR(pps_devt), pps->id);
return 0;
del_cdev:
cdev_del(&pps->cdev);
free_idr:
mutex_lock(&pps_idr_lock);
idr_remove(&pps_idr, pps->id);
out_unlock:
mutex_unlock(&pps_idr_lock);
return err;
}
void pps_unregister_cdev(struct pps_device *pps)
{
pr_debug("unregistering pps%d\n", pps->id);
pps->lookup_cookie = NULL;
device_destroy(pps_class, pps->dev->devt);
}
/*
* Look up a pps device by magic cookie.
* The cookie is usually a pointer to some enclosing device, but this
* code doesn't care; you should never be dereferencing it.
*
* This is a bit of a kludge that is currently used only by the PPS
* serial line discipline. It may need to be tweaked when a second user
* is found.
*
* There is no function interface for setting the lookup_cookie field.
* It's initialized to NULL when the pps device is created, and if a
* client wants to use it, just fill it in afterward.
*
* The cookie is automatically set to NULL in pps_unregister_source()
* so that it will not be used again, even if the pps device cannot
* be removed from the idr due to pending references holding the minor
* number in use.
*/
struct pps_device *pps_lookup_dev(void const *cookie)
{
struct pps_device *pps;
unsigned id;
rcu_read_lock();
idr_for_each_entry(&pps_idr, pps, id)
if (cookie == pps->lookup_cookie)
break;
rcu_read_unlock();
return pps;
}
EXPORT_SYMBOL(pps_lookup_dev);
/*
* Module stuff
*/
static void __exit pps_exit(void)
{
class_destroy(pps_class);
unregister_chrdev_region(pps_devt, PPS_MAX_SOURCES);
}
static int __init pps_init(void)
{
int err;
pps_class = class_create(THIS_MODULE, "pps");
if (IS_ERR(pps_class)) {
pr_err("failed to allocate class\n");
return PTR_ERR(pps_class);
}
pps_class->dev_groups = pps_groups;
err = alloc_chrdev_region(&pps_devt, 0, PPS_MAX_SOURCES, "pps");
if (err < 0) {
pr_err("failed to allocate char device region\n");
goto remove_class;
}
pr_info("LinuxPPS API ver. %d registered\n", PPS_API_VERS);
pr_info("Software ver. %s - Copyright 2005-2007 Rodolfo Giometti "
"<giometti@linux.it>\n", PPS_VERSION);
return 0;
remove_class:
class_destroy(pps_class);
return err;
}
subsys_initcall(pps_init);
module_exit(pps_exit);
MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>");
MODULE_DESCRIPTION("LinuxPPS support (RFC 2783) - ver. " PPS_VERSION);
MODULE_LICENSE("GPL");

113
drivers/pps/sysfs.c Normal file
View file

@ -0,0 +1,113 @@
/*
* PPS sysfs support
*
*
* Copyright (C) 2007-2009 Rodolfo Giometti <giometti@linux.it>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/pps_kernel.h>
/*
* Attribute functions
*/
static ssize_t assert_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct pps_device *pps = dev_get_drvdata(dev);
if (!(pps->info.mode & PPS_CAPTUREASSERT))
return 0;
return sprintf(buf, "%lld.%09d#%d\n",
(long long) pps->assert_tu.sec, pps->assert_tu.nsec,
pps->assert_sequence);
}
static DEVICE_ATTR_RO(assert);
static ssize_t clear_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct pps_device *pps = dev_get_drvdata(dev);
if (!(pps->info.mode & PPS_CAPTURECLEAR))
return 0;
return sprintf(buf, "%lld.%09d#%d\n",
(long long) pps->clear_tu.sec, pps->clear_tu.nsec,
pps->clear_sequence);
}
static DEVICE_ATTR_RO(clear);
static ssize_t mode_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct pps_device *pps = dev_get_drvdata(dev);
return sprintf(buf, "%4x\n", pps->info.mode);
}
static DEVICE_ATTR_RO(mode);
static ssize_t echo_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct pps_device *pps = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", !!pps->info.echo);
}
static DEVICE_ATTR_RO(echo);
static ssize_t name_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct pps_device *pps = dev_get_drvdata(dev);
return sprintf(buf, "%s\n", pps->info.name);
}
static DEVICE_ATTR_RO(name);
static ssize_t path_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct pps_device *pps = dev_get_drvdata(dev);
return sprintf(buf, "%s\n", pps->info.path);
}
static DEVICE_ATTR_RO(path);
static struct attribute *pps_attrs[] = {
&dev_attr_assert.attr,
&dev_attr_clear.attr,
&dev_attr_mode.attr,
&dev_attr_echo.attr,
&dev_attr_name.attr,
&dev_attr_path.attr,
NULL,
};
static const struct attribute_group pps_group = {
.attrs = pps_attrs,
};
const struct attribute_group *pps_groups[] = {
&pps_group,
NULL,
};