mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-08 09:08:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
47
drivers/pps/Kconfig
Normal file
47
drivers/pps/Kconfig
Normal 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
10
drivers/pps/Makefile
Normal 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
|
41
drivers/pps/clients/Kconfig
Normal file
41
drivers/pps/clients/Kconfig
Normal 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
|
10
drivers/pps/clients/Makefile
Normal file
10
drivers/pps/clients/Makefile
Normal 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
|
213
drivers/pps/clients/pps-gpio.c
Normal file
213
drivers/pps/clients/pps-gpio.c
Normal 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");
|
101
drivers/pps/clients/pps-ktimer.c
Normal file
101
drivers/pps/clients/pps-ktimer.c
Normal 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");
|
163
drivers/pps/clients/pps-ldisc.c
Normal file
163
drivers/pps/clients/pps-ldisc.c
Normal 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");
|
250
drivers/pps/clients/pps_parport.c
Normal file
250
drivers/pps/clients/pps_parport.c
Normal 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");
|
13
drivers/pps/generators/Kconfig
Normal file
13
drivers/pps/generators/Kconfig
Normal 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.
|
9
drivers/pps/generators/Makefile
Normal file
9
drivers/pps/generators/Makefile
Normal 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
|
277
drivers/pps/generators/pps_gen_parport.c
Normal file
277
drivers/pps/generators/pps_gen_parport.c
Normal 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
236
drivers/pps/kapi.c
Normal 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
122
drivers/pps/kc.c
Normal 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
46
drivers/pps/kc.h
Normal 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
434
drivers/pps/pps.c
Normal 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, ¶ms, 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(¶ms, 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
113
drivers/pps/sysfs.c
Normal 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,
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue