diff --git a/target/linux/generic-2.6/patches-2.6.23/230-pps_support.patch b/target/linux/generic-2.6/patches-2.6.23/230-pps_support.patch new file mode 100644 index 0000000000..52aa586fce --- /dev/null +++ b/target/linux/generic-2.6/patches-2.6.23/230-pps_support.patch @@ -0,0 +1,2215 @@ +diff --git a/Documentation/pps/Makefile b/Documentation/pps/Makefile +new file mode 100644 +index 0000000..a2660a2 +--- /dev/null ++++ b/Documentation/pps/Makefile +@@ -0,0 +1,27 @@ ++TARGETS = ppstest ppsctl ++ ++CFLAGS += -Wall -O2 -D_GNU_SOURCE ++CFLAGS += -I . ++CFLAGS += -ggdb ++ ++# -- Actions section ---------------------------------------------------------- ++ ++.PHONY : all depend dep ++ ++all : .depend $(TARGETS) ++ ++.depend depend dep : ++ $(CC) $(CFLAGS) -M $(TARGETS:=.c) > .depend ++ ++ifeq (.depend,$(wildcard .depend)) ++include .depend ++endif ++ ++ ++# -- Clean section ------------------------------------------------------------ ++ ++.PHONY : clean ++ ++clean : ++ rm -f *.o *~ core .depend ++ rm -f ${TARGETS} +diff --git a/Documentation/pps/pps.txt b/Documentation/pps/pps.txt +new file mode 100644 +index 0000000..9538925 +--- /dev/null ++++ b/Documentation/pps/pps.txt +@@ -0,0 +1,170 @@ ++ ++ PPS - Pulse Per Second ++ ---------------------- ++ ++(C) Copyright 2007 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. ++ ++ ++ ++Overview ++-------- ++ ++LinuxPPS provides a programming interface (API) to define into the ++system several PPS sources. ++ ++PPS means "pulse per second" and a PPS source is just a device which ++provides a high precision signal each second so that an application ++can use it to adjust system clock time. ++ ++A PPS source can be connected to a serial port (usually to the Data ++Carrier Detect pin) or to a parallel port (ACK-pin) or to a special ++CPU's GPIOs (this is the common case in embedded systems) but in each ++case when a new pulse comes the system must apply to it a timestamp ++and record it for the userland. ++ ++Common use is the combination of the NTPD as userland program with a ++GPS receiver as PPS source to obtain a wallclock-time with ++sub-millisecond synchronisation to UTC. ++ ++ ++RFC considerations ++------------------ ++ ++While implementing a PPS API as RFC 2783 defines and using an embedded ++CPU GPIO-Pin as physical link to the signal, I encountered a deeper ++problem: ++ ++ At startup it needs a file descriptor as argument for the function ++ time_pps_create(). ++ ++This implies that the source has a /dev/... entry. This assumption is ++ok for the serial and parallel port, where you can do something ++useful besides(!) the gathering of timestamps as it is the central ++task for a PPS-API. But this assumption does not work for a single ++purpose GPIO line. In this case even basic file-related functionality ++(like read() and write()) makes no sense at all and should not be a ++precondition for the use of a PPS-API. ++ ++The problem can be simply solved if you consider that a PPS source is ++not always connected with a GPS data source. ++ ++So your programs should check if the GPS data source (the serial port ++for instance) is a PPS source too, otherwise they should provide the ++possibility to open another device as PPS source. ++ ++In LinuxPPS the PPS sources are simply char devices usually mapped ++into files /dev/pps0, /dev/pps1, etc.. ++ ++ ++Coding example ++-------------- ++ ++To register a PPS source into the kernel you should define a struct ++pps_source_info_s as follow: ++ ++ static struct pps_source_info_s pps_ktimer_info = { ++ name : "ktimer", ++ path : "", ++ mode : PPS_CAPTUREASSERT | PPS_OFFSETASSERT | \ ++ PPS_ECHOASSERT | \ ++ PPS_CANWAIT | PPS_TSFMT_TSPEC, ++ echo : pps_ktimer_echo, ++ owner : THIS_MODULE, ++ }; ++ ++and then calling the function pps_register_source() in your ++intialization routine as follow: ++ ++ source = pps_register_source(&pps_ktimer_info, ++ PPS_CAPTUREASSERT | PPS_OFFSETASSERT); ++ ++The pps_register_source() prototype is: ++ ++ int pps_register_source(struct pps_source_info_s *info, int default_params) ++ ++where "info" is a pointer to a structure that describes a particular ++PPS source, "default_params" tells the system what the initial default ++parameters for the device should be (is obvious that these parameters ++must be a subset of ones defined into the struct ++pps_source_info_s which describe the capabilities of the driver). ++ ++Once you have registered a new PPS source into the system you can ++signal an assert event (for example in the interrupt handler routine) ++just using: ++ ++ pps_event(source, PPS_CAPTUREASSERT, ptr); ++ ++The same function may also run the defined echo function ++(pps_ktimer_echo(), passing to it the "ptr" pointer) if the user ++asked for that... etc.. ++ ++Please see the file drivers/pps/clients/ktimer.c for an example code. ++ ++ ++SYSFS support ++------------- ++ ++If the SYSFS filesystem is enabled in the kernel it provides a new class: ++ ++ $ ls /sys/class/pps/ ++ pps0/ pps1/ pps2/ ++ ++Every directory is the ID of a PPS sources defined into the system and ++inside you find several files: ++ ++ $ ls /sys/class/pps/pps0/ ++ assert clear echo mode name path subsystem@ uevent ++ ++Inside each "assert" and "clear" file you can find the timestamp and a ++sequence number: ++ ++ $ cat /sys/class/pps/pps0/assert ++ 1170026870.983207967#8 ++ ++Where before the "#" is the timestamp in seconds and after it is the ++sequence number. Other files are: ++ ++* echo: reports if the PPS source has an echo function or not; ++ ++* mode: reports available PPS functioning modes; ++ ++* name: reports the PPS source's name; ++ ++* path: reports the PPS source's device path, that is the device the ++ PPS source is connected to (if it exists). ++ ++ ++Testing the PPS support ++----------------------- ++ ++In order to test the PPS support even without specific hardware you can use ++the ktimer driver (see the client subsection in the PPS configuration menu) ++and the userland tools provided into Documentaion/pps/ directory. ++ ++Once you have enabled the compilation of ktimer just modprobe it (if ++not statically compiled): ++ ++ # modprobe ktimer ++ ++and the run ppstest as follow: ++ ++ $ ./ppstest /dev/pps0 ++ trying PPS source "/dev/pps1" ++ found PPS source "/dev/pps1" ++ ok, found 1 source(s), now start fetching data... ++ source 0 - assert 1186592699.388832443, sequence: 364 - clear 0.000000000, sequence: 0 ++ source 0 - assert 1186592700.388931295, sequence: 365 - clear 0.000000000, sequence: 0 ++ source 0 - assert 1186592701.389032765, sequence: 366 - clear 0.000000000, sequence: 0 ++ ++Please, note that to compile userland programs you need the file timepps.h ++(see Documentation/pps/). +diff --git a/Documentation/pps/ppsctl.c b/Documentation/pps/ppsctl.c +new file mode 100644 +index 0000000..83fd08a +--- /dev/null ++++ b/Documentation/pps/ppsctl.c +@@ -0,0 +1,62 @@ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++void usage(char *name) ++{ ++ fprintf(stderr, "usage: %s [enable|disable]\n", name); ++ ++ exit(EXIT_FAILURE); ++} ++ ++int main(int argc, char *argv[]) ++{ ++ int fd; ++ int ret; ++ struct serial_struct ss; ++ ++ if (argc < 2) ++ usage(argv[0]); ++ ++ fd = open(argv[1], O_RDWR); ++ if (fd < 0) { ++ perror("open"); ++ exit(EXIT_FAILURE); ++ } ++ ++ ret = ioctl(fd, TIOCGSERIAL, &ss); ++ if (ret < 0) { ++ perror("ioctl(TIOCGSERIAL)"); ++ exit(EXIT_FAILURE); ++ } ++ ++ if (argc < 3) { /* just read PPS status */ ++ printf("PPS is %sabled\n", ++ ss.flags & ASYNC_HARDPPS_CD ? "en" : "dis"); ++ exit(EXIT_SUCCESS); ++ } ++ ++ if (argv[2][0] == 'e' || argv[2][0] == '1') ++ ss.flags |= ASYNC_HARDPPS_CD; ++ else if (argv[2][0] == 'd' || argv[2][0] == '0') ++ ss.flags &= ~ASYNC_HARDPPS_CD; ++ else { ++ fprintf(stderr, "invalid state argument \"%s\"\n", argv[2]); ++ exit(EXIT_FAILURE); ++ } ++ ++ ret = ioctl(fd, TIOCSSERIAL, &ss); ++ if (ret < 0) { ++ perror("ioctl(TIOCSSERIAL)"); ++ exit(EXIT_FAILURE); ++ } ++ ++ return 0; ++} +diff --git a/Documentation/pps/ppsfind b/Documentation/pps/ppsfind +new file mode 100755 +index 0000000..93c0e17 +--- /dev/null ++++ b/Documentation/pps/ppsfind +@@ -0,0 +1,17 @@ ++#!/bin/sh ++ ++SYS="/sys/class/pps/" ++ ++if [ $# -lt 1 ] ; then ++ echo "usage: ppsfind " >&2 ++ exit 1 ++fi ++ ++for d in $(ls $SYS) ; do ++ if grep $1 $SYS/$d/name >& /dev/null || \ ++ grep $1 $SYS/$d/path >& /dev/null ; then ++ echo "$d: name=$(cat $SYS/$d/name) path=$(cat $SYS/$d/path)" ++ fi ++done ++ ++exit 0 +diff --git a/Documentation/pps/ppstest.c b/Documentation/pps/ppstest.c +new file mode 100644 +index 0000000..d125ffa +--- /dev/null ++++ b/Documentation/pps/ppstest.c +@@ -0,0 +1,151 @@ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++int find_source(char *path, pps_handle_t *handle, int *avail_mode) ++{ ++ pps_params_t params; ++ int ret; ++ ++ printf("trying PPS source \"%s\"\n", path); ++ ++ /* Try to find the source by using the supplied "path" name */ ++ ret = open(path, O_RDWR); ++ if (ret < 0) { ++ fprintf(stderr, "unable to open device \"%s\" (%m)\n", path); ++ return ret; ++ } ++ ++ /* Open the PPS source (and check the file descriptor) */ ++ ret = time_pps_create(ret, handle); ++ if (ret < 0) { ++ fprintf(stderr, "cannot create a PPS source from device " ++ "\"%s\" (%m)\n", path); ++ return -1; ++ } ++ printf("found PPS source \"%s\"\n", path); ++ ++ /* Find out what features are supported */ ++ ret = time_pps_getcap(*handle, avail_mode); ++ if (ret < 0) { ++ fprintf(stderr, "cannot get capabilities (%m)\n"); ++ return -1; ++ } ++ if ((*avail_mode & PPS_CAPTUREASSERT) == 0) { ++ fprintf(stderr, "cannot CAPTUREASSERT\n"); ++ return -1; ++ } ++ if ((*avail_mode & PPS_OFFSETASSERT) == 0) { ++ fprintf(stderr, "cannot OFFSETASSERT\n"); ++ return -1; ++ } ++ ++ /* Capture assert timestamps, and compensate for a 675 nsec ++ * propagation delay */ ++ ret = time_pps_getparams(*handle, ¶ms); ++ if (ret < 0) { ++ fprintf(stderr, "cannot get parameters (%m)\n"); ++ return -1; ++ } ++ params.assert_offset.tv_sec = 0; ++ params.assert_offset.tv_nsec = 675; ++ params.mode |= PPS_CAPTUREASSERT | PPS_OFFSETASSERT; ++ ret = time_pps_setparams(*handle, ¶ms); ++ if (ret < 0) { ++ fprintf(stderr, "cannot set parameters (%m)\n"); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++int fetch_source(int i, pps_handle_t *handle, int *avail_mode) ++{ ++ struct timespec timeout; ++ pps_info_t infobuf; ++ int ret; ++ ++ /* create a zero-valued timeout */ ++ timeout.tv_sec = 3; ++ timeout.tv_nsec = 0; ++ ++retry: ++ if (*avail_mode & PPS_CANWAIT) /* waits for the next event */ ++ ret = time_pps_fetch(*handle, PPS_TSFMT_TSPEC, &infobuf, ++ &timeout); ++ else { ++ sleep(1); ++ ret = time_pps_fetch(*handle, PPS_TSFMT_TSPEC, &infobuf, ++ &timeout); ++ } ++ if (ret < 0) { ++ if (ret == -EINTR) { ++ fprintf(stderr, "time_pps_fetch() got a signal!\n"); ++ goto retry; ++ } ++ ++ fprintf(stderr, "time_pps_fetch() error %d (%m)\n", ret); ++ return -1; ++ } ++ ++ printf("source %d - " ++ "assert %ld.%09ld, sequence: %ld - " ++ "clear %ld.%09ld, sequence: %ld\n", ++ i, ++ infobuf.assert_timestamp.tv_sec, ++ infobuf.assert_timestamp.tv_nsec, ++ infobuf.assert_sequence, ++ infobuf.clear_timestamp.tv_sec, ++ infobuf.clear_timestamp.tv_nsec, infobuf.clear_sequence); ++ ++ return 0; ++} ++ ++void usage(char *name) ++{ ++ fprintf(stderr, "usage: %s [ ...]\n", name); ++ exit(EXIT_FAILURE); ++} ++ ++int main(int argc, char *argv[]) ++{ ++ int num; ++ pps_handle_t handle[4]; ++ int avail_mode[4]; ++ int i = 0; ++ int ret; ++ ++ /* Check the command line */ ++ if (argc < 2) ++ usage(argv[0]); ++ ++ for (i = 1; i < argc && i <= 4; i++) { ++ ret = find_source(argv[i], &handle[i - 1], &avail_mode[i - 1]); ++ if (ret < 0) ++ exit(EXIT_FAILURE); ++ } ++ ++ num = i - 1; ++ printf("ok, found %d source(s), now start fetching data...\n", num); ++ ++ /* loop, printing the most recent timestamp every second or so */ ++ while (1) { ++ for (i = 0; i < num; i++) { ++ ret = fetch_source(i, &handle[i], &avail_mode[i]); ++ if (ret < 0 && errno != ETIMEDOUT) ++ exit(EXIT_FAILURE); ++ } ++ } ++ ++ for (; i >= 0; i--) ++ time_pps_destroy(handle[i]); ++ ++ return 0; ++} +diff --git a/Documentation/pps/timepps.h b/Documentation/pps/timepps.h +new file mode 100644 +index 0000000..28ebf4c +--- /dev/null ++++ b/Documentation/pps/timepps.h +@@ -0,0 +1,193 @@ ++/* ++ * timepps.h -- PPS API main header ++ * ++ * Copyright (C) 2005-2007 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. ++ */ ++ ++#ifndef _SYS_TIMEPPS_H_ ++#define _SYS_TIMEPPS_H_ ++ ++#include ++#include ++#include ++#include ++ ++/* ++ * New data structures ++ */ ++ ++struct ntp_fp { ++ unsigned int integral; ++ unsigned int fractional; ++}; ++ ++union pps_timeu { ++ struct timespec tspec; ++ struct ntp_fp ntpfp; ++ unsigned long longpad[3]; ++}; ++ ++struct pps_info { ++ unsigned long assert_sequence; /* seq. num. of assert event */ ++ unsigned long clear_sequence; /* seq. num. of clear event */ ++ union pps_timeu assert_tu; /* time of assert event */ ++ union pps_timeu clear_tu; /* time of clear event */ ++ int current_mode; /* current mode bits */ ++}; ++ ++struct pps_params { ++ int api_version; /* API version # */ ++ int mode; /* mode bits */ ++ union pps_timeu assert_off_tu; /* offset compensation for assert */ ++ union pps_timeu clear_off_tu; /* offset compensation for clear */ ++}; ++ ++typedef int pps_handle_t; /* represents a PPS source */ ++typedef unsigned long pps_seq_t; /* sequence number */ ++typedef struct ntp_fp ntp_fp_t; /* NTP-compatible time stamp */ ++typedef union pps_timeu pps_timeu_t; /* generic data type for time stamps */ ++typedef struct pps_info pps_info_t; ++typedef struct pps_params pps_params_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 ++ ++#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 ++ ++/* ++ * The PPS API ++ */ ++ ++static __inline int time_pps_create(int source, pps_handle_t *handle) ++{ ++ int ret; ++ ++ if (!handle) { ++ errno = EINVAL; ++ return -1; ++ } ++ ++ /* First we check if current device is a PPS valid PPS one... ++ */ ++ ret = ioctl(source, PPS_CHECK); ++ if (ret) { ++ errno = EOPNOTSUPP; ++ return -1; ++ } ++ ++ /* ... then since in LinuxPPS there are no differences between a ++ * "PPS source" and a "PPS handle", we simply return the same value. ++ */ ++ *handle = source; ++ ++ return 0; ++} ++ ++static __inline int time_pps_destroy(pps_handle_t handle) ++{ ++ return close(handle); ++} ++ ++static __inline int time_pps_getparams(pps_handle_t handle, ++ pps_params_t *ppsparams) ++{ ++ int ret; ++ struct pps_kparams __ppsparams; ++ ++ ret = ioctl(handle, PPS_GETPARAMS, &__ppsparams); ++ ++ ppsparams->api_version = __ppsparams.api_version; ++ ppsparams->mode = __ppsparams.mode; ++ ppsparams->assert_off_tu.tspec.tv_sec = __ppsparams.assert_off_tu.sec; ++ ppsparams->assert_off_tu.tspec.tv_nsec = __ppsparams.assert_off_tu.nsec; ++ ppsparams->clear_off_tu.tspec.tv_sec = __ppsparams.clear_off_tu.sec; ++ ppsparams->clear_off_tu.tspec.tv_nsec = __ppsparams.clear_off_tu.nsec; ++ ++ return ret; ++} ++ ++static __inline int time_pps_setparams(pps_handle_t handle, ++ const pps_params_t *ppsparams) ++{ ++ struct pps_kparams __ppsparams; ++ ++ __ppsparams.api_version = ppsparams->api_version; ++ __ppsparams.mode = ppsparams->mode; ++ __ppsparams.assert_off_tu.sec = ppsparams->assert_off_tu.tspec.tv_sec; ++ __ppsparams.assert_off_tu.nsec = ppsparams->assert_off_tu.tspec.tv_nsec; ++ __ppsparams.clear_off_tu.sec = ppsparams->clear_off_tu.tspec.tv_sec; ++ __ppsparams.clear_off_tu.nsec = ppsparams->clear_off_tu.tspec.tv_nsec; ++ ++ return ioctl(handle, PPS_SETPARAMS, &__ppsparams); ++} ++ ++/* Get capabilities for handle */ ++static __inline int time_pps_getcap(pps_handle_t handle, int *mode) ++{ ++ return ioctl(handle, PPS_GETCAP, mode); ++} ++ ++static __inline int time_pps_fetch(pps_handle_t handle, const int tsformat, ++ pps_info_t *ppsinfobuf, ++ const struct timespec *timeout) ++{ ++ struct pps_fdata __fdata; ++ int ret; ++ ++ /* Sanity checks */ ++ if (tsformat != PPS_TSFMT_TSPEC) { ++ errno = EINVAL; ++ return -1; ++ } ++ ++ if (timeout) { ++ __fdata.timeout.sec = timeout->tv_sec; ++ __fdata.timeout.nsec = timeout->tv_nsec; ++ __fdata.timeout.flags = ~PPS_TIME_INVALID; ++ } else ++ __fdata.timeout.flags = PPS_TIME_INVALID; ++ ++ ret = ioctl(handle, PPS_FETCH, &__fdata); ++ ++ ppsinfobuf->assert_sequence = __fdata.info.assert_sequence; ++ ppsinfobuf->clear_sequence = __fdata.info.clear_sequence; ++ ppsinfobuf->assert_tu.tspec.tv_sec = __fdata.info.assert_tu.sec; ++ ppsinfobuf->assert_tu.tspec.tv_nsec = __fdata.info.assert_tu.nsec; ++ ppsinfobuf->clear_tu.tspec.tv_sec = __fdata.info.clear_tu.sec; ++ ppsinfobuf->clear_tu.tspec.tv_nsec = __fdata.info.clear_tu.nsec; ++ ppsinfobuf->current_mode = __fdata.info.current_mode; ++ ++ return ret; ++} ++ ++static __inline int time_pps_kcbind(pps_handle_t handle, ++ const int kernel_consumer, ++ const int edge, const int tsformat) ++{ ++ /* LinuxPPS doesn't implement kernel consumer feature */ ++ errno = EOPNOTSUPP; ++ return -1; ++} ++ ++#endif /* _SYS_TIMEPPS_H_ */ +diff --git a/MAINTAINERS b/MAINTAINERS +index 9a91d9e..f45e974 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -3011,6 +3011,13 @@ P: James Chapman + M: jchapman@katalix.com + S: Maintained + ++PPS SUPPORT ++P: Rodolfo Giometti ++M: giometti@enneenne.com ++W: http://wiki.enneenne.com/index.php/LinuxPPS_support ++L: linuxpps@ml.enneenne.com ++S: Maintained ++ + PREEMPTIBLE KERNEL + P: Robert Love + M: rml@tech9.net +diff --git a/drivers/Kconfig b/drivers/Kconfig +index 3e1c442..bffc48e 100644 +--- a/drivers/Kconfig ++++ b/drivers/Kconfig +@@ -52,6 +52,8 @@ source "drivers/i2c/Kconfig" + + source "drivers/spi/Kconfig" + ++source "drivers/pps/Kconfig" ++ + source "drivers/w1/Kconfig" + + source "drivers/power/Kconfig" +diff --git a/drivers/Makefile b/drivers/Makefile +index f0878b2..2e84e49 100644 +--- a/drivers/Makefile ++++ b/drivers/Makefile +@@ -63,6 +63,7 @@ obj-$(CONFIG_INPUT) += input/ + obj-$(CONFIG_I2O) += message/ + obj-$(CONFIG_RTC_LIB) += rtc/ + obj-y += i2c/ ++obj-$(CONFIG_PPS) += pps/ + obj-$(CONFIG_W1) += w1/ + obj-$(CONFIG_POWER_SUPPLY) += power/ + obj-$(CONFIG_HWMON) += hwmon/ +diff --git a/drivers/char/lp.c b/drivers/char/lp.c +index 62051f8..e0a8364 100644 +--- a/drivers/char/lp.c ++++ b/drivers/char/lp.c +@@ -746,6 +746,27 @@ static struct console lpcons = { + + #endif /* console on line printer */ + ++/* Support for PPS signal on the line printer */ ++ ++#ifdef CONFIG_PPS_CLIENT_LP ++ ++static void lp_pps_echo(int source, int event, void *data) ++{ ++ struct parport *port = data; ++ unsigned char status = parport_read_status(port); ++ ++ /* echo event via SEL bit */ ++ parport_write_control(port, ++ parport_read_control(port) | PARPORT_CONTROL_SELECT); ++ ++ /* signal no event */ ++ if ((status & PARPORT_STATUS_ACK) != 0) ++ parport_write_control(port, ++ parport_read_control(port) & ~PARPORT_CONTROL_SELECT); ++} ++ ++#endif ++ + /* --- initialisation code ------------------------------------- */ + + static int parport_nr[LP_NO] = { [0 ... LP_NO-1] = LP_PARPORT_UNSPEC }; +@@ -817,6 +838,38 @@ static int lp_register(int nr, struct parport *port) + } + #endif + ++#ifdef CONFIG_PPS_CLIENT_LP ++ port->pps_info.owner = THIS_MODULE; ++ port->pps_info.dev = port->dev; ++ snprintf(port->pps_info.path, PPS_MAX_NAME_LEN, "/dev/lp%d", nr); ++ ++ /* No PPS support if lp port has no IRQ line */ ++ if (port->irq != PARPORT_IRQ_NONE) { ++ strncpy(port->pps_info.name, port->name, PPS_MAX_NAME_LEN); ++ ++ port->pps_info.mode = PPS_CAPTUREASSERT | PPS_OFFSETASSERT | \ ++ PPS_ECHOASSERT | \ ++ PPS_CANWAIT | PPS_TSFMT_TSPEC; ++ ++ port->pps_info.echo = lp_pps_echo; ++ ++ port->pps_source = pps_register_source(&(port->pps_info), ++ PPS_CAPTUREASSERT | PPS_OFFSETASSERT); ++ if (port->pps_source < 0) ++ dev_err(port->dev, ++ "cannot register PPS source \"%s\"\n", ++ port->pps_info.path); ++ else ++ dev_info(port->dev, "PPS source #%d \"%s\" added\n", ++ port->pps_source, port->pps_info.path); ++ } else { ++ port->pps_source = -1; ++ dev_err(port->dev, "PPS support disabled due port \"%s\" is " ++ "in polling mode\n", ++ port->pps_info.path); ++ } ++#endif ++ + return 0; + } + +@@ -860,6 +913,14 @@ static void lp_detach (struct parport *port) + console_registered = NULL; + } + #endif /* CONFIG_LP_CONSOLE */ ++ ++#ifdef CONFIG_PPS_CLIENT_LP ++ if (port->pps_source >= 0) { ++ pps_unregister_source(port->pps_source); ++ dev_dbg(port->dev, "PPS source #%d \"%s\" removed\n", ++ port->pps_source, port->pps_info.path); ++ } ++#endif + } + + static struct parport_driver lp_driver = { +diff --git a/drivers/pps/Kconfig b/drivers/pps/Kconfig +new file mode 100644 +index 0000000..bfe6621 +--- /dev/null ++++ b/drivers/pps/Kconfig +@@ -0,0 +1,34 @@ ++# ++# PPS support 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 ++ antennae. Userland can use it to get an high 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. ++ ++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 --git a/drivers/pps/Makefile b/drivers/pps/Makefile +new file mode 100644 +index 0000000..d8ec308 +--- /dev/null ++++ b/drivers/pps/Makefile +@@ -0,0 +1,11 @@ ++# ++# Makefile for the PPS core. ++# ++ ++pps_core-objs += pps.o kapi.o sysfs.o ++obj-$(CONFIG_PPS) += pps_core.o ++obj-y += clients/ ++ ++ifeq ($(CONFIG_PPS_DEBUG),y) ++EXTRA_CFLAGS += -DDEBUG ++endif +diff --git a/drivers/pps/clients/Kconfig b/drivers/pps/clients/Kconfig +new file mode 100644 +index 0000000..09ba5c3 +--- /dev/null ++++ b/drivers/pps/clients/Kconfig +@@ -0,0 +1,38 @@ ++# ++# 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 ktimer.ko. ++ ++comment "UART serial support (forced off)" ++ depends on ! (SERIAL_CORE != n && !(PPS = m && SERIAL_CORE = y)) ++ ++config PPS_CLIENT_UART ++ bool "UART serial support" ++ depends on SERIAL_CORE != n && !(PPS = m && SERIAL_CORE = y) ++ help ++ If you say yes here you get support for a PPS source connected ++ with the CD (Carrier Detect) pin of your serial port. ++ ++comment "Parallel printer support (forced off)" ++ depends on !( PRINTER != n && !(PPS = m && PRINTER = y)) ++ ++config PPS_CLIENT_LP ++ bool "Parallel printer support" ++ depends on PRINTER != n && !(PPS = m && PRINTER = y) ++ help ++ If you say yes here you get support for a PPS source connected ++ with the interrupt pin of your parallel port. ++ ++endif +diff --git a/drivers/pps/clients/Makefile b/drivers/pps/clients/Makefile +new file mode 100644 +index 0000000..f3c1e39 +--- /dev/null ++++ b/drivers/pps/clients/Makefile +@@ -0,0 +1,9 @@ ++# ++# Makefile for PPS clients. ++# ++ ++obj-$(CONFIG_PPS_CLIENT_KTIMER) += ktimer.o ++ ++ifeq ($(CONFIG_PPS_DEBUG),y) ++EXTRA_CFLAGS += -DDEBUG ++endif +diff --git a/drivers/pps/clients/ktimer.c b/drivers/pps/clients/ktimer.c +new file mode 100644 +index 0000000..4d613ab +--- /dev/null ++++ b/drivers/pps/clients/ktimer.c +@@ -0,0 +1,114 @@ ++/* ++ * 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 ++ ++/* ++ * Global variables ++ */ ++ ++static int source; ++static struct timer_list ktimer; ++ ++/* ++ * The kernel timer ++ */ ++ ++static void pps_ktimer_event(unsigned long ptr) ++{ ++ pr_info("PPS event at %lu\n", jiffies); ++ ++ pps_event(source, PPS_CAPTUREASSERT, NULL); ++ ++ mod_timer(&ktimer, jiffies + HZ); ++} ++ ++/* ++ * The echo function ++ */ ++ ++static void pps_ktimer_echo(int source, int event, void *data) ++{ ++ pr_info("echo %s %s for source %d\n", ++ event & PPS_CAPTUREASSERT ? "assert" : "", ++ event & PPS_CAPTURECLEAR ? "clear" : "", ++ source); ++} ++ ++/* ++ * 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, ++ echo : pps_ktimer_echo, ++ owner : THIS_MODULE, ++}; ++ ++/* ++ * Module staff ++ */ ++ ++static void __exit pps_ktimer_exit(void) ++{ ++ del_timer_sync(&ktimer); ++ pps_unregister_source(source); ++ ++ pr_info("ktimer PPS source unregistered\n"); ++} ++ ++static int __init pps_ktimer_init(void) ++{ ++ int ret; ++ ++ ret = pps_register_source(&pps_ktimer_info, ++ PPS_CAPTUREASSERT | PPS_OFFSETASSERT); ++ if (ret < 0) { ++ printk(KERN_ERR "cannot register ktimer source\n"); ++ return ret; ++ } ++ source = ret; ++ ++ setup_timer(&ktimer, pps_ktimer_event, 0); ++ mod_timer(&ktimer, jiffies + HZ); ++ ++ pr_info("ktimer PPS source registered at %d\n", source); ++ ++ return 0; ++} ++ ++module_init(pps_ktimer_init); ++module_exit(pps_ktimer_exit); ++ ++MODULE_AUTHOR("Rodolfo Giometti "); ++MODULE_DESCRIPTION("dummy PPS source by using a kernel timer (just for debug)"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/pps/kapi.c b/drivers/pps/kapi.c +new file mode 100644 +index 0000000..67290d5 +--- /dev/null ++++ b/drivers/pps/kapi.c +@@ -0,0 +1,271 @@ ++/* ++ * kapi.c -- kernel API ++ * ++ * ++ * Copyright (C) 2005-2007 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 ++#include ++ ++/* ++ * Local variables ++ */ ++ ++static spinlock_t idr_lock = SPIN_LOCK_UNLOCKED; ++static DEFINE_IDR(pps_idr); ++ ++/* ++ * Local functions ++ */ ++ ++static void pps_add_offset(struct pps_ktime *ts, struct pps_ktime *offset) ++{ ++ ts->nsec += offset->nsec; ++ if (ts->nsec >= NSEC_PER_SEC) { ++ ts->nsec -= NSEC_PER_SEC; ++ ts->sec++; ++ } else if (ts->nsec < 0) { ++ ts->nsec += NSEC_PER_SEC; ++ ts->sec--; ++ } ++ ts->sec += offset->sec; ++} ++ ++/* ++ * Exported functions ++ */ ++ ++int pps_register_source(struct pps_source_info *info, int default_params) ++{ ++ struct pps_device *pps; ++ int id; ++ int err; ++ ++ /* Sanity checks */ ++ if ((info->mode & default_params) != default_params) { ++ printk(KERN_ERR "pps: %s: unsupported default parameters\n", ++ info->name); ++ err = -EINVAL; ++ goto pps_register_source_exit; ++ } ++ if ((info->mode & (PPS_ECHOASSERT | PPS_ECHOCLEAR)) != 0 && ++ info->echo == NULL) { ++ printk(KERN_ERR "pps: %s: echo function is not defined\n", ++ info->name); ++ err = -EINVAL; ++ goto pps_register_source_exit; ++ } ++ if ((info->mode & (PPS_TSFMT_TSPEC | PPS_TSFMT_NTPFP)) == 0) { ++ printk(KERN_ERR "pps: %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; ++ } ++ ++ /* Get new ID for the new PPS source */ ++ if (idr_pre_get(&pps_idr, GFP_KERNEL) == 0) { ++ err = -ENOMEM; ++ goto kfree_pps; ++ } ++ ++ spin_lock_irq(&idr_lock); ++ err = idr_get_new(&pps_idr, pps, &id); ++ spin_unlock_irq(&idr_lock); ++ ++ if (err < 0) ++ goto kfree_pps; ++ ++ id = id & MAX_ID_MASK; ++ if (id >= PPS_MAX_SOURCES) { ++ printk(KERN_ERR "pps: %s: too much PPS sources in the system\n", ++ info->name); ++ err = -EBUSY; ++ goto free_idr; ++ } ++ ++ pps->id = id; ++ pps->params.api_version = PPS_API_VERS; ++ pps->params.mode = default_params; ++ pps->info = *info; ++ ++ init_waitqueue_head(&pps->queue); ++ spin_lock_init(&pps->lock); ++ atomic_set(&pps->usage, 0); ++ init_waitqueue_head(&pps->usage_queue); ++ ++ /* Create the char device */ ++ err = pps_register_cdev(pps); ++ if (err < 0) { ++ printk(KERN_ERR "pps: %s: unable to create char device\n", ++ info->name); ++ goto free_idr; ++ } ++ ++ /* Create the sysfs entry */ ++ err = pps_sysfs_create_source_entry(pps); ++ if (err < 0) { ++ printk(KERN_ERR "pps: %s: unable to create sysfs entries\n", ++ info->name); ++ goto unregister_cdev; ++ } ++ ++ pr_info("new PPS source %s at ID %d\n", info->name, id); ++ ++ return id; ++ ++unregister_cdev: ++ pps_unregister_cdev(pps); ++ ++free_idr: ++ spin_lock_irq(&idr_lock); ++ idr_remove(&pps_idr, id); ++ spin_unlock_irq(&idr_lock); ++ ++kfree_pps: ++ kfree(pps); ++ ++pps_register_source_exit: ++ printk(KERN_ERR "pps: %s: unable to register source\n", info->name); ++ ++ return err; ++} ++EXPORT_SYMBOL(pps_register_source); ++ ++void pps_unregister_source(int source) ++{ ++ struct pps_device *pps; ++ ++ spin_lock_irq(&idr_lock); ++ pps = idr_find(&pps_idr, source); ++ ++ if (!pps) { ++ spin_unlock_irq(&idr_lock); ++ return; ++ } ++ ++ /* This should be done first in order to deny IRQ handlers ++ * to access PPS structs ++ */ ++ ++ idr_remove(&pps_idr, pps->id); ++ spin_unlock_irq(&idr_lock); ++ ++ wait_event(pps->usage_queue, atomic_read(&pps->usage) == 0); ++ ++ pps_sysfs_remove_source_entry(pps); ++ pps_unregister_cdev(pps); ++ kfree(pps); ++} ++EXPORT_SYMBOL(pps_unregister_source); ++ ++void pps_event(int source, int event, void *data) ++{ ++ struct pps_device *pps; ++ struct timespec __ts; ++ struct pps_ktime ts; ++ unsigned long flags; ++ ++ /* First of all we get the time stamp... */ ++ getnstimeofday(&__ts); ++ ++ /* ... and translate it to PPS time data struct */ ++ ts.sec = __ts.tv_sec; ++ ts.nsec = __ts.tv_nsec; ++ ++ if ((event & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR)) == 0 ) { ++ printk(KERN_ERR "pps: unknown event (%x) for source %d\n", ++ event, source); ++ return; ++ } ++ ++ spin_lock_irqsave(&idr_lock, flags); ++ pps = idr_find(&pps_idr, source); ++ ++ /* If we find a valid PPS source we lock it before leaving ++ * the lock! ++ */ ++ if (pps) ++ atomic_inc(&pps->usage); ++ spin_unlock_irqrestore(&idr_lock, flags); ++ ++ if (!pps) ++ return; ++ ++ pr_debug("PPS event on source %d at at %llu.%06u\n", ++ pps->id, ts.sec, ts.nsec); ++ ++ spin_lock_irqsave(&pps->lock, flags); ++ ++ /* Must call the echo function? */ ++ if ((pps->params.mode & (PPS_ECHOASSERT | PPS_ECHOCLEAR))) ++ pps->info.echo(source, event, data); ++ ++ /* Check the event */ ++ pps->current_mode = pps->params.mode; ++ if (event & PPS_CAPTUREASSERT) { ++ /* We have to add an offset? */ ++ if (pps->params.mode & PPS_OFFSETASSERT) ++ pps_add_offset(&ts, &pps->params.assert_off_tu); ++ ++ /* Save the time stamp */ ++ pps->assert_tu = ts; ++ pps->assert_sequence++; ++ pr_debug("capture assert seq #%u for source %d\n", ++ pps->assert_sequence, source); ++ } ++ if (event & PPS_CAPTURECLEAR) { ++ /* We have to add an offset? */ ++ if (pps->params.mode & PPS_OFFSETCLEAR) ++ pps_add_offset(&ts, &pps->params.clear_off_tu); ++ ++ /* Save the time stamp */ ++ pps->clear_tu = ts; ++ pps->clear_sequence++; ++ pr_debug("capture clear seq #%u for source %d\n", ++ pps->clear_sequence, source); ++ } ++ ++ pps->go = ~0; ++ wake_up_interruptible(&pps->queue); ++ ++ kill_fasync(&pps->async_queue, SIGIO, POLL_IN); ++ ++ spin_unlock_irqrestore(&pps->lock, flags); ++ ++ /* Now we can release the PPS source for (possible) deregistration */ ++ spin_lock_irqsave(&idr_lock, flags); ++ atomic_dec(&pps->usage); ++ wake_up_all(&pps->usage_queue); ++ spin_unlock_irqrestore(&idr_lock, flags); ++} ++EXPORT_SYMBOL(pps_event); +diff --git a/drivers/pps/pps.c b/drivers/pps/pps.c +new file mode 100644 +index 0000000..52de2f1 +--- /dev/null ++++ b/drivers/pps/pps.c +@@ -0,0 +1,332 @@ ++/* ++ * pps.c -- Main PPS support file ++ * ++ * ++ * Copyright (C) 2005-2007 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 ++#include ++ ++/* ++ * Local variables ++ */ ++ ++static dev_t pps_devt; ++static struct class *pps_class; ++ ++/* ++ * 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 int pps_cdev_ioctl(struct inode *inode, struct file *file, ++ unsigned int cmd, unsigned long arg) ++{ ++ struct pps_device *pps = file->private_data; ++ struct pps_kparams params; ++ struct pps_fdata fdata; ++ unsigned long ticks; ++ void __user *uarg = (void __user *) arg; ++ int __user *iuarg = (int __user *) arg; ++ int err; ++ ++ switch (cmd) { ++ case PPS_CHECK: ++ ++ /* This does nothing but signal we are a PPS source... */ ++ ++ return 0; ++ ++ case PPS_GETPARAMS: ++ pr_debug("PPS_GETPARAMS: source %d\n", pps->id); ++ ++ /* Sanity checks */ ++ if (!uarg) ++ return -EINVAL; ++ ++ /* Return current parameters */ ++ err = copy_to_user(uarg, &pps->params, ++ sizeof(struct pps_kparams)); ++ if (err) ++ return -EFAULT; ++ ++ break; ++ ++ case PPS_SETPARAMS: ++ pr_debug("PPS_SETPARAMS: source %d\n", pps->id); ++ ++ /* Check the capabilities */ ++ if (!capable(CAP_SYS_TIME)) ++ return -EPERM; ++ ++ /* Sanity checks */ ++ if (!uarg) ++ return -EINVAL; ++ err = copy_from_user(¶ms, uarg, sizeof(struct pps_kparams)); ++ if (err) ++ return -EFAULT; ++ if (!(params.mode & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR))) { ++ pr_debug("capture mode unspecified (%x)\n", ++ params.mode); ++ return -EINVAL; ++ } ++ ++ /* Check for supported capabilities */ ++ if ((params.mode & ~pps->info.mode) != 0) { ++ pr_debug("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 */ ++ pr_debug("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: ++ pr_debug("PPS_GETCAP: source %d\n", pps->id); ++ ++ /* Sanity checks */ ++ if (!uarg) ++ return -EINVAL; ++ ++ err = put_user(pps->info.mode, iuarg); ++ if (err) ++ return -EFAULT; ++ ++ break; ++ ++ case PPS_FETCH: ++ pr_debug("PPS_FETCH: source %d\n", pps->id); ++ ++ if (!uarg) ++ return -EINVAL; ++ err = copy_from_user(&fdata, uarg, sizeof(struct pps_fdata)); ++ if (err) ++ return -EFAULT; ++ ++ pps->go = 0; ++ ++ /* Manage the timeout */ ++ if (fdata.timeout.flags & PPS_TIME_INVALID) ++ err = wait_event_interruptible(pps->queue, pps->go); ++ else { ++ pr_debug("timeout %lld.%09d\n", ++ 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, pps->go, ticks); ++ if (err == 0) ++ return -ETIMEDOUT; ++ } ++ } ++ ++ /* Check for pending signals */ ++ if (err == -ERESTARTSYS) { ++ pr_debug("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; ++ ++ default: ++ return -ENOTTY; ++ break; ++ } ++ ++ 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); ++ ++ /* Lock the PPS source against (possible) deregistration */ ++ atomic_inc(&pps->usage); ++ ++ file->private_data = pps; ++ ++ return 0; ++} ++ ++static int pps_cdev_release(struct inode *inode, struct file *file) ++{ ++ struct pps_device *pps = file->private_data; ++ ++ /* Free the PPS source and wake up (possible) deregistration */ ++ atomic_dec(&pps->usage); ++ wake_up_all(&pps->usage_queue); ++ ++ 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, ++ .ioctl = pps_cdev_ioctl, ++ .open = pps_cdev_open, ++ .release = pps_cdev_release, ++}; ++ ++int pps_register_cdev(struct pps_device *pps) ++{ ++ int err; ++ ++ pps->devno = MKDEV(MAJOR(pps_devt), pps->id); ++ cdev_init(&pps->cdev, &pps_cdev_fops); ++ pps->cdev.owner = pps->info.owner; ++ ++ err = cdev_add(&pps->cdev, pps->devno, 1); ++ if (err) { ++ printk(KERN_ERR "pps: %s: failed to add char device %d:%d\n", ++ pps->info.name, MAJOR(pps_devt), pps->id); ++ return err; ++ } ++ pps->dev = device_create(pps_class, pps->info.dev, pps->devno, ++ "pps%d", pps->id); ++ if (err) ++ goto del_cdev; ++ dev_set_drvdata(pps->dev, pps); ++ ++ 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); ++ ++ return err; ++} ++ ++void pps_unregister_cdev(struct pps_device *pps) ++{ ++ device_destroy(pps_class, pps->devno); ++ cdev_del(&pps->cdev); ++} ++ ++/* ++ * Module staff ++ */ ++ ++static void __exit pps_exit(void) ++{ ++ class_destroy(pps_class); ++ ++ if (pps_devt) ++ unregister_chrdev_region(pps_devt, PPS_MAX_SOURCES); ++ ++ pr_info("LinuxPPS API ver. %d removed\n", PPS_API_VERS); ++} ++ ++static int __init pps_init(void) ++{ ++ int err; ++ ++ pps_class = class_create(THIS_MODULE, "pps"); ++ if (!pps_class) { ++ printk(KERN_ERR "pps: ailed to allocate class\n"); ++ return -ENOMEM; ++ } ++ ++ err = alloc_chrdev_region(&pps_devt, 0, PPS_MAX_SOURCES, "pps"); ++ if (err < 0) { ++ printk(KERN_ERR "pps: 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 " ++ "\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 "); ++MODULE_DESCRIPTION("LinuxPPS support (RFC 2783) - ver. " PPS_VERSION); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/pps/sysfs.c b/drivers/pps/sysfs.c +new file mode 100644 +index 0000000..8541be7 +--- /dev/null ++++ b/drivers/pps/sysfs.c +@@ -0,0 +1,124 @@ ++/* ++ * sysfs.c -- sysfs support ++ * ++ * ++ * Copyright (C) 2007 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 ++ ++/* ++ * Attribute functions ++ */ ++ ++static ssize_t pps_show_assert(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct pps_device *pps = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%lld.%09d#%d\n", ++ pps->assert_tu.sec, pps->assert_tu.nsec, ++ pps->assert_sequence); ++} ++DEVICE_ATTR(assert, S_IRUGO, pps_show_assert, NULL); ++ ++static ssize_t pps_show_clear(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct pps_device *pps = dev_get_drvdata(dev); ++ ++ return sprintf(buf, "%lld.%09d#%d\n", ++ pps->clear_tu.sec, pps->clear_tu.nsec, ++ pps->clear_sequence); ++} ++DEVICE_ATTR(clear, S_IRUGO, pps_show_clear, NULL); ++ ++static ssize_t pps_show_mode(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); ++} ++DEVICE_ATTR(mode, S_IRUGO, pps_show_mode, NULL); ++ ++static ssize_t pps_show_echo(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); ++} ++DEVICE_ATTR(echo, S_IRUGO, pps_show_echo, NULL); ++ ++static ssize_t pps_show_name(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); ++} ++DEVICE_ATTR(name, S_IRUGO, pps_show_name, NULL); ++ ++static ssize_t pps_show_path(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); ++} ++DEVICE_ATTR(path, S_IRUGO, pps_show_path, NULL); ++ ++/* ++ * Public functions ++ */ ++ ++void pps_sysfs_remove_source_entry(struct pps_device *pps) ++{ ++ /* Delete info files */ ++ if (pps->info.mode & PPS_CAPTUREASSERT) ++ device_remove_file(pps->dev, &dev_attr_assert); ++ ++ if (pps->info.mode & PPS_CAPTURECLEAR) ++ device_remove_file(pps->dev, &dev_attr_clear); ++ ++ device_remove_file(pps->dev, &dev_attr_mode); ++ device_remove_file(pps->dev, &dev_attr_echo); ++ device_remove_file(pps->dev, &dev_attr_name); ++ device_remove_file(pps->dev, &dev_attr_path); ++} ++ ++int pps_sysfs_create_source_entry(struct pps_device *pps) ++{ ++ /* Create file "assert" and "clear" according to source capability */ ++ if (pps->info.mode & PPS_CAPTUREASSERT) ++ device_create_file(pps->dev, &dev_attr_assert); ++ ++ if (pps->info.mode & PPS_CAPTURECLEAR) ++ device_create_file(pps->dev, &dev_attr_clear); ++ ++ device_create_file(pps->dev, &dev_attr_mode); ++ device_create_file(pps->dev, &dev_attr_echo); ++ device_create_file(pps->dev, &dev_attr_name); ++ device_create_file(pps->dev, &dev_attr_path); ++ ++ return 0; ++} +diff --git a/drivers/serial/8250.c b/drivers/serial/8250.c +index f94109c..a5e83f8 100644 +--- a/drivers/serial/8250.c ++++ b/drivers/serial/8250.c +@@ -2118,6 +2118,8 @@ serial8250_set_termios(struct uart_port *port, struct ktermios *termios, + up->ier |= UART_IER_MSI; + if (up->capabilities & UART_CAP_UUE) + up->ier |= UART_IER_UUE | UART_IER_RTOIE; ++ if (up->port.flags & UPF_HARDPPS_CD) ++ up->ier |= UART_IER_MSI; /* enable interrupts */ + + serial_out(up, UART_IER, up->ier); + +diff --git a/drivers/serial/serial_core.c b/drivers/serial/serial_core.c +index a055f58..a40b87c 100644 +--- a/drivers/serial/serial_core.c ++++ b/drivers/serial/serial_core.c +@@ -33,6 +33,7 @@ + #include /* for serial_state and serial_icounter_struct */ + #include + #include ++#include + + #include + #include +@@ -633,6 +634,54 @@ static int uart_get_info(struct uart_state *state, + return 0; + } + ++#ifdef CONFIG_PPS_CLIENT_UART ++ ++static int ++uart_register_pps_port(struct uart_state *state, struct uart_port *port) ++{ ++ struct tty_driver *drv = port->info->tty->driver; ++ int ret; ++ ++ state->pps_info.owner = THIS_MODULE; ++ state->pps_info.dev = port->dev; ++ snprintf(state->pps_info.name, PPS_MAX_NAME_LEN, "%s%d", ++ drv->driver_name, port->line); ++ snprintf(state->pps_info.path, PPS_MAX_NAME_LEN, "/dev/%s%d", ++ drv->name, port->line); ++ ++ state->pps_info.mode = PPS_CAPTUREBOTH | \ ++ PPS_OFFSETASSERT | PPS_OFFSETCLEAR | \ ++ PPS_CANWAIT | PPS_TSFMT_TSPEC; ++ ++ ret = pps_register_source(&state->pps_info, PPS_CAPTUREBOTH | \ ++ PPS_OFFSETASSERT | PPS_OFFSETCLEAR); ++ if (ret < 0) { ++ dev_err(port->dev, "cannot register PPS source \"%s\"\n", ++ state->pps_info.path); ++ return ret; ++ } ++ port->pps_source = ret; ++ dev_dbg(port->dev, "PPS source #%d \"%s\" added\n", ++ port->pps_source, state->pps_info.path); ++ ++ return 0; ++} ++ ++static void ++uart_unregister_pps_port(struct uart_state *state, struct uart_port *port) ++{ ++ pps_unregister_source(port->pps_source); ++ dev_dbg(port->dev, "PPS source #%d \"%s\" removed\n", ++ port->pps_source, state->pps_info.path); ++} ++ ++#else ++ ++#define uart_register_pps_port(state, port) do { } while (0) ++#define uart_unregister_pps_port(state, port) do { } while (0) ++ ++#endif /* CONFIG_PPS_CLIENT_UART */ ++ + static int uart_set_info(struct uart_state *state, + struct serial_struct __user *newinfo) + { +@@ -807,11 +856,19 @@ static int uart_set_info(struct uart_state *state, + (port->flags & UPF_LOW_LATENCY) ? 1 : 0; + + check_and_exit: ++ /* PPS support enabled/disabled? */ ++ if ((old_flags & UPF_HARDPPS_CD) != (new_flags & UPF_HARDPPS_CD)) { ++ if (new_flags & UPF_HARDPPS_CD) ++ uart_register_pps_port(state, port); ++ else ++ uart_unregister_pps_port(state, port); ++ } ++ + retval = 0; + if (port->type == PORT_UNKNOWN) + goto exit; + if (state->info->flags & UIF_INITIALIZED) { +- if (((old_flags ^ port->flags) & UPF_SPD_MASK) || ++ if (((old_flags ^ port->flags) & (UPF_SPD_MASK|UPF_HARDPPS_CD)) || + old_custom_divisor != port->custom_divisor) { + /* + * If they're setting up a custom divisor or speed, +@@ -2110,6 +2167,12 @@ uart_configure_port(struct uart_driver *drv, struct uart_state *state, + port->ops->config_port(port, flags); + } + ++ /* ++ * Add the PPS support for the current port. ++ */ ++ if (port->flags & UPF_HARDPPS_CD) ++ uart_register_pps_port(state, port); ++ + if (port->type != PORT_UNKNOWN) { + unsigned long flags; + +@@ -2359,6 +2422,12 @@ int uart_remove_one_port(struct uart_driver *drv, struct uart_port *port) + mutex_unlock(&state->mutex); + + /* ++ * Remove PPS support from the current port. ++ */ ++ if (port->flags & UPF_HARDPPS_CD) ++ uart_unregister_pps_port(state, port); ++ ++ /* + * Remove the devices from the tty layer + */ + tty_unregister_device(drv->tty_driver, port->line); +diff --git a/include/linux/Kbuild b/include/linux/Kbuild +index 818cc3a..0a9394f 100644 +--- a/include/linux/Kbuild ++++ b/include/linux/Kbuild +@@ -295,6 +295,7 @@ unifdef-y += pmu.h + unifdef-y += poll.h + unifdef-y += ppp_defs.h + unifdef-y += ppp-comp.h ++unifdef-y += pps.h + unifdef-y += ptrace.h + unifdef-y += qnx4_fs.h + unifdef-y += quota.h +diff --git a/include/linux/parport.h b/include/linux/parport.h +index 9cdd694..549de6e 100644 +--- a/include/linux/parport.h ++++ b/include/linux/parport.h +@@ -100,6 +100,7 @@ typedef enum { + #include + #include + #include ++#include + #include + #include + #include +@@ -327,6 +328,11 @@ struct parport { + + struct list_head full_list; + struct parport *slaves[3]; ++ ++#ifdef CONFIG_PPS_CLIENT_LP ++ struct pps_source_info pps_info; ++ int pps_source; /* PPS source ID */ ++#endif + }; + + #define DEFAULT_SPIN_TIME 500 /* us */ +@@ -517,6 +523,12 @@ extern int parport_daisy_select (struct parport *port, int daisy, int mode); + /* Lowlevel drivers _can_ call this support function to handle irqs. */ + static __inline__ void parport_generic_irq(int irq, struct parport *port) + { ++#ifdef CONFIG_PPS_CLIENT_LP ++ pps_event(port->pps_source, PPS_CAPTUREASSERT, port); ++ dev_dbg(port->dev, "PPS assert at %lu on source #%d\n", ++ jiffies, port->pps_source); ++#endif ++ + parport_ieee1284_interrupt (irq, port); + read_lock(&port->cad_lock); + if (port->cad && port->cad->irq_func) +diff --git a/include/linux/pps.h b/include/linux/pps.h +new file mode 100644 +index 0000000..5bdb593 +--- /dev/null ++++ b/include/linux/pps.h +@@ -0,0 +1,196 @@ ++/* ++ * pps.h -- PPS API kernel header. ++ * ++ * ++ * Copyright (C) 2005-2007 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. ++ */ ++ ++ ++#ifndef _PPS_H_ ++#define _PPS_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 ++ */ ++ ++#ifndef __KERNEL__ ++#include ++#include ++#include ++#else ++#include ++#endif ++ ++#define PPS_API_VERS 1 ++#define PPS_MAX_NAME_LEN 32 ++ ++/* 32-bit vs. 64-bit compatibility. ++ * ++ * 0n i386, the alignment of a uint64_t is only 4 bytes, while on most other ++ * architectures it's 8 bytes. On i386, there will be no padding between the ++ * two consecutive 'struct pps_ktime' members of struct pps_kinfo and struct ++ * pps_kparams. But on most platforms there will be padding to ensure correct ++ * alignment. ++ * ++ * The simple fix is probably to add an explicit padding. ++ * [David Woodhouse] ++ */ ++struct pps_ktime { ++ __u64 sec; ++ __u32 nsec; ++ __u32 flags; ++}; ++#define PPS_TIME_INVALID (1<<0) /* used to specify timeout==NULL */ ++ ++struct pps_kinfo { ++ __u32 assert_sequence; /* seq. num. of assert event */ ++ __u32 clear_sequence; /* seq. num. of clear event */ ++ struct pps_ktime assert_tu; /* time of assert event */ ++ struct pps_ktime clear_tu; /* time of clear event */ ++ int current_mode; /* current mode bits */ ++}; ++ ++struct pps_kparams { ++ int api_version; /* API version # */ ++ int mode; /* mode bits */ ++ struct pps_ktime assert_off_tu; /* offset compensation for assert */ ++ struct pps_ktime clear_off_tu; /* offset compensation for clear */ ++}; ++ ++/* ++ * 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 /* bit 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! ++ */ ++ ++struct pps_fdata { ++ struct pps_kinfo info; ++ struct pps_ktime timeout; ++}; ++ ++#include ++ ++#define PPS_CHECK _IO('P', 0) ++#define PPS_GETPARAMS _IOR('P', 1, struct pps_kparams *) ++#define PPS_SETPARAMS _IOW('P', 2, struct pps_kparams *) ++#define PPS_GETCAP _IOR('P', 3, int *) ++#define PPS_FETCH _IOWR('P', 4, struct pps_fdata *) ++ ++#ifdef __KERNEL__ ++ ++#include ++#include ++ ++#define PPS_VERSION "5.0.0-rc2" ++#define PPS_MAX_SOURCES 16 /* should be enought... */ ++ ++/* ++ * Global defines ++ */ ++ ++/* The specific PPS source info */ ++struct pps_source_info { ++ char name[PPS_MAX_NAME_LEN]; /* simbolic name */ ++ char path[PPS_MAX_NAME_LEN]; /* path of connected device */ ++ int mode; /* PPS's allowed mode */ ++ ++ void (*echo)(int source, int event, void *data); /* PPS echo function */ ++ ++ struct module *owner; ++ struct device *dev; ++}; ++ ++/* The main struct */ ++struct pps_device { ++ struct pps_source_info info; /* PSS source info */ ++ ++ struct pps_kparams params; /* PPS's current params */ ++ ++ __u32 assert_sequence; /* PPS' assert event seq # */ ++ __u32 clear_sequence; /* PPS' clear event seq # */ ++ struct pps_ktime assert_tu; ++ struct pps_ktime clear_tu; ++ int current_mode; /* PPS mode at event time */ ++ ++ int go; /* PPS event is arrived? */ ++ wait_queue_head_t queue; /* PPS event queue */ ++ ++ unsigned int id; /* PPS source unique ID */ ++ struct cdev cdev; ++ struct device *dev; ++ int devno; ++ struct fasync_struct *async_queue; /* fasync method */ ++ spinlock_t lock; ++ ++ atomic_t usage; /* usage count */ ++ wait_queue_head_t usage_queue; ++ ++ struct class_device class_dev; ++}; ++ ++/* ++ * Exported functions ++ */ ++ ++extern int pps_register_source(struct pps_source_info *info, ++ int default_params); ++extern void pps_unregister_source(int source); ++extern int pps_register_cdev(struct pps_device *pps); ++extern void pps_unregister_cdev(struct pps_device *pps); ++extern void pps_event(int source, int event, void *data); ++ ++extern int pps_sysfs_create_source_entry(struct pps_device *pps); ++extern void pps_sysfs_remove_source_entry(struct pps_device *pps); ++ ++#endif /* __KERNEL__ */ ++ ++#endif /* _PPS_H_ */ +diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h +index 09d17b0..f9aefad 100644 +--- a/include/linux/serial_core.h ++++ b/include/linux/serial_core.h +@@ -157,6 +157,7 @@ + #include + #include + #include ++#include + + struct uart_port; + struct uart_info; +@@ -236,6 +237,9 @@ struct uart_port { + unsigned char regshift; /* reg offset shift */ + unsigned char iotype; /* io access style */ + unsigned char unused1; ++#ifdef CONFIG_PPS_CLIENT_UART ++ int pps_source; /* PPS source ID */ ++#endif + + #define UPIO_PORT (0) + #define UPIO_HUB6 (1) +@@ -280,7 +284,8 @@ struct uart_port { + #define UPF_IOREMAP ((__force upf_t) (1 << 31)) + + #define UPF_CHANGE_MASK ((__force upf_t) (0x17fff)) +-#define UPF_USR_MASK ((__force upf_t) (UPF_SPD_MASK|UPF_LOW_LATENCY)) ++#define UPF_USR_MASK ((__force upf_t) (UPF_SPD_MASK|UPF_LOW_LATENCY\ ++ |UPF_HARDPPS_CD)) + + unsigned int mctrl; /* current modem ctrl settings */ + unsigned int timeout; /* character-based timeout */ +@@ -312,6 +317,10 @@ struct uart_state { + struct uart_info *info; + struct uart_port *port; + ++#ifdef CONFIG_PPS_CLIENT_UART ++ struct pps_source_info pps_info; ++#endif ++ + struct mutex mutex; + }; + +@@ -476,13 +485,22 @@ uart_handle_dcd_change(struct uart_port *port, unsigned int status) + { + struct uart_info *info = port->info; + +- port->icount.dcd++; +- +-#ifdef CONFIG_HARD_PPS +- if ((port->flags & UPF_HARDPPS_CD) && status) +- hardpps(); ++#ifdef CONFIG_PPS_CLIENT_UART ++ if (port->flags & UPF_HARDPPS_CD) { ++ if (status) { ++ pps_event(port->pps_source, PPS_CAPTUREASSERT, port); ++ dev_dbg(port->dev, "PPS assert at %lu on source #%d\n", ++ jiffies, port->pps_source); ++ } else { ++ pps_event(port->pps_source, PPS_CAPTURECLEAR, port); ++ dev_dbg(port->dev, "PPS clear at %lu on source #%d\n", ++ jiffies, port->pps_source); ++ } ++ } + #endif + ++ port->icount.dcd++; ++ + if (info->flags & UIF_CHECK_CD) { + if (status) + wake_up_interruptible(&info->open_wait);