diff -Nur linux-2.6.18gum/arch/arm/Kconfig linux-2.6.18gum/arch/arm/Kconfig --- linux-2.6.18gum/arch/arm/Kconfig 2006-11-24 15:58:40.000000000 -0500 +++ linux-2.6.18gum/arch/arm/Kconfig 2006-10-01 12:02:02.000000000 -0400 @@ -896,6 +883,8 @@ source "drivers/spi/Kconfig" +source "drivers/pps/Kconfig" + source "drivers/w1/Kconfig" source "drivers/hwmon/Kconfig" diff -Nur linux-2.6.18gum/arch/arm/mach-pxa/gumstix.c linux-2.6.18gum/arch/arm/mach-pxa/gumstix.c --- linux-2.6.18gum/arch/arm/mach-pxa/gumstix.c 2006-11-24 15:58:40.000000000 -0500 +++ linux-2.6.18gum/arch/arm/mach-pxa/gumstix.c 2006-10-01 17:36:51.000000000 -0400 @@ -31,6 +31,13 @@ static struct pxamci_platform_data gumstix_mci_platform_data; +static int pxa_init_pps(void) +{ + pxa_gpio_mode(GUMSTIX_GPIO_PPS | GPIO_IN); + set_irq_type(GUMSTIX_IRQ_GPIO_PPS, IRQT_RISING); + return 0; +} + static int gumstix_mci_init(struct device *dev, irqreturn_t (*gumstix_detect_int)(int, void *, struct pt_regs *), void *data) { int err; @@ -92,6 +99,7 @@ static void __init gumstix_init(void) { + pxa_init_pps(); pxa_set_mci_info(&gumstix_mci_platform_data); pxa_set_udc_info(&gumstix_udc_info); (void) platform_add_devices(devices, ARRAY_SIZE(devices)); diff -Nur linux-2.6.18gum/drivers/Kconfig linux-2.6.18gum/drivers/Kconfig --- linux-2.6.18gum/drivers/Kconfig 2006-09-19 23:42:06.000000000 -0400 +++ linux-2.6.18gum/drivers/Kconfig 2006-10-01 12:00:14.000000000 -0400 @@ -46,6 +46,8 @@ source "drivers/spi/Kconfig" +source "drivers/pps/Kconfig" + source "drivers/w1/Kconfig" source "drivers/hwmon/Kconfig" diff -Nur linux-2.6.18gum/drivers/Makefile linux-2.6.18gum/drivers/Makefile --- linux-2.6.18gum/drivers/Makefile 2006-11-24 15:58:40.000000000 -0500 +++ linux-2.6.18gum/drivers/Makefile 2006-10-01 11:50:37.000000000 -0400 @@ -56,6 +56,7 @@ obj-$(CONFIG_I2O) += message/ obj-$(CONFIG_RTC_LIB) += rtc/ obj-$(CONFIG_I2C) += i2c/ +obj-$(CONFIG_PPS) += pps/ obj-$(CONFIG_W1) += w1/ obj-$(CONFIG_HWMON) += hwmon/ obj-$(CONFIG_PHONE) += telephony/ diff -Nur linux-2.6.18gum/drivers/pps/clients/Kconfig linux-2.6.18gum/drivers/pps/clients/Kconfig --- linux-2.6.18gum/drivers/pps/clients/Kconfig 1969-12-31 19:00:00.000000000 -0500 +++ linux-2.6.18gum/drivers/pps/clients/Kconfig 2006-10-01 15:04:40.000000000 -0400 @@ -0,0 +1,30 @@ +# +# LinuxPPS clients configuration +# + +comment "PPS clients support" + depends on PPS + +config PPS_CLIENT_KTIMER + tristate "Kernel timer client (Testing client, use for debug)" + depends on PPS && EXPERIMENTAL + 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 ktimer.o. + +config PPS_CLIENT_8250 + bool "8250 serial support" + depends on PPS && SERIAL_8250 && ! ( PPS = m && SERIAL_8250 = y ) && EXPERIMENTAL + help + If you say yes here you get support for a PPS source connected + with the CD (Carrier Detect) pin of your 8250 serial line chip. + +config PPS_CLIENT_PXA + bool "pxa serial support" + depends on PPS && SERIAL_PXA && ! ( PPS = m && SERIAL_PXA = y ) && EXPERIMENTAL + help + If you say yes here you get support for a PPS source connected + to the PWM1 pin of your gumstix. diff -Nur linux-2.6.18gum/drivers/pps/clients/ktimer.c linux-2.6.18gum/drivers/pps/clients/ktimer.c --- linux-2.6.18gum/drivers/pps/clients/ktimer.c 1969-12-31 19:00:00.000000000 -0500 +++ linux-2.6.18gum/drivers/pps/clients/ktimer.c 2006-10-01 11:50:37.000000000 -0400 @@ -0,0 +1,108 @@ +/* + * ktimer.c -- kernel timer test client + * + * + * Copyright (C) 2005-2006 Rodolfo Giometti + * + * 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 +#include +#include +#include +#include +#include + +#include +#include + +/* --- Global variables ----------------------------------------------------- */ + +static int source; +static struct timer_list ktimer; + +/* --- The kernel timer ----------------------------------------------------- */ + +static void linuxpps_ktimer_event(unsigned long ptr) { + info("PPS event at %lu", jiffies); + + linuxpps_event(source, PPS_CAPTUREASSERT); + + /* Rescheduling */ + ktimer.expires = jiffies+HZ; /* 1 second */ + add_timer(&ktimer); +} + +/* --- The echo function ---------------------------------------------------- */ + +static void linuxpps_ktimer_echo(int source, int event) +{ + info("echo %s %s for source %d", + event&PPS_CAPTUREASSERT ? "assert" : "", + event&PPS_CAPTURECLEAR ? "clear" : "", + source); +} + +/* --- The PPS info struct -------------------------------------------------- */ + +static struct linuxpps_source_info_s linuxpps_ktimer_info = { + name : "ktimer", + path : "", + mode : PPS_CAPTUREASSERT|PPS_OFFSETASSERT|PPS_ECHOASSERT| \ + PPS_CANWAIT|PPS_TSFMT_TSPEC, + echo : linuxpps_ktimer_echo, +}; + +/* --- Module staff -------------------------------------------------------- */ + +void __exit linuxpps_ktimer_exit(void) +{ + del_timer_sync(&ktimer); + linuxpps_unregister_source(&linuxpps_ktimer_info); + + info("ktimer PPS source unregistered"); +} + +int __init linuxpps_ktimer_init(void) +{ + int ret; + + ret = linuxpps_register_source(&linuxpps_ktimer_info, + PPS_CAPTUREASSERT|PPS_OFFSETASSERT, + -1 /* is up to the system */); + if (ret < 0) { + err("cannot register ktimer source"); + return ret; + } + source = ret; + + init_timer(&ktimer); + ktimer.function = linuxpps_ktimer_event; + ktimer.expires = jiffies+HZ; /* 1 second */ + add_timer(&ktimer); + + info("ktimer PPS source registered at %d", source); + + return 0; +} + +module_init(linuxpps_ktimer_init); +module_exit(linuxpps_ktimer_exit); + +MODULE_AUTHOR("Rodolfo Giometti "); +MODULE_DESCRIPTION("testing PPS source by using a kernel timer (just for debug)"); +MODULE_LICENSE("GPL"); diff -Nur linux-2.6.18gum/drivers/pps/clients/Makefile linux-2.6.18gum/drivers/pps/clients/Makefile --- linux-2.6.18gum/drivers/pps/clients/Makefile 1969-12-31 19:00:00.000000000 -0500 +++ linux-2.6.18gum/drivers/pps/clients/Makefile 2006-10-01 11:50:37.000000000 -0400 @@ -0,0 +1,6 @@ +# +# Makefile for miscellaneous I2C chip drivers. +# + +obj-$(CONFIG_PPS_CLIENT_KTIMER) += ktimer.o + diff -Nur linux-2.6.18gum/drivers/pps/kapi.c linux-2.6.18gum/drivers/pps/kapi.c --- linux-2.6.18gum/drivers/pps/kapi.c 1969-12-31 19:00:00.000000000 -0500 +++ linux-2.6.18gum/drivers/pps/kapi.c 2006-10-01 11:50:37.000000000 -0400 @@ -0,0 +1,173 @@ +/* + * kapi.c -- kernel API + * + * + * Copyright (C) 2005-2006 Rodolfo Giometti + * + * 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 +#include +#include +#include +#include + +#include +#include + +/* --- Local functions ----------------------------------------------------- */ + +#ifndef NSEC_PER_SEC +#define NSEC_PER_SEC 1000000000 +#endif +static void linuxpps_add_offeset(struct timespec *ts, struct timespec *offset) +{ + ts->tv_nsec += offset->tv_nsec; + if (ts->tv_nsec >= NSEC_PER_SEC) { + ts->tv_nsec -= NSEC_PER_SEC; + ts->tv_sec++; + } else if (ts->tv_nsec < 0) { + ts->tv_nsec += NSEC_PER_SEC; + ts->tv_sec--; + } + ts->tv_sec += offset->tv_sec; +} + +/* --- Exported functions -------------------------------------------------- */ + +int linuxpps_register_source(struct linuxpps_source_info_s *info, int default_params, int try_id) +{ + int i, ret; + + if (try_id >= 0) { + if (linuxpps_is_allocated(try_id)) { + err("source id %d busy", try_id); + return -EBUSY; + } + i = try_id; + } + else { + for (i = 0; i < LINUXPPS_MAX_SOURCES; i++) + if (!linuxpps_is_allocated(i)) + break; + if (i >= LINUXPPS_MAX_SOURCES) { + err("no free source ids"); + return -ENOMEM; + } + } + + /* Sanity checks */ + if ((info->mode&default_params) != default_params) { + err("unsupported default parameters"); + return -EINVAL; + } + if ((info->mode&(PPS_ECHOASSERT|PPS_ECHOCLEAR)) != 0 && info->echo == NULL) { + err("echo function is not defined"); + return -EINVAL; + } + if ((info->mode&(PPS_TSFMT_TSPEC|PPS_TSFMT_NTPFP)) == 0) { + err("unspecified time format"); + return -EINVAL; + } + + /* Allocate the PPS source */ + memset(&linuxpps_source[i], 0, sizeof(struct linuxpps_s)); + linuxpps_source[i].info = info; + linuxpps_source[i].params.api_version = PPS_API_VERS; + linuxpps_source[i].params.mode = default_params; + init_waitqueue_head(&linuxpps_source[i].queue); + + ret = linuxpps_procfs_create_source_entry(info, i); + if (ret < 0) + err("unable to create procfs entry for source %d", i); + + return i; +} + +void linuxpps_unregister_source(struct linuxpps_source_info_s *info) +{ + int i; + + for (i = 0; i < LINUXPPS_MAX_SOURCES; i++) + if (linuxpps_is_allocated(i) && linuxpps_source[i].info == info) + break; + + if (i >= LINUXPPS_MAX_SOURCES) { + err("warning! Try to unregister an unknow PPS source"); + return; + } + + /* Deallocate the PPS source */ + linuxpps_procfs_remove_source_entry(info, i); + linuxpps_source[i].info = NULL; +} + +void linuxpps_event(int source, int event) +{ + struct timespec ts; + + /* First of all we get the time stamp... */ + do_gettimeofday((struct timeval *) &ts); + ts.tv_nsec *= 1000; /* microseconds to nanoseconds */ + + /* ... then we can do some sanity checks */ + if (!linuxpps_is_allocated(source)) { + err("unknow source for event!"); + return; + } + if ((event&(PPS_CAPTUREASSERT|PPS_CAPTURECLEAR)) == 0 ) { + err("unknow event (%x) for source %d", event, source); + return; + } + + /* Must call the echo function? */ + if ((linuxpps_source[source].params.mode&(PPS_ECHOASSERT|PPS_ECHOCLEAR)) != 0) + linuxpps_source[source].info->echo(source, event); + + /* Check the event */ + linuxpps_source[source].current_mode = linuxpps_source[source].params.mode; + if (event&PPS_CAPTUREASSERT) { + /* We have to add an offset? */ + if (linuxpps_source[source].params.mode&PPS_OFFSETASSERT) + linuxpps_add_offeset(&ts, &linuxpps_source[source].params.assert_off_tu.tspec); + + /* Save the time stamp */ + linuxpps_source[source].assert_tu.tspec = ts; + linuxpps_source[source].assert_sequence++; + dbg("capture assert seq #%lu for source %d", + linuxpps_source[source].assert_sequence, source); + } + if (event&PPS_CAPTURECLEAR) { + /* We have to add an offset? */ + if (linuxpps_source[source].params.mode&PPS_OFFSETCLEAR) + linuxpps_add_offeset(&ts, &linuxpps_source[source].params.clear_off_tu.tspec); + + /* Save the time stamp */ + linuxpps_source[source].clear_tu.tspec = ts; + linuxpps_source[source].clear_sequence++; + dbg("capture clear seq #%lu for source %d", + linuxpps_source[source].clear_sequence, source); + } + + wake_up_interruptible(&linuxpps_source[source].queue); +} + +/* --- Exported functions -------------------------------------------------- */ + +EXPORT_SYMBOL(linuxpps_register_source); +EXPORT_SYMBOL(linuxpps_unregister_source); +EXPORT_SYMBOL(linuxpps_event); diff -Nur linux-2.6.18gum/drivers/pps/Kconfig linux-2.6.18gum/drivers/pps/Kconfig --- linux-2.6.18gum/drivers/pps/Kconfig 1969-12-31 19:00:00.000000000 -0500 +++ linux-2.6.18gum/drivers/pps/Kconfig 2006-10-01 12:00:03.000000000 -0400 @@ -0,0 +1,43 @@ +# +# Character device configuration +# + +menu "PPS support" + +config PPS + tristate "PPS support" + depends on EXPERIMENTAL + ---help--- + PPS (Pulse Per Second) is a special pulse provided by some GPS + antennas. Userland can use it to get an high time reference. + + Some antennas' 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 antennas' PPS signals are connected with some special host + inputs so you have to enable the corresponding client support. + + This PPS support can also be built as a module. If so, the module + will be called pps-core. + +config PPS_PROCFS + bool "PPS procfs support" + depends on PPS + default y + help + Say Y here if you want the PPS support to produce a new file into + the "/proc" directory which holds useful information regarding + registered PPS clients into the system. + +config PPS_DEBUG + bool "PPS debugging messages" + depends on PPS + 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. + +source drivers/pps/clients/Kconfig + +endmenu diff -Nur linux-2.6.18gum/drivers/pps/Makefile linux-2.6.18gum/drivers/pps/Makefile --- linux-2.6.18gum/drivers/pps/Makefile 1969-12-31 19:00:00.000000000 -0500 +++ linux-2.6.18gum/drivers/pps/Makefile 2006-10-01 11:50:37.000000000 -0400 @@ -0,0 +1,7 @@ +# +# Makefile for the i2c core. +# + +pps_core-objs += pps.o kapi.o procfs.o +obj-$(CONFIG_PPS) += pps_core.o +obj-y += clients/ diff -Nur linux-2.6.18gum/drivers/pps/pps.c linux-2.6.18gum/drivers/pps/pps.c --- linux-2.6.18gum/drivers/pps/pps.c 1969-12-31 19:00:00.000000000 -0500 +++ linux-2.6.18gum/drivers/pps/pps.c 2006-10-01 11:50:37.000000000 -0400 @@ -0,0 +1,380 @@ +/* + * main.c -- Main driver file + * + * + * Copyright (C) 2005-2006 Rodolfo Giometti + * + * 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 +#include +#include +#include +#include +#include + +#include +#include + +/* --- Global variables ----------------------------------------------------- */ + +struct linuxpps_s linuxpps_source[LINUXPPS_MAX_SOURCES]; +static struct sock *nl_sk = NULL; + +/* --- Misc functions ------------------------------------------------------ */ + +static inline int linuxpps_find_source(int source) +{ + int i; + + if (source >= 0) { + if (source >= LINUXPPS_MAX_SOURCES || !linuxpps_is_allocated(source)) + return -EINVAL; + else + return source; + } + + for (i = 0; i < LINUXPPS_MAX_SOURCES; i++) + if (linuxpps_is_allocated(i)) + break; + + if (i >= LINUXPPS_MAX_SOURCES) + return -EINVAL; + + return i; +} + +static inline int linuxpps_find_path(char *path) +{ + int i; + + for (i = 0; i < LINUXPPS_MAX_SOURCES; i++) + if (linuxpps_is_allocated(i) && + (strncmp(linuxpps_source[i].info->path, path, + LINUXPPS_MAX_NAME_LEN) == 0 || + strncmp(linuxpps_source[i].info->name, path, + LINUXPPS_MAX_NAME_LEN) == 0)) + break; + + if (i >= LINUXPPS_MAX_SOURCES) + return -EINVAL; + + return i; +} + +/* --- Input function ------------------------------------------------------ */ + +static void linuxpps_nl_data_ready(struct sock *sk, int len) +{ + struct sk_buff *skb; + struct nlmsghdr *nlh; + struct pps_netlink_msg *nlpps; + + int cmd, source; + wait_queue_head_t *queue; + unsigned long timeout; + + int ret; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) + while ((skb = skb_dequeue(&sk->sk_receive_queue)) != NULL) { +#else + while ((skb = skb_dequeue(&sk->receive_queue)) != NULL) { +#endif + nlh = (struct nlmsghdr *) skb->data; + dbg("New message from PID %d (flags %x)", + nlh->nlmsg_pid, nlh->nlmsg_flags); + + /* Decode the userland command */ + nlpps = (struct pps_netlink_msg*) NLMSG_DATA(nlh); + cmd = nlpps->cmd; + source = nlpps->source; + + switch (cmd) { + case PPS_CREATE : { + dbg("PPS_CREATE: source %d", source); + + /* Check if the requested source is allocated */ + ret = linuxpps_find_source(source); + if (ret < 0) { + nlpps->ret = ret; + break; + } + + nlpps->source = ret; + nlpps->ret = 0; + + break; + } + + case PPS_DESTROY : { + dbg("PPS_DESTROY: source %d", source); + + /* Nothing to do here! Just answer ok... */ + nlpps->ret = 0; + + break; + } + + case PPS_SETPARMS : { + dbg("PPS_SETPARMS: source %d", source); + + /* Check the capabilities */ + if (!capable(CAP_SYS_TIME)) { + nlpps->ret = -EPERM; + break; + } + + /* Sanity checks */ + if (nlpps->source < 0 || !linuxpps_is_allocated(source)) { + nlpps->ret = -EINVAL; + break; + } + if ((nlpps->params.mode&~linuxpps_source[source].info->mode) != 0) { + dbg("unsupported capabilities"); + nlpps->ret = -EINVAL; + break; + } + if ((nlpps->params.mode&(PPS_CAPTUREASSERT|PPS_CAPTURECLEAR)) == 0) { + dbg("capture mode unspecified"); + nlpps->ret = -EINVAL; + break; + } + if ((nlpps->params.mode&(PPS_TSFMT_TSPEC|PPS_TSFMT_NTPFP)) == 0) { + /* section 3.3 of RFC 2783 interpreted */ + dbg("time format unspecified"); + nlpps->params.mode |= PPS_TSFMT_TSPEC; + } + + /* Save the new parameters */ + linuxpps_source[source].params = nlpps->params; + + /* Restore the read only parameters */ + if (linuxpps_source[source].info->mode&PPS_CANWAIT) + linuxpps_source[source].params.mode |= PPS_CANWAIT; + linuxpps_source[source].params.api_version = PPS_API_VERS; + + nlpps->ret = 0; + + break; + } + + case PPS_GETPARMS : { + dbg("PPS_GETPARMS: source %d", source); + + /* Sanity checks */ + if (nlpps->source < 0 || !linuxpps_is_allocated(source)) { + nlpps->ret = -EINVAL; + break; + } + + nlpps->params = linuxpps_source[source].params; + nlpps->ret = 0; + + break; + } + + case PPS_GETCAP : { + dbg("PPS_GETCAP: source %d", source); + + /* Sanity checks */ + if (nlpps->source < 0 || !linuxpps_is_allocated(source)) { + nlpps->ret = -EINVAL; + break; + } + + nlpps->mode = linuxpps_source[source].info->mode; + nlpps->ret = 0; + + break; + } + + case PPS_FETCH : { + dbg("PPS_FETCH: source %d", source); + queue = &linuxpps_source[source].queue; + + /* Sanity checks */ + if (nlpps->source < 0 || !linuxpps_is_allocated(source)) { + nlpps->ret = -EINVAL; + break; + } + if ((nlpps->tsformat != PPS_TSFMT_TSPEC) != 0 ) { + dbg("unsupported time format"); + nlpps->ret = -EINVAL; + break; + } + + /* Manage the timeout */ + if (nlpps->timeout.tv_sec != -1) { + timeout = nlpps->timeout.tv_sec*HZ; + timeout += nlpps->timeout.tv_nsec/(1000000000/HZ); + + if (timeout != 0) { + timeout = interruptible_sleep_on_timeout(queue, timeout); + if (timeout <= 0) { + dbg("timeout expired"); + nlpps->ret = -ETIMEDOUT; + break; + } + } + } + else + interruptible_sleep_on(queue); + + /* Return the fetched timestamp */ + nlpps->info.assert_sequence = linuxpps_source[source].assert_sequence; + nlpps->info.clear_sequence = linuxpps_source[source].clear_sequence; + nlpps->info.assert_tu = linuxpps_source[source].assert_tu; + nlpps->info.clear_tu = linuxpps_source[source].clear_tu; + nlpps->info.current_mode = linuxpps_source[source].current_mode; + + nlpps->ret = 0; + + break; + } + + case PPS_KC_BIND : { + /* Feature currently not supported */ + nlpps->ret = -EOPNOTSUPP; + + break; + } + + case PPS_FIND_SRC : { + source = linuxpps_find_source(source); + if (source < 0) { + dbg("no PPS devices found"); + nlpps->ret = -ENODEV; + break; + } + + /* Found! So copy the info */ + nlpps->source = source; + strncpy(nlpps->name, linuxpps_source[source].info->name, + LINUXPPS_MAX_NAME_LEN); + strncpy(nlpps->path, linuxpps_source[source].info->path, + LINUXPPS_MAX_NAME_LEN); + nlpps->ret = 0; + + break; + } + + case PPS_FIND_PATH : { + source = linuxpps_find_path(nlpps->path); + if (source < 0) { + dbg("no PPS devices found"); + nlpps->ret = -ENODEV; + break; + } + + /* Found! So copy the info */ + nlpps->source = source; + strncpy(nlpps->name, linuxpps_source[source].info->name, + LINUXPPS_MAX_NAME_LEN); + strncpy(nlpps->path, linuxpps_source[source].info->path, + LINUXPPS_MAX_NAME_LEN); + nlpps->ret = 0; + + break; + } + + default : { + /* Unknow command */ + dbg("unknow command %d", nlpps->cmd); + + nlpps->ret = -EINVAL; + } + } + + /* Send an answer to the userland */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,15) + NETLINK_CB(skb).groups = 0; /* not in mcast groups */ +#endif + NETLINK_CB(skb).pid = 0; /* from the kernel */ + NETLINK_CB(skb).dst_pid = nlh->nlmsg_pid; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,15) + NETLINK_CB(skb).dst_groups = 0; /* not in mcast groups */ +#else + NETLINK_CB(skb).dst_group = 0; /* not in mcast groups */ +#endif + + netlink_unicast(nl_sk, skb, nlh->nlmsg_pid, MSG_DONTWAIT); + } +} + +/* --- Module staff -------------------------------------------------------- */ + +void __exit linuxpps_exit(void) +{ + linuxpps_procfs_unregister(); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) + sock_release(nl_sk->sk_socket); +#else + sock_release(nl_sk->socket); +#endif + + info("LinuxPPS API ver. %d removed", PPS_API_VERS); +} + +int __init linuxpps_init(void) +{ + int ret; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,14) + nl_sk = netlink_kernel_create(NETLINK_PPSAPI, linuxpps_nl_data_ready); +#else + nl_sk = netlink_kernel_create(NETLINK_PPSAPI, 0, + linuxpps_nl_data_ready, THIS_MODULE); +#endif + if (nl_sk == NULL) { + err("unable to create netlink kernel socket"); + return -EBUSY; + } + dbg("netlink protocol %d created", NETLINK_PPSAPI); + + /* Init the main struct */ + memset(linuxpps_source, 0, sizeof(struct linuxpps_s)*LINUXPPS_MAX_SOURCES); + + /* Register to procfs */ + ret = linuxpps_procfs_register(); + if (ret < 0) { + err("unable to register procfs"); + goto linuxpps_procfs_register_error; + } + + info("LinuxPPS API ver. %d registered", PPS_API_VERS); + info("Software ver. %s - Copyright 2005-2006 Rodolfo Giometti ", PPS_VERSION); + + return 0; + +linuxpps_procfs_register_error : + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0) + sock_release(nl_sk->sk_socket); +#else + sock_release(nl_sk->socket); +#endif + + return ret; +} + +subsys_initcall(linuxpps_init); +module_exit(linuxpps_exit); + +MODULE_AUTHOR("Rodolfo Giometti "); +MODULE_DESCRIPTION("LinuxPPS support (RFC 2783) - ver. " PPS_VERSION); +MODULE_LICENSE("GPL"); diff -Nur linux-2.6.18gum/drivers/pps/procfs.c linux-2.6.18gum/drivers/pps/procfs.c --- linux-2.6.18gum/drivers/pps/procfs.c 1969-12-31 19:00:00.000000000 -0500 +++ linux-2.6.18gum/drivers/pps/procfs.c 2006-10-01 11:50:37.000000000 -0400 @@ -0,0 +1,180 @@ +/* + * procfs.c -- procfs support + * + * + * Copyright (C) 2005-2006 Rodolfo Giometti + * + * 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 +#include +#include + +#include +#include + +#ifdef CONFIG_PPS_PROCFS +/* ----- Global variables --------------------------------------------- */ + +struct proc_dir_entry *procfs_root_dir; + +/* ----- Private functions -------------------------------------------- */ + +static int linuxpps_procfs_assert_read(char *page, char **start, off_t off, int count, int *eof, void *data) +{ + int len = 0; + int id = (int) data; + len += sprintf(page+len, "%ld.%09ld #%ld\n", + linuxpps_source[id].assert_tu.tspec.tv_sec, + linuxpps_source[id].assert_tu.tspec.tv_nsec, + linuxpps_source[id].assert_sequence); + + *eof = 1; + return len; +} + +static int linuxpps_procfs_clear_read(char *page, char **start, off_t off, int count, int *eof, void *data) +{ + int len = 0; + int id = (int) data; + len += sprintf(page+len, "%ld.%09ld #%ld\n", + linuxpps_source[id].clear_tu.tspec.tv_sec, + linuxpps_source[id].clear_tu.tspec.tv_nsec, + linuxpps_source[id].clear_sequence); + + *eof = 1; + return len; +} + +static int linuxpps_sources_read(char *page, char **start, off_t off, int count, int *eof, void *data) +{ + int i; + int len = 0; + + len += sprintf(page+len, "id\tmode\techo\tname\t\t\tpath\n"); + len += sprintf(page+len, "----\t------\t----\t----------------\t----------------\n"); + for (i = 0; i < LINUXPPS_MAX_SOURCES; i++) + if (linuxpps_is_allocated(i)) + len += sprintf(page+len, "%.02d\t%4x\t%s\t%s\t\t%s\n", + i, + linuxpps_source[i].info->mode, + linuxpps_source[i].info->echo ? "yes" : "no", + linuxpps_source[i].info->name, + linuxpps_source[i].info->path); + + *eof = 1; + return len; +} + +/* ----- Public functions --------------------------------------------- */ + +void linuxpps_procfs_remove_source_entry(struct linuxpps_source_info_s *info, int id) { + char buf[32]; + + /* Sanity checks */ + if (info == NULL || info->dir == NULL || id >= LINUXPPS_MAX_SOURCES) + return; + + remove_proc_entry("clear", info->dir); + remove_proc_entry("assert", info->dir); + sprintf(buf, "%.02d", id); + remove_proc_entry(buf, procfs_root_dir); +} + +int linuxpps_procfs_create_source_entry(struct linuxpps_source_info_s *info, int id) { + struct proc_dir_entry *procfs_file; + char buf[32]; + + /* Sanity checks */ + if (info == NULL || id >= LINUXPPS_MAX_SOURCES) + return -EINVAL; + + /* Create dir "/proc/pps/" */ + sprintf(buf, "%.02d", id); + info->dir = proc_mkdir(buf, procfs_root_dir); + if (info->dir == NULL) + return -ENOMEM; + + /* Create file "assert" and "clear" according to source capability */ + if (info->mode&PPS_CAPTUREASSERT) { + procfs_file = create_proc_entry("assert", S_IRUGO|S_IWUSR, info->dir); + if (procfs_file == NULL) + goto exit; + procfs_file->read_proc = linuxpps_procfs_assert_read; + procfs_file->data = (void *) id; + } + if (info->mode&PPS_CAPTURECLEAR) { + procfs_file = create_proc_entry("clear", S_IRUGO|S_IWUSR, info->dir); + if (procfs_file == NULL) + goto exit; + procfs_file->read_proc = linuxpps_procfs_clear_read; + procfs_file->data = (void *) id; + } + + return 0; + +exit : + linuxpps_procfs_remove_source_entry(info, id); + + return -ENOMEM; +} + +void linuxpps_procfs_unregister(void) +{ + remove_proc_entry("pps/sources", NULL); + + /* The root dir in /proc/pps */ + remove_proc_entry("pps", NULL); +} + +int linuxpps_procfs_register(void) +{ + struct proc_dir_entry *procfs_file; + + /* The root dir in /proc */ + procfs_root_dir = proc_mkdir("pps", NULL); + if (procfs_root_dir == NULL) + return -ENOMEM; + + /* The file "sources" */ + procfs_file = create_proc_entry("sources", S_IRUGO|S_IWUSR, procfs_root_dir); + if (procfs_file == NULL) { + linuxpps_procfs_unregister(); + return -ENOMEM; + } + procfs_file->owner = THIS_MODULE; + procfs_file->read_proc = linuxpps_sources_read; + procfs_file->write_proc = NULL; /* no write method */ + + info("procfs enabled"); + return 0; +} + +#else /* CONFIG_PPS_PROCFS */ + +void linuxpps_procfs_unregister(void) +{ + /* Nothing to do here... */ + return; +} + +int linuxpps_procfs_register(void) +{ + info("procfs not enabled"); + return 0; +} +#endif /* CONFIG_PPS_PROCFS */ diff -Nur linux-2.6.18gum/drivers/serial/8250.c linux-2.6.18gum/drivers/serial/8250.c --- linux-2.6.18gum/drivers/serial/8250.c 2006-09-19 23:42:06.000000000 -0400 +++ linux-2.6.18gum/drivers/serial/8250.c 2006-10-01 11:50:37.000000000 -0400 @@ -44,6 +44,11 @@ #include #include +#ifdef CONFIG_PPS_CLIENT_8250 +#include +#include +#endif + #include "8250.h" /* @@ -120,6 +125,10 @@ struct uart_port port; struct timer_list timer; /* "no irq" timer */ struct list_head list; /* ports on this IRQ */ +#ifdef CONFIG_PPS_CLIENT_8250 + struct linuxpps_source_info_s linuxpps_8250_info; + int source; +#endif unsigned short capabilities; /* port capabilities */ unsigned short bugs; /* port bugs */ unsigned int tx_loadsz; /* transmit fifo load size */ @@ -1294,8 +1303,19 @@ up->port.icount.rng++; if (status & UART_MSR_DDSR) up->port.icount.dsr++; - if (status & UART_MSR_DDCD) + if (status & UART_MSR_DDCD) { +#ifdef CONFIG_PPS_CLIENT_8250 + if (status & UART_MSR_DCD) { + linuxpps_event(up->source, PPS_CAPTUREASSERT); + dbg("serial8250: PPS assert event at %lu", jiffies); + } + else { + linuxpps_event(up->source, PPS_CAPTURECLEAR); + dbg("serial8250: PPS clear event at %lu", jiffies); + } +#endif uart_handle_dcd_change(&up->port, status & UART_MSR_DCD); + } if (status & UART_MSR_DCTS) uart_handle_cts_change(&up->port, status & UART_MSR_CTS); @@ -1879,6 +1899,9 @@ up->ier |= UART_IER_MSI; if (up->capabilities & UART_CAP_UUE) up->ier |= UART_IER_UUE | UART_IER_RTOIE; +#ifdef CONFIG_PPS_CLIENT_8250 + up->ier |= UART_IER_MSI; /* enable interrupts */ +#endif serial_out(up, UART_IER, up->ier); @@ -2185,6 +2208,7 @@ serial8250_register_ports(struct uart_driver *drv, struct device *dev) { int i; + int ret; serial8250_isa_init_ports(); @@ -2192,7 +2216,31 @@ struct uart_8250_port *up = &serial8250_ports[i]; up->port.dev = dev; - uart_add_one_port(drv, &up->port); + ret = uart_add_one_port(drv, &up->port); +#ifdef CONFIG_PPS_CLIENT_8250 + if (ret == 0) { + snprintf(up->linuxpps_8250_info.name, + LINUXPPS_MAX_NAME_LEN, "pps_8250_%d", + up->port.line); + snprintf(up->linuxpps_8250_info.path, + LINUXPPS_MAX_NAME_LEN, "/dev/ttyS%d", + up->port.line); + up->linuxpps_8250_info.mode = + PPS_CAPTUREBOTH|PPS_OFFSETASSERT|PPS_OFFSETCLEAR| + PPS_CANWAIT|PPS_TSFMT_TSPEC; + ret = linuxpps_register_source( + &(up->linuxpps_8250_info), + PPS_CAPTUREBOTH|PPS_OFFSETASSERT|PPS_OFFSETCLEAR, + -1 /* is up to the system */); + if (ret >= 0) { + up->source = ret; + dbg("registered 8250 source %d at line %d", + up->source, up->port.line); + } + else + err("cannot register 8250 source at line %d", up->port.line); + } +#endif } } @@ -2594,8 +2642,33 @@ uart->port.dev = port->dev; ret = uart_add_one_port(&serial8250_reg, &uart->port); - if (ret == 0) + if (ret == 0) { +#ifdef CONFIG_PPS_CLIENT_8250 + snprintf(uart->linuxpps_8250_info.name, + LINUXPPS_MAX_NAME_LEN, "pps_8250_%d", + uart->port.line); + snprintf(uart->linuxpps_8250_info.path, + LINUXPPS_MAX_NAME_LEN, "/dev/ttyS%d", + uart->port.line); + uart->linuxpps_8250_info.mode = + PPS_CAPTUREBOTH|PPS_OFFSETASSERT|PPS_OFFSETCLEAR| + PPS_CANWAIT|PPS_TSFMT_TSPEC; + ret = linuxpps_register_source( + &(uart->linuxpps_8250_info), + PPS_CAPTUREBOTH|PPS_OFFSETASSERT|PPS_OFFSETCLEAR, + -1 /* is up to the system */); + if (ret >= 0) { + uart->source = ret; + ret = uart->port.line; + dbg("registered 8250 source %d at line %d", + uart->source, uart->port.line); + + } else + err("cannot register 8250 source at line %d", uart->port.line); +#else ret = uart->port.line; +#endif + } } mutex_unlock(&serial_mutex); @@ -2615,6 +2688,12 @@ struct uart_8250_port *uart = &serial8250_ports[line]; mutex_lock(&serial_mutex); + +#ifdef CONFIG_PPS_CLIENT_8250 + linuxpps_unregister_source(&(uart->linuxpps_8250_info)); + dbg("unregistered 8250 source %d from line %d", uart->source, line); +#endif + uart_remove_one_port(&serial8250_reg, &uart->port); if (serial8250_isa_devs) { uart->port.flags &= ~UPF_BOOT_AUTOCONF; diff -Nur linux-2.6.18gum/drivers/serial/pxa.c linux-2.6.18gum/drivers/serial/pxa.c --- linux-2.6.18gum/drivers/serial/pxa.c 2006-11-24 15:58:40.000000000 -0500 +++ linux-2.6.18gum/drivers/serial/pxa.c 2006-10-01 18:18:18.000000000 -0400 @@ -49,9 +49,18 @@ #include #include +#ifdef CONFIG_PPS_CLIENT_PXA +#include +#include +#include +#endif struct uart_pxa_port { struct uart_port port; +#ifdef CONFIG_PPS_CLIENT_PXA + struct linuxpps_source_info_s linuxpps_info; + int source; +#endif unsigned char ier; unsigned char lcr; unsigned char mcr; @@ -232,6 +241,24 @@ wake_up_interruptible(&up->port.info->delta_msr_wait); } +#ifdef CONFIG_PPS_CLIENT_PXA +/* + * This handles the interrupt from the gps pps chip. + */ +static inline irqreturn_t +serial_pxa_pps_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + int *pSource = (int *)dev_id; + + if (GPLR(GUMSTIX_GPIO_PPS) & GPIO_bit(GUMSTIX_GPIO_PPS)) { + linuxpps_event(*pSource, PPS_CAPTUREASSERT); + dbg("pxa: PPS assert event at %lu", jiffies); + } + + return IRQ_HANDLED; +} +#endif + /* * This handles the interrupt from one port. */ @@ -369,6 +397,15 @@ if (retval) return retval; +#ifdef CONFIG_PPS_CLIENT_PXA + if (port->line == 2) { /* STUART */ + retval = request_irq(GUMSTIX_IRQ_GPIO_PPS, serial_pxa_pps_irq, + IRQF_DISABLED, "PPS Interrupt", &up->source); + if (retval) + return retval; + } +#endif + /* * Clear the FIFO buffers and disable them. * (they will be reenabled in set_termios()) @@ -420,6 +457,11 @@ struct uart_pxa_port *up = (struct uart_pxa_port *)port; unsigned long flags; +#ifdef CONFIG_PPS_CLIENT_PXA + if (port->line == 2) { /* STUART */ + free_irq(GUMSTIX_IRQ_GPIO_PPS, &up->source); + } +#endif free_irq(up->port.irq, up); /* @@ -825,9 +867,36 @@ static int serial_pxa_probe(struct platform_device *dev) { - serial_pxa_ports[dev->id].port.dev = &dev->dev; - uart_add_one_port(&serial_pxa_reg, &serial_pxa_ports[dev->id].port); - platform_set_drvdata(dev, &serial_pxa_ports[dev->id]); + struct uart_pxa_port *up = &serial_pxa_ports[dev->id]; + int ret; + + up->port.dev = &dev->dev; + ret = uart_add_one_port(&serial_pxa_reg, &up->port); +#ifdef CONFIG_PPS_CLIENT_PXA + if(ret == 0 && up->port.line == 2) { /* STUART only */ + snprintf(up->linuxpps_info.name, + LINUXPPS_MAX_NAME_LEN, "pps_pxa_%d", + up->port.line); + snprintf(up->linuxpps_info.path, + LINUXPPS_MAX_NAME_LEN, "/dev/ttyS%d", + up->port.line); + up->linuxpps_info.mode = + PPS_CAPTUREASSERT|PPS_OFFSETASSERT| + PPS_CANWAIT|PPS_TSFMT_TSPEC; + ret = linuxpps_register_source( + &(up->linuxpps_info), + PPS_CAPTUREASSERT|PPS_OFFSETASSERT, + -1 /* is up to the system */); + if (ret >= 0) { + up->source = ret; + dbg("registered pxa source %d at line %d", + up->source, up->port.line); + } + else + err("cannot register pxa source at line %d", up->port.line); + } +#endif + platform_set_drvdata(dev, up); return 0; } @@ -837,8 +906,13 @@ platform_set_drvdata(dev, NULL); - if (sport) + if (sport) { +#ifdef CONFIG_PPS_CLIENT_8250 + linuxpps_unregister_source(&(sport->linuxpps_info)); + dbg("unregistered pxa source %d from line %d", sport->source, line); +#endif uart_remove_one_port(&serial_pxa_reg, &sport->port); + } return 0; } diff -Nur linux-2.6.18gum/include/asm-arm/arch-pxa/gumstix.h linux-2.6.18gum/include/asm-arm/arch-pxa/gumstix.h --- linux-2.6.18gum/include/asm-arm/arch-pxa/gumstix.h 2006-11-24 15:58:40.000000000 -0500 +++ linux-2.6.18gum/include/asm-arm/arch-pxa/gumstix.h 2006-10-01 15:42:00.000000000 -0400 @@ -15,7 +15,7 @@ /* GPIOn - Input from MAX823 (or equiv), normalizing USB +5V into a clean interrupt signal for determining cable presence On the original gumstix, this is GPIO81, and GPIO83 needs to be defined as well. - On the gumstix F, this moves to GPIO17 and GPIO37 */ + On the gumstix F, this moves to GPIO35 and GPIO41 */ /* GPIOx - Connects to USB D+ and used as a pull-up after GPIOn has detected a cable insertion; driven low otherwise. */ @@ -44,6 +44,12 @@ #define GUMSTIX_IRQ_GPIO_nSD_DETECT IRQ_GPIO(GUMSTIX_GPIO_nSD_DETECT) /* + * PPS interrupt on GPIO17 + */ +#define GUMSTIX_GPIO_PPS 17 +#define GUMSTIX_IRQ_GPIO_PPS IRQ_GPIO(GUMSTIX_GPIO_PPS) + +/* * SMC Ethernet definitions * ETH_RST provides a hardware reset line to the ethernet chip * ETH is the IRQ line in from the ethernet chip to the PXA diff -Nur linux-2.6.18gum/include/linux/netlink.h linux-2.6.18gum/include/linux/netlink.h --- linux-2.6.18gum/include/linux/netlink.h 2006-09-19 23:42:06.000000000 -0400 +++ linux-2.6.18gum/include/linux/netlink.h 2006-10-01 11:50:37.000000000 -0400 @@ -21,6 +21,7 @@ #define NETLINK_DNRTMSG 14 /* DECnet routing messages */ #define NETLINK_KOBJECT_UEVENT 15 /* Kernel messages to userspace */ #define NETLINK_GENERIC 16 +#define NETLINK_PPSAPI 17 /* linuxPPS support */ #define MAX_LINKS 32 diff -Nur linux-2.6.18gum/include/linux/pps.h linux-2.6.18gum/include/linux/pps.h --- linux-2.6.18gum/include/linux/pps.h 1969-12-31 19:00:00.000000000 -0500 +++ linux-2.6.18gum/include/linux/pps.h 2006-10-01 11:50:37.000000000 -0400 @@ -0,0 +1,93 @@ +/* + * pps.h -- PPS API kernel header. + * + * + * Copyright (C) 2005-2006 Rodolfo Giometti + * + * 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. + */ + + +/* ----- Misc macros -------------------------------------------------- */ + +#define PPS_VERSION "2.2.0" + +#ifdef CONFIG_PPS_DEBUG +#define dbg(format, arg...) printk(KERN_DEBUG "%s: " format "\n" , \ + __FILE__ , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#endif + +#define err(format, arg...) printk(KERN_ERR "%s: " format "\n" , \ + __FILE__ , ## arg) +#define info(format, arg...) printk(KERN_INFO "%s: " format "\n" , \ + __FILE__ , ## arg) +#define warn(format, arg...) printk(KERN_WARNING "%s: " format "\n" , \ + __FILE__ , ## arg) + +/* --- Global defines ------------------------------------------------------ */ + +#define LINUXPPS_MAX_SOURCES 16 + +/* --- Global variables ---------------------------------------------------- */ + +/* The specific PPS source info */ +struct linuxpps_source_info_s { + char name[LINUXPPS_MAX_NAME_LEN]; /* simbolic name */ + char path[LINUXPPS_MAX_NAME_LEN]; /* path of connected device */ + int mode; /* PPS's allowed mode */ + + void (*echo)(int source, int event); /* the PPS echo function */ + + /* procfs section */ + struct proc_dir_entry *dir; +}; + +/* The main struct */ +struct linuxpps_s { + struct linuxpps_source_info_s *info; /* PSS source info */ + + pps_params_t params; /* PPS's current params */ + + volatile pps_seq_t assert_sequence; /* PPS' assert event seq # */ + volatile pps_seq_t clear_sequence; /* PPS' clear event seq # */ + volatile pps_timeu_t assert_tu; + volatile pps_timeu_t clear_tu; + int current_mode; /* PPS mode at event time */ + + wait_queue_head_t queue; /* PPS event queue */ +}; + +/* --- Global variables ---------------------------------------------------- */ + +extern struct linuxpps_s linuxpps_source[LINUXPPS_MAX_SOURCES]; + +/* --- Global functions ---------------------------------------------------- */ + +static inline int linuxpps_is_allocated(int source) { + return linuxpps_source[source].info != NULL; +} + +/* --- Exported functions -------------------------------------------------- */ + +extern int linuxpps_register_source(struct linuxpps_source_info_s *info, int default_params, int try_id); +extern void linuxpps_unregister_source(struct linuxpps_source_info_s *info); +extern void linuxpps_event(int source, int event); + +extern int linuxpps_procfs_create_source_entry(struct linuxpps_source_info_s *info, int id); +extern void linuxpps_procfs_remove_source_entry(struct linuxpps_source_info_s *info, int id); +extern int linuxpps_procfs_register(void); +extern void linuxpps_procfs_unregister(void); diff -Nur linux-2.6.18gum/include/linux/timepps.h linux-2.6.18gum/include/linux/timepps.h --- linux-2.6.18gum/include/linux/timepps.h 1969-12-31 19:00:00.000000000 -0500 +++ linux-2.6.18gum/include/linux/timepps.h 2006-10-01 11:50:37.000000000 -0400 @@ -0,0 +1,515 @@ +/* + * timepps.h -- PPS API main header + * + * + * Copyright (C) 2005-2006 Rodolfo Giometti + * + * 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. + * + * NOTE: this file is *strongly* based on a previous job by Ulrich Windl. + * The original copyright note follows: + * + * Interface to the PPS API described in RFC 2783 (March 2000) + * + * Copyright (c) 1999, 2001, 2004 by Ulrich Windl, + * based on code by Reg Clemens + * based on code by Poul-Henning Kamp + * + * ---------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * wrote this file. As long as you retain this notice + * you can do whatever you want with this stuff. If we meet some day, and + * you think this stuff is worth it, you can buy me a beer in return. + * Poul-Henning Kamp + * ---------------------------------------------------------------------- + */ + +#ifndef _SYS_TIMEPPS_H_ +#define _SYS_TIMEPPS_H_ + +/* Implementation note: the logical states ``assert'' and ``clear'' + * are implemented in terms of the chip register, i.e. ``assert'' + * means the bit is set. */ + +/* --- 3.2 New data structures --------------------------------------------- */ + +#define PPS_API_VERS_2 2 /* LinuxPPS proposal, dated 2006-05 */ +#define PPS_API_VERS PPS_API_VERS_2 +#define LINUXPSS_API 1 /* mark LinuxPPS API */ +#define NETLINK_PPSAPI 17 /* we use just one free number... */ + +typedef struct pps_handle_s { + int source; + int socket; +} pps_handle_t; /* represents a PPS source */ + +typedef unsigned long pps_seq_t; /* sequence number */ + +typedef struct ntp_fp { + unsigned int integral; + unsigned int fractional; +} ntp_fp_t; /* NTP-compatible time stamp */ + +typedef union pps_timeu { + struct timespec tspec; + ntp_fp_t ntpfp; + unsigned long longpad[3]; +} pps_timeu_t; /* generic data type to represent time stamps */ + +typedef struct pps_info { + pps_seq_t assert_sequence; /* seq. num. of assert event */ + pps_seq_t clear_sequence; /* seq. num. of clear event */ + pps_timeu_t assert_tu; /* time of assert event */ + pps_timeu_t clear_tu; /* time of clear event */ + int current_mode; /* current mode bits */ +} pps_info_t; + +#define assert_timestamp assert_tu.tspec +#define clear_timestamp clear_tu.tspec + +#define assert_timestamp_ntpfp assert_tu.ntpfp +#define clear_timestamp_ntpfp clear_tu.ntpfp + +typedef struct pps_params { + int api_version; /* API version # */ + int mode; /* mode bits */ + pps_timeu_t assert_off_tu; /* offset compensation for assert */ + pps_timeu_t clear_off_tu; /* offset compensation for clear */ +} pps_params_t; + +#define assert_offset assert_off_tu.tspec +#define clear_offset clear_off_tu.tspec + +#define assert_offset_ntpfp assert_off_tu.ntpfp +#define clear_offset_ntpfp clear_off_tu.ntpfp + +/* --- 3.3 Mode bit definitions -------------------------------------------- */ + +/* Device/implementation parameters */ +#define PPS_CAPTUREASSERT 0x01 /* capture assert events */ +#define PPS_CAPTURECLEAR 0x02 /* capture clear events */ +#define PPS_CAPTUREBOTH 0x03 /* capture assert and clear events */ + +#define PPS_OFFSETASSERT 0x10 /* apply compensation for assert ev. */ +#define PPS_OFFSETCLEAR 0x20 /* apply compensation for clear ev. */ + +#define PPS_CANWAIT 0x100 /* Can we wait for an event? */ +#define PPS_CANPOLL 0x200 /* "This bit is reserved for + future use." */ + +/* Kernel actions */ +#define PPS_ECHOASSERT 0x40 /* feed back assert event to output */ +#define PPS_ECHOCLEAR 0x80 /* feed back clear event to output */ + +/* Timestamp formats */ +#define PPS_TSFMT_TSPEC 0x1000 /* select timespec format */ +#define PPS_TSFMT_NTPFP 0x2000 /* select NTP format */ + +/* --- 3.4.4 New functions: disciplining the kernel timebase --------------- */ + +/* Kernel consumers */ +#define PPS_KC_HARDPPS 0 /* hardpps() (or equivalent) */ +#define PPS_KC_HARDPPS_PLL 1 /* hardpps() constrained to + use a phase-locked loop */ +#define PPS_KC_HARDPPS_FLL 2 /* hardpps() constrained to + use a frequency-locked loop */ + +/* --- Here begins the implementation-specific part! ----------------------- */ + +#define LINUXPPS_MAX_NAME_LEN 32 +struct pps_netlink_msg { + int cmd; /* the command to execute */ + int source; + char name[LINUXPPS_MAX_NAME_LEN]; /* symbolic name */ + char path[LINUXPPS_MAX_NAME_LEN]; /* path of the connected device */ + int consumer; /* selected kernel consumer */ + pps_params_t params; + int mode; /* edge */ + int tsformat; /* format of time stamps */ + pps_info_t info; + struct timespec timeout; + int ret; +}; +#define PPSAPI_MAX_PAYLOAD sizeof(struct pps_netlink_msg) + +/* check Documentation/ioctl-number.txt! */ +#define PPS_CREATE 1 +#define PPS_DESTROY 2 +#define PPS_SETPARMS 3 +#define PPS_GETPARMS 4 +#define PPS_GETCAP 5 +#define PPS_FETCH 6 +#define PPS_KC_BIND 7 +#define PPS_FIND_SRC 8 +#define PPS_FIND_PATH 9 + +#ifdef __KERNEL__ + +#include +#include +#include + +struct pps_state { + pps_params_t parm; /* PPS parameters */ + pps_info_t info; /* PPS information */ + int cap; /* PPS capabilities */ + long ecount; /* interpolation offset of event */ + struct timespec etime; /* kernel time of event */ + wait_queue_head_t ewait; /* wait queue for event */ +}; + +/* State variables to bind kernel consumer */ +/* PPS API (RFC 2783): current source and mode for ``kernel consumer'' */ +extern const struct pps *pps_kc_hardpps_dev; /* some unique pointer to device */ +extern int pps_kc_hardpps_mode; /* mode bits for kernel consumer */ + +/* Return allowed mode bits for given pps struct, file's mode, and user. + * Bits set in `*obligatory' must be set. Returned bits may be set. */ +extern int pps_allowed_mode(const struct pps *pps, mode_t fmode, int *obligatory); + +#else /* !__KERNEL__ */ + +/* --- 3.4 Functions ------------------------------------------------------- */ + +#include +#include +#include +#include +#include + +/* Private functions */ + +static int netlink_msg(int socket, struct pps_netlink_msg *nlpps) +{ + struct sockaddr_nl dest_addr; + struct nlmsghdr *nlh; + struct iovec iov; + struct msghdr msg; + + int ret; + + memset(&msg, 0, sizeof(msg)); + + /* Create the destination address */ + memset(&dest_addr, 0, sizeof(dest_addr)); + dest_addr.nl_family = AF_NETLINK; + dest_addr.nl_pid = 0; /* for the kernel */ + dest_addr.nl_groups = 0; /* not in mcast groups */ + + nlh = (struct nlmsghdr *) alloca(NLMSG_SPACE(PPSAPI_MAX_PAYLOAD)); + if (nlh == NULL) + return -1; + + /* Fill the netlink message header */ + nlh->nlmsg_len = NLMSG_SPACE(PPSAPI_MAX_PAYLOAD); + nlh->nlmsg_pid = getpid(); + nlh->nlmsg_flags = 0; + memcpy(NLMSG_DATA(nlh), nlpps, sizeof(struct pps_netlink_msg)); + + iov.iov_base = (void *) nlh; + iov.iov_len = nlh->nlmsg_len; + msg.msg_name = (void *) &dest_addr; + msg.msg_namelen = sizeof(dest_addr); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + /* Send the message */ + ret = sendmsg(socket, &msg, 0); + if (ret < 0) + return ret; + + /* Wait for the answer */ + memset(nlh, 0, NLMSG_SPACE(PPSAPI_MAX_PAYLOAD)); + ret = recvmsg(socket, &msg, 0); + if (ret < 0) + return ret; + + /* Check the return value */ + memcpy(nlpps, NLMSG_DATA(nlh), sizeof(struct pps_netlink_msg)); + if (nlpps->ret < 0) { + errno = -nlpps->ret; + return -1; + } + + return 0; +} + +/* The PPSAPI functions */ + +/* Create PPS handle from file descriptor */ +/* We simply ignore "filedes" */ +static __inline int time_pps_create(int filedes, pps_handle_t *handle) +{ + struct sockaddr_nl src_addr, dest_addr; + struct pps_netlink_msg nlpps; + + int ret; + + /* Create the netlink socket */ + ret = socket(PF_NETLINK, SOCK_RAW, NETLINK_PPSAPI); + if (ret < 0) + return ret; + handle->socket = ret; + + /* Bind the socket with the source address */ + memset(&src_addr, 0, sizeof(src_addr)); + src_addr.nl_family = AF_NETLINK; + src_addr.nl_pid = getpid(); /* self PID as unique ID */ + src_addr.nl_groups = 0; /* not in mcast groups */ + ret = bind(handle->socket, (struct sockaddr *) &src_addr, sizeof(src_addr)); + if (ret < 0) { + close(handle->socket); + return ret; + } + + /* Now ask the kernel to create the PPS source */ + nlpps.cmd = PPS_CREATE; + nlpps.source = filedes; + ret = netlink_msg(handle->socket, &nlpps); + if (ret < 0) + return ret; + + /* Save the PPS source returned by the kernel */ + handle->source = nlpps.source; + + return 0; +} + +/* Release PPS handle */ +static __inline int time_pps_destroy(pps_handle_t handle) +{ + struct pps_netlink_msg nlpps; + + int ret; + + /* Ask the kernel to destroy the PPS source */ + nlpps.cmd = PPS_DESTROY; + nlpps.source = handle.source; + ret = netlink_msg(handle.socket, &nlpps); + if (ret < 0) + return ret; + + /* Now we can destroy the netlink socket */ + close(handle.socket); + + return 0; +} + +/* Set parameters for handle */ +static __inline int time_pps_setparams(pps_handle_t handle, const pps_params_t *ppsparams) +{ + struct pps_netlink_msg nlpps; + + int ret; + + /* Ask the kernel to set the new PPS source's parameters */ + nlpps.cmd = PPS_SETPARMS; + nlpps.source = handle.source; + nlpps.params = *ppsparams; + ret = netlink_msg(handle.socket, &nlpps); + if (ret < 0) + return ret; + + return 0; +} + +static __inline int time_pps_getparams(pps_handle_t handle, pps_params_t *ppsparams) +{ + struct pps_netlink_msg nlpps; + + int ret; + + /* Ask the kernel to return the PPS source's parameters */ + nlpps.cmd = PPS_GETPARMS; + nlpps.source = handle.source; + ret = netlink_msg(handle.socket, &nlpps); + if (ret < 0) + return ret; + + /* Return the parameters */ + *ppsparams = nlpps.params; + + return 0; +} + +/* Get capabilities for handle */ +static __inline int time_pps_getcap(pps_handle_t handle, int *mode) +{ + struct pps_netlink_msg nlpps; + + int ret; + + /* Ask the kernel to return the PPS source's capabilities */ + nlpps.cmd = PPS_GETCAP; + nlpps.source = handle.source; + ret = netlink_msg(handle.socket, &nlpps); + if (ret < 0) + return ret; + + /* Return the capabilities */ + *mode = nlpps.mode; + + return 0; +} + +/* current event for handle */ +static __inline int time_pps_fetch(pps_handle_t handle, const int tsformat, pps_info_t *ppsinfobuf, const struct timespec *timeout) +{ + struct pps_netlink_msg nlpps; + + int ret; + + /* Ask the kernel to return the PPS source's capabilities */ + nlpps.cmd = PPS_FETCH; + nlpps.source = handle.source; + nlpps.tsformat = tsformat; + if (timeout) + nlpps.timeout = *timeout; + else /* wait forever */ + nlpps.timeout.tv_sec = nlpps.timeout.tv_nsec = -1; + + ret = netlink_msg(handle.socket, &nlpps); + if (ret < 0) + return ret; + + /* Return the timestamps */ + *ppsinfobuf = nlpps.info; + + return 0; +} + +/* Specify kernel consumer */ +static __inline int time_pps_kcbind(pps_handle_t handle, const int kernel_consumer, const int edge, const int tsformat) +{ + struct pps_netlink_msg nlpps; + + int ret; + + /* Ask the kernel to destroy the PPS source */ + nlpps.cmd = PPS_KC_BIND; + nlpps.source = handle.source; + nlpps.consumer = kernel_consumer; + nlpps.mode = edge; + nlpps.tsformat = tsformat; + ret = netlink_msg(handle.socket, &nlpps); + if (ret < 0) + return ret; + + return 0; +} + +/* Find a PPS source */ +#define PPS_HAVE_FINDSOURCE 1 +static __inline int time_pps_findsource(int index, char *path, int pathlen, char *idstring, int idlen) +{ + int sock; + struct sockaddr_nl src_addr, dest_addr; + struct pps_netlink_msg nlpps; + + int ret; + + /* Create the netlink socket */ + ret = socket(PF_NETLINK, SOCK_RAW, NETLINK_PPSAPI); + if (ret < 0) + return ret; + sock = ret; + + /* Bind the socket with the source address */ + memset(&src_addr, 0, sizeof(src_addr)); + src_addr.nl_family = AF_NETLINK; + src_addr.nl_pid = getpid(); /* self PID as unique ID */ + src_addr.nl_groups = 0; /* not in mcast groups */ + ret = bind(sock, (struct sockaddr *) &src_addr, sizeof(src_addr)); + if (ret < 0) { + close(sock); + return ret; + } + + /* Ask the kernel to destroy the PPS source */ + nlpps.cmd = PPS_FIND_SRC; + nlpps.source = index; + ret = netlink_msg(sock, &nlpps); + if (ret < 0) { + close(sock); + return ret; + } + + strncpy(path, nlpps.path, pathlen); + strncpy(idstring, nlpps.name, idlen); + + close(sock); + return nlpps.source; +} + +#define PPS_HAVE_FINDPATH 1 +static __inline void time_pps_readlink(char *link, int linklen, char *path, int pathlen) +{ + int i; + + i = readlink(link, path, pathlen-1); + if (i <= 0) { + /* "link" is not a valid symbolic so we directly use it */ + strncpy(path, link, linklen <= pathlen ? linklen : pathlen); + return; + } + + /* Return the file name where "link" points to */ + path[i] = '\0'; + return; +} + +static __inline int time_pps_findpath(char *path, int pathlen, char *idstring, int idlen) +{ + int sock; + struct sockaddr_nl src_addr, dest_addr; + struct pps_netlink_msg nlpps; + + int ret; + + /* Create the netlink socket */ + ret = socket(PF_NETLINK, SOCK_RAW, NETLINK_PPSAPI); + if (ret < 0) + return ret; + sock = ret; + + /* Bind the socket with the source address */ + memset(&src_addr, 0, sizeof(src_addr)); + src_addr.nl_family = AF_NETLINK; + src_addr.nl_pid = getpid(); /* self PID as unique ID */ + src_addr.nl_groups = 0; /* not in mcast groups */ + ret = bind(sock, (struct sockaddr *) &src_addr, sizeof(src_addr)); + if (ret < 0) { + close(sock); + return ret; + } + + /* Ask the kernel to destroy the PPS source */ + nlpps.cmd = PPS_FIND_PATH; + strncpy(nlpps.path, path, pathlen); + ret = netlink_msg(sock, &nlpps); + if (ret < 0) { + close(sock); + return ret; + } + + strncpy(path, nlpps.path, pathlen); + strncpy(idstring, nlpps.name, idlen); + + close(sock); + return nlpps.source; +} + +#endif /* !__KERNEL__ */ +#endif /* _SYS_TIMEPPS_H_ */