* [PATCH 1/5] ptp: Added a brand new class driver for ptp clocks.
2010-08-16 11:17 [PATCH v5 0/5] ptp: IEEE 1588 clock support Richard Cochran
@ 2010-08-16 11:17 ` Richard Cochran
[not found] ` <363bd749a38d0b785d8431e591bf54c38db4c2d7.1281956490.git.richard.cochran-3mrvs1K0uXizZXS1Dc/lvw@public.gmane.org>
2010-08-16 19:24 ` john stultz
2010-08-16 11:18 ` [PATCH 2/5] ptp: Added a clock that uses the Linux system time Richard Cochran
` (3 subsequent siblings)
4 siblings, 2 replies; 56+ messages in thread
From: Richard Cochran @ 2010-08-16 11:17 UTC (permalink / raw)
To: netdev
Cc: linux-kernel, linuxppc-dev, devicetree-discuss, linux-arm-kernel,
Krzysztof Halasa, Rodolfo Giometti
This patch adds an infrastructure for hardware clocks that implement
IEEE 1588, the Precision Time Protocol (PTP). A class driver offers a
registration method to particular hardware clock drivers. Each clock is
exposed to user space as a character device with ioctls that allow tuning
of the PTP clock.
Signed-off-by: Richard Cochran <richard.cochran@omicron.at>
---
Documentation/ptp/ptp.txt | 95 +++++++
Documentation/ptp/testptp.c | 306 ++++++++++++++++++++++
Documentation/ptp/testptp.mk | 33 +++
drivers/Kconfig | 2 +
drivers/Makefile | 1 +
drivers/ptp/Kconfig | 27 ++
drivers/ptp/Makefile | 5 +
drivers/ptp/ptp_clock.c | 514 ++++++++++++++++++++++++++++++++++++++
include/linux/Kbuild | 1 +
include/linux/ptp_clock.h | 79 ++++++
include/linux/ptp_clock_kernel.h | 137 ++++++++++
11 files changed, 1200 insertions(+), 0 deletions(-)
create mode 100644 Documentation/ptp/ptp.txt
create mode 100644 Documentation/ptp/testptp.c
create mode 100644 Documentation/ptp/testptp.mk
create mode 100644 drivers/ptp/Kconfig
create mode 100644 drivers/ptp/Makefile
create mode 100644 drivers/ptp/ptp_clock.c
create mode 100644 include/linux/ptp_clock.h
create mode 100644 include/linux/ptp_clock_kernel.h
diff --git a/Documentation/ptp/ptp.txt b/Documentation/ptp/ptp.txt
new file mode 100644
index 0000000..46858b3
--- /dev/null
+++ b/Documentation/ptp/ptp.txt
@@ -0,0 +1,95 @@
+
+* PTP infrastructure for Linux
+
+ This patch set introduces support for IEEE 1588 PTP clocks in
+ Linux. Together with the SO_TIMESTAMPING socket options, this
+ presents a standardized method for developing PTP user space
+ programs, synchronizing Linux with external clocks, and using the
+ ancillary features of PTP hardware clocks.
+
+ A new class driver exports a kernel interface for specific clock
+ drivers and a user space interface. The infrastructure supports a
+ complete set of PTP functionality.
+
+ + Basic clock operations
+ - Set time
+ - Get time
+ - Shift the clock by a given offset atomically
+ - Adjust clock frequency
+
+ + Ancillary clock features
+ - One short or periodic alarms, with signal delivery to user program
+ - Time stamp external events
+ - Period output signals configurable from user space
+ - Synchronization of the Linux system time via the PPS subsystem
+
+** PTP kernel API
+
+ A PTP clock driver registers itself with the class driver. The
+ class driver handles all of the dealings with user space. The
+ author of a clock driver need only implement the details of
+ programming the clock hardware. The clock driver notifies the class
+ driver of asynchronous events (alarms and external time stamps) via
+ a simple message passing interface.
+
+ The class driver supports multiple PTP clock drivers. In normal use
+ cases, only one PTP clock is needed. However, for testing and
+ development, it can be useful to have more than one clock in a
+ single system, in order to allow performance comparisons.
+
+** PTP user space API
+
+ The class driver creates a character device for each registered PTP
+ clock. User space programs may control the clock using standardized
+ ioctls. A program may query, enable, configure, and disable the
+ ancillary clock features. User space can receive time stamped
+ events via blocking read() and poll(). One shot and periodic
+ signals may be configured via an ioctl API with semantics similar
+ to the POSIX timer_settime() system call.
+
+ As an real life example, the following two patches for ptpd version
+ 1.0.0 demonstrate how the API works.
+
+ https://sourceforge.net/tracker/?func=detail&aid=2992845&group_id=139814&atid=744634
+
+ https://sourceforge.net/tracker/?func=detail&aid=2992847&group_id=139814&atid=744634
+
+** Writing clock drivers
+
+ Clock drivers include include/linux/ptp_clock_kernel.h and register
+ themselves by presenting a 'struct ptp_clock_info' to the
+ registration method. Clock drivers must implement all of the
+ functions in the interface. If a clock does not offer a particular
+ ancillary feature, then the driver should just return -EOPNOTSUPP
+ from those functions.
+
+ Drivers must ensure that all of the methods in interface are
+ reentrant. Since most hardware implementations treat the time value
+ as a 64 bit integer accessed as two 32 bit registers, drivers
+ should use spin_lock_irqsave/spin_unlock_irqrestore to protect
+ against concurrent access. This locking cannot be accomplished in
+ class driver, since the lock may also be needed by the clock
+ driver's interrupt service routine.
+
+** Supported hardware
+
+ + Standard Linux system timer
+ - No special PTP features
+ - For use with software time stamping
+
+ + Freescale eTSEC gianfar
+ - 2 Time stamp external triggers, programmable polarity (opt. interrupt)
+ - 2 Alarm registers (optional interrupt)
+ - 3 Periodic signals (optional interrupt)
+
+ + National DP83640
+ - 6 GPIOs programmable as inputs or outputs
+ - 6 GPIOs with dedicated functions (LED/JTAG/clock) can also be
+ used as general inputs or outputs
+ - GPIO inputs can time stamp external triggers
+ - GPIO outputs can produce periodic signals
+ - 1 interrupt pin
+
+ + Intel IXP465
+ - Auxiliary Slave/Master Mode Snapshot (optional interrupt)
+ - Target Time (optional interrupt)
diff --git a/Documentation/ptp/testptp.c b/Documentation/ptp/testptp.c
new file mode 100644
index 0000000..41a9839
--- /dev/null
+++ b/Documentation/ptp/testptp.c
@@ -0,0 +1,306 @@
+/*
+ * PTP 1588 clock support - User space test program
+ *
+ * Copyright (C) 2010 OMICRON electronics GmbH
+ *
+ * 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 <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <linux/ptp_clock.h>
+
+static void handle_alarm(int s)
+{
+ printf("received signal %d\n", s);
+}
+
+static int install_handler(int signum, void (*handler)(int))
+{
+ struct sigaction action;
+ sigset_t mask;
+
+ /* Unblock the signal. */
+ sigemptyset(&mask);
+ sigaddset(&mask, signum);
+ sigprocmask(SIG_UNBLOCK, &mask, NULL);
+
+ /* Install the signal handler. */
+ action.sa_handler = handler;
+ action.sa_flags = 0;
+ sigemptyset(&action.sa_mask);
+ sigaction(signum, &action, NULL);
+
+ return 0;
+}
+
+static void usage(char *progname)
+{
+ fprintf(stderr,
+ "usage: %s [options]\n"
+ " -a val request a one-shot alarm after 'val' seconds\n"
+ " -A val request a periodic alarm every 'val' seconds\n"
+ " -c query the ptp clock's capabilities\n"
+ " -d name device to open\n"
+ " -e val read 'val' external time stamp events\n"
+ " -f val adjust the ptp clock frequency by 'val' PPB\n"
+ " -g get the ptp clock time\n"
+ " -h prints this message\n"
+ " -p val enable output with a period of 'val' nanoseconds\n"
+ " -P val enable or disable (val=1|0) the system clock PPS\n"
+ " -s set the ptp clock time from the system time\n"
+ " -t val shift the ptp clock time by 'val' seconds\n"
+ " -v query the ptp clock api version\n",
+ progname);
+}
+
+int main(int argc, char *argv[])
+{
+ struct ptp_clock_caps caps;
+ struct ptp_clock_timer timer;
+ struct ptp_extts_event event;
+ struct ptp_clock_request request;
+ struct timespec ts;
+ char *progname;
+ int c, cnt, fd, val = 0;
+
+ char *device = "/dev/ptp_clock_0";
+ int adjfreq = 0x7fffffff;
+ int adjtime = 0;
+ int capabilities = 0;
+ int extts = 0;
+ int gettime = 0;
+ int oneshot = 0;
+ int periodic = 0;
+ int perout = -1;
+ int pps = -1;
+ int settime = 0;
+ int version = 0;
+
+ progname = strrchr(argv[0], '/');
+ progname = progname ? 1+progname : argv[0];
+ while (EOF != (c = getopt(argc, argv, "a:A:cd:e:f:ghp:P:st:v"))) {
+ switch (c) {
+ case 'a':
+ oneshot = atoi(optarg);
+ break;
+ case 'A':
+ periodic = atoi(optarg);
+ break;
+ case 'c':
+ capabilities = 1;
+ break;
+ case 'd':
+ device = optarg;
+ break;
+ case 'e':
+ extts = atoi(optarg);
+ break;
+ case 'f':
+ adjfreq = atoi(optarg);
+ break;
+ case 'g':
+ gettime = 1;
+ break;
+ case 'p':
+ perout = atoi(optarg);
+ break;
+ case 'P':
+ pps = atoi(optarg);
+ break;
+ case 's':
+ settime = 1;
+ break;
+ case 't':
+ adjtime = atoi(optarg);
+ break;
+ case 'v':
+ version = 1;
+ break;
+ case 'h':
+ usage(progname);
+ return 0;
+ case '?':
+ default:
+ usage(progname);
+ return -1;
+ }
+ }
+
+ fd = open(device, O_RDWR);
+ if (fd < 0) {
+ fprintf(stderr, "cannot open %s: %s", device, strerror(errno));
+ return -1;
+ }
+
+ if (version) {
+ if (ioctl(fd, PTP_CLOCK_APIVERS, &val)) {
+ perror("PTP_CLOCK_APIVERS");
+ } else {
+ printf("version = 0x%08x\n", val);
+ }
+ }
+
+ if (capabilities) {
+ if (ioctl(fd, PTP_CLOCK_GETCAPS, &caps)) {
+ perror("PTP_CLOCK_GETCAPS");
+ } else {
+ printf("capabilities:\n"
+ " %d maximum frequency adjustment (PPB)\n"
+ " %d programmable alarms\n"
+ " %d external time stamp channels\n"
+ " %d programmable periodic signals\n"
+ " %d pulse per second\n",
+ caps.max_adj,
+ caps.n_alarm,
+ caps.n_ext_ts,
+ caps.n_per_out,
+ caps.pps);
+ }
+ }
+
+ if (0x7fffffff != adjfreq) {
+ if (ioctl(fd, PTP_CLOCK_ADJFREQ, adjfreq)) {
+ perror("PTP_CLOCK_ADJFREQ");
+ } else {
+ puts("frequency adjustment okay");
+ }
+ }
+
+ if (adjtime) {
+ ts.tv_sec = adjtime;
+ ts.tv_nsec = 0;
+ if (ioctl(fd, PTP_CLOCK_ADJTIME, &ts)) {
+ perror("PTP_CLOCK_ADJTIME");
+ } else {
+ puts("time shift okay");
+ }
+ }
+
+ if (gettime) {
+ if (ioctl(fd, PTP_CLOCK_GETTIME, &ts)) {
+ perror("PTP_CLOCK_GETTIME");
+ } else {
+ printf("clock time: %ld.%09ld or %s",
+ ts.tv_sec, ts.tv_nsec, ctime(&ts.tv_sec));
+ }
+ }
+
+ if (settime) {
+ clock_gettime(CLOCK_REALTIME, &ts);
+ if (ioctl(fd, PTP_CLOCK_SETTIME, &ts)) {
+ perror("PTP_CLOCK_SETTIME");
+ } else {
+ puts("set time okay");
+ }
+ }
+
+ if (extts) {
+ memset(&request, 0, sizeof(request));
+ request.type = PTP_REQUEST_EXTTS;
+ request.index = 0;
+ request.flags = PTP_ENABLE_FEATURE;
+ if (ioctl(fd, PTP_FEATURE_REQUEST, &request)) {
+ perror("PTP_FEATURE_REQUEST");
+ extts = 0;
+ } else {
+ puts("external time stamp request okay");
+ }
+ for (; extts; extts--) {
+ cnt = read(fd, &event, sizeof(event));
+ if (cnt != sizeof(event)) {
+ perror("read");
+ break;
+ }
+ printf("event index %d at %ld.%09ld\n", event.index,
+ event.ts.tv_sec, event.ts.tv_nsec);
+ fflush(stdout);
+ }
+ /* Disable the feature again. */
+ request.flags = 0;
+ if (ioctl(fd, PTP_FEATURE_REQUEST, &request)) {
+ perror("PTP_FEATURE_REQUEST");
+ }
+ }
+
+ if (oneshot) {
+ install_handler(SIGALRM, handle_alarm);
+ memset(&timer, 0, sizeof(timer));
+ timer.signum = SIGALRM;
+ timer.tsp.it_value.tv_sec = oneshot;
+ if (ioctl(fd, PTP_CLOCK_SETTIMER, &timer)) {
+ perror("PTP_CLOCK_SETTIMER");
+ } else {
+ puts("set timer okay");
+ }
+ pause();
+ }
+
+ if (periodic) {
+ install_handler(SIGALRM, handle_alarm);
+ memset(&timer, 0, sizeof(timer));
+ timer.signum = SIGALRM;
+ timer.tsp.it_value.tv_sec = periodic;
+ timer.tsp.it_interval.tv_sec = periodic;
+ if (ioctl(fd, PTP_CLOCK_SETTIMER, &timer)) {
+ perror("PTP_CLOCK_SETTIMER");
+ } else {
+ puts("set timer okay");
+ }
+ while (1) {
+ pause();
+ }
+ }
+
+ if (perout >= 0) {
+ memset(&request, 0, sizeof(request));
+ request.type = PTP_REQUEST_PEROUT;
+ request.index = 0;
+ request.ts.tv_sec = 0;
+ request.ts.tv_nsec = perout;
+ request.flags = perout ? PTP_ENABLE_FEATURE : 0;
+ if (ioctl(fd, PTP_FEATURE_REQUEST, &request)) {
+ perror("PTP_FEATURE_REQUEST");
+ extts = 0;
+ } else {
+ puts("periodic output request okay");
+ }
+ }
+
+ if (pps != -1) {
+ memset(&request, 0, sizeof(request));
+ request.type = PTP_REQUEST_PPS;
+ request.flags = perout ? PTP_ENABLE_FEATURE : 0;
+ if (ioctl(fd, PTP_FEATURE_REQUEST, &request)) {
+ perror("PTP_FEATURE_REQUEST");
+ } else {
+ puts("pps for system time request okay");
+ }
+ }
+
+ close(fd);
+ return 0;
+}
diff --git a/Documentation/ptp/testptp.mk b/Documentation/ptp/testptp.mk
new file mode 100644
index 0000000..4ef2d97
--- /dev/null
+++ b/Documentation/ptp/testptp.mk
@@ -0,0 +1,33 @@
+# PTP 1588 clock support - User space test program
+#
+# Copyright (C) 2010 OMICRON electronics GmbH
+#
+# 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.
+
+CC = $(CROSS_COMPILE)gcc
+INC = -I$(KBUILD_OUTPUT)/usr/include
+CFLAGS = -Wall $(INC)
+LDLIBS = -lrt
+PROGS = testptp
+
+all: $(PROGS)
+
+testptp: testptp.o
+
+clean:
+ rm -f testptp.o
+
+distclean: clean
+ rm -f $(PROGS)
diff --git a/drivers/Kconfig b/drivers/Kconfig
index a2b902f..774fbd7 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -52,6 +52,8 @@ source "drivers/spi/Kconfig"
source "drivers/pps/Kconfig"
+source "drivers/ptp/Kconfig"
+
source "drivers/gpio/Kconfig"
source "drivers/w1/Kconfig"
diff --git a/drivers/Makefile b/drivers/Makefile
index 91874e0..6d12b48 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -76,6 +76,7 @@ obj-$(CONFIG_I2O) += message/
obj-$(CONFIG_RTC_LIB) += rtc/
obj-y += i2c/ media/
obj-$(CONFIG_PPS) += pps/
+obj-$(CONFIG_PTP_1588_CLOCK) += ptp/
obj-$(CONFIG_W1) += w1/
obj-$(CONFIG_POWER_SUPPLY) += power/
obj-$(CONFIG_HWMON) += hwmon/
diff --git a/drivers/ptp/Kconfig b/drivers/ptp/Kconfig
new file mode 100644
index 0000000..cd7becb
--- /dev/null
+++ b/drivers/ptp/Kconfig
@@ -0,0 +1,27 @@
+#
+# PTP clock support configuration
+#
+
+menu "PTP clock support"
+
+config PTP_1588_CLOCK
+ tristate "PTP clock support"
+ depends on EXPERIMENTAL
+ depends on PPS
+ help
+ The IEEE 1588 standard defines a method to precisely
+ synchronize distributed clocks over Ethernet networks. The
+ standard defines a Precision Time Protocol (PTP), which can
+ be used to achieve synchronization within a few dozen
+ microseconds. In addition, with the help of special hardware
+ time stamping units, it can be possible to achieve
+ synchronization to within a few hundred nanoseconds.
+
+ This driver adds support for PTP clocks as character
+ devices. If you want to use a PTP clock, then you should
+ also enable at least one clock driver as well.
+
+ To compile this driver as a module, choose M here: the module
+ will be called ptp_clock.
+
+endmenu
diff --git a/drivers/ptp/Makefile b/drivers/ptp/Makefile
new file mode 100644
index 0000000..b86695c
--- /dev/null
+++ b/drivers/ptp/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for PTP 1588 clock support.
+#
+
+obj-$(CONFIG_PTP_1588_CLOCK) += ptp_clock.o
diff --git a/drivers/ptp/ptp_clock.c b/drivers/ptp/ptp_clock.c
new file mode 100644
index 0000000..1ebb4b0
--- /dev/null
+++ b/drivers/ptp/ptp_clock.c
@@ -0,0 +1,514 @@
+/*
+ * PTP 1588 clock support
+ *
+ * Partially adapted from the Linux PPS driver.
+ *
+ * Copyright (C) 2010 OMICRON electronics GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include <linux/bitops.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/poll.h>
+#include <linux/pps_kernel.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include <linux/ptp_clock_kernel.h>
+#include <linux/ptp_clock.h>
+
+#define PTP_MAX_ALARMS 4
+#define PTP_MAX_CLOCKS BITS_PER_LONG
+#define PTP_MAX_TIMESTAMPS 128
+#define PTP_PPS_DEFAULTS (PPS_CAPTUREASSERT | PPS_OFFSETASSERT)
+#define PTP_PPS_EVENT PPS_CAPTUREASSERT
+#define PTP_PPS_MODE (PTP_PPS_DEFAULTS | PPS_CANWAIT | PPS_TSFMT_TSPEC)
+
+struct alarm {
+ struct pid *pid;
+ int sig;
+};
+
+struct timestamp_event_queue {
+ struct ptp_extts_event buf[PTP_MAX_TIMESTAMPS];
+ int head;
+ int tail;
+ int overflow;
+};
+
+struct ptp_clock {
+ struct list_head list;
+ struct cdev cdev;
+ struct device *dev;
+ struct ptp_clock_info *info;
+ dev_t devid;
+ int index; /* index into clocks.map, also the minor number */
+ int pps_source;
+
+ struct alarm alarm[PTP_MAX_ALARMS];
+ struct mutex alarm_mux; /* one process at a time setting an alarm */
+
+ struct timestamp_event_queue tsevq; /* simple fifo for time stamps */
+ struct mutex tsevq_mux; /* one process at a time reading the fifo */
+ wait_queue_head_t tsev_wq;
+};
+
+/* private globals */
+
+static const struct file_operations ptp_fops;
+static dev_t ptp_devt;
+static struct class *ptp_class;
+
+static struct {
+ struct list_head list;
+ DECLARE_BITMAP(map, PTP_MAX_CLOCKS);
+} clocks;
+static DEFINE_MUTEX(clocks_mux); /* protects 'clocks' */
+
+/* time stamp event queue operations */
+
+static inline int queue_cnt(struct timestamp_event_queue *q)
+{
+ int cnt = q->tail - q->head;
+ return cnt < 0 ? PTP_MAX_TIMESTAMPS + cnt : cnt;
+}
+
+static inline int queue_free(struct timestamp_event_queue *q)
+{
+ return PTP_MAX_TIMESTAMPS - queue_cnt(q) - 1;
+}
+
+static void enqueue_external_timestamp(struct timestamp_event_queue *queue,
+ struct ptp_clock_event *src)
+{
+ struct ptp_extts_event *dst;
+ u32 remainder;
+
+ dst = &queue->buf[queue->tail];
+
+ dst->index = src->index;
+ dst->ts.tv_sec = div_u64_rem(src->timestamp, 1000000000, &remainder);
+ dst->ts.tv_nsec = remainder;
+
+ if (!queue_free(queue))
+ queue->overflow++;
+
+ queue->tail = (queue->tail + 1) % PTP_MAX_TIMESTAMPS;
+}
+
+/* public interface */
+
+struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info)
+{
+ struct ptp_clock *ptp;
+ int err = 0, index, major = MAJOR(ptp_devt);
+
+ if (info->n_alarm > PTP_MAX_ALARMS)
+ return ERR_PTR(-EINVAL);
+
+ /* Find a free clock slot and reserve it. */
+ err = -EBUSY;
+ mutex_lock(&clocks_mux);
+ index = find_first_zero_bit(clocks.map, PTP_MAX_CLOCKS);
+ if (index < PTP_MAX_CLOCKS)
+ set_bit(index, clocks.map);
+ else
+ goto no_clock;
+
+ /* Initialize a clock structure. */
+ err = -ENOMEM;
+ ptp = kzalloc(sizeof(struct ptp_clock), GFP_KERNEL);
+ if (ptp == NULL)
+ goto no_memory;
+
+ ptp->info = info;
+ ptp->devid = MKDEV(major, index);
+ ptp->index = index;
+ mutex_init(&ptp->alarm_mux);
+ mutex_init(&ptp->tsevq_mux);
+ init_waitqueue_head(&ptp->tsev_wq);
+
+ /* Create a new device in our class. */
+ ptp->dev = device_create(ptp_class, NULL, ptp->devid, ptp,
+ "ptp_clock_%d", ptp->index);
+ if (IS_ERR(ptp->dev))
+ goto no_device;
+
+ dev_set_drvdata(ptp->dev, ptp);
+
+ /* Register a character device. */
+ cdev_init(&ptp->cdev, &ptp_fops);
+ ptp->cdev.owner = info->owner;
+ err = cdev_add(&ptp->cdev, ptp->devid, 1);
+ if (err)
+ goto no_cdev;
+
+ /* Register a new PPS source. */
+ if (info->pps) {
+ struct pps_source_info pps;
+ memset(&pps, 0, sizeof(pps));
+ snprintf(pps.name, PPS_MAX_NAME_LEN, "ptp%d", index);
+ pps.mode = PTP_PPS_MODE;
+ pps.owner = info->owner;
+ pps.dev = ptp->dev;
+ err = pps_register_source(&pps, PTP_PPS_DEFAULTS);
+ if (err < 0) {
+ pr_err("failed to register pps source\n");
+ goto no_pps;
+ } else
+ ptp->pps_source = err;
+ }
+
+ /* Clock is ready, add it into the list. */
+ list_add(&ptp->list, &clocks.list);
+
+ mutex_unlock(&clocks_mux);
+ return ptp;
+
+no_pps:
+no_cdev:
+ device_destroy(ptp_class, ptp->devid);
+no_device:
+ mutex_destroy(&ptp->alarm_mux);
+ mutex_destroy(&ptp->tsevq_mux);
+ kfree(ptp);
+no_memory:
+ clear_bit(index, clocks.map);
+no_clock:
+ mutex_unlock(&clocks_mux);
+ return ERR_PTR(err);
+}
+EXPORT_SYMBOL(ptp_clock_register);
+
+int ptp_clock_unregister(struct ptp_clock *ptp)
+{
+ /* Release the clock's resources. */
+ if (ptp->info->pps)
+ pps_unregister_source(ptp->pps_source);
+ cdev_del(&ptp->cdev);
+ device_destroy(ptp_class, ptp->devid);
+ mutex_destroy(&ptp->alarm_mux);
+ mutex_destroy(&ptp->tsevq_mux);
+
+ /* Remove the clock from the list. */
+ mutex_lock(&clocks_mux);
+ list_del(&ptp->list);
+ clear_bit(ptp->index, clocks.map);
+ mutex_unlock(&clocks_mux);
+
+ kfree(ptp);
+
+ return 0;
+}
+EXPORT_SYMBOL(ptp_clock_unregister);
+
+void ptp_clock_event(struct ptp_clock *ptp, struct ptp_clock_event *event)
+{
+ struct timespec ts;
+ struct pps_ktime pps_ts;
+
+ switch (event->type) {
+
+ case PTP_CLOCK_ALARM:
+ kill_pid(ptp->alarm[event->index].pid,
+ ptp->alarm[event->index].sig, 1);
+ break;
+
+ case PTP_CLOCK_EXTTS:
+ enqueue_external_timestamp(&ptp->tsevq, event);
+ wake_up_interruptible(&ptp->tsev_wq);
+ break;
+
+ case PTP_CLOCK_PPS:
+ getnstimeofday(&ts);
+ pps_ts.sec = ts.tv_sec;
+ pps_ts.nsec = ts.tv_nsec;
+ pps_event(ptp->pps_source, &pps_ts, PTP_PPS_EVENT, NULL);
+ break;
+ }
+}
+EXPORT_SYMBOL(ptp_clock_event);
+
+/* character device operations */
+
+static int ptp_ioctl(struct inode *node, struct file *fp,
+ unsigned int cmd, unsigned long arg)
+{
+ struct ptp_clock_caps caps;
+ struct ptp_clock_request req;
+ struct ptp_clock_timer timer;
+ struct ptp_clock *ptp = fp->private_data;
+ struct ptp_clock_info *ops = ptp->info;
+ void *priv = ops->priv;
+ struct timespec ts;
+ int flags, index;
+ int err = 0;
+ s32 ppb;
+
+ switch (cmd) {
+
+ case PTP_CLOCK_APIVERS:
+ err = put_user(PTP_CLOCK_VERSION, (u32 __user *)arg);
+ break;
+
+ case PTP_CLOCK_ADJFREQ:
+ if (!capable(CAP_SYS_TIME))
+ return -EPERM;
+ ppb = arg;
+ if (ppb > ops->max_adj || ppb < -ops->max_adj)
+ return -EINVAL;
+ err = ops->adjfreq(priv, ppb);
+ break;
+
+ case PTP_CLOCK_ADJTIME:
+ if (!capable(CAP_SYS_TIME))
+ return -EPERM;
+ if (copy_from_user(&ts, (void __user *)arg, sizeof(ts)))
+ err = -EFAULT;
+ else
+ err = ops->adjtime(priv, &ts);
+ break;
+
+ case PTP_CLOCK_GETTIME:
+ err = ops->gettime(priv, &ts);
+ if (err)
+ break;
+ err = copy_to_user((void __user *)arg, &ts, sizeof(ts));
+ break;
+
+ case PTP_CLOCK_SETTIME:
+ if (!capable(CAP_SYS_TIME))
+ return -EPERM;
+ if (copy_from_user(&ts, (void __user *)arg, sizeof(ts)))
+ err = -EFAULT;
+ else
+ err = ops->settime(priv, &ts);
+ break;
+
+ case PTP_CLOCK_GETCAPS:
+ memset(&caps, 0, sizeof(caps));
+ caps.max_adj = ptp->info->max_adj;
+ caps.n_alarm = ptp->info->n_alarm;
+ caps.n_ext_ts = ptp->info->n_ext_ts;
+ caps.n_per_out = ptp->info->n_per_out;
+ caps.pps = ptp->info->pps;
+ err = copy_to_user((void __user *)arg, &caps, sizeof(caps));
+ break;
+
+ case PTP_CLOCK_GETTIMER:
+ if (copy_from_user(&timer, (void __user *)arg, sizeof(timer))) {
+ err = -EFAULT;
+ break;
+ }
+ index = timer.alarm_index;
+ if (index < 0 || index >= ptp->info->n_alarm) {
+ err = -EINVAL;
+ break;
+ }
+ err = ops->gettimer(priv, index, &timer.tsp);
+ if (err)
+ break;
+ err = copy_to_user((void __user *)arg, &timer, sizeof(timer));
+ break;
+
+ case PTP_CLOCK_SETTIMER:
+ if (copy_from_user(&timer, (void __user *)arg, sizeof(timer))) {
+ err = -EFAULT;
+ break;
+ }
+ index = timer.alarm_index;
+ if (index < 0 || index >= ptp->info->n_alarm) {
+ err = -EINVAL;
+ break;
+ }
+ if (!valid_signal(timer.signum))
+ return -EINVAL;
+ flags = timer.flags;
+ if (flags & (flags != TIMER_ABSTIME)) {
+ err = -EINVAL;
+ break;
+ }
+ if (mutex_lock_interruptible(&ptp->alarm_mux))
+ return -ERESTARTSYS;
+
+ if (ptp->alarm[index].pid)
+ put_pid(ptp->alarm[index].pid);
+
+ ptp->alarm[index].pid = get_pid(task_pid(current));
+ ptp->alarm[index].sig = timer.signum;
+ err = ops->settimer(priv, index, flags, &timer.tsp);
+
+ mutex_unlock(&ptp->alarm_mux);
+ break;
+
+ case PTP_FEATURE_REQUEST:
+ if (copy_from_user(&req, (void __user *)arg, sizeof(req))) {
+ err = -EFAULT;
+ break;
+ }
+ switch (req.type) {
+ case PTP_REQUEST_EXTTS:
+ case PTP_REQUEST_PEROUT:
+ break;
+ case PTP_REQUEST_PPS:
+ if (!capable(CAP_SYS_TIME))
+ return -EPERM;
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+ if (err)
+ break;
+ err = ops->enable(priv, &req,
+ req.flags & PTP_ENABLE_FEATURE ? 1 : 0);
+ break;
+
+ default:
+ err = -ENOTTY;
+ break;
+ }
+ return err;
+}
+
+static int ptp_open(struct inode *inode, struct file *fp)
+{
+ struct ptp_clock *ptp;
+ ptp = container_of(inode->i_cdev, struct ptp_clock, cdev);
+
+ fp->private_data = ptp;
+
+ return 0;
+}
+
+static unsigned int ptp_poll(struct file *fp, poll_table *wait)
+{
+ struct ptp_clock *ptp = fp->private_data;
+
+ poll_wait(fp, &ptp->tsev_wq, wait);
+
+ return queue_cnt(&ptp->tsevq) ? POLLIN : 0;
+}
+
+static ssize_t ptp_read(struct file *fp, char __user *buf,
+ size_t cnt, loff_t *off)
+{
+ struct ptp_clock *ptp = fp->private_data;
+ struct timestamp_event_queue *queue = &ptp->tsevq;
+ struct ptp_extts_event *event;
+ size_t qcnt;
+
+ if (mutex_lock_interruptible(&ptp->tsevq_mux))
+ return -ERESTARTSYS;
+
+ cnt = cnt / sizeof(struct ptp_extts_event);
+
+ if (wait_event_interruptible(ptp->tsev_wq,
+ (qcnt = queue_cnt(&ptp->tsevq)))) {
+ mutex_unlock(&ptp->tsevq_mux);
+ return -ERESTARTSYS;
+ }
+
+ if (cnt > qcnt)
+ cnt = qcnt;
+
+ event = &queue->buf[queue->head];
+
+ if (copy_to_user(buf, event, cnt * sizeof(struct ptp_extts_event))) {
+ mutex_unlock(&ptp->tsevq_mux);
+ return -EFAULT;
+ }
+ queue->head = (queue->head + cnt) % PTP_MAX_TIMESTAMPS;
+
+ mutex_unlock(&ptp->tsevq_mux);
+
+ return cnt * sizeof(struct ptp_extts_event);
+}
+
+static int ptp_release(struct inode *inode, struct file *fp)
+{
+ struct ptp_clock *ptp;
+ struct itimerspec ts = {
+ {0, 0}, {0, 0}
+ };
+ int i;
+
+ ptp = container_of(inode->i_cdev, struct ptp_clock, cdev);
+
+ for (i = 0; i < ptp->info->n_alarm; i++) {
+ if (ptp->alarm[i].pid) {
+ ptp->info->settimer(ptp->info->priv, i, 0, &ts);
+ put_pid(ptp->alarm[i].pid);
+ ptp->alarm[i].pid = NULL;
+ }
+ }
+ return 0;
+}
+
+static const struct file_operations ptp_fops = {
+ .owner = THIS_MODULE,
+ .ioctl = ptp_ioctl,
+ .open = ptp_open,
+ .poll = ptp_poll,
+ .read = ptp_read,
+ .release = ptp_release,
+};
+
+/* module operations */
+
+static void __exit ptp_exit(void)
+{
+ class_destroy(ptp_class);
+ unregister_chrdev_region(ptp_devt, PTP_MAX_CLOCKS);
+}
+
+static int __init ptp_init(void)
+{
+ int err;
+
+ INIT_LIST_HEAD(&clocks.list);
+
+ ptp_class = class_create(THIS_MODULE, "ptp");
+ if (!ptp_class) {
+ printk(KERN_ERR "ptp: failed to allocate class\n");
+ return -ENOMEM;
+ }
+
+ err = alloc_chrdev_region(&ptp_devt, 0, PTP_MAX_CLOCKS, "ptp");
+ if (err < 0) {
+ printk(KERN_ERR "ptp: failed to allocate char device region\n");
+ goto no_region;
+ }
+
+ pr_info("PTP clock support registered\n");
+ return 0;
+
+no_region:
+ class_destroy(ptp_class);
+ return err;
+}
+
+subsys_initcall(ptp_init);
+module_exit(ptp_exit);
+
+MODULE_AUTHOR("Richard Cochran <richard.cochran@omicron.at>");
+MODULE_DESCRIPTION("PTP clocks support");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/Kbuild b/include/linux/Kbuild
index 9aa9bca..471205a 100644
--- a/include/linux/Kbuild
+++ b/include/linux/Kbuild
@@ -140,6 +140,7 @@ header-y += pkt_sched.h
header-y += posix_types.h
header-y += ppdev.h
header-y += prctl.h
+header-y += ptp_clock.h
header-y += qnxtypes.h
header-y += qnx4_fs.h
header-y += radeonfb.h
diff --git a/include/linux/ptp_clock.h b/include/linux/ptp_clock.h
new file mode 100644
index 0000000..5a509c5
--- /dev/null
+++ b/include/linux/ptp_clock.h
@@ -0,0 +1,79 @@
+/*
+ * PTP 1588 clock support - user space interface
+ *
+ * Copyright (C) 2010 OMICRON electronics GmbH
+ *
+ * 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 _PTP_CLOCK_H_
+#define _PTP_CLOCK_H_
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+#define PTP_ENABLE_FEATURE (1<<0)
+#define PTP_RISING_EDGE (1<<1)
+#define PTP_FALLING_EDGE (1<<2)
+
+enum ptp_request_types {
+ PTP_REQUEST_EXTTS,
+ PTP_REQUEST_PEROUT,
+ PTP_REQUEST_PPS,
+};
+
+struct ptp_clock_caps {
+ __s32 max_adj; /* Maximum frequency adjustment, parts per billon. */
+ int n_alarm; /* Number of programmable alarms. */
+ int n_ext_ts; /* Number of external time stamp channels. */
+ int n_per_out; /* Number of programmable periodic signals. */
+ int pps; /* Whether the clock supports a PPS callback. */
+};
+
+struct ptp_clock_timer {
+ int alarm_index; /* Which alarm to query or configure. */
+ int signum; /* Requested signal. */
+ int flags; /* Zero or TIMER_ABSTIME, see TIMER_SETTIME(2) */
+ struct itimerspec tsp; /* See TIMER_SETTIME(2) */
+};
+
+struct ptp_clock_request {
+ int type; /* One of the ptp_request_types enumeration values. */
+ int index; /* Which channel to configure. */
+ struct timespec ts; /* For period signals, the desired period. */
+ int flags; /* Bit field for PTP_ENABLE_FEATURE or other flags. */
+};
+
+struct ptp_extts_event {
+ int index;
+ struct timespec ts;
+};
+
+#define PTP_CLOCK_VERSION 0x00000001
+
+#define PTP_CLK_MAGIC '='
+
+#define PTP_CLOCK_APIVERS _IOR (PTP_CLK_MAGIC, 1, __u32)
+#define PTP_CLOCK_ADJFREQ _IO (PTP_CLK_MAGIC, 2)
+#define PTP_CLOCK_ADJTIME _IOW (PTP_CLK_MAGIC, 3, struct timespec)
+#define PTP_CLOCK_GETTIME _IOR (PTP_CLK_MAGIC, 4, struct timespec)
+#define PTP_CLOCK_SETTIME _IOW (PTP_CLK_MAGIC, 5, struct timespec)
+
+#define PTP_CLOCK_GETCAPS _IOR (PTP_CLK_MAGIC, 6, struct ptp_clock_caps)
+#define PTP_CLOCK_GETTIMER _IOWR (PTP_CLK_MAGIC, 7, struct ptp_clock_timer)
+#define PTP_CLOCK_SETTIMER _IOW (PTP_CLK_MAGIC, 8, struct ptp_clock_timer)
+#define PTP_FEATURE_REQUEST _IOW (PTP_CLK_MAGIC, 9, struct ptp_clock_request)
+
+#endif
diff --git a/include/linux/ptp_clock_kernel.h b/include/linux/ptp_clock_kernel.h
new file mode 100644
index 0000000..d6cc158
--- /dev/null
+++ b/include/linux/ptp_clock_kernel.h
@@ -0,0 +1,137 @@
+/*
+ * PTP 1588 clock support
+ *
+ * Copyright (C) 2010 OMICRON electronics GmbH
+ *
+ * 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 _PTP_CLOCK_KERNEL_H_
+#define _PTP_CLOCK_KERNEL_H_
+
+#include <linux/ptp_clock.h>
+
+/**
+ * struct ptp_clock_info - decribes a PTP hardware clock
+ *
+ * @owner: The clock driver should set to THIS_MODULE.
+ * @name: A short name to identify the clock.
+ * @max_adj: The maximum possible frequency adjustment, in parts per billon.
+ * @n_alarm: The number of programmable alarms.
+ * @n_ext_ts: The number of external time stamp channels.
+ * @n_per_out: The number of programmable periodic signals.
+ * @pps: Indicates whether the clock supports a PPS callback.
+ * @priv: Passed to the clock operations, for the driver's private use.
+ *
+ * clock operations
+ *
+ * @adjfreq: Adjusts the frequency of the hardware clock.
+ * parameter delta: Desired period change in parts per billion.
+ *
+ * @adjtime: Shifts the time of the hardware clock.
+ * parameter ts: Desired change in seconds and nanoseconds.
+ *
+ * @gettime: Reads the current time from the hardware clock.
+ * parameter ts: Holds the result.
+ *
+ * @settime: Set the current time on the hardware clock.
+ * parameter ts: Time value to set.
+ *
+ * @gettimer: Reads the time remaining from the given timer.
+ * parameter index: Which alarm to query.
+ * parameter ts: Holds the result.
+ *
+ * @settimer: Arms the given timer for periodic or one shot operation.
+ * parameter index: Which alarm to set.
+ * parameter abs: TIMER_ABSTIME, or zero for relative timer.
+ * parameter ts: Alarm time and period to set.
+ *
+ * @enable: Request driver to enable or disable an ancillary feature.
+ * parameter request: Desired resource to enable or disable.
+ * parameter on: Caller passes one to enable or zero to disable.
+ *
+ * The callbacks must all return zero on success, non-zero otherwise.
+ */
+
+struct ptp_clock_info {
+ struct module *owner;
+ char name[16];
+ s32 max_adj;
+ int n_alarm;
+ int n_ext_ts;
+ int n_per_out;
+ int pps;
+ void *priv;
+ int (*adjfreq)(void *priv, s32 delta);
+ int (*adjtime)(void *priv, struct timespec *ts);
+ int (*gettime)(void *priv, struct timespec *ts);
+ int (*settime)(void *priv, struct timespec *ts);
+ int (*gettimer)(void *priv, int index, struct itimerspec *ts);
+ int (*settimer)(void *priv, int index, int abs, struct itimerspec *ts);
+ int (*enable)(void *priv, struct ptp_clock_request *request, int on);
+};
+
+struct ptp_clock;
+
+/**
+ * ptp_clock_register() - register a PTP hardware clock driver
+ *
+ * @info: Structure describing the new clock.
+ */
+
+extern struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info);
+
+/**
+ * ptp_clock_unregister() - unregister a PTP hardware clock driver
+ *
+ * @ptp: The clock to remove from service.
+ */
+
+extern int ptp_clock_unregister(struct ptp_clock *ptp);
+
+
+enum ptp_clock_events {
+ PTP_CLOCK_ALARM,
+ PTP_CLOCK_EXTTS,
+ PTP_CLOCK_PPS,
+};
+
+/**
+ * struct ptp_clock_event - decribes a PTP hardware clock event
+ *
+ * @type: One of the ptp_clock_events enumeration values.
+ * @index: Identifies the source of the event.
+ * @timestamp: When the event occured.
+ */
+
+struct ptp_clock_event {
+ int type;
+ int index;
+ u64 timestamp;
+};
+
+/**
+ * ptp_clock_event() - notify the PTP layer about an event
+ *
+ * This function should only be called from interrupt context.
+ *
+ * @ptp: The clock obtained from ptp_clock_register().
+ * @event: Message structure describing the event.
+ */
+
+extern void ptp_clock_event(struct ptp_clock *ptp,
+ struct ptp_clock_event *event);
+
+#endif
--
1.7.0.4
^ permalink raw reply related [flat|nested] 56+ messages in thread
[parent not found: <363bd749a38d0b785d8431e591bf54c38db4c2d7.1281956490.git.richard.cochran-3mrvs1K0uXizZXS1Dc/lvw@public.gmane.org>]
* Re: [PATCH 1/5] ptp: Added a brand new class driver for ptp clocks.
[not found] ` <363bd749a38d0b785d8431e591bf54c38db4c2d7.1281956490.git.richard.cochran-3mrvs1K0uXizZXS1Dc/lvw@public.gmane.org>
@ 2010-08-16 14:26 ` Arnd Bergmann
2010-08-16 19:00 ` Richard Cochran
0 siblings, 1 reply; 56+ messages in thread
From: Arnd Bergmann @ 2010-08-16 14:26 UTC (permalink / raw)
To: Richard Cochran
Cc: Rodolfo Giometti, netdev-u79uwXL29TY76Z2rM5mHXA,
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linuxppc-dev-uLR06cmDAlY/bJ5BZ2RsiQ,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
Krzysztof Halasa
On Monday 16 August 2010, Richard Cochran wrote:
> This patch adds an infrastructure for hardware clocks that implement
> IEEE 1588, the Precision Time Protocol (PTP). A class driver offers a
> registration method to particular hardware clock drivers. Each clock is
> exposed to user space as a character device with ioctls that allow tuning
> of the PTP clock.
Have you considered integrating the subsystem into the Posix clock/timer
framework?
I can't really tell from reading the source if this is possible or
not, but my feeling is that if it can be done, that would be a much
nicer interface. We already have clock_gettime/clock_settime/
timer_settime/... system calls, and while you'd need to add another
clockid and some syscalls, my feeling is that it will be more
usable in the end.
> +static const struct file_operations ptp_fops = {
> + .owner = THIS_MODULE,
> + .ioctl = ptp_ioctl,
> + .open = ptp_open,
> + .poll = ptp_poll,
> + .read = ptp_read,
> + .release = ptp_release,
> +};
.ioctl is gone, you have to use .unlocked_ioctl and make sure that
your ptp_ioctl function can handle being called concurrently.
You should also add a .compat_ioctl function, ideally one that
points to ptp_ioctl so you don't have to list every command as
being compatible. Right now, some commands are incompatible,
which means you need more changes to the interface.
> +struct ptp_clock_timer {
> + int alarm_index; /* Which alarm to query or configure. */
> + int signum; /* Requested signal. */
> + int flags; /* Zero or TIMER_ABSTIME, see TIMER_SETTIME(2) */
> + struct itimerspec tsp; /* See TIMER_SETTIME(2) */
> +};
This data structure is incompatible between 32 and 64 bit user space,
so you would need a compat_ioctl() function to convert between the
two formats. Better define the structure in a compatible way, avoiding
variable-length fields and implicit padding.
> +struct ptp_clock_request {
> + int type; /* One of the ptp_request_types enumeration values. */
> + int index; /* Which channel to configure. */
> + struct timespec ts; /* For period signals, the desired period. */
> + int flags; /* Bit field for PTP_ENABLE_FEATURE or other flags. */
> +};
Same problem here, timespec is either 64 or 128 bits long and has different
alignment.
> +struct ptp_extts_event {
> + int index;
> + struct timespec ts;
> +};
here too.
> +#define PTP_CLOCK_VERSION 0x00000001
> +
> +#define PTP_CLK_MAGIC '='
> +
> +#define PTP_CLOCK_APIVERS _IOR (PTP_CLK_MAGIC, 1, __u32)
Try avoiding versioned ABIs. It's better to just add new ioctls
or syscalls when you need some extra functionality and leave the
old ones around.
> +#define PTP_CLOCK_ADJTIME _IOW (PTP_CLK_MAGIC, 3, struct timespec)
> +#define PTP_CLOCK_GETTIME _IOR (PTP_CLK_MAGIC, 4, struct timespec)
> +#define PTP_CLOCK_SETTIME _IOW (PTP_CLK_MAGIC, 5, struct timespec)
These are also incompatible.
Arnd
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH 1/5] ptp: Added a brand new class driver for ptp clocks.
2010-08-16 14:26 ` Arnd Bergmann
@ 2010-08-16 19:00 ` Richard Cochran
2010-08-16 19:59 ` Arnd Bergmann
0 siblings, 1 reply; 56+ messages in thread
From: Richard Cochran @ 2010-08-16 19:00 UTC (permalink / raw)
To: Arnd Bergmann
Cc: netdev, linux-kernel, linuxppc-dev, devicetree-discuss,
linux-arm-kernel, Krzysztof Halasa, Rodolfo Giometti
On Mon, Aug 16, 2010 at 04:26:23PM +0200, Arnd Bergmann wrote:
> Have you considered integrating the subsystem into the Posix clock/timer
> framework?
Yes, but see below.
> I can't really tell from reading the source if this is possible or
> not, but my feeling is that if it can be done, that would be a much
> nicer interface. We already have clock_gettime/clock_settime/
> timer_settime/... system calls, and while you'd need to add another
> clockid and some syscalls, my feeling is that it will be more
> usable in the end.
You are not the first person to ask about this. See this link for
longer explanation of why I did not go that way:
http://marc.info/?l=linux-netdev&m=127669810232201&w=2
You *could* offer the PTP clock as a Linux clock source/event device,
and I agree that it would be nicer. However, the problem is, what do
you do with the PHY based clocks? Just one 16 bit read from a PHY
clock can take 40 usec, and you need four such read operations just to
get the current time value.
Also, I really did not want to add or change any syscalls. I could not
see a practical way to extend the existing syscalls to accommodate PTP
clocks.
Richard
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH 1/5] ptp: Added a brand new class driver for ptp clocks.
2010-08-16 19:00 ` Richard Cochran
@ 2010-08-16 19:59 ` Arnd Bergmann
2010-08-17 8:32 ` Richard Cochran
0 siblings, 1 reply; 56+ messages in thread
From: Arnd Bergmann @ 2010-08-16 19:59 UTC (permalink / raw)
To: linuxppc-dev
Cc: Richard Cochran, Rodolfo Giometti, netdev, devicetree-discuss,
linux-kernel, linux-arm-kernel, Krzysztof Halasa
On Monday 16 August 2010 21:00:03 Richard Cochran wrote:
>
> On Mon, Aug 16, 2010 at 04:26:23PM +0200, Arnd Bergmann wrote:
> > Have you considered integrating the subsystem into the Posix clock/timer
> > framework?
>
> Yes, but see below.
>
> > I can't really tell from reading the source if this is possible or
> > not, but my feeling is that if it can be done, that would be a much
> > nicer interface. We already have clock_gettime/clock_settime/
> > timer_settime/... system calls, and while you'd need to add another
> > clockid and some syscalls, my feeling is that it will be more
> > usable in the end.
>
> You are not the first person to ask about this. See this link for
> longer explanation of why I did not go that way:
>
> http://marc.info/?l=linux-netdev&m=127669810232201&w=2
>
> You could offer the PTP clock as a Linux clock source/event device,
> and I agree that it would be nicer. However, the problem is, what do
> you do with the PHY based clocks? Just one 16 bit read from a PHY
> clock can take 40 usec, and you need four such read operations just to
> get the current time value.
Why does it matter how long it takes to read the clock? I wasn't thinking
of replacing the system clock with this, just exposing the additional
clock as a new clockid_t value that can be accessed using the existing
syscalls.
> Also, I really did not want to add or change any syscalls. I could not
> see a practical way to extend the existing syscalls to accommodate PTP
> clocks.
Why did you not want to add syscalls? Adding ioctls instead of syscalls
does not make the interface better, just less visible.
Out of the ioctl commands you define, we already seem to have half or more:
PTP_CLOCK_APIVERS -> not needed
PTP_CLOCK_ADJFREQ -> new clock_adjfreq
PTP_CLOCK_ADJTIME -> new clock_adjtime
PTP_CLOCK_GETTIME -> clock_gettime
PTP_CLOCK_SETTIME -> clock_settime
PTP_CLOCK_GETCAPS -> new clock_getcaps
PTP_CLOCK_GETTIMER -> timer_gettime
PTP_CLOCK_SETTIMER -> timer_create/timer_settime
PTP_FEATURE_REQUEST -> possibly clock_feature
Arnd
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH 1/5] ptp: Added a brand new class driver for ptp clocks.
2010-08-16 19:59 ` Arnd Bergmann
@ 2010-08-17 8:32 ` Richard Cochran
2010-08-17 9:25 ` Arnd Bergmann
0 siblings, 1 reply; 56+ messages in thread
From: Richard Cochran @ 2010-08-17 8:32 UTC (permalink / raw)
To: Arnd Bergmann
Cc: linuxppc-dev, Rodolfo Giometti, netdev, devicetree-discuss,
linux-kernel, linux-arm-kernel, Krzysztof Halasa
On Mon, Aug 16, 2010 at 09:59:39PM +0200, Arnd Bergmann wrote:
> Why does it matter how long it takes to read the clock? I wasn't thinking
> of replacing the system clock with this, just exposing the additional
> clock as a new clockid_t value that can be accessed using the existing
> syscalls.
Okay, now I see. You are suggesting this:
clock_gettime(CLOCK_PTP, &ts);
clock_settime(CLOCK_PTP, &ts);
I like this. If there is agreement about it, I am happy to implement
the PTP stuff that way.
> Why did you not want to add syscalls? Adding ioctls instead of syscalls
> does not make the interface better, just less visible.
I bet that, had I posted patch set with new syscalls, someone would
have said, "You are adding new syscalls. Can't you just use a char
device instead!"
If you add syscalls and introduce CLOCK_PTP, then you add it to
everyone's kernel, even those people who never heard of PTP. A char
device has the advantage that can it be simply ignored. Also, a
syscall has got to have the right form from the very beginning. If the
next generation of PTP hardware looks very different, then it is not
that much of a crime to change an ioctl interface, provided it has
versioning.
Richard
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH 1/5] ptp: Added a brand new class driver for ptp clocks.
2010-08-17 8:32 ` Richard Cochran
@ 2010-08-17 9:25 ` Arnd Bergmann
[not found] ` <201008170925.55592.arnd-r2nGTMty4D4@public.gmane.org>
0 siblings, 1 reply; 56+ messages in thread
From: Arnd Bergmann @ 2010-08-17 9:25 UTC (permalink / raw)
To: Richard Cochran
Cc: linuxppc-dev, Rodolfo Giometti, netdev, devicetree-discuss,
linux-kernel, linux-arm-kernel, Krzysztof Halasa
On Tuesday 17 August 2010, Richard Cochran wrote:
> > Why did you not want to add syscalls? Adding ioctls instead of syscalls
> > does not make the interface better, just less visible.
>
> I bet that, had I posted patch set with new syscalls, someone would
> have said, "You are adding new syscalls. Can't you just use a char
> device instead!"
Very possible, but after considering both options, I think we would
still end up with the same conclusion.
> If you add syscalls and introduce CLOCK_PTP, then you add it to
> everyone's kernel, even those people who never heard of PTP. A char
> device has the advantage that can it be simply ignored.
It's a matter of perspective whether you consider this an advantage
or disadvantage. I would expect that since you are trying to get support
for PTP into the kernel, you'd be happy for people to know about it and
use your code ;-).
> Also, a syscall has got to have the right form from the very beginning.
> If the next generation of PTP hardware looks very different, then it is not
> that much of a crime to change an ioctl interface, provided it has
> versioning.
No, that's just a myth. The rules for ABI stability are pretty much the
same. We try hard to avoid ever changing an existing ABI, for both
syscalls and ioctl. In either case, if you get it wrong, you have to support
the old interface and create a new syscall or ioctl command.
As mentioned, versioning does not solve this, it just adds another
indirection (which we try to avoid).
One difference is that more people take a look at your code when you suggest
a new syscall, so the chances of getting it right in the first upstream
version are higher.
Another difference is that we generally use ioctl for devices that can
be enumerated, while syscalls are for system services that are not tied to
a specific device. This argument works both ways for PTP IMHO: On the one
hand you want to have a reliable clock that you can use without knowing
where it comes from, on the other you might have multiple PTP sources that
you need to differentiate.
Arnd
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH 1/5] ptp: Added a brand new class driver for ptp clocks.
2010-08-16 11:17 ` [PATCH 1/5] ptp: Added a brand new class driver for ptp clocks Richard Cochran
[not found] ` <363bd749a38d0b785d8431e591bf54c38db4c2d7.1281956490.git.richard.cochran-3mrvs1K0uXizZXS1Dc/lvw@public.gmane.org>
@ 2010-08-16 19:24 ` john stultz
2010-08-16 19:38 ` john stultz
[not found] ` <AANLkTik_2MKMhOuDGOmu8Kzyq-ipLe+Bxrb3FaD+Tv4U-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
1 sibling, 2 replies; 56+ messages in thread
From: john stultz @ 2010-08-16 19:24 UTC (permalink / raw)
To: Richard Cochran
Cc: netdev, linux-kernel, linuxppc-dev, devicetree-discuss,
linux-arm-kernel, Krzysztof Halasa, Rodolfo Giometti
On Mon, Aug 16, 2010 at 4:17 AM, Richard Cochran
<richardcochran@gmail.com> wrote:
> This patch adds an infrastructure for hardware clocks that implement
> IEEE 1588, the Precision Time Protocol (PTP). A class driver offers a
> registration method to particular hardware clock drivers. Each clock is
> exposed to user space as a character device with ioctls that allow tuning
> of the PTP clock.
>
> Signed-off-by: Richard Cochran <richard.cochran@omicron.at>
Hey Richard!
Its very cool to see this work on lkml! I'm excited to see more
work done on ptp. We had a short private thread discussion earlier (I
got busy and never replied to your last message, my apologies!), but I
wanted to bring up the concerns I have here as well.
A few comments below....
> +** PTP user space API
> +
> + The class driver creates a character device for each registered PTP
> + clock. User space programs may control the clock using standardized
> + ioctls. A program may query, enable, configure, and disable the
> + ancillary clock features. User space can receive time stamped
> + events via blocking read() and poll(). One shot and periodic
> + signals may be configured via an ioctl API with semantics similar
> + to the POSIX timer_settime() system call.
As I mentioned earlier, I'm not a huge fan of the char device
interface for abstracted PTP clocks.
If it was just the direct hardware access, similar to RTC, which user
apps then use as a timesource, I'd not have much of a problem. But as
I mentioned in an earlier private mail, the abstraction level concerns
me.
1) The driver-like model exposes a char dev for each clock, which
allows for poorly-written userland applications to hit portability
issues (ie: /dev/hpet vs /dev/rtc). Granted this isn't a huge flaw,
but good APIs should be hard to get wrong.
2) As Arnd already mentioned, the chardev interface seems to duplicate
the clock_gettime/settime() and adjtimex() interfaces.
3) I'm not sure I see the benefit of being able to have multiple
frequency corrected time domains. In other words, what benefit would
you get from adjusting a PTP clock's frequency instead of just
adjusting the system's time freq? Having the PTP time as a reference
to correct the system time seems reasonable, but I'm not sure I see
why userland would want to adjust the PTP clock's freq.
thanks
-john
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH 1/5] ptp: Added a brand new class driver for ptp clocks.
2010-08-16 19:24 ` john stultz
@ 2010-08-16 19:38 ` john stultz
[not found] ` <AANLkTik_2MKMhOuDGOmu8Kzyq-ipLe+Bxrb3FaD+Tv4U-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
1 sibling, 0 replies; 56+ messages in thread
From: john stultz @ 2010-08-16 19:38 UTC (permalink / raw)
To: Richard Cochran
Cc: netdev, linux-kernel, linuxppc-dev, devicetree-discuss,
linux-arm-kernel, Krzysztof Halasa, Rodolfo Giometti
On Mon, Aug 16, 2010 at 12:24 PM, john stultz <johnstul@us.ibm.com> wrote:
> On Mon, Aug 16, 2010 at 4:17 AM, Richard Cochran
> A few comments below....
>
>> +** PTP user space API
>> +
>> + The class driver creates a character device for each registered PTP
>> + clock. User space programs may control the clock using standardized
>> + ioctls. A program may query, enable, configure, and disable the
>> + ancillary clock features. User space can receive time stamped
>> + events via blocking read() and poll(). One shot and periodic
>> + signals may be configured via an ioctl API with semantics similar
>> + to the POSIX timer_settime() system call.
>
> As I mentioned earlier, I'm not a huge fan of the char device
> interface for abstracted PTP clocks.
> If it was just the direct hardware access, similar to RTC, which user
> apps then use as a timesource, I'd not have much of a problem. But as
> I mentioned in an earlier private mail, the abstraction level concerns
> me.
[snip]
> 2) As Arnd already mentioned, the chardev interface seems to duplicate
> the clock_gettime/settime() and adjtimex() interfaces.
And maybe just to clarify, as I saw your response to Arnd, I'm not
suggesting using PTP clocks as clocksources for the internal
timekeeping core. Instead I'm trying to understand why PTP clocks need
the equivalent of the existing posix clocks/timer interface. Why would
only having a read-time interface not suffice?
thanks
-john
^ permalink raw reply [flat|nested] 56+ messages in thread
[parent not found: <AANLkTik_2MKMhOuDGOmu8Kzyq-ipLe+Bxrb3FaD+Tv4U-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>]
* Re: [PATCH 1/5] ptp: Added a brand new class driver for ptp clocks.
[not found] ` <AANLkTik_2MKMhOuDGOmu8Kzyq-ipLe+Bxrb3FaD+Tv4U-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
@ 2010-08-17 8:53 ` Richard Cochran
2010-08-18 0:22 ` john stultz
0 siblings, 1 reply; 56+ messages in thread
From: Richard Cochran @ 2010-08-17 8:53 UTC (permalink / raw)
To: john stultz
Cc: Rodolfo Giometti, netdev-u79uwXL29TY76Z2rM5mHXA,
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linuxppc-dev-uLR06cmDAlY/bJ5BZ2RsiQ,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
Krzysztof Halasa
On Mon, Aug 16, 2010 at 12:24:48PM -0700, john stultz wrote:
> 3) I'm not sure I see the benefit of being able to have multiple
> frequency corrected time domains. In other words, what benefit would
> you get from adjusting a PTP clock's frequency instead of just
> adjusting the system's time freq? Having the PTP time as a reference
> to correct the system time seems reasonable, but I'm not sure I see
> why userland would want to adjust the PTP clock's freq.
For PTP enabled hardware, the timestamp on the network packet comes
from from the PTP clock, not from the system time.
Of course, you can always just leave the PTP clock alone, figure the
needed correction, and apply it whenever needed. But this has some
disadvantages. First of all, the (one and only) open source PTPd does
not do it that way. Also, only one program (the PTPd or equivalent)
will know the correct time. Other programs will not be able to ask the
operating system for time services. Instead, they would need to use
IPC to the PTPd.
The PTP protocol (and some PTP hardware) offers a "one step" method,
where the timestamps are inserted by the hardware on the fly. Here you
really do need the PTP clock to be correctly adjusted.
All of the PTP hardware that I am familiar with provides a clock
adjustment method, so it simpler and cleaner just to use this facility
to tune the PTP clock.
Richard
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH 1/5] ptp: Added a brand new class driver for ptp clocks.
2010-08-17 8:53 ` Richard Cochran
@ 2010-08-18 0:22 ` john stultz
2010-08-18 7:19 ` Richard Cochran
0 siblings, 1 reply; 56+ messages in thread
From: john stultz @ 2010-08-18 0:22 UTC (permalink / raw)
To: Richard Cochran
Cc: netdev, linux-kernel, linuxppc-dev, devicetree-discuss,
linux-arm-kernel, Krzysztof Halasa, Rodolfo Giometti,
Arnd Bergmann
On Tue, 2010-08-17 at 10:53 +0200, Richard Cochran wrote:
> On Mon, Aug 16, 2010 at 12:24:48PM -0700, john stultz wrote:
> > 3) I'm not sure I see the benefit of being able to have multiple
> > frequency corrected time domains. In other words, what benefit would
> > you get from adjusting a PTP clock's frequency instead of just
> > adjusting the system's time freq? Having the PTP time as a reference
> > to correct the system time seems reasonable, but I'm not sure I see
> > why userland would want to adjust the PTP clock's freq.
>
> For PTP enabled hardware, the timestamp on the network packet comes
> from from the PTP clock, not from the system time.
>
> Of course, you can always just leave the PTP clock alone, figure the
> needed correction, and apply it whenever needed. But this has some
> disadvantages. First of all, the (one and only) open source PTPd does
> not do it that way. Also, only one program (the PTPd or equivalent)
> will know the correct time. Other programs will not be able to ask the
> operating system for time services. Instead, they would need to use
> IPC to the PTPd.
Why would system time not be adjusted to the PTP time?
This is my main concern, that we're presenting a fractured API to
userland. Suddenly there isn't just system time, but ptp time as well,
and possibly multiple different ptp times.
Forgive me, as I suspect I'm missing something key here.
> The PTP protocol (and some PTP hardware) offers a "one step" method,
> where the timestamps are inserted by the hardware on the fly. Here you
> really do need the PTP clock to be correctly adjusted.
>
> All of the PTP hardware that I am familiar with provides a clock
> adjustment method, so it simpler and cleaner just to use this facility
> to tune the PTP clock.
Hmm. So trying to recap here to see if I'm understanding properly:
The PTP clock is a bit of hardware (usually on the NIC) that can put
timestamps on packets (both incoming or outgoing?).
The need to offset/freq correct the PTP clock is important so that the
timestamps (incoming and outgoing) are correct, which makes future
offset calculations simpler.
Hmm. So I'm actually starting to come around to the chardev interface.
In a way, it seems it has some similarities to how the RTC device is
used in NTPd. Granted, NTPd doesn't correct the RTC (the kernel does
when we're synced, but its not a perfect parallel), but it can be used
as an input the steer the clock.
So while to me, it think it would be more ideal (or maybe just less
different) to have a read-only interface (like the RTC), leaving PTPd to
manage offset calculations and use that to steer the system time. I can
acknowledge the need to have some way to correct the freq so the packet
timestamps are corrected.
I still feel a little concerned over the timer/alarm related interfaces.
Could you explain why the alarm interface is necessary?
So really I think my initial negative gut reaction to this was mostly
out of the fact that you introduced a char dev that provides almost 100%
coverage of the posix-time interface. That is duplication we definitely
don't want.
Also I think the documentation I've read about PTP (likely just due to
the engineering focus) has an odd inverted sense of priority, focusing
on keeping obscure hardware clocks on NIC cards in sync, rather then the
the more tangible feature of keeping the system time in sync.
This could be comically interpreted as trying to create a shadow-time on
the system that is the "real time" and "yea, maybe we'll let the system
know what time it is, but user-apps who want to know the score can send
a magic ioctl to /dev/something and get the real deal". ;) I'm sure
that's not the case, but I'd like to keep any confusion in userland
about which time is the best time to a minimum (ie: use the system
time).
thanks
-john
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH 1/5] ptp: Added a brand new class driver for ptp clocks.
2010-08-18 0:22 ` john stultz
@ 2010-08-18 7:19 ` Richard Cochran
2010-08-19 0:12 ` john stultz
0 siblings, 1 reply; 56+ messages in thread
From: Richard Cochran @ 2010-08-18 7:19 UTC (permalink / raw)
To: john stultz
Cc: netdev, linux-kernel, linuxppc-dev, devicetree-discuss,
linux-arm-kernel, Krzysztof Halasa, Rodolfo Giometti,
Arnd Bergmann
On Tue, Aug 17, 2010 at 05:22:43PM -0700, john stultz wrote:
> Why would system time not be adjusted to the PTP time?
>
> This is my main concern, that we're presenting a fractured API to
> userland. Suddenly there isn't just system time, but ptp time as well,
> and possibly multiple different ptp times.
John, it is a good thing to make thoughts about the big picture with
PTP clocks and the system time, like you are doing. However, the
situation is not as troubled as you think. Let me try to explain.
> The PTP clock is a bit of hardware (usually on the NIC) that can put
> timestamps on packets (both incoming or outgoing?).
Not only on the NIC. There are bunch of new products doing the
timestamping in the PHY or in a switch fabric attached to the host
like a PHY. The synchronization that one can achieve with PHY
timestamps is better that that with MAC timestamping.
> So while to me, it think it would be more ideal (or maybe just less
> different) to have a read-only interface (like the RTC), leaving PTPd to
> manage offset calculations and use that to steer the system time. I can
> acknowledge the need to have some way to correct the freq so the packet
> timestamps are corrected.
The PTPd need not change the system time at all for PTP clock to be
useful. (see below)
> I still feel a little concerned over the timer/alarm related interfaces.
> Could you explain why the alarm interface is necessary?
The timer/alarm stuff is "ancillary" and is not at all necessary. It
is just a "nice to have." I will happily remove it, if it is too
troubling for people.
> So really I think my initial negative gut reaction to this was mostly
> out of the fact that you introduced a char dev that provides almost 100%
> coverage of the posix-time interface. That is duplication we definitely
> don't want.
The reason why I modelled the char device on the posix interface was
to make the API more familiar to application programmers. After the
recent discussion (and having reviewed the posix clock implementation
in Linux), I now think it would be even better to simply offer a new
posic clock ID for PTP.
I was emulating the posix interface. Instead I should use it directly.
> Also I think the documentation I've read about PTP (likely just due to
> the engineering focus) has an odd inverted sense of priority, focusing
> on keeping obscure hardware clocks on NIC cards in sync, rather then the
> the more tangible feature of keeping the system time in sync.
>
> This could be comically interpreted as trying to create a shadow-time on
> the system that is the "real time" and "yea, maybe we'll let the system
> know what time it is, but user-apps who want to know the score can send
> a magic ioctl to /dev/something and get the real deal". ;) I'm sure
> that's not the case, but I'd like to keep any confusion in userland
> about which time is the best time to a minimum (ie: use the system
> time).
You are right. As John Eidson's excellent book points out, modern
computers and operating systems provide surprisingly little support
for programming based on absolute time. It is not PTP's fault. PTP is
actually a step in the right direction, but it doesn't yet really fit
in to the present computing world.
Okay, here is the Big Picture.
1. Use Case: SW timestamping
PTP with software timestamping (ie without special hardware) can
acheive synchronization within a few dozen microseconds, after
about twenty minutes. This is sufficient for very many people. The
new API (whether char device or syscall) fully and simply supports
this use case. When the PTPd adjusts the PTP clock, it is actually
adjusting the system time, just like NTPd.
2. Use Case: HW timestamping for industrial control
PTP with hardware timestamping can acheive synchronization within
100 nanoseconds after one minute. If you want to do something with
your wonderfully synchronization PTP clock, it must have some kind
of special hardware, like timestamping external signals or
generating one-shot or periodic outputs. The new API (whether char
device or syscall) supports this use case via the ancillary
commands.
In this case, the end user has an application that interfaces with
the outside world via the PTP clock API. Such a specialized
application (for example, motor control) uses only the PTP API,
since it knows that the standard posix API cannot help. It is
irrelevant that the system time is not synchronized, in this case.
The PTP clock hardware may or may not provide a hardware interface
(interrupt) to the main CPU. In this case, it does not matter. The
PTP clock is useful all by itself.
3. Use Case: HW timestamping with PPS to host
This case is the same as case 2, with the exception that the PTP
clock can interrupt the main CPU. The PTP clock driver advertises
the "PPS" capability. When enabled, the PTP layer delivers events
via the existing Linux PPS subsystem. Programs like NTPd can use
these events to regulate the system time.
This means that the system clock and the PTP clock will be at least
as well synchronized as when using a traditionial radio clock, GPS,
or IRIG-B method. In my opinion, this will be good enough for any
practical purpose. For example, let's say you want to run a
periodic task synchronized to the absolute wall clock time. Your
scheduling latency will be a dozen microseconds or so. Your PPS
synchronized system clock should be close enough to the PTP clock
to support this.
The API that I have suggested, whether offered as a char device or as
syscalls, supports all of the use cases using a single API.
Richard
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH 1/5] ptp: Added a brand new class driver for ptp clocks.
2010-08-18 7:19 ` Richard Cochran
@ 2010-08-19 0:12 ` john stultz
[not found] ` <1282176776.2865.100.camel-bi+AKbBUZKY6gyzm1THtWbp2dZbC/Bob@public.gmane.org>
0 siblings, 1 reply; 56+ messages in thread
From: john stultz @ 2010-08-19 0:12 UTC (permalink / raw)
To: Richard Cochran
Cc: netdev, linux-kernel, linuxppc-dev, devicetree-discuss,
linux-arm-kernel, Krzysztof Halasa, Rodolfo Giometti,
Arnd Bergmann
On Wed, 2010-08-18 at 09:19 +0200, Richard Cochran wrote:
> On Tue, Aug 17, 2010 at 05:22:43PM -0700, john stultz wrote:
>>
> > So while to me, it think it would be more ideal (or maybe just less
> > different) to have a read-only interface (like the RTC), leaving PTPd to
> > manage offset calculations and use that to steer the system time. I can
> > acknowledge the need to have some way to correct the freq so the packet
> > timestamps are corrected.
>
> The PTPd need not change the system time at all for PTP clock to be
> useful. (see below)
Right, obviously an ok-solution is often more useful then no-solution.
But that doesn't mean we shouldn't shoot for a good or even
great-solution. :)
> > I still feel a little concerned over the timer/alarm related interfaces.
> > Could you explain why the alarm interface is necessary?
>
> The timer/alarm stuff is "ancillary" and is not at all necessary. It
> is just a "nice to have." I will happily remove it, if it is too
> troubling for people.
If there's a compelling argument for it, I'm interested to hear. But
again, it seems like just
yet-another-way-to-get-alarm/timer-functionality, so before we add an
extra API (or widen an existing API) I'd like to understand the need.
But maybe it might simplify the discussion to pull it for now, but
keeping it in mind to possibly include later as an extension?
> > So really I think my initial negative gut reaction to this was mostly
> > out of the fact that you introduced a char dev that provides almost 100%
> > coverage of the posix-time interface. That is duplication we definitely
> > don't want.
>
> The reason why I modelled the char device on the posix interface was
> to make the API more familiar to application programmers. After the
> recent discussion (and having reviewed the posix clock implementation
> in Linux), I now think it would be even better to simply offer a new
> posic clock ID for PTP.
>
> I was emulating the posix interface. Instead I should use it directly.
I'm definitely interested to see what you come up with here. I'm still
hesitant with adding a PTP clock_id, but extending the posix-clocks
interface in this way isn't unprecedented (see: CLOCK_SGI_CYCLE) I just
would like to make sure we don't end up with a clock_id namespace
littered with oddball clocks that were not well abstracted (see:
CLOCK_SGI_CYCLE :).
For instance: imagine if instead of keeping the clocksource abstraction
internal to the timekeeping core, we exposed each clocksource to
userland via a clock_id. Every arch would have different ids, and each
arch might have multiple ids. Programming against that would be a huge
pain.
So in thinking about this, try to focus on what the new clock_id
provides that the other existing clockids do not? Are they at comparable
levels of abstraction? 15 years from now, are folks likely to still be
using it? Will it be maintainable? etc...
> > Also I think the documentation I've read about PTP (likely just due to
> > the engineering focus) has an odd inverted sense of priority, focusing
> > on keeping obscure hardware clocks on NIC cards in sync, rather then the
> > the more tangible feature of keeping the system time in sync.
> >
> > This could be comically interpreted as trying to create a shadow-time on
> > the system that is the "real time" and "yea, maybe we'll let the system
> > know what time it is, but user-apps who want to know the score can send
> > a magic ioctl to /dev/something and get the real deal". ;) I'm sure
> > that's not the case, but I'd like to keep any confusion in userland
> > about which time is the best time to a minimum (ie: use the system
> > time).
>
> You are right. As John Eidson's excellent book points out, modern
> computers and operating systems provide surprisingly little support
> for programming based on absolute time. It is not PTP's fault. PTP is
> actually a step in the right direction, but it doesn't yet really fit
> in to the present computing world.
You'll have to forgive me, as I haven't had the time to check out that
book. What exactly do you mean by operating systems provide little
support for programming based on absolute time?
> Okay, here is the Big Picture.
>
> 1. Use Case: SW timestamping
>
> PTP with software timestamping (ie without special hardware) can
> acheive synchronization within a few dozen microseconds, after
> about twenty minutes. This is sufficient for very many people. The
> new API (whether char device or syscall) fully and simply supports
> this use case. When the PTPd adjusts the PTP clock, it is actually
> adjusting the system time, just like NTPd.
Again this illustrates the inversion of focus: system time is merely one
of many possible PTP clocks in the larger PTP framework.
The way I tend to see it: PTP is just one of the many ways to sync
system time.
> 2. Use Case: HW timestamping for industrial control
>
> PTP with hardware timestamping can acheive synchronization within
> 100 nanoseconds after one minute. If you want to do something with
> your wonderfully synchronization PTP clock, it must have some kind
> of special hardware, like timestamping external signals or
> generating one-shot or periodic outputs. The new API (whether char
> device or syscall) supports this use case via the ancillary
> commands.
>
> In this case, the end user has an application that interfaces with
> the outside world via the PTP clock API. Such a specialized
> application (for example, motor control) uses only the PTP API,
> since it knows that the standard posix API cannot help. It is
> irrelevant that the system time is not synchronized, in this case.
>
> The PTP clock hardware may or may not provide a hardware interface
> (interrupt) to the main CPU. In this case, it does not matter. The
> PTP clock is useful all by itself.
These specialized applications are part of what concerns me the most.
For example, I can see some parallels between things like audio
processing, where you have a buffer consumed by the card at a certain
rate. Now, the card has its own crystal it uses to time its consumption,
so it has its own time domain, and could drift from system time. Thus
you want to trigger buffer-refill interrupts off of the audio card's
clock, not the system time which might run the risk of being late.
But again, we don't expose the audio hardware clock to userland in the
same way we expose system time.
So, instead of a chardev or a posix clock id, would it make more sense
for the PTP adjustment interface to be more closely to the
nic/phy/whatever interface? Much like how the audio apis do it?
Again, my knowledge in the networking stack is pretty limited. But it
would seem that having an interface that does something to the effect of
"adjust the timestamp clock on the hardware that generated it from this
packet by Xppb" would feel like the right level of abstraction. Its
closely related to SO_TIMESTAMP, option right? Would something like
using the setsockopt/getsockopt interface with
SO_TIMESTAMP_ADJUST/OFFSET/SET/etc be reasonable?
> 3. Use Case: HW timestamping with PPS to host
>
> This case is the same as case 2, with the exception that the PTP
> clock can interrupt the main CPU. The PTP clock driver advertises
> the "PPS" capability. When enabled, the PTP layer delivers events
> via the existing Linux PPS subsystem. Programs like NTPd can use
> these events to regulate the system time.
>
> This means that the system clock and the PTP clock will be at least
> as well synchronized as when using a traditionial radio clock, GPS,
> or IRIG-B method. In my opinion, this will be good enough for any
> practical purpose. For example, let's say you want to run a
> periodic task synchronized to the absolute wall clock time. Your
> scheduling latency will be a dozen microseconds or so. Your PPS
> synchronized system clock should be close enough to the PTP clock
> to support this.
And yes, this seems perfectly reasonable feature to add. Its not
controversial to me, because its likely to work within the existing
interfaces and won't expose anything new to userland.
Again, sorry to be such a pain about all of this. I know its frustrating
to bring your hard work to lkml and then get a lot of non-specific
push-back to "do it differently". You're earlier comments about being
wary of adding syscalls because it requires lots of review and debate
were spot on, but *any* user-visible API really requires the same level
of care (which, of course, they don't always get). So please don't take
my comments personally. Keep pushing and I will either come around or
maybe we can find a better middle-ground.
thanks
-john
^ permalink raw reply [flat|nested] 56+ messages in thread
* [PATCH 2/5] ptp: Added a clock that uses the Linux system time.
2010-08-16 11:17 [PATCH v5 0/5] ptp: IEEE 1588 clock support Richard Cochran
2010-08-16 11:17 ` [PATCH 1/5] ptp: Added a brand new class driver for ptp clocks Richard Cochran
@ 2010-08-16 11:18 ` Richard Cochran
2010-08-16 11:18 ` [PATCH 3/5] ptp: Added a clock that uses the eTSEC found on the MPC85xx Richard Cochran
` (2 subsequent siblings)
4 siblings, 0 replies; 56+ messages in thread
From: Richard Cochran @ 2010-08-16 11:18 UTC (permalink / raw)
To: netdev
Cc: linuxppc-dev, devicetree-discuss, linux-kernel, Rodolfo Giometti,
linux-arm-kernel, Krzysztof Halasa
This PTP clock simply uses the NTP time adjustment system calls. The
driver can be used in systems that lack a special hardware PTP clock.
Signed-off-by: Richard Cochran <richard.cochran@omicron.at>
---
drivers/ptp/Kconfig | 12 ++++
drivers/ptp/Makefile | 1 +
drivers/ptp/ptp_linux.c | 136 +++++++++++++++++++++++++++++++++++++++++++++++
kernel/time/ntp.c | 2 +
4 files changed, 151 insertions(+), 0 deletions(-)
create mode 100644 drivers/ptp/ptp_linux.c
diff --git a/drivers/ptp/Kconfig b/drivers/ptp/Kconfig
index cd7becb..3aa517a 100644
--- a/drivers/ptp/Kconfig
+++ b/drivers/ptp/Kconfig
@@ -24,4 +24,16 @@ config PTP_1588_CLOCK
To compile this driver as a module, choose M here: the module
will be called ptp_clock.
+config PTP_1588_CLOCK_LINUX
+ tristate "Linux system timer as PTP clock"
+ depends on PTP_1588_CLOCK
+ help
+ This driver adds support for using the standard Linux time
+ source as a PTP clock. This clock is only useful if your PTP
+ programs are using software time stamps for the PTP Ethernet
+ packets.
+
+ To compile this driver as a module, choose M here: the module
+ will be called ptp_linux.
+
endmenu
diff --git a/drivers/ptp/Makefile b/drivers/ptp/Makefile
index b86695c..1651d52 100644
--- a/drivers/ptp/Makefile
+++ b/drivers/ptp/Makefile
@@ -3,3 +3,4 @@
#
obj-$(CONFIG_PTP_1588_CLOCK) += ptp_clock.o
+obj-$(CONFIG_PTP_1588_CLOCK_LINUX) += ptp_linux.o
diff --git a/drivers/ptp/ptp_linux.c b/drivers/ptp/ptp_linux.c
new file mode 100644
index 0000000..f93ae0c
--- /dev/null
+++ b/drivers/ptp/ptp_linux.c
@@ -0,0 +1,136 @@
+/*
+ * PTP 1588 clock using the Linux system clock
+ *
+ * Copyright (C) 2010 OMICRON electronics GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/hrtimer.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/timex.h>
+
+#include <linux/ptp_clock_kernel.h>
+
+static struct ptp_clock *linux_clock;
+
+DEFINE_SPINLOCK(adjtime_lock);
+
+static int ptp_linux_adjfreq(void *priv, s32 delta)
+{
+ struct timex txc;
+ s64 tmp = delta;
+ int err;
+ txc.freq = div_s64(tmp<<16, 1000);
+ txc.modes = ADJ_FREQUENCY;
+ err = do_adjtimex(&txc);
+ return err < 0 ? err : 0;
+}
+
+static int ptp_linux_adjtime(void *priv, struct timespec *ts)
+{
+ s64 delta;
+ ktime_t now;
+ struct timespec t2;
+ unsigned long flags;
+ int err;
+
+ delta = 1000000000LL * ts->tv_sec + ts->tv_nsec;
+
+ spin_lock_irqsave(&adjtime_lock, flags);
+
+ now = ktime_get_real();
+
+ now = delta < 0 ? ktime_sub_ns(now, -delta) : ktime_add_ns(now, delta);
+
+ t2 = ktime_to_timespec(now);
+
+ err = do_settimeofday(&t2);
+
+ spin_unlock_irqrestore(&adjtime_lock, flags);
+
+ return err;
+}
+
+static int ptp_linux_gettime(void *priv, struct timespec *ts)
+{
+ getnstimeofday(ts);
+ return 0;
+}
+
+static int ptp_linux_settime(void *priv, struct timespec *ts)
+{
+ return do_settimeofday(ts);
+}
+
+static int ptp_linux_gettimer(void *priv, int index, struct itimerspec *ts)
+{
+ /* We do not offer any ancillary features at all. */
+ return -EOPNOTSUPP;
+}
+
+static int ptp_linux_settimer(void *p, int i, int abs, struct itimerspec *ts)
+{
+ /* We do not offer any ancillary features at all. */
+ return -EOPNOTSUPP;
+}
+
+static int ptp_linux_enable(void *priv, struct ptp_clock_request *rq, int on)
+{
+ /* We do not offer any ancillary features at all. */
+ return -EOPNOTSUPP;
+}
+
+static struct ptp_clock_info ptp_linux_caps = {
+ .owner = THIS_MODULE,
+ .name = "Linux timer",
+ .max_adj = 512000,
+ .n_alarm = 0,
+ .n_ext_ts = 0,
+ .n_per_out = 0,
+ .pps = 0,
+ .priv = NULL,
+ .adjfreq = ptp_linux_adjfreq,
+ .adjtime = ptp_linux_adjtime,
+ .gettime = ptp_linux_gettime,
+ .settime = ptp_linux_settime,
+ .gettimer = ptp_linux_gettimer,
+ .settimer = ptp_linux_settimer,
+ .enable = ptp_linux_enable,
+};
+
+/* module operations */
+
+static void __exit ptp_linux_exit(void)
+{
+ ptp_clock_unregister(linux_clock);
+}
+
+static int __init ptp_linux_init(void)
+{
+ linux_clock = ptp_clock_register(&ptp_linux_caps);
+
+ return IS_ERR(linux_clock) ? PTR_ERR(linux_clock) : 0;
+}
+
+module_init(ptp_linux_init);
+module_exit(ptp_linux_exit);
+
+MODULE_AUTHOR("Richard Cochran <richard.cochran@omicron.at>");
+MODULE_DESCRIPTION("PTP clock using the Linux system timer");
+MODULE_LICENSE("GPL");
diff --git a/kernel/time/ntp.c b/kernel/time/ntp.c
index c631168..eba3bcf 100644
--- a/kernel/time/ntp.c
+++ b/kernel/time/ntp.c
@@ -14,6 +14,7 @@
#include <linux/timex.h>
#include <linux/time.h>
#include <linux/mm.h>
+#include <linux/module.h>
/*
* NTP timekeeping variables:
@@ -535,6 +536,7 @@ int do_adjtimex(struct timex *txc)
return result;
}
+EXPORT_SYMBOL(do_adjtimex);
static int __init ntp_tick_adj_setup(char *str)
{
--
1.7.0.4
^ permalink raw reply related [flat|nested] 56+ messages in thread
* [PATCH 3/5] ptp: Added a clock that uses the eTSEC found on the MPC85xx.
2010-08-16 11:17 [PATCH v5 0/5] ptp: IEEE 1588 clock support Richard Cochran
2010-08-16 11:17 ` [PATCH 1/5] ptp: Added a brand new class driver for ptp clocks Richard Cochran
2010-08-16 11:18 ` [PATCH 2/5] ptp: Added a clock that uses the Linux system time Richard Cochran
@ 2010-08-16 11:18 ` Richard Cochran
2010-08-16 11:18 ` [PATCH 4/5] ptp: Added a clock driver for the IXP46x Richard Cochran
2010-08-16 11:19 ` [PATCH 5/5] ptp: Added a clock driver for the National Semiconductor PHYTER Richard Cochran
4 siblings, 0 replies; 56+ messages in thread
From: Richard Cochran @ 2010-08-16 11:18 UTC (permalink / raw)
To: netdev
Cc: linuxppc-dev, devicetree-discuss, linux-kernel, Rodolfo Giometti,
linux-arm-kernel, Krzysztof Halasa
The eTSEC includes a PTP clock with quite a few features. This patch adds
support for the basic clock adjustment functions, plus two external time
stamps and one alarm.
Signed-off-by: Richard Cochran <richard.cochran@omicron.at>
---
Documentation/powerpc/dts-bindings/fsl/tsec.txt | 57 +++
arch/powerpc/boot/dts/mpc8313erdb.dts | 14 +
arch/powerpc/boot/dts/mpc8572ds.dts | 14 +
arch/powerpc/boot/dts/p2020ds.dts | 14 +
arch/powerpc/boot/dts/p2020rdb.dts | 14 +
drivers/net/Makefile | 1 +
drivers/net/gianfar_ptp.c | 527 +++++++++++++++++++++++
drivers/net/gianfar_ptp_reg.h | 113 +++++
drivers/ptp/Kconfig | 13 +
9 files changed, 767 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/gianfar_ptp.c
create mode 100644 drivers/net/gianfar_ptp_reg.h
diff --git a/Documentation/powerpc/dts-bindings/fsl/tsec.txt b/Documentation/powerpc/dts-bindings/fsl/tsec.txt
index edb7ae1..f6edbb8 100644
--- a/Documentation/powerpc/dts-bindings/fsl/tsec.txt
+++ b/Documentation/powerpc/dts-bindings/fsl/tsec.txt
@@ -74,3 +74,60 @@ Example:
interrupt-parent = <&mpic>;
phy-handle = <&phy0>
};
+
+* Gianfar PTP clock nodes
+
+General Properties:
+
+ - compatible Should be "fsl,etsec-ptp"
+ - reg Offset and length of the register set for the device
+ - interrupts There should be at least two interrupts. Some devices
+ have as many as four PTP related interrupts.
+
+Clock Properties:
+
+ - tclk-period Timer reference clock period in nanoseconds.
+ - tmr-prsc Prescaler, divides the output clock.
+ - tmr-add Frequency compensation value.
+ - cksel 0= external clock, 1= eTSEC system clock, 3= RTC clock input.
+ Currently the driver only supports choice "1".
+ - tmr-fiper1 Fixed interval period pulse generator.
+ - tmr-fiper2 Fixed interval period pulse generator.
+ - max-adj Maximum frequency adjustment in parts per billion.
+
+ These properties set the operational parameters for the PTP
+ clock. You must choose these carefully for the clock to work right.
+ Here is how to figure good values:
+
+ TimerOsc = system clock MHz
+ tclk_period = desired clock period nanoseconds
+ NominalFreq = 1000 / tclk_period MHz
+ FreqDivRatio = TimerOsc / NominalFreq (must be greater that 1.0)
+ tmr_add = ceil(2^32 / FreqDivRatio)
+ OutputClock = NominalFreq / tmr_prsc MHz
+ PulseWidth = 1 / OutputClock microseconds
+ FiperFreq1 = desired frequency in Hz
+ FiperDiv1 = 1000000 * OutputClock / FiperFreq1
+ tmr_fiper1 = tmr_prsc * tclk_period * FiperDiv1 - tclk_period
+ max_adj = 1000000000 * (FreqDivRatio - 1.0) - 1
+
+ The calculation for tmr_fiper2 is the same as for tmr_fiper1. The
+ driver expects that tmr_fiper1 will be correctly set to produce a 1
+ Pulse Per Second (PPS) signal, since this will be offered to the PPS
+ subsystem to synchronize the Linux clock.
+
+Example:
+
+ ptp_clock@24E00 {
+ compatible = "fsl,etsec-ptp";
+ reg = <0x24E00 0xB0>;
+ interrupts = <12 0x8 13 0x8>;
+ interrupt-parent = < &ipic >;
+ tclk-period = <10>;
+ tmr-prsc = <100>;
+ tmr-add = <0x999999A4>;
+ cksel = <0x1>;
+ tmr-fiper1 = <0x3B9AC9F6>;
+ tmr-fiper2 = <0x00018696>;
+ max-adj = <659999998>;
+ };
diff --git a/arch/powerpc/boot/dts/mpc8313erdb.dts b/arch/powerpc/boot/dts/mpc8313erdb.dts
index 183f2aa..85a7eaa 100644
--- a/arch/powerpc/boot/dts/mpc8313erdb.dts
+++ b/arch/powerpc/boot/dts/mpc8313erdb.dts
@@ -208,6 +208,20 @@
sleep = <&pmc 0x00300000>;
};
+ ptp_clock@24E00 {
+ compatible = "fsl,etsec-ptp";
+ reg = <0x24E00 0xB0>;
+ interrupts = <12 0x8 13 0x8>;
+ interrupt-parent = < &ipic >;
+ tclk-period = <10>;
+ tmr-prsc = <100>;
+ tmr-add = <0x999999A4>;
+ cksel = <0x1>;
+ tmr-fiper1 = <0x3B9AC9F6>;
+ tmr-fiper2 = <0x00018696>;
+ max-adj = <659999998>;
+ };
+
enet0: ethernet@24000 {
#address-cells = <1>;
#size-cells = <1>;
diff --git a/arch/powerpc/boot/dts/mpc8572ds.dts b/arch/powerpc/boot/dts/mpc8572ds.dts
index cafc128..74208cd 100644
--- a/arch/powerpc/boot/dts/mpc8572ds.dts
+++ b/arch/powerpc/boot/dts/mpc8572ds.dts
@@ -324,6 +324,20 @@
};
};
+ ptp_clock@24E00 {
+ compatible = "fsl,etsec-ptp";
+ reg = <0x24E00 0xB0>;
+ interrupts = <68 2 69 2 70 2 71 2>;
+ interrupt-parent = < &mpic >;
+ tclk-period = <5>;
+ tmr-prsc = <200>;
+ tmr-add = <0xAAAAAAAB>;
+ cksel = <1>;
+ tmr-fiper1 = <0x3B9AC9FB>;
+ tmr-fiper2 = <0x3B9AC9FB>;
+ max-adj = <499999999>;
+ };
+
enet0: ethernet@24000 {
#address-cells = <1>;
#size-cells = <1>;
diff --git a/arch/powerpc/boot/dts/p2020ds.dts b/arch/powerpc/boot/dts/p2020ds.dts
index 1101914..39d73bb 100644
--- a/arch/powerpc/boot/dts/p2020ds.dts
+++ b/arch/powerpc/boot/dts/p2020ds.dts
@@ -336,6 +336,20 @@
phy_type = "ulpi";
};
+ ptp_clock@24E00 {
+ compatible = "fsl,etsec-ptp";
+ reg = <0x24E00 0xB0>;
+ interrupts = <68 2 69 2 70 2>;
+ interrupt-parent = < &mpic >;
+ tclk-period = <5>;
+ tmr-prsc = <200>;
+ tmr-add = <0xCCCCCCCD>;
+ cksel = <1>;
+ tmr-fiper1 = <0x3B9AC9FB>;
+ tmr-fiper2 = <0x0001869B>;
+ max-adj = <249999999>;
+ };
+
enet0: ethernet@24000 {
#address-cells = <1>;
#size-cells = <1>;
diff --git a/arch/powerpc/boot/dts/p2020rdb.dts b/arch/powerpc/boot/dts/p2020rdb.dts
index da4cb0d..5498fb9 100644
--- a/arch/powerpc/boot/dts/p2020rdb.dts
+++ b/arch/powerpc/boot/dts/p2020rdb.dts
@@ -396,6 +396,20 @@
phy_type = "ulpi";
};
+ ptp_clock@24E00 {
+ compatible = "fsl,etsec-ptp";
+ reg = <0x24E00 0xB0>;
+ interrupts = <68 2 69 2 70 2>;
+ interrupt-parent = < &mpic >;
+ tclk-period = <5>;
+ tmr-prsc = <200>;
+ tmr-add = <0xCCCCCCCD>;
+ cksel = <1>;
+ tmr-fiper1 = <0x3B9AC9FB>;
+ tmr-fiper2 = <0x0001869B>;
+ max-adj = <249999999>;
+ };
+
enet0: ethernet@24000 {
#address-cells = <1>;
#size-cells = <1>;
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index 56e8c27..ea164ba 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -29,6 +29,7 @@ obj-$(CONFIG_ATL2) += atlx/
obj-$(CONFIG_ATL1E) += atl1e/
obj-$(CONFIG_ATL1C) += atl1c/
obj-$(CONFIG_GIANFAR) += gianfar_driver.o
+obj-$(CONFIG_PTP_1588_CLOCK_GIANFAR) += gianfar_ptp.o
obj-$(CONFIG_TEHUTI) += tehuti.o
obj-$(CONFIG_ENIC) += enic/
obj-$(CONFIG_JME) += jme.o
diff --git a/drivers/net/gianfar_ptp.c b/drivers/net/gianfar_ptp.c
new file mode 100644
index 0000000..637c8a7
--- /dev/null
+++ b/drivers/net/gianfar_ptp.c
@@ -0,0 +1,527 @@
+/*
+ * PTP 1588 clock using the eTSEC
+ *
+ * Copyright (C) 2010 OMICRON electronics GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include <linux/device.h>
+#include <linux/hrtimer.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/timex.h>
+#include <linux/io.h>
+
+#include <linux/ptp_clock_kernel.h>
+
+#include "gianfar_ptp_reg.h"
+#include "gianfar.h"
+
+#define DRIVER "gianfar_ptp"
+#define N_ALARM 1 /* first alarm is used internally to reset fipers */
+#define N_EXT_TS 2
+#define REG_SIZE sizeof(struct gianfar_ptp_registers)
+
+struct etsects {
+ struct gianfar_ptp_registers *regs;
+ struct ptp_clock *clock;
+ int irq;
+ u64 alarm_interval; /* for periodic alarm */
+ u64 alarm_value;
+ u32 tclk_period; /* nanoseconds */
+ u32 tmr_prsc;
+ u32 tmr_add;
+ u32 cksel;
+ u32 tmr_fiper1;
+ u32 tmr_fiper2;
+};
+
+/* Private globals */
+static struct etsects the_clock;
+DEFINE_SPINLOCK(register_lock);
+
+/*
+ * Register access functions
+ */
+
+static u64 tmr_cnt_read(struct etsects *etsects)
+{
+ u64 ns;
+ u32 lo, hi;
+
+ lo = gfar_read(&etsects->regs->tmr_cnt_l);
+ hi = gfar_read(&etsects->regs->tmr_cnt_h);
+ ns = ((u64) hi) << 32;
+ ns |= lo;
+ return ns;
+}
+
+static void tmr_cnt_write(struct etsects *etsects, u64 ns)
+{
+ u32 hi = ns >> 32;
+ u32 lo = ns & 0xffffffff;
+
+ gfar_write(&etsects->regs->tmr_cnt_l, lo);
+ gfar_write(&etsects->regs->tmr_cnt_h, hi);
+}
+
+static void set_alarm(struct etsects *etsects)
+{
+ u64 ns;
+ u32 lo, hi;
+
+ ns = tmr_cnt_read(etsects) + 1500000000ULL;
+ ns = div_u64(ns, 1000000000UL) * 1000000000ULL;
+ ns -= etsects->tclk_period;
+ hi = ns >> 32;
+ lo = ns & 0xffffffff;
+ gfar_write(&etsects->regs->tmr_alarm1_l, lo);
+ gfar_write(&etsects->regs->tmr_alarm1_h, hi);
+}
+
+static void set_fipers(struct etsects *etsects)
+{
+ u32 tmr_ctrl = gfar_read(&etsects->regs->tmr_ctrl);
+
+ gfar_write(&etsects->regs->tmr_ctrl, tmr_ctrl & (~TE));
+ gfar_write(&etsects->regs->tmr_prsc, etsects->tmr_prsc);
+ gfar_write(&etsects->regs->tmr_fiper1, etsects->tmr_fiper1);
+ gfar_write(&etsects->regs->tmr_fiper2, etsects->tmr_fiper2);
+ set_alarm(etsects);
+ gfar_write(&etsects->regs->tmr_ctrl, tmr_ctrl|TE);
+}
+
+/*
+ * Interrupt service routine
+ */
+
+static irqreturn_t isr(int irq, void *priv)
+{
+ struct etsects *etsects = priv;
+ struct ptp_clock_event event;
+ u64 ns;
+ u32 ack = 0, lo, hi, mask, val;
+
+ val = gfar_read(&etsects->regs->tmr_tevent);
+
+ if (val & ETS1) {
+ ack |= ETS1;
+ hi = gfar_read(&etsects->regs->tmr_etts1_h);
+ lo = gfar_read(&etsects->regs->tmr_etts1_l);
+ event.type = PTP_CLOCK_EXTTS;
+ event.index = 0;
+ event.timestamp = ((u64) hi) << 32;
+ event.timestamp |= lo;
+ ptp_clock_event(etsects->clock, &event);
+ }
+
+ if (val & ETS2) {
+ ack |= ETS2;
+ hi = gfar_read(&etsects->regs->tmr_etts2_h);
+ lo = gfar_read(&etsects->regs->tmr_etts2_l);
+ event.type = PTP_CLOCK_EXTTS;
+ event.index = 1;
+ event.timestamp = ((u64) hi) << 32;
+ event.timestamp |= lo;
+ ptp_clock_event(etsects->clock, &event);
+ }
+
+ if (val & ALM2) {
+ ack |= ALM2;
+ if (etsects->alarm_value) {
+ event.type = PTP_CLOCK_ALARM;
+ event.index = 0;
+ event.timestamp = etsects->alarm_value;
+ ptp_clock_event(etsects->clock, &event);
+ }
+ if (etsects->alarm_interval) {
+ ns = etsects->alarm_value + etsects->alarm_interval;
+ hi = ns >> 32;
+ lo = ns & 0xffffffff;
+ spin_lock(®ister_lock);
+ gfar_write(&etsects->regs->tmr_alarm2_l, lo);
+ gfar_write(&etsects->regs->tmr_alarm2_h, hi);
+ spin_unlock(®ister_lock);
+ etsects->alarm_value = ns;
+ } else {
+ gfar_write(&etsects->regs->tmr_tevent, ALM2);
+ spin_lock(®ister_lock);
+ mask = gfar_read(&etsects->regs->tmr_temask);
+ mask &= ~ALM2EN;
+ gfar_write(&etsects->regs->tmr_temask, mask);
+ spin_unlock(®ister_lock);
+ etsects->alarm_value = 0;
+ etsects->alarm_interval = 0;
+ }
+ }
+
+ if (val & PP1) {
+ ack |= PP1;
+ event.type = PTP_CLOCK_PPS;
+ ptp_clock_event(etsects->clock, &event);
+ }
+
+ if (ack) {
+ gfar_write(&etsects->regs->tmr_tevent, ack);
+ return IRQ_HANDLED;
+ } else
+ return IRQ_NONE;
+}
+
+/*
+ * PTP clock operations
+ */
+
+static int ptp_gianfar_adjfreq(void *priv, s32 ppb)
+{
+ u64 adj;
+ u32 diff, tmr_add;
+ int neg_adj = 0;
+ struct etsects *etsects = priv;
+
+ if (!ppb)
+ return 0;
+
+ if (ppb < 0) {
+ neg_adj = 1;
+ ppb = -ppb;
+ }
+ tmr_add = etsects->tmr_add;
+ adj = tmr_add;
+ adj *= ppb;
+ diff = div_u64(adj, 1000000000ULL);
+
+ tmr_add = neg_adj ? tmr_add - diff : tmr_add + diff;
+
+ gfar_write(&etsects->regs->tmr_add, tmr_add);
+
+ return 0;
+}
+
+static int ptp_gianfar_adjtime(void *priv, struct timespec *ts)
+{
+ s64 delta, now;
+ unsigned long flags;
+ struct etsects *etsects = priv;
+
+ delta = 1000000000LL * ts->tv_sec;
+ delta += ts->tv_nsec;
+
+ spin_lock_irqsave(®ister_lock, flags);
+
+ now = tmr_cnt_read(etsects);
+ now += delta;
+ tmr_cnt_write(etsects, now);
+
+ spin_unlock_irqrestore(®ister_lock, flags);
+
+ set_fipers(etsects);
+
+ return 0;
+}
+
+static int ptp_gianfar_gettime(void *priv, struct timespec *ts)
+{
+ u64 ns;
+ u32 remainder;
+ unsigned long flags;
+ struct etsects *etsects = priv;
+
+ spin_lock_irqsave(®ister_lock, flags);
+
+ ns = tmr_cnt_read(etsects);
+
+ spin_unlock_irqrestore(®ister_lock, flags);
+
+ ts->tv_sec = div_u64_rem(ns, 1000000000, &remainder);
+ ts->tv_nsec = remainder;
+ return 0;
+}
+
+static int ptp_gianfar_settime(void *priv, struct timespec *ts)
+{
+ u64 ns;
+ unsigned long flags;
+ struct etsects *etsects = priv;
+
+ ns = ts->tv_sec * 1000000000ULL;
+ ns += ts->tv_nsec;
+
+ spin_lock_irqsave(®ister_lock, flags);
+
+ tmr_cnt_write(etsects, ns);
+ set_fipers(etsects);
+
+ spin_unlock_irqrestore(®ister_lock, flags);
+
+ return 0;
+}
+
+static int ptp_gianfar_gettimer(void *priv, int index, struct itimerspec *ts)
+{
+ u64 now, ns;
+ u32 remainder;
+ unsigned long flags;
+ struct etsects *etsects = priv;
+
+ ns = etsects->alarm_interval;
+
+ ts->it_interval.tv_sec = div_u64_rem(ns, 1000000000, &remainder);
+ ts->it_interval.tv_nsec = remainder;
+
+ spin_lock_irqsave(®ister_lock, flags);
+ now = tmr_cnt_read(etsects);
+ spin_unlock_irqrestore(®ister_lock, flags);
+
+ ns = etsects->alarm_value - now;
+
+ ts->it_value.tv_sec = div_u64_rem(ns, 1000000000, &remainder);
+ ts->it_value.tv_nsec = remainder;
+
+ return 0;
+}
+
+static int ptp_gianfar_settimer(void *p, int i, int abs, struct itimerspec *ts)
+{
+ u64 ns;
+ u32 lo, hi, mask;
+ unsigned long flags;
+ struct etsects *etsects = p;
+
+ ns = ts->it_interval.tv_sec * 1000000000ULL;
+ ns += ts->it_interval.tv_nsec;
+
+ etsects->alarm_interval = ns;
+
+ ns = ts->it_value.tv_sec * 1000000000ULL;
+ ns += ts->it_value.tv_nsec;
+
+ if (!ns) {
+ /* Cancel the timer. */
+ etsects->alarm_value = 0;
+ etsects->alarm_interval = 0;
+ return 0;
+ }
+
+ if (!abs) {
+ spin_lock_irqsave(®ister_lock, flags);
+ ns += tmr_cnt_read(etsects);
+ spin_unlock_irqrestore(®ister_lock, flags);
+ }
+
+ etsects->alarm_value = ns;
+
+ hi = ns >> 32;
+ lo = ns & 0xffffffff;
+
+ spin_lock_irqsave(®ister_lock, flags);
+
+ gfar_write(&etsects->regs->tmr_alarm2_l, lo);
+ gfar_write(&etsects->regs->tmr_alarm2_h, hi);
+
+ mask = gfar_read(&etsects->regs->tmr_temask);
+ mask |= ALM2EN;
+ gfar_write(&etsects->regs->tmr_temask, mask);
+
+ spin_unlock_irqrestore(®ister_lock, flags);
+
+ return 0;
+}
+
+static int ptp_gianfar_enable(void *priv, struct ptp_clock_request *rq, int on)
+{
+ struct etsects *etsects = priv;
+ unsigned long flags;
+ u32 bit, mask;
+
+ switch (rq->type) {
+ case PTP_REQUEST_EXTTS:
+ switch (rq->index) {
+ case 0:
+ bit = ETS1EN;
+ break;
+ case 1:
+ bit = ETS2EN;
+ break;
+ default:
+ return -EINVAL;
+ }
+ spin_lock_irqsave(®ister_lock, flags);
+ mask = gfar_read(&etsects->regs->tmr_temask);
+ if (on)
+ mask |= bit;
+ else
+ mask &= ~bit;
+ gfar_write(&etsects->regs->tmr_temask, mask);
+ spin_unlock_irqrestore(®ister_lock, flags);
+ return 0;
+
+ case PTP_REQUEST_PPS:
+ spin_lock_irqsave(®ister_lock, flags);
+ mask = gfar_read(&etsects->regs->tmr_temask);
+ if (on)
+ mask |= PP1EN;
+ else
+ mask &= ~PP1EN;
+ gfar_write(&etsects->regs->tmr_temask, mask);
+ spin_unlock_irqrestore(®ister_lock, flags);
+ return 0;
+
+ default:
+ break;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static struct ptp_clock_info ptp_gianfar_caps = {
+ .owner = THIS_MODULE,
+ .name = "gianfar clock",
+ .max_adj = 512000,
+ .n_alarm = N_ALARM,
+ .n_ext_ts = N_EXT_TS,
+ .n_per_out = 0,
+ .pps = 1,
+ .priv = &the_clock,
+ .adjfreq = ptp_gianfar_adjfreq,
+ .adjtime = ptp_gianfar_adjtime,
+ .gettime = ptp_gianfar_gettime,
+ .settime = ptp_gianfar_settime,
+ .gettimer = ptp_gianfar_gettimer,
+ .settimer = ptp_gianfar_settimer,
+ .enable = ptp_gianfar_enable,
+};
+
+/* OF device tree */
+
+static int get_of_u32(struct device_node *node, char *str, u32 *val)
+{
+ int plen;
+ const u32 *prop = of_get_property(node, str, &plen);
+
+ if (!prop || plen != sizeof(*prop))
+ return -1;
+ *val = *prop;
+ return 0;
+}
+
+static int gianfar_ptp_probe(struct of_device *dev,
+ const struct of_device_id *match)
+{
+ struct device_node *node = dev->dev.of_node;
+ struct etsects *etsects = &the_clock;
+ struct timespec now;
+ u32 tmr_ctrl;
+
+ if (get_of_u32(node, "tclk-period", &etsects->tclk_period) ||
+ get_of_u32(node, "tmr-prsc", &etsects->tmr_prsc) ||
+ get_of_u32(node, "tmr-add", &etsects->tmr_add) ||
+ get_of_u32(node, "cksel", &etsects->cksel) ||
+ get_of_u32(node, "tmr-fiper1", &etsects->tmr_fiper1) ||
+ get_of_u32(node, "tmr-fiper2", &etsects->tmr_fiper2) ||
+ get_of_u32(node, "max-adj", &ptp_gianfar_caps.max_adj)) {
+ pr_err("device tree node missing required elements\n");
+ return -ENODEV;
+ }
+
+ etsects->irq = irq_of_parse_and_map(node, 0);
+
+ if (etsects->irq == NO_IRQ) {
+ pr_err("irq not in device tree\n");
+ return -ENODEV;
+ }
+ if (request_irq(etsects->irq, isr, 0, DRIVER, etsects)) {
+ pr_err("request_irq failed\n");
+ return -ENODEV;
+ }
+
+ etsects->regs = of_iomap(node, 0);
+ if (!etsects->regs) {
+ pr_err("of_iomap ptp registers failed\n");
+ return -EINVAL;
+ }
+
+ tmr_ctrl =
+ (etsects->tclk_period & TCLK_PERIOD_MASK) << TCLK_PERIOD_SHIFT |
+ (etsects->cksel & CKSEL_MASK) << CKSEL_SHIFT;
+
+ getnstimeofday(&now);
+ ptp_gianfar_settime(etsects, &now);
+
+ gfar_write(&etsects->regs->tmr_ctrl, tmr_ctrl);
+ gfar_write(&etsects->regs->tmr_add, etsects->tmr_add);
+ gfar_write(&etsects->regs->tmr_prsc, etsects->tmr_prsc);
+ gfar_write(&etsects->regs->tmr_fiper1, etsects->tmr_fiper1);
+ gfar_write(&etsects->regs->tmr_fiper2, etsects->tmr_fiper2);
+ set_alarm(etsects);
+ gfar_write(&etsects->regs->tmr_ctrl, tmr_ctrl|FS|RTPE|TE);
+
+ etsects->clock = ptp_clock_register(&ptp_gianfar_caps);
+
+ return IS_ERR(etsects->clock) ? PTR_ERR(etsects->clock) : 0;
+}
+
+static int gianfar_ptp_remove(struct of_device *dev)
+{
+ gfar_write(&the_clock.regs->tmr_temask, 0);
+ gfar_write(&the_clock.regs->tmr_ctrl, 0);
+
+ ptp_clock_unregister(the_clock.clock);
+
+ free_irq(the_clock.irq, &the_clock);
+
+ iounmap(the_clock.regs);
+
+ return 0;
+}
+
+static struct of_device_id match_table[] = {
+ { .compatible = "fsl,etsec-ptp" },
+ {},
+};
+
+static struct of_platform_driver gianfar_ptp_driver = {
+ .driver = {
+ .name = "gianfar_ptp",
+ .of_match_table = match_table,
+ .owner = THIS_MODULE,
+ },
+ .probe = gianfar_ptp_probe,
+ .remove = gianfar_ptp_remove,
+};
+
+/* module operations */
+
+static int __init ptp_gianfar_init(void)
+{
+ return of_register_platform_driver(&gianfar_ptp_driver);
+}
+
+module_init(ptp_gianfar_init);
+
+static void __exit ptp_gianfar_exit(void)
+{
+ of_unregister_platform_driver(&gianfar_ptp_driver);
+}
+
+module_exit(ptp_gianfar_exit);
+
+MODULE_AUTHOR("Richard Cochran <richard.cochran@omicron.at>");
+MODULE_DESCRIPTION("PTP clock using the eTSEC");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/gianfar_ptp_reg.h b/drivers/net/gianfar_ptp_reg.h
new file mode 100644
index 0000000..95e171f
--- /dev/null
+++ b/drivers/net/gianfar_ptp_reg.h
@@ -0,0 +1,113 @@
+/* gianfar_ptp_reg.h
+ * Generated by regen.tcl on Thu May 13 01:38:57 PM CEST 2010
+ *
+ * PTP 1588 clock using the gianfar eTSEC
+ *
+ * Copyright (C) 2010 OMICRON electronics GmbH
+ *
+ * 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 _GIANFAR_PTP_REG_H_
+#define _GIANFAR_PTP_REG_H_
+
+struct gianfar_ptp_registers {
+ u32 tmr_ctrl; /* Timer control register */
+ u32 tmr_tevent; /* Timestamp event register */
+ u32 tmr_temask; /* Timer event mask register */
+ u32 tmr_pevent; /* Timestamp event register */
+ u32 tmr_pemask; /* Timer event mask register */
+ u32 tmr_stat; /* Timestamp status register */
+ u32 tmr_cnt_h; /* Timer counter high register */
+ u32 tmr_cnt_l; /* Timer counter low register */
+ u32 tmr_add; /* Timer drift compensation addend register */
+ u32 tmr_acc; /* Timer accumulator register */
+ u32 tmr_prsc; /* Timer prescale */
+ u8 res1[4];
+ u32 tmroff_h; /* Timer offset high */
+ u32 tmroff_l; /* Timer offset low */
+ u8 res2[8];
+ u32 tmr_alarm1_h; /* Timer alarm 1 high register */
+ u32 tmr_alarm1_l; /* Timer alarm 1 high register */
+ u32 tmr_alarm2_h; /* Timer alarm 2 high register */
+ u32 tmr_alarm2_l; /* Timer alarm 2 high register */
+ u8 res3[48];
+ u32 tmr_fiper1; /* Timer fixed period interval */
+ u32 tmr_fiper2; /* Timer fixed period interval */
+ u32 tmr_fiper3; /* Timer fixed period interval */
+ u8 res4[20];
+ u32 tmr_etts1_h; /* Timestamp of general purpose external trigger */
+ u32 tmr_etts1_l; /* Timestamp of general purpose external trigger */
+ u32 tmr_etts2_h; /* Timestamp of general purpose external trigger */
+ u32 tmr_etts2_l; /* Timestamp of general purpose external trigger */
+};
+
+/* Bit definitions for the TMR_CTRL register */
+#define ALM1P (1<<31) /* Alarm1 output polarity */
+#define ALM2P (1<<30) /* Alarm2 output polarity */
+#define FS (1<<28) /* FIPER start indication */
+#define PP1L (1<<27) /* Fiper1 pulse loopback mode enabled. */
+#define PP2L (1<<26) /* Fiper2 pulse loopback mode enabled. */
+#define TCLK_PERIOD_SHIFT (16) /* 1588 timer reference clock period. */
+#define TCLK_PERIOD_MASK (0x3ff)
+#define RTPE (1<<15) /* Record Tx Timestamp to PAL Enable. */
+#define FRD (1<<14) /* FIPER Realignment Disable */
+#define ESFDP (1<<11) /* External Tx/Rx SFD Polarity. */
+#define ESFDE (1<<10) /* External Tx/Rx SFD Enable. */
+#define ETEP2 (1<<9) /* External trigger 2 edge polarity */
+#define ETEP1 (1<<8) /* External trigger 1 edge polarity */
+#define COPH (1<<7) /* Generated clock (TSEC_1588_GCLK) output phase. */
+#define CIPH (1<<6) /* External oscillator input clock phase. */
+#define TMSR (1<<5) /* Timer soft reset. When enabled, it resets all the timer registers and state machines. */
+#define BYP (1<<3) /* Bypass drift compensated clock */
+#define TE (1<<2) /* 1588 timer enable. If not enabled, all the timer registers and state machines are disabled. */
+#define CKSEL_SHIFT (0) /* 1588 Timer reference clock source select. */
+#define CKSEL_MASK (0x3)
+
+/* Bit definitions for the TMR_TEVENT register */
+#define ETS2 (1<<25) /* External trigger 2 timestamp sampled */
+#define ETS1 (1<<24) /* External trigger 1 timestamp sampled */
+#define ALM2 (1<<17) /* Current time equaled alarm time register 2 */
+#define ALM1 (1<<16) /* Current time equaled alarm time register 1 */
+#define PP1 (1<<7) /* Indicates that a periodic pulse has been generated based on FIPER1 register */
+#define PP2 (1<<6) /* Indicates that a periodic pulse has been generated based on FIPER2 register */
+#define PP3 (1<<5) /* Indicates that a periodic pulse has been generated based on FIPER3 register */
+
+/* Bit definitions for the TMR_TEMASK register */
+#define ETS2EN (1<<25) /* External trigger 2 timestamp sample event enable */
+#define ETS1EN (1<<24) /* External trigger 1 timestamp sample event enable */
+#define ALM2EN (1<<17) /* Timer ALM2 event enable */
+#define ALM1EN (1<<16) /* Timer ALM1 event enable */
+#define PP1EN (1<<7) /* Periodic pulse event 1 enable */
+#define PP2EN (1<<6) /* Periodic pulse event 2 enable */
+
+/* Bit definitions for the TMR_PEVENT register */
+#define TXP2 (1<<9) /* Indicates that a PTP frame has been transmitted and its timestamp is stored in TXTS2 register */
+#define TXP1 (1<<8) /* Indicates that a PTP frame has been transmitted and its timestamp is stored in TXTS1 register */
+#define RXP (1<<0) /* Indicates that a PTP frame has been received */
+
+/* Bit definitions for the TMR_PEMASK register */
+#define TXP2EN (1<<9) /* Transmit PTP packet event 2 enable */
+#define TXP1EN (1<<8) /* Transmit PTP packet event 1 enable */
+#define RXPEN (1<<0) /* Receive PTP packet event enable */
+
+/* Bit definitions for the TMR_STAT register */
+#define STAT_VEC_SHIFT (0) /* Timer general purpose status vector */
+#define STAT_VEC_MASK (0x3f)
+
+/* Bit definitions for the TMR_PRSC register */
+#define PRSC_OCK_SHIFT (0) /* Output clock division/prescale factor. */
+#define PRSC_OCK_MASK (0xffff)
+
+#endif
diff --git a/drivers/ptp/Kconfig b/drivers/ptp/Kconfig
index 3aa517a..aaf957f 100644
--- a/drivers/ptp/Kconfig
+++ b/drivers/ptp/Kconfig
@@ -36,4 +36,17 @@ config PTP_1588_CLOCK_LINUX
To compile this driver as a module, choose M here: the module
will be called ptp_linux.
+config PTP_1588_CLOCK_GIANFAR
+ tristate "Freescale eTSEC as PTP clock"
+ depends on PTP_1588_CLOCK
+ depends on GIANFAR
+ help
+ This driver adds support for using the eTSEC as a PTP
+ clock. This clock is only useful if your PTP programs are
+ getting hardware time stamps on the PTP Ethernet packets
+ using the SO_TIMESTAMPING API.
+
+ To compile this driver as a module, choose M here: the module
+ will be called gianfar_ptp.
+
endmenu
--
1.7.0.4
^ permalink raw reply related [flat|nested] 56+ messages in thread
* [PATCH 4/5] ptp: Added a clock driver for the IXP46x.
2010-08-16 11:17 [PATCH v5 0/5] ptp: IEEE 1588 clock support Richard Cochran
` (2 preceding siblings ...)
2010-08-16 11:18 ` [PATCH 3/5] ptp: Added a clock that uses the eTSEC found on the MPC85xx Richard Cochran
@ 2010-08-16 11:18 ` Richard Cochran
2010-08-16 11:19 ` [PATCH 5/5] ptp: Added a clock driver for the National Semiconductor PHYTER Richard Cochran
4 siblings, 0 replies; 56+ messages in thread
From: Richard Cochran @ 2010-08-16 11:18 UTC (permalink / raw)
To: netdev
Cc: linuxppc-dev, devicetree-discuss, linux-kernel, Rodolfo Giometti,
linux-arm-kernel, Krzysztof Halasa
This patch adds a driver for the hardware time stamping unit found on the
IXP465. The basic clock operations and an external trigger are implemented.
Signed-off-by: Richard Cochran <richard.cochran@omicron.at>
---
arch/arm/mach-ixp4xx/include/mach/ixp46x_ts.h | 78 ++++++
drivers/net/arm/ixp4xx_eth.c | 191 +++++++++++++
drivers/ptp/Kconfig | 13 +
drivers/ptp/Makefile | 1 +
drivers/ptp/ptp_ixp46x.c | 359 +++++++++++++++++++++++++
5 files changed, 642 insertions(+), 0 deletions(-)
create mode 100644 arch/arm/mach-ixp4xx/include/mach/ixp46x_ts.h
create mode 100644 drivers/ptp/ptp_ixp46x.c
diff --git a/arch/arm/mach-ixp4xx/include/mach/ixp46x_ts.h b/arch/arm/mach-ixp4xx/include/mach/ixp46x_ts.h
new file mode 100644
index 0000000..729a6b2
--- /dev/null
+++ b/arch/arm/mach-ixp4xx/include/mach/ixp46x_ts.h
@@ -0,0 +1,78 @@
+/*
+ * PTP 1588 clock using the IXP46X
+ *
+ * Copyright (C) 2010 OMICRON electronics GmbH
+ *
+ * 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 _IXP46X_TS_H_
+#define _IXP46X_TS_H_
+
+#define DEFAULT_ADDEND 0xF0000029
+#define TICKS_NS_SHIFT 4
+
+struct ixp46x_channel_ctl {
+ u32 Ch_Control; /* 0x40 Time Synchronization Channel Control */
+ u32 Ch_Event; /* 0x44 Time Synchronization Channel Event */
+ u32 TxSnapLo; /* 0x48 Transmit Snapshot Low Register */
+ u32 TxSnapHi; /* 0x4C Transmit Snapshot High Register */
+ u32 RxSnapLo; /* 0x50 Receive Snapshot Low Register */
+ u32 RxSnapHi; /* 0x54 Receive Snapshot High Register */
+ u32 SrcUUIDLo; /* 0x58 Source UUID0 Low Register */
+ u32 SrcUUIDHi; /* 0x5C Sequence Identifier/Source UUID0 High */
+};
+
+struct ixp46x_ts_regs {
+ u32 Control; /* 0x00 Time Sync Control Register */
+ u32 Event; /* 0x04 Time Sync Event Register */
+ u32 Addend; /* 0x08 Time Sync Addend Register */
+ u32 Accum; /* 0x0C Time Sync Accumulator Register */
+ u32 Test; /* 0x10 Time Sync Test Register */
+ u32 Unused; /* 0x14 */
+ u32 RSysTime_Lo; /* 0x18 RawSystemTime_Low Register */
+ u32 RSysTimeHi; /* 0x1C RawSystemTime_High Register */
+ u32 SysTimeLo; /* 0x20 SystemTime_Low Register */
+ u32 SysTimeHi; /* 0x24 SystemTime_High Register */
+ u32 TrgtLo; /* 0x28 TargetTime_Low Register */
+ u32 TrgtHi; /* 0x2C TargetTime_High Register */
+ u32 ASMSLo; /* 0x30 Auxiliary Slave Mode Snapshot Low */
+ u32 ASMSHi; /* 0x34 Auxiliary Slave Mode Snapshot High */
+ u32 AMMSLo; /* 0x38 Auxiliary Master Mode Snapshot Low */
+ u32 AMMSHi; /* 0x3C Auxiliary Master Mode Snapshot High */
+
+ struct ixp46x_channel_ctl channel[3];
+};
+
+/* 0x00 Time Sync Control Register Bits */
+#define TSCR_AMM (1<<3)
+#define TSCR_ASM (1<<2)
+#define TSCR_TTM (1<<1)
+#define TSCR_RST (1<<0)
+
+/* 0x04 Time Sync Event Register Bits */
+#define TSER_SNM (1<<3)
+#define TSER_SNS (1<<2)
+#define TTIPEND (1<<1)
+
+/* 0x40 Time Synchronization Channel Control Register Bits */
+#define MASTER_MODE (1<<0)
+#define TIMESTAMP_ALL (1<<1)
+
+/* 0x44 Time Synchronization Channel Event Register Bits */
+#define TX_SNAPSHOT_LOCKED (1<<0)
+#define RX_SNAPSHOT_LOCKED (1<<1)
+
+#endif
diff --git a/drivers/net/arm/ixp4xx_eth.c b/drivers/net/arm/ixp4xx_eth.c
index 4f1cc71..3d36154 100644
--- a/drivers/net/arm/ixp4xx_eth.c
+++ b/drivers/net/arm/ixp4xx_eth.c
@@ -30,9 +30,12 @@
#include <linux/etherdevice.h>
#include <linux/io.h>
#include <linux/kernel.h>
+#include <linux/net_tstamp.h>
#include <linux/phy.h>
#include <linux/platform_device.h>
+#include <linux/ptp_classify.h>
#include <linux/slab.h>
+#include <mach/ixp46x_ts.h>
#include <mach/npe.h>
#include <mach/qmgr.h>
@@ -67,6 +70,14 @@
#define RXFREE_QUEUE(port_id) (NPE_ID(port_id) + 26)
#define TXDONE_QUEUE 31
+#define PTP_SLAVE_MODE 1
+#define PTP_MASTER_MODE 2
+#define PORT2CHANNEL(p) 1
+/*
+ * PHYSICAL_ID(p->id) ?
+ * TODO - Figure out correct mapping.
+ */
+
/* TX Control Registers */
#define TX_CNTRL0_TX_EN 0x01
#define TX_CNTRL0_HALFDUPLEX 0x02
@@ -171,6 +182,8 @@ struct port {
int id; /* logical port ID */
int speed, duplex;
u8 firmware[4];
+ int hwts_tx_en;
+ int hwts_rx_en;
};
/* NPE message structure */
@@ -246,6 +259,171 @@ static int ports_open;
static struct port *npe_port_tab[MAX_NPES];
static struct dma_pool *dma_pool;
+static struct sock_filter ptp_filter[] = {
+ PTP_FILTER
+};
+
+static int match(struct sk_buff *skb, u16 uid_hi, u32 uid_lo, u16 seq)
+{
+ unsigned int type;
+ u16 *hi, *id;
+ u8 *lo, *data = skb->data;
+
+ type = sk_run_filter(skb, ptp_filter, ARRAY_SIZE(ptp_filter));
+
+ if (PTP_CLASS_V1_IPV4 == type) {
+
+ id = (u16 *)(data + 42 + 30);
+ hi = (u16 *)(data + 42 + 22);
+ lo = data + 42 + 24;
+
+ return (uid_hi == *hi &&
+ 0 == memcmp(&uid_lo, lo, sizeof(uid_lo)) &&
+ seq == *id);
+ }
+
+ return 0;
+}
+
+static void do_rx_timestamp(struct port *port, struct sk_buff *skb)
+{
+ struct skb_shared_hwtstamps *shhwtstamps;
+ struct ixp46x_ts_regs *regs;
+ u64 ns;
+ u32 ch, hi, lo, val;
+ u16 uid, seq;
+
+ if (!port->hwts_rx_en)
+ return;
+
+ ch = PORT2CHANNEL(port);
+
+ regs = (struct ixp46x_ts_regs __iomem *) IXP4XX_TIMESYNC_BASE_VIRT;
+
+ val = __raw_readl(®s->channel[ch].Ch_Event);
+
+ if (!(val & RX_SNAPSHOT_LOCKED))
+ return;
+
+ lo = __raw_readl(®s->channel[ch].SrcUUIDLo);
+ hi = __raw_readl(®s->channel[ch].SrcUUIDHi);
+
+ uid = hi & 0xffff;
+ seq = (hi >> 16) & 0xffff;
+
+ if (!match(skb, htons(uid), htonl(lo), htons(seq)))
+ goto out;
+
+ lo = __raw_readl(®s->channel[ch].RxSnapLo);
+ hi = __raw_readl(®s->channel[ch].RxSnapHi);
+ ns = ((u64) hi) << 32;
+ ns |= lo;
+ ns <<= TICKS_NS_SHIFT;
+
+ shhwtstamps = skb_hwtstamps(skb);
+ memset(shhwtstamps, 0, sizeof(*shhwtstamps));
+ shhwtstamps->hwtstamp = ns_to_ktime(ns);
+out:
+ __raw_writel(RX_SNAPSHOT_LOCKED, ®s->channel[ch].Ch_Event);
+}
+
+static void do_tx_timestamp(struct port *port, struct sk_buff *skb)
+{
+#ifdef __ARMEB__
+ struct skb_shared_hwtstamps shhwtstamps;
+ struct ixp46x_ts_regs *regs;
+ union skb_shared_tx *shtx;
+ u64 ns;
+ u32 ch, cnt, hi, lo, val;
+
+ shtx = skb_tx(skb);
+ if (unlikely(shtx->hardware && port->hwts_tx_en))
+ shtx->in_progress = 1;
+ else
+ return;
+
+ ch = PORT2CHANNEL(port);
+
+ regs = (struct ixp46x_ts_regs __iomem *) IXP4XX_TIMESYNC_BASE_VIRT;
+
+ /*
+ * This really stinks, but we have to poll for the Tx time stamp.
+ * Usually, the time stamp is ready after 4 to 6 microseconds.
+ */
+ for (cnt = 0; cnt < 100; cnt++) {
+ val = __raw_readl(®s->channel[ch].Ch_Event);
+ if (val & TX_SNAPSHOT_LOCKED)
+ break;
+ udelay(1);
+ }
+ if (!(val & TX_SNAPSHOT_LOCKED)) {
+ shtx->in_progress = 0;
+ return;
+ }
+
+ lo = __raw_readl(®s->channel[ch].TxSnapLo);
+ hi = __raw_readl(®s->channel[ch].TxSnapHi);
+ ns = ((u64) hi) << 32;
+ ns |= lo;
+ ns <<= TICKS_NS_SHIFT;
+
+ memset(&shhwtstamps, 0, sizeof(shhwtstamps));
+ shhwtstamps.hwtstamp = ns_to_ktime(ns);
+ skb_tstamp_tx(skb, &shhwtstamps);
+
+ __raw_writel(TX_SNAPSHOT_LOCKED, ®s->channel[ch].Ch_Event);
+#endif
+}
+
+static int hwtstamp_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd)
+{
+ struct hwtstamp_config cfg;
+ struct ixp46x_ts_regs *regs;
+ struct port *port = netdev_priv(netdev);
+ int ch;
+
+ if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg)))
+ return -EFAULT;
+
+ if (cfg.flags) /* reserved for future extensions */
+ return -EINVAL;
+
+ ch = PORT2CHANNEL(port);
+ regs = (struct ixp46x_ts_regs __iomem *) IXP4XX_TIMESYNC_BASE_VIRT;
+
+ switch (cfg.tx_type) {
+ case HWTSTAMP_TX_OFF:
+ port->hwts_tx_en = 0;
+ break;
+ case HWTSTAMP_TX_ON:
+ port->hwts_tx_en = 1;
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ switch (cfg.rx_filter) {
+ case HWTSTAMP_FILTER_NONE:
+ port->hwts_rx_en = 0;
+ break;
+ case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
+ port->hwts_rx_en = PTP_SLAVE_MODE;
+ __raw_writel(0, ®s->channel[ch].Ch_Control);
+ break;
+ case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
+ port->hwts_rx_en = PTP_MASTER_MODE;
+ __raw_writel(MASTER_MODE, ®s->channel[ch].Ch_Control);
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ /* Clear out any old time stamps. */
+ __raw_writel(TX_SNAPSHOT_LOCKED | RX_SNAPSHOT_LOCKED,
+ ®s->channel[ch].Ch_Event);
+
+ return copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg)) ? -EFAULT : 0;
+}
static int ixp4xx_mdio_cmd(struct mii_bus *bus, int phy_id, int location,
int write, u16 cmd)
@@ -573,6 +751,7 @@ static int eth_poll(struct napi_struct *napi, int budget)
debug_pkt(dev, "eth_poll", skb->data, skb->len);
+ do_rx_timestamp(port, skb);
skb->protocol = eth_type_trans(skb, dev);
dev->stats.rx_packets++;
dev->stats.rx_bytes += skb->len;
@@ -728,6 +907,10 @@ static int eth_xmit(struct sk_buff *skb, struct net_device *dev)
#if DEBUG_TX
printk(KERN_DEBUG "%s: eth_xmit end\n", dev->name);
#endif
+
+ do_tx_timestamp(port, skb);
+ skb_tx_timestamp(skb);
+
return NETDEV_TX_OK;
}
@@ -783,6 +966,9 @@ static int eth_ioctl(struct net_device *dev, struct ifreq *req, int cmd)
if (!netif_running(dev))
return -EINVAL;
+ if (cpu_is_ixp46x() && cmd == SIOCSHWTSTAMP)
+ return hwtstamp_ioctl(dev, req, cmd);
+
return phy_mii_ioctl(port->phydev, req, cmd);
}
@@ -1171,6 +1357,11 @@ static int __devinit eth_init_one(struct platform_device *pdev)
char phy_id[MII_BUS_ID_SIZE + 3];
int err;
+ if (ptp_filter_init(ptp_filter, ARRAY_SIZE(ptp_filter))) {
+ pr_err("ixp4xx_eth: bad ptp filter\n");
+ return -EINVAL;
+ }
+
if (!(dev = alloc_etherdev(sizeof(struct port))))
return -ENOMEM;
diff --git a/drivers/ptp/Kconfig b/drivers/ptp/Kconfig
index aaf957f..cc2d3a1 100644
--- a/drivers/ptp/Kconfig
+++ b/drivers/ptp/Kconfig
@@ -49,4 +49,17 @@ config PTP_1588_CLOCK_GIANFAR
To compile this driver as a module, choose M here: the module
will be called gianfar_ptp.
+config PTP_1588_CLOCK_IXP46X
+ tristate "Intel IXP46x as PTP clock"
+ depends on PTP_1588_CLOCK
+ depends on IXP4XX_ETH
+ help
+ This driver adds support for using the IXP46X as a PTP
+ clock. This clock is only useful if your PTP programs are
+ getting hardware time stamps on the PTP Ethernet packets
+ using the SO_TIMESTAMPING API.
+
+ To compile this driver as a module, choose M here: the module
+ will be called ptp_ixp46x.
+
endmenu
diff --git a/drivers/ptp/Makefile b/drivers/ptp/Makefile
index 1651d52..5018f58 100644
--- a/drivers/ptp/Makefile
+++ b/drivers/ptp/Makefile
@@ -4,3 +4,4 @@
obj-$(CONFIG_PTP_1588_CLOCK) += ptp_clock.o
obj-$(CONFIG_PTP_1588_CLOCK_LINUX) += ptp_linux.o
+obj-$(CONFIG_PTP_1588_CLOCK_IXP46X) += ptp_ixp46x.o
diff --git a/drivers/ptp/ptp_ixp46x.c b/drivers/ptp/ptp_ixp46x.c
new file mode 100644
index 0000000..4530256
--- /dev/null
+++ b/drivers/ptp/ptp_ixp46x.c
@@ -0,0 +1,359 @@
+/*
+ * PTP 1588 clock using the IXP46X
+ *
+ * Copyright (C) 2010 OMICRON electronics GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include <linux/ptp_clock_kernel.h>
+#include <mach/ixp46x_ts.h>
+
+#define DRIVER "ptp_ixp46x"
+#define N_EXT_TS 2
+#define MASTER_GPIO 8
+#define MASTER_IRQ 25
+#define SLAVE_GPIO 7
+#define SLAVE_IRQ 24
+
+struct ixp_clock {
+ struct ixp46x_ts_regs *regs;
+ struct ptp_clock *ptp_clock;
+ int exts0_enabled;
+ int exts1_enabled;
+};
+
+DEFINE_SPINLOCK(register_lock);
+
+/*
+ * Register access functions
+ */
+
+static inline u32 ixp_read(volatile unsigned __iomem *addr)
+{
+ u32 val;
+ val = __raw_readl(addr);
+ return val;
+}
+
+static inline void ixp_write(volatile unsigned __iomem *addr, u32 val)
+{
+ __raw_writel(val, addr);
+}
+
+static u64 sys_time_read(struct ixp46x_ts_regs *regs)
+{
+ u64 ns;
+ u32 lo, hi;
+
+ lo = ixp_read(®s->SysTimeLo);
+ hi = ixp_read(®s->SysTimeHi);
+
+ ns = ((u64) hi) << 32;
+ ns |= lo;
+ ns <<= TICKS_NS_SHIFT;
+
+ return ns;
+}
+
+static void sys_time_write(struct ixp46x_ts_regs *regs, u64 ns)
+{
+ u32 hi, lo;
+
+ ns >>= TICKS_NS_SHIFT;
+ hi = ns >> 32;
+ lo = ns & 0xffffffff;
+
+ ixp_write(®s->SysTimeLo, lo);
+ ixp_write(®s->SysTimeHi, hi);
+}
+
+/*
+ * Interrupt service routine
+ */
+
+static irqreturn_t isr(int irq, void *priv)
+{
+ struct ixp_clock *ixp_clock = priv;
+ struct ixp46x_ts_regs *regs = ixp_clock->regs;
+ struct ptp_clock_event event;
+ u32 ack = 0, lo, hi, val;
+
+ val = ixp_read(®s->Event);
+
+ if (val & TSER_SNS) {
+ ack |= TSER_SNS;
+ if (ixp_clock->exts0_enabled) {
+ hi = ixp_read(®s->ASMSHi);
+ lo = ixp_read(®s->ASMSLo);
+ event.type = PTP_CLOCK_EXTTS;
+ event.index = 0;
+ event.timestamp = ((u64) hi) << 32;
+ event.timestamp |= lo;
+ event.timestamp <<= TICKS_NS_SHIFT;
+ ptp_clock_event(ixp_clock->ptp_clock, &event);
+ }
+ }
+
+ if (val & TSER_SNM) {
+ ack |= TSER_SNM;
+ if (ixp_clock->exts1_enabled) {
+ hi = ixp_read(®s->AMMSHi);
+ lo = ixp_read(®s->AMMSLo);
+ event.type = PTP_CLOCK_EXTTS;
+ event.index = 1;
+ event.timestamp = ((u64) hi) << 32;
+ event.timestamp |= lo;
+ event.timestamp <<= TICKS_NS_SHIFT;
+ ptp_clock_event(ixp_clock->ptp_clock, &event);
+ }
+ }
+
+ if (val & TTIPEND)
+ ack |= TTIPEND; /* this bit seems to be always set */
+
+ if (ack) {
+ ixp_write(®s->Event, ack);
+ return IRQ_HANDLED;
+ } else
+ return IRQ_NONE;
+}
+
+/*
+ * PTP clock operations
+ */
+
+static int ptp_ixp_adjfreq(void *priv, s32 ppb)
+{
+ u64 adj;
+ u32 diff, addend;
+ int neg_adj = 0;
+ struct ixp_clock *ixp_clock = priv;
+ struct ixp46x_ts_regs *regs = ixp_clock->regs;
+
+ if (!ppb)
+ return 0;
+
+ if (ppb < 0) {
+ neg_adj = 1;
+ ppb = -ppb;
+ }
+ addend = DEFAULT_ADDEND;
+ adj = addend;
+ adj *= ppb;
+ diff = div_u64(adj, 1000000000ULL);
+
+ addend = neg_adj ? addend - diff : addend + diff;
+
+ ixp_write(®s->Addend, addend);
+
+ return 0;
+}
+
+static int ptp_ixp_adjtime(void *priv, struct timespec *ts)
+{
+ s64 delta, now;
+ unsigned long flags;
+ struct ixp_clock *ixp_clock = priv;
+ struct ixp46x_ts_regs *regs = ixp_clock->regs;
+
+ delta = 1000000000LL * ts->tv_sec;
+ delta += ts->tv_nsec;
+
+ spin_lock_irqsave(®ister_lock, flags);
+
+ now = sys_time_read(regs);
+ now += delta;
+ sys_time_write(regs, now);
+
+ spin_unlock_irqrestore(®ister_lock, flags);
+
+ return 0;
+}
+
+static int ptp_ixp_gettime(void *priv, struct timespec *ts)
+{
+ u64 ns;
+ u32 remainder;
+ unsigned long flags;
+ struct ixp_clock *ixp_clock = priv;
+ struct ixp46x_ts_regs *regs = ixp_clock->regs;
+
+ spin_lock_irqsave(®ister_lock, flags);
+
+ ns = sys_time_read(regs);
+
+ spin_unlock_irqrestore(®ister_lock, flags);
+
+ ts->tv_sec = div_u64_rem(ns, 1000000000, &remainder);
+ ts->tv_nsec = remainder;
+ return 0;
+}
+
+static int ptp_ixp_settime(void *priv, struct timespec *ts)
+{
+ u64 ns;
+ unsigned long flags;
+ struct ixp_clock *ixp_clock = priv;
+ struct ixp46x_ts_regs *regs = ixp_clock->regs;
+
+ ns = ts->tv_sec * 1000000000ULL;
+ ns += ts->tv_nsec;
+
+ spin_lock_irqsave(®ister_lock, flags);
+
+ sys_time_write(regs, ns);
+
+ spin_unlock_irqrestore(®ister_lock, flags);
+
+ return 0;
+}
+
+static int ptp_ixp_gettimer(void *priv, int index, struct itimerspec *ts)
+{
+ /* We do not offer any ancillary timer features. */
+ return -EOPNOTSUPP;
+}
+
+static int ptp_ixp_settimer(void *p, int i, int abs, struct itimerspec *ts)
+{
+ /* We do not offer any ancillary timer features. */
+ return -EOPNOTSUPP;
+}
+
+static int ptp_ixp_enable(void *priv, struct ptp_clock_request *rq, int on)
+{
+ struct ixp_clock *ixp_clock = priv;
+
+ switch (rq->type) {
+ case PTP_REQUEST_EXTTS:
+ switch (rq->index) {
+ case 0:
+ ixp_clock->exts0_enabled = on ? 1 : 0;
+ break;
+ case 1:
+ ixp_clock->exts1_enabled = on ? 1 : 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+ default:
+ break;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static struct ptp_clock_info ptp_ixp_caps = {
+ .owner = THIS_MODULE,
+ .name = "IXP46X timer",
+ .max_adj = 66666655,
+ .n_ext_ts = N_EXT_TS,
+ .pps = 0,
+ .priv = NULL,
+ .adjfreq = ptp_ixp_adjfreq,
+ .adjtime = ptp_ixp_adjtime,
+ .gettime = ptp_ixp_gettime,
+ .settime = ptp_ixp_settime,
+ .gettimer = ptp_ixp_gettimer,
+ .settimer = ptp_ixp_settimer,
+ .enable = ptp_ixp_enable,
+};
+
+/* module operations */
+
+static struct ixp_clock ixp_clock;
+
+static int setup_interrupt(int gpio)
+{
+ int irq;
+
+ gpio_line_config(gpio, IXP4XX_GPIO_IN);
+
+ irq = gpio_to_irq(gpio);
+
+ if (NO_IRQ == irq)
+ return NO_IRQ;
+
+ if (set_irq_type(irq, IRQF_TRIGGER_FALLING)) {
+ pr_err("cannot set trigger type for irq %d\n", irq);
+ return NO_IRQ;
+ }
+
+ if (request_irq(irq, isr, 0, DRIVER, &ixp_clock)) {
+ pr_err("request_irq failed for irq %d\n", irq);
+ return NO_IRQ;
+ }
+
+ return irq;
+}
+
+static void __exit ptp_ixp_exit(void)
+{
+ free_irq(MASTER_IRQ, &ixp_clock);
+ free_irq(SLAVE_IRQ, &ixp_clock);
+ ptp_clock_unregister(ixp_clock.ptp_clock);
+}
+
+static int __init ptp_ixp_init(void)
+{
+ ixp_clock.regs =
+ (struct ixp46x_ts_regs __iomem *)IXP4XX_TIMESYNC_BASE_VIRT;
+
+ ptp_ixp_caps.priv = &ixp_clock;
+
+ ixp_clock.ptp_clock = ptp_clock_register(&ptp_ixp_caps);
+
+ if (IS_ERR(ixp_clock.ptp_clock))
+ return PTR_ERR(ixp_clock.ptp_clock);
+
+ ixp_write(&ixp_clock.regs->Addend, DEFAULT_ADDEND);
+ ixp_write(&ixp_clock.regs->TrgtLo, 1);
+ ixp_write(&ixp_clock.regs->TrgtHi, 0);
+ ixp_write(&ixp_clock.regs->Event, TTIPEND);
+
+ if (MASTER_IRQ != setup_interrupt(MASTER_GPIO)) {
+ pr_err("failed to setup gpio %d as irq\n", MASTER_GPIO);
+ goto no_master;
+ }
+ if (SLAVE_IRQ != setup_interrupt(SLAVE_GPIO)) {
+ pr_err("failed to setup gpio %d as irq\n", SLAVE_GPIO);
+ goto no_slave;
+ }
+
+ return 0;
+no_slave:
+ free_irq(MASTER_IRQ, &ixp_clock);
+no_master:
+ ptp_clock_unregister(ixp_clock.ptp_clock);
+ return -ENODEV;
+}
+
+module_init(ptp_ixp_init);
+module_exit(ptp_ixp_exit);
+
+MODULE_AUTHOR("Richard Cochran <richard.cochran@omicron.at>");
+MODULE_DESCRIPTION("PTP clock using the IXP46X timer");
+MODULE_LICENSE("GPL");
--
1.7.0.4
^ permalink raw reply related [flat|nested] 56+ messages in thread
* [PATCH 5/5] ptp: Added a clock driver for the National Semiconductor PHYTER.
2010-08-16 11:17 [PATCH v5 0/5] ptp: IEEE 1588 clock support Richard Cochran
` (3 preceding siblings ...)
2010-08-16 11:18 ` [PATCH 4/5] ptp: Added a clock driver for the IXP46x Richard Cochran
@ 2010-08-16 11:19 ` Richard Cochran
4 siblings, 0 replies; 56+ messages in thread
From: Richard Cochran @ 2010-08-16 11:19 UTC (permalink / raw)
To: netdev
Cc: linux-kernel, linuxppc-dev, devicetree-discuss, linux-arm-kernel,
Krzysztof Halasa, Rodolfo Giometti
This patch adds support for the PTP clock found on the DP83640.
The basic clock operations and one external time stamp have
been implemented.
Signed-off-by: Richard Cochran <richard.cochran@omicron.at>
---
drivers/net/phy/Kconfig | 29 ++
drivers/net/phy/Makefile | 1 +
drivers/net/phy/dp83640.c | 904 +++++++++++++++++++++++++++++++++++++++++
drivers/net/phy/dp83640_reg.h | 261 ++++++++++++
4 files changed, 1195 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/phy/dp83640.c
create mode 100644 drivers/net/phy/dp83640_reg.h
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index a527e37..a2d0753 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -77,6 +77,35 @@ config NATIONAL_PHY
---help---
Currently supports the DP83865 PHY.
+config DP83640_PHY
+ tristate "Driver for the National Semiconductor DP83640 PHYTER"
+ depends on PTP_1588_CLOCK
+ depends on NETWORK_PHY_TIMESTAMPING
+ ---help---
+ Supports the DP83640 PHYTER with IEEE 1588 features.
+
+ This driver adds support for using the DP83640 as a PTP
+ clock. This clock is only useful if your PTP programs are
+ getting hardware time stamps on the PTP Ethernet packets
+ using the SO_TIMESTAMPING API.
+
+ In order for this to work, your MAC driver must also
+ implement the skb_tx_timetamp() function.
+
+config DP83640_PHY_STATUS_FRAMES
+ bool "DP83640 Status Frames"
+ default y
+ depends on DP83640_PHY
+ ---help---
+ This option allows the DP83640 PHYTER driver to obtain time
+ stamps from the PHY via special status frames, rather than
+ reading over the MDIO bus. Using status frames is therefore
+ more efficient. However, if enabled, this option will cause
+ the driver to add a mutlicast address to the MAC.
+
+ Say Y here, unless your MAC does not support multicast
+ destination addresses.
+
config STE10XP
depends on PHYLIB
tristate "Driver for STMicroelectronics STe10Xp PHYs"
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 13bebab..2333215 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_FIXED_PHY) += fixed.o
obj-$(CONFIG_MDIO_BITBANG) += mdio-bitbang.o
obj-$(CONFIG_MDIO_GPIO) += mdio-gpio.o
obj-$(CONFIG_NATIONAL_PHY) += national.o
+obj-$(CONFIG_DP83640_PHY) += dp83640.o
obj-$(CONFIG_STE10XP) += ste10Xp.o
obj-$(CONFIG_MICREL_PHY) += micrel.o
obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o
diff --git a/drivers/net/phy/dp83640.c b/drivers/net/phy/dp83640.c
new file mode 100644
index 0000000..38a7202
--- /dev/null
+++ b/drivers/net/phy/dp83640.c
@@ -0,0 +1,904 @@
+/*
+ * Driver for the National Semiconductor DP83640 PHYTER
+ *
+ * Copyright (C) 2010 OMICRON electronics GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include <linux/ethtool.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/net_tstamp.h>
+#include <linux/netdevice.h>
+#include <linux/phy.h>
+#include <linux/ptp_classify.h>
+#include <linux/ptp_clock_kernel.h>
+
+#include "dp83640_reg.h"
+
+#ifdef CONFIG_DP83640_PHY_STATUS_FRAMES
+#define USE_STATUS_FRAMES
+#endif
+
+#define DP83640_PHY_ID 0x20005ce1
+#define PAGESEL 0x13
+#define LAYER4 0x02
+#define LAYER2 0x01
+#define MAX_RXTS 4
+#define MAX_TXTS 4
+#define N_EXT_TS 1
+#define PSF_PTPVER 2
+#define PSF_EVNT 0x4000
+#define PSF_RX 0x2000
+#define PSF_TX 0x1000
+#define EXT_EVENT 1
+#define EXT_GPIO 1
+
+#if defined(__BIG_ENDIAN)
+#define ENDIAN_FLAG 0
+#elif defined(__LITTLE_ENDIAN)
+#define ENDIAN_FLAG PSF_ENDIAN
+#endif
+
+#define SKB_PTP_TYPE(__skb) (*(unsigned int *)((__skb)->cb))
+
+struct phy_rxts {
+ u16 ns_lo; /* ns[15:0] */
+ u16 ns_hi; /* overflow[1:0], ns[29:16] */
+ u16 sec_lo; /* sec[15:0] */
+ u16 sec_hi; /* sec[31:16] */
+ u16 seqid; /* sequenceId[15:0] */
+ u16 msgtype; /* messageType[3:0], hash[11:0] */
+};
+
+struct phy_txts {
+ u16 ns_lo; /* ns[15:0] */
+ u16 ns_hi; /* overflow[1:0], ns[29:16] */
+ u16 sec_lo; /* sec[15:0] */
+ u16 sec_hi; /* sec[31:16] */
+};
+
+struct rxts {
+ struct list_head list;
+ unsigned long tmo;
+ u64 ns;
+ u16 seqid;
+ u8 msgtype;
+ u16 hash;
+};
+
+struct dp83640_private {
+ struct phy_device *phydev;
+ struct work_struct ts_work;
+ int hwts_tx_en;
+ int hwts_rx_en;
+ int layer;
+ int version;
+ /* protects extended registers from concurrent access */
+ struct mutex extreg_mux;
+ int page;
+ /* remember the last event time stamp */
+ struct phy_txts edata;
+ /* list of rx timestamps */
+ struct list_head rxts;
+ struct list_head rxpool;
+ struct rxts rx_pool_data[MAX_RXTS];
+ /* protects above three fields from concurrent access */
+ spinlock_t rx_lock;
+
+ struct sk_buff_head rx_queue;
+ struct sk_buff_head tx_queue;
+};
+
+/* globals */
+
+static struct ptp_clock *dp83640_clock;
+DEFINE_MUTEX(clock_lock); /* protects the one and only dp83640_clock */
+
+static void do_timestamp_work(struct work_struct *work);
+
+/* extended register access functions */
+
+static int ext_read(struct phy_device *phydev, int page, u32 regnum)
+{
+ struct dp83640_private *dp83640 = phydev->priv;
+ int val;
+
+ if (dp83640->page != page) {
+ phy_write(phydev, PAGESEL, page);
+ dp83640->page = page;
+ }
+ val = phy_read(phydev, regnum);
+
+ return val;
+}
+
+static void ext_write(struct phy_device *phydev, int page, u32 regnum, u16 val)
+{
+ struct dp83640_private *dp83640 = phydev->priv;
+
+ if (dp83640->page != page) {
+ phy_write(phydev, PAGESEL, page);
+ dp83640->page = page;
+ }
+ phy_write(phydev, regnum, val);
+}
+
+static int tdr_write(struct phy_device *phydev, struct timespec *ts, u16 cmd)
+{
+ ext_write(phydev, PAGE4, PTP_TDR, ts->tv_nsec & 0xffff);/* ns[15:0] */
+ ext_write(phydev, PAGE4, PTP_TDR, ts->tv_nsec >> 16); /* ns[31:16] */
+ ext_write(phydev, PAGE4, PTP_TDR, ts->tv_sec & 0xffff); /* sec[15:0] */
+ ext_write(phydev, PAGE4, PTP_TDR, ts->tv_sec >> 16); /* sec[31:16] */
+
+ ext_write(phydev, PAGE4, PTP_CTL, cmd);
+
+ return 0;
+}
+
+/* convert phy timestamps into driver timestamps */
+
+static void phy2rxts(struct phy_rxts *p, struct rxts *rxts)
+{
+ u32 sec;
+
+ sec = p->sec_lo;
+ sec |= p->sec_hi << 16;
+
+ rxts->ns = p->ns_lo;
+ rxts->ns |= (p->ns_hi & 0x3fff) << 16;
+ rxts->ns += ((u64)sec) * 1000000000ULL;
+ rxts->seqid = p->seqid;
+ rxts->msgtype = (p->msgtype >> 12) & 0xf;
+ rxts->hash = p->msgtype & 0x0fff;
+}
+
+static u64 phy2txts(struct phy_txts *p)
+{
+ u64 ns;
+ u32 sec;
+
+ sec = p->sec_lo;
+ sec |= p->sec_hi << 16;
+
+ ns = p->ns_lo;
+ ns |= (p->ns_hi & 0x3fff) << 16;
+ ns += ((u64)sec) * 1000000000ULL;
+
+ return ns;
+}
+
+/* ptp clock methods */
+
+static int ptp_dp83640_adjfreq(void *priv, s32 ppb)
+{
+ struct dp83640_private *dp83640 = priv;
+ struct phy_device *phydev = dp83640->phydev;
+ u64 rate;
+ int neg_adj = 0;
+ u16 hi, lo;
+
+ if (!ppb)
+ return 0;
+
+ if (ppb < 0) {
+ neg_adj = 1;
+ ppb = -ppb;
+ }
+ rate = ppb;
+ rate <<= 26;
+ rate = div_u64(rate, 1953125);
+
+ hi = (rate >> 16) & PTP_RATE_HI_MASK;
+ if (neg_adj)
+ hi |= PTP_RATE_DIR;
+
+ lo = rate & 0xffff;
+
+ mutex_lock(&dp83640->extreg_mux);
+
+ ext_write(phydev, PAGE4, PTP_RATEH, hi);
+ ext_write(phydev, PAGE4, PTP_RATEL, lo);
+
+ mutex_unlock(&dp83640->extreg_mux);
+
+ return 0;
+}
+
+static int ptp_dp83640_adjtime(void *priv, struct timespec *ts)
+{
+ struct dp83640_private *dp83640 = priv;
+ int err;
+
+ mutex_lock(&dp83640->extreg_mux);
+
+ err = tdr_write(dp83640->phydev, ts, PTP_STEP_CLK);
+
+ mutex_unlock(&dp83640->extreg_mux);
+
+ return err;
+}
+
+static int ptp_dp83640_gettime(void *priv, struct timespec *ts)
+{
+ struct dp83640_private *dp83640 = priv;
+ struct phy_device *phydev = dp83640->phydev;
+ unsigned int val[4];
+
+ mutex_lock(&dp83640->extreg_mux);
+
+ ext_write(phydev, PAGE4, PTP_CTL, PTP_RD_CLK);
+
+ val[0] = ext_read(phydev, PAGE4, PTP_TDR); /* ns[15:0] */
+ val[1] = ext_read(phydev, PAGE4, PTP_TDR); /* ns[31:16] */
+ val[2] = ext_read(phydev, PAGE4, PTP_TDR); /* sec[15:0] */
+ val[3] = ext_read(phydev, PAGE4, PTP_TDR); /* sec[31:16] */
+
+ mutex_unlock(&dp83640->extreg_mux);
+
+ ts->tv_nsec = val[0] | (val[1] << 16);
+ ts->tv_sec = val[2] | (val[3] << 16);
+
+ return 0;
+}
+
+static int ptp_dp83640_settime(void *priv, struct timespec *ts)
+{
+ struct dp83640_private *dp83640 = priv;
+ int err;
+
+ mutex_lock(&dp83640->extreg_mux);
+
+ err = tdr_write(dp83640->phydev, ts, PTP_LOAD_CLK);
+
+ mutex_unlock(&dp83640->extreg_mux);
+
+ return err;
+}
+
+static int ptp_dp83640_gettimer(void *priv, int index, struct itimerspec *ts)
+{
+ /* We do not offer any ancillary timer features. */
+ return -EOPNOTSUPP;
+}
+
+static int ptp_dp83640_settimer(void *p, int i, int abs, struct itimerspec *ts)
+{
+ /* We do not offer any ancillary timer features. */
+ return -EOPNOTSUPP;
+}
+
+static int ptp_dp83640_enable(void *priv, struct ptp_clock_request *rq, int on)
+{
+ struct dp83640_private *dp83640 = priv;
+ struct phy_device *phydev = dp83640->phydev;
+ u16 evnt;
+
+ switch (rq->type) {
+ case PTP_REQUEST_EXTTS:
+ if (rq->index != 0)
+ return -EINVAL;
+ evnt = EVNT_WR | (EXT_EVENT & EVNT_SEL_MASK) << EVNT_SEL_SHIFT;
+ if (on) {
+ evnt |= (EXT_GPIO & EVNT_GPIO_MASK) << EVNT_GPIO_SHIFT;
+ evnt |= EVNT_RISE;
+ }
+ ext_write(phydev, PAGE5, PTP_EVNT, evnt);
+ return 0;
+ default:
+ break;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static struct ptp_clock_info ptp_dp83640_caps = {
+ .owner = THIS_MODULE,
+ .name = "dp83640 timer",
+ .max_adj = 1953124,
+ .n_alarm = 0,
+ .n_ext_ts = N_EXT_TS,
+ .n_per_out = 0,
+ .pps = 0,
+ .priv = NULL,
+ .adjfreq = ptp_dp83640_adjfreq,
+ .adjtime = ptp_dp83640_adjtime,
+ .gettime = ptp_dp83640_gettime,
+ .settime = ptp_dp83640_settime,
+ .gettimer = ptp_dp83640_gettimer,
+ .settimer = ptp_dp83640_settimer,
+ .enable = ptp_dp83640_enable,
+};
+
+/* status frame conditional code */
+
+#ifdef USE_STATUS_FRAMES
+
+static u8 status_frame_dst[6] = { 0x01, 0x1B, 0x19, 0x00, 0x00, 0x00 };
+static u8 status_frame_src[6] = { 0x08, 0x00, 0x17, 0x0B, 0x6B, 0x0F };
+
+static void enable_status_frames(struct phy_device *phydev, bool on)
+{
+ u16 cfg0 = 0, ver;
+
+ if (on)
+ cfg0 = PSF_EVNT_EN | PSF_RXTS_EN | PSF_TXTS_EN | ENDIAN_FLAG;
+
+ ver = (PSF_PTPVER & VERSIONPTP_MASK) << VERSIONPTP_SHIFT;
+
+ ext_write(phydev, PAGE5, PSF_CFG0, cfg0);
+ ext_write(phydev, PAGE6, PSF_CFG1, ver);
+
+ if (!phydev->attached_dev) {
+ pr_err("expected to find an attached netdevice\n");
+ BUG();
+ }
+
+ if (on) {
+ if (dev_mc_add(phydev->attached_dev, status_frame_dst))
+ pr_warning("dp83640: failed to add mc address\n");
+ } else {
+ if (dev_mc_del(phydev->attached_dev, status_frame_dst))
+ pr_warning("dp83640: failed to delete mc address\n");
+ }
+}
+
+static bool is_status_frame(struct sk_buff *skb, int type)
+{
+ struct ethhdr *h = eth_hdr(skb);
+
+ if (PTP_CLASS_V2_L2 == type &&
+ !memcmp(h->h_source, status_frame_src, sizeof(status_frame_src)))
+ return true;
+ else
+ return false;
+}
+
+static void rx_reading_work(struct dp83640_private *dp83640)
+{
+}
+
+static void tx_timestamp_work(struct dp83640_private *dp83640)
+{
+}
+
+#else /* USE_STATUS_FRAMES */
+
+static void enable_status_frames(struct phy_device *phydev, bool on)
+{
+}
+
+static bool is_status_frame(struct sk_buff *skb, int type)
+{
+ return false;
+}
+
+static void read_rxts(struct phy_device *phydev, struct rxts *rxts)
+{
+ struct phy_rxts p;
+
+ p.ns_lo = ext_read(phydev, PAGE4, PTP_RXTS);
+ p.ns_hi = ext_read(phydev, PAGE4, PTP_RXTS);
+ p.sec_lo = ext_read(phydev, PAGE4, PTP_RXTS);
+ p.sec_hi = ext_read(phydev, PAGE4, PTP_RXTS);
+ p.seqid = ext_read(phydev, PAGE4, PTP_RXTS);
+ p.msgtype = ext_read(phydev, PAGE4, PTP_RXTS);
+
+ rxts->tmo = jiffies + HZ;
+ phy2rxts(&p, rxts);
+}
+
+static u64 read_txts(struct phy_device *phydev)
+{
+ struct phy_txts p;
+
+ p.ns_lo = ext_read(phydev, PAGE4, PTP_TXTS);
+ p.ns_hi = ext_read(phydev, PAGE4, PTP_TXTS);
+ p.sec_lo = ext_read(phydev, PAGE4, PTP_TXTS);
+ p.sec_hi = ext_read(phydev, PAGE4, PTP_TXTS);
+
+ return phy2txts(&p);
+}
+
+static void rx_reading_work(struct dp83640_private *dp83640)
+{
+ struct rxts *rxts;
+
+ mutex_lock(&dp83640->extreg_mux);
+
+ for (;;) {
+ int val = ext_read(dp83640->phydev, PAGE4, PTP_STS);
+ if (!(val & RXTS_RDY))
+ break;
+ if (list_empty(&dp83640->rxpool)) {
+ pr_warning("dp83640: rx timestamp pool is empty\n");
+ break;
+ }
+ rxts = list_first_entry(&dp83640->rxpool, struct rxts, list);
+ list_del_init(&rxts->list);
+ read_rxts(dp83640->phydev, rxts);
+ list_add_tail(&rxts->list, &dp83640->rxts);
+ }
+
+ mutex_unlock(&dp83640->extreg_mux);
+}
+
+static void tx_timestamp_work(struct dp83640_private *dp83640)
+{
+ struct skb_shared_hwtstamps shhwtstamps;
+ struct sk_buff *skb;
+ u64 ns;
+ int val;
+
+ mutex_lock(&dp83640->extreg_mux);
+
+ while ((skb = skb_dequeue(&dp83640->tx_queue)) != NULL) {
+
+ val = ext_read(dp83640->phydev, PAGE4, PTP_STS);
+ if (!(val & TXTS_RDY)) {
+ skb_queue_head(&dp83640->tx_queue, skb);
+ break;
+ }
+ ns = read_txts(dp83640->phydev);
+ memset(&shhwtstamps, 0, sizeof(shhwtstamps));
+ shhwtstamps.hwtstamp = ns_to_ktime(ns);
+ skb_complete_tx_timestamp(skb, &shhwtstamps);
+ }
+
+ mutex_unlock(&dp83640->extreg_mux);
+}
+
+#endif /* !USE_STATUS_FRAMES */
+
+/* time stamping methods */
+
+static void decode_evnt(struct dp83640_private *dp83640,
+ struct phy_txts *phy_txts, u16 ests)
+{
+ struct ptp_clock_event event;
+ int words = (ests >> EVNT_TS_LEN_SHIFT) & EVNT_TS_LEN_MASK;
+
+ switch (words) { /* fall through in every case */
+ case 3:
+ dp83640->edata.sec_hi = phy_txts->sec_hi;
+ case 2:
+ dp83640->edata.sec_lo = phy_txts->sec_lo;
+ case 1:
+ dp83640->edata.ns_hi = phy_txts->ns_hi;
+ case 0:
+ dp83640->edata.ns_lo = phy_txts->ns_lo;
+ }
+
+ event.type = PTP_CLOCK_EXTTS;
+ event.index = 0;
+ event.timestamp = phy2txts(&dp83640->edata);
+
+ ptp_clock_event(dp83640_clock, &event);
+}
+
+static void decode_rxts(struct dp83640_private *dp83640,
+ struct phy_rxts *phy_rxts)
+{
+ struct rxts *rxts;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dp83640->rx_lock, flags);
+
+ if (list_empty(&dp83640->rxpool)) {
+ pr_warning("dp83640: rx timestamp pool is empty\n");
+ goto out;
+ }
+ rxts = list_first_entry(&dp83640->rxpool, struct rxts, list);
+ list_del_init(&rxts->list);
+ phy2rxts(phy_rxts, rxts);
+ list_add_tail(&rxts->list, &dp83640->rxts);
+out:
+ spin_unlock_irqrestore(&dp83640->rx_lock, flags);
+}
+
+static void decode_txts(struct dp83640_private *dp83640,
+ struct phy_txts *phy_txts)
+{
+ struct skb_shared_hwtstamps shhwtstamps;
+ struct sk_buff *skb;
+ u64 ns;
+
+ /* We must already have the skb that triggered this. */
+
+ skb = skb_dequeue(&dp83640->tx_queue);
+
+ if (!skb) {
+ pr_warning("dp83640: have timestamp but tx_queue empty\n");
+ return;
+ }
+ ns = phy2txts(phy_txts);
+ memset(&shhwtstamps, 0, sizeof(shhwtstamps));
+ shhwtstamps.hwtstamp = ns_to_ktime(ns);
+ skb_complete_tx_timestamp(skb, &shhwtstamps);
+}
+
+static void decode_status_frame(struct dp83640_private *dp83640,
+ struct sk_buff *skb)
+{
+ struct phy_rxts *phy_rxts;
+ struct phy_txts *phy_txts;
+ u8 *ptr;
+ int len, size;
+ u16 ests, type;
+
+ ptr = skb->data + 2;
+
+ for (len = skb_headlen(skb) - 2; len > sizeof(type); len -= size) {
+
+ type = *(u16 *)ptr;
+ ests = type & 0x0fff;
+ type = type & 0xf000;
+ len -= sizeof(type);
+ ptr += sizeof(type);
+
+ if (PSF_RX == type && len >= sizeof(*phy_rxts)) {
+
+ phy_rxts = (struct phy_rxts *) ptr;
+ decode_rxts(dp83640, phy_rxts);
+ size = sizeof(*phy_rxts);
+
+ } else if (PSF_TX == type && len >= sizeof(*phy_txts)) {
+
+ phy_txts = (struct phy_txts *) ptr;
+ decode_txts(dp83640, phy_txts);
+ size = sizeof(*phy_txts);
+
+ } else if (PSF_EVNT == type && len >= sizeof(*phy_txts)) {
+
+ phy_txts = (struct phy_txts *) ptr;
+ decode_evnt(dp83640, phy_txts, ests);
+ size = sizeof(*phy_txts);
+
+ } else {
+ size = 0;
+ break;
+ }
+ ptr += size;
+ }
+}
+
+static int expired(struct rxts *rxts)
+{
+ return time_after(jiffies, rxts->tmo);
+}
+
+static int match(struct sk_buff *skb, unsigned int type, struct rxts *rxts)
+{
+ u16 *seqid;
+ u8 *msgtype, *data = skb_mac_header(skb);
+
+ /* check sequenceID, messageType, 12 bit hash of offset 20-29 */
+ /* We assume that the IPv4 header has no options. */
+
+ switch (type) {
+ case PTP_CLASS_V1_IPV4:
+ msgtype = data + 42 + 32;
+ seqid = (u16 *)(data + 42 + 30);
+ break;
+ case PTP_CLASS_V1_IPV6:
+ msgtype = data + 62 + 32;
+ seqid = (u16 *)(data + 62 + 30);
+ break;
+ case PTP_CLASS_V2_IPV4:
+ msgtype = data + 42 + 0;
+ seqid = (u16 *)(data + 42 + 30);
+ break;
+ case PTP_CLASS_V2_IPV6:
+ msgtype = data + 62 + 0;
+ seqid = (u16 *)(data + 62 + 30);
+ break;
+ case PTP_CLASS_V2_L2:
+ msgtype = data + 14 + 0;
+ seqid = (u16 *)(data + 14 + 30);
+ break;
+ case PTP_CLASS_V2_VLAN:
+ msgtype = data + 18 + 0;
+ seqid = (u16 *)(data + 18 + 30);
+ break;
+ default:
+ return 0;
+ }
+
+ return ((*msgtype & 0xf) == rxts->msgtype && *seqid == rxts->seqid);
+}
+
+static int dp83640_probe(struct phy_device *phydev)
+{
+ struct dp83640_private *dp83640;
+ int i;
+
+ dp83640 = kzalloc(sizeof(struct dp83640_private), GFP_KERNEL);
+ if (!dp83640)
+ return -ENOMEM;
+
+ dp83640->phydev = phydev;
+ INIT_WORK(&dp83640->ts_work, do_timestamp_work);
+ mutex_init(&dp83640->extreg_mux);
+
+ INIT_LIST_HEAD(&dp83640->rxts);
+ INIT_LIST_HEAD(&dp83640->rxpool);
+ for (i = 0; i < MAX_RXTS; i++)
+ list_add(&dp83640->rx_pool_data[i].list, &dp83640->rxpool);
+
+ phydev->priv = dp83640;
+
+ spin_lock_init(&dp83640->rx_lock);
+ skb_queue_head_init(&dp83640->rx_queue);
+ skb_queue_head_init(&dp83640->tx_queue);
+
+ mutex_lock(&clock_lock);
+
+ if (!dp83640_clock) {
+ ptp_dp83640_caps.priv = dp83640;
+ dp83640_clock = ptp_clock_register(&ptp_dp83640_caps);
+ if (IS_ERR(dp83640_clock)) {
+ mutex_unlock(&clock_lock);
+ kfree(dp83640);
+ return PTR_ERR(dp83640_clock);
+ }
+ }
+ mutex_unlock(&clock_lock);
+
+ return 0;
+}
+
+static void dp83640_remove(struct phy_device *phydev)
+{
+ struct dp83640_private *dp83640 = phydev->priv;
+
+ enable_status_frames(phydev, false);
+
+ cancel_work_sync(&dp83640->ts_work);
+
+ mutex_lock(&clock_lock);
+
+ if (ptp_dp83640_caps.priv == dp83640) {
+ ptp_clock_unregister(dp83640_clock);
+ dp83640_clock = NULL;
+ ptp_dp83640_caps.priv = NULL;
+ }
+ mutex_unlock(&clock_lock);
+
+ mutex_destroy(&dp83640->extreg_lock);
+
+ kfree(dp83640);
+}
+
+static int dp83640_hwtstamp(struct phy_device *phydev, struct ifreq *ifr)
+{
+ struct dp83640_private *dp83640 = phydev->priv;
+ struct hwtstamp_config cfg;
+ u16 txcfg0, rxcfg0;
+
+ if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg)))
+ return -EFAULT;
+
+ if (cfg.flags) /* reserved for future extensions */
+ return -EINVAL;
+
+ switch (cfg.tx_type) {
+ case HWTSTAMP_TX_OFF:
+ dp83640->hwts_tx_en = 0;
+ break;
+ case HWTSTAMP_TX_ON:
+ dp83640->hwts_tx_en = 1;
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ switch (cfg.rx_filter) {
+ case HWTSTAMP_FILTER_NONE:
+ dp83640->hwts_rx_en = 0;
+ dp83640->layer = 0;
+ dp83640->version = 0;
+ break;
+ case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
+ case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
+ case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
+ dp83640->hwts_rx_en = 1;
+ dp83640->layer = LAYER4;
+ dp83640->version = 1;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
+ dp83640->hwts_rx_en = 1;
+ dp83640->layer = LAYER4;
+ dp83640->version = 2;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
+ dp83640->hwts_rx_en = 1;
+ dp83640->layer = LAYER2;
+ dp83640->version = 2;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
+ dp83640->hwts_rx_en = 1;
+ dp83640->layer = LAYER4|LAYER2;
+ dp83640->version = 2;
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ txcfg0 = (dp83640->version & TX_PTP_VER_MASK) << TX_PTP_VER_SHIFT;
+ rxcfg0 = (dp83640->version & TX_PTP_VER_MASK) << TX_PTP_VER_SHIFT;
+
+ if (dp83640->layer & LAYER2) {
+ txcfg0 |= TX_L2_EN;
+ rxcfg0 |= RX_L2_EN;
+ }
+ if (dp83640->layer & LAYER4) {
+ txcfg0 |= TX_IPV6_EN | TX_IPV4_EN;
+ rxcfg0 |= RX_IPV6_EN | RX_IPV4_EN;
+ }
+
+ if (dp83640->hwts_tx_en)
+ txcfg0 |= TX_TS_EN;
+
+ if (dp83640->hwts_rx_en)
+ rxcfg0 |= RX_TS_EN;
+
+ mutex_lock(&dp83640->extreg_mux);
+
+ if (dp83640->hwts_tx_en || dp83640->hwts_rx_en) {
+ enable_status_frames(phydev, true);
+ ext_write(phydev, PAGE4, PTP_CTL, PTP_ENABLE);
+ }
+
+ ext_write(phydev, PAGE5, PTP_TXCFG0, txcfg0);
+ ext_write(phydev, PAGE5, PTP_RXCFG0, rxcfg0);
+
+ mutex_unlock(&dp83640->extreg_mux);
+
+ return copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg)) ? -EFAULT : 0;
+}
+
+static void rx_timestamp_work(struct dp83640_private *dp83640)
+{
+ struct list_head *this, *next;
+ struct rxts *rxts;
+ struct skb_shared_hwtstamps *shhwtstamps;
+ struct sk_buff *skb;
+ unsigned int type;
+ unsigned long flags;
+
+ /* Deliver each deferred packet, with or without a time stamp. */
+
+ while ((skb = skb_dequeue(&dp83640->rx_queue)) != NULL) {
+ type = SKB_PTP_TYPE(skb);
+ spin_lock_irqsave(&dp83640->rx_lock, flags);
+ list_for_each_safe(this, next, &dp83640->rxts) {
+ rxts = list_entry(this, struct rxts, list);
+ if (match(skb, type, rxts)) {
+ shhwtstamps = skb_hwtstamps(skb);
+ memset(shhwtstamps, 0, sizeof(*shhwtstamps));
+ shhwtstamps->hwtstamp = ns_to_ktime(rxts->ns);
+ list_del_init(&rxts->list);
+ list_add(&rxts->list, &dp83640->rxpool);
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&dp83640->rx_lock, flags);
+ netif_rx(skb);
+ }
+
+ /* Clear out expired time stamps. */
+
+ spin_lock_irqsave(&dp83640->rx_lock, flags);
+ list_for_each_safe(this, next, &dp83640->rxts) {
+ rxts = list_entry(this, struct rxts, list);
+ if (expired(rxts)) {
+ list_del_init(&rxts->list);
+ list_add(&rxts->list, &dp83640->rxpool);
+ }
+ }
+ spin_unlock_irqrestore(&dp83640->rx_lock, flags);
+}
+
+static void do_timestamp_work(struct work_struct *work)
+{
+ struct dp83640_private *dp83640 =
+ container_of(work, struct dp83640_private, ts_work);
+
+ rx_reading_work(dp83640);
+ rx_timestamp_work(dp83640);
+ tx_timestamp_work(dp83640);
+}
+
+static bool dp83640_rxtstamp(struct phy_device *phydev,
+ struct sk_buff *skb, int type)
+{
+ struct dp83640_private *dp83640 = phydev->priv;
+
+ if (!dp83640->hwts_rx_en)
+ return false;
+
+ if (is_status_frame(skb, type)) {
+ decode_status_frame(dp83640, skb);
+ /* Let the stack drop this frame. */
+ return false;
+ }
+
+ SKB_PTP_TYPE(skb) = type;
+ skb_queue_tail(&dp83640->rx_queue, skb);
+ schedule_work(&dp83640->ts_work);
+
+ return true;
+}
+
+static void dp83640_txtstamp(struct phy_device *phydev,
+ struct sk_buff *skb, int type)
+{
+ struct dp83640_private *dp83640 = phydev->priv;
+
+ if (!dp83640->hwts_tx_en) {
+ kfree_skb(skb);
+ return;
+ }
+ skb_queue_tail(&dp83640->tx_queue, skb);
+ schedule_work(&dp83640->ts_work);
+}
+
+static struct phy_driver dp83640_driver = {
+ .phy_id = DP83640_PHY_ID,
+ .phy_id_mask = 0xfffffff0,
+ .name = "NatSemi DP83640",
+ .features = PHY_BASIC_FEATURES,
+ .flags = 0,
+ .probe = dp83640_probe,
+ .remove = dp83640_remove,
+ .config_aneg = genphy_config_aneg,
+ .read_status = genphy_read_status,
+ .hwtstamp = dp83640_hwtstamp,
+ .rxtstamp = dp83640_rxtstamp,
+ .txtstamp = dp83640_txtstamp,
+ .driver = {.owner = THIS_MODULE,}
+};
+
+static int __init dp83640_init(void)
+{
+ return phy_driver_register(&dp83640_driver);
+}
+
+static void __exit dp83640_exit(void)
+{
+ phy_driver_unregister(&dp83640_driver);
+}
+
+MODULE_DESCRIPTION("National Semiconductor DP83640 PHY driver");
+MODULE_AUTHOR("Richard Cochran <richard.cochran@omicron.at>");
+MODULE_LICENSE("GPL");
+
+module_init(dp83640_init);
+module_exit(dp83640_exit);
+
+static struct mdio_device_id dp83640_tbl[] = {
+ { DP83640_PHY_ID, 0xfffffff0 },
+ { }
+};
+
+MODULE_DEVICE_TABLE(mdio, dp83640_tbl);
diff --git a/drivers/net/phy/dp83640_reg.h b/drivers/net/phy/dp83640_reg.h
new file mode 100644
index 0000000..9d34bb6
--- /dev/null
+++ b/drivers/net/phy/dp83640_reg.h
@@ -0,0 +1,261 @@
+/* dp83640_reg.h
+ * Generated by regen.tcl on Fri Jul 23 09:45:18 AM CEST 2010
+ */
+#ifndef HAVE_DP83640_REGISTERS
+#define HAVE_DP83640_REGISTERS
+
+#define PAGE4 0x0004
+#define PTP_CTL 0x0014 /* PTP Control Register */
+#define PTP_TDR 0x0015 /* PTP Time Data Register */
+#define PTP_STS 0x0016 /* PTP Status Register */
+#define PTP_TSTS 0x0017 /* PTP Trigger Status Register */
+#define PTP_RATEL 0x0018 /* PTP Rate Low Register */
+#define PTP_RATEH 0x0019 /* PTP Rate High Register */
+#define PTP_RDCKSUM 0x001a /* PTP Read Checksum */
+#define PTP_WRCKSUM 0x001b /* PTP Write Checksum */
+#define PTP_TXTS 0x001c /* PTP Transmit Timestamp Register, in four 16-bit reads */
+#define PTP_RXTS 0x001d /* PTP Receive Timestamp Register, in six? 16-bit reads */
+#define PTP_ESTS 0x001e /* PTP Event Status Register */
+#define PTP_EDATA 0x001f /* PTP Event Data Register */
+
+#define PAGE5 0x0005
+#define PTP_TRIG 0x0014 /* PTP Trigger Configuration Register */
+#define PTP_EVNT 0x0015 /* PTP Event Configuration Register */
+#define PTP_TXCFG0 0x0016 /* PTP Transmit Configuration Register 0 */
+#define PTP_TXCFG1 0x0017 /* PTP Transmit Configuration Register 1 */
+#define PSF_CFG0 0x0018 /* PHY Status Frame Configuration Register 0 */
+#define PTP_RXCFG0 0x0019 /* PTP Receive Configuration Register 0 */
+#define PTP_RXCFG1 0x001a /* PTP Receive Configuration Register 1 */
+#define PTP_RXCFG2 0x001b /* PTP Receive Configuration Register 2 */
+#define PTP_RXCFG3 0x001c /* PTP Receive Configuration Register 3 */
+#define PTP_RXCFG4 0x001d /* PTP Receive Configuration Register 4 */
+#define PTP_TRDL 0x001e /* PTP Temporary Rate Duration Low Register */
+#define PTP_TRDH 0x001f /* PTP Temporary Rate Duration High Register */
+
+#define PAGE6 0x0006
+#define PTP_COC 0x0014 /* PTP Clock Output Control Register */
+#define PSF_CFG1 0x0015 /* PHY Status Frame Configuration Register 1 */
+#define PSF_CFG2 0x0016 /* PHY Status Frame Configuration Register 2 */
+#define PSF_CFG3 0x0017 /* PHY Status Frame Configuration Register 3 */
+#define PSF_CFG4 0x0018 /* PHY Status Frame Configuration Register 4 */
+#define PTP_SFDCFG 0x0019 /* PTP SFD Configuration Register */
+#define PTP_INTCTL 0x001a /* PTP Interrupt Control Register */
+#define PTP_CLKSRC 0x001b /* PTP Clock Source Register */
+#define PTP_ETR 0x001c /* PTP Ethernet Type Register */
+#define PTP_OFF 0x001d /* PTP Offset Register */
+#define PTP_GPIOMON 0x001e /* PTP GPIO Monitor Register */
+#define PTP_RXHASH 0x001f /* PTP Receive Hash Register */
+
+/* Bit definitions for the PTP_CTL register */
+#define TRIG_SEL_SHIFT (10) /* PTP Trigger Select */
+#define TRIG_SEL_MASK (0x7)
+#define TRIG_DIS (1<<9) /* Disable PTP Trigger */
+#define TRIG_EN (1<<8) /* Enable PTP Trigger */
+#define TRIG_READ (1<<7) /* Read PTP Trigger */
+#define TRIG_LOAD (1<<6) /* Load PTP Trigger */
+#define PTP_RD_CLK (1<<5) /* Read PTP Clock */
+#define PTP_LOAD_CLK (1<<4) /* Load PTP Clock */
+#define PTP_STEP_CLK (1<<3) /* Step PTP Clock */
+#define PTP_ENABLE (1<<2) /* Enable PTP Clock */
+#define PTP_DISABLE (1<<1) /* Disable PTP Clock */
+#define PTP_RESET (1<<0) /* Reset PTP Clock */
+
+/* Bit definitions for the PTP_STS register */
+#define TXTS_RDY (1<<11) /* Transmit Timestamp Ready */
+#define RXTS_RDY (1<<10) /* Receive Timestamp Ready */
+#define TRIG_DONE (1<<9) /* PTP Trigger Done */
+#define EVENT_RDY (1<<8) /* PTP Event Timestamp Ready */
+#define TXTS_IE (1<<3) /* Transmit Timestamp Interrupt Enable */
+#define RXTS_IE (1<<2) /* Receive Timestamp Interrupt Enable */
+#define TRIG_IE (1<<1) /* Trigger Interrupt Enable */
+#define EVENT_IE (1<<0) /* Event Interrupt Enable */
+
+/* Bit definitions for the PTP_TSTS register */
+#define TRIG7_ERROR (1<<15) /* Trigger 7 Error */
+#define TRIG7_ACTIVE (1<<14) /* Trigger 7 Active */
+#define TRIG6_ERROR (1<<13) /* Trigger 6 Error */
+#define TRIG6_ACTIVE (1<<12) /* Trigger 6 Active */
+#define TRIG5_ERROR (1<<11) /* Trigger 5 Error */
+#define TRIG5_ACTIVE (1<<10) /* Trigger 5 Active */
+#define TRIG4_ERROR (1<<9) /* Trigger 4 Error */
+#define TRIG4_ACTIVE (1<<8) /* Trigger 4 Active */
+#define TRIG3_ERROR (1<<7) /* Trigger 3 Error */
+#define TRIG3_ACTIVE (1<<6) /* Trigger 3 Active */
+#define TRIG2_ERROR (1<<5) /* Trigger 2 Error */
+#define TRIG2_ACTIVE (1<<4) /* Trigger 2 Active */
+#define TRIG1_ERROR (1<<3) /* Trigger 1 Error */
+#define TRIG1_ACTIVE (1<<2) /* Trigger 1 Active */
+#define TRIG0_ERROR (1<<1) /* Trigger 0 Error */
+#define TRIG0_ACTIVE (1<<0) /* Trigger 0 Active */
+
+/* Bit definitions for the PTP_RATEH register */
+#define PTP_RATE_DIR (1<<15) /* PTP Rate Direction */
+#define PTP_TMP_RATE (1<<14) /* PTP Temporary Rate */
+#define PTP_RATE_HI_SHIFT (0) /* PTP Rate High 10-bits */
+#define PTP_RATE_HI_MASK (0x3ff)
+
+/* Bit definitions for the PTP_ESTS register */
+#define EVNTS_MISSED_SHIFT (8) /* Indicates number of events missed */
+#define EVNTS_MISSED_MASK (0x7)
+#define EVNT_TS_LEN_SHIFT (6) /* Indicates length of the Timestamp field in 16-bit words minus 1 */
+#define EVNT_TS_LEN_MASK (0x3)
+#define EVNT_RF (1<<5) /* Indicates whether the event is a rise or falling event */
+#define EVNT_NUM_SHIFT (2) /* Indicates Event Timestamp Unit which detected an event */
+#define EVNT_NUM_MASK (0x7)
+#define MULT_EVNT (1<<1) /* Indicates multiple events were detected at the same time */
+#define EVENT_DET (1<<0) /* PTP Event Detected */
+
+/* Bit definitions for the PTP_EDATA register */
+#define E7_RISE (1<<15) /* Indicates direction of Event 7 */
+#define E7_DET (1<<14) /* Indicates Event 7 detected */
+#define E6_RISE (1<<13) /* Indicates direction of Event 6 */
+#define E6_DET (1<<12) /* Indicates Event 6 detected */
+#define E5_RISE (1<<11) /* Indicates direction of Event 5 */
+#define E5_DET (1<<10) /* Indicates Event 5 detected */
+#define E4_RISE (1<<9) /* Indicates direction of Event 4 */
+#define E4_DET (1<<8) /* Indicates Event 4 detected */
+#define E3_RISE (1<<7) /* Indicates direction of Event 3 */
+#define E3_DET (1<<6) /* Indicates Event 3 detected */
+#define E2_RISE (1<<5) /* Indicates direction of Event 2 */
+#define E2_DET (1<<4) /* Indicates Event 2 detected */
+#define E1_RISE (1<<3) /* Indicates direction of Event 1 */
+#define E1_DET (1<<2) /* Indicates Event 1 detected */
+#define E0_RISE (1<<1) /* Indicates direction of Event 0 */
+#define E0_DET (1<<0) /* Indicates Event 0 detected */
+
+/* Bit definitions for the PTP_TRIG register */
+#define TRIG_PULSE (1<<15) /* generate a Pulse rather than a single edge */
+#define TRIG_PER (1<<14) /* generate a periodic signal */
+#define TRIG_IF_LATE (1<<13) /* trigger immediately if already past */
+#define TRIG_NOTIFY (1<<12) /* Trigger Notification Enable */
+#define TRIG_GPIO_SHIFT (8) /* Trigger GPIO Connection, value 1-12 */
+#define TRIG_GPIO_MASK (0xf)
+#define TRIG_TOGGLE (1<<7) /* Trigger Toggle Mode Enable */
+#define TRIG_CSEL_SHIFT (1) /* Trigger Configuration Select */
+#define TRIG_CSEL_MASK (0x7)
+#define TRIG_WR (1<<0) /* Trigger Configuration Write */
+
+/* Bit definitions for the PTP_EVNT register */
+#define EVNT_RISE (1<<14) /* Event Rise Detect Enable */
+#define EVNT_FALL (1<<13) /* Event Fall Detect Enable */
+#define EVNT_SINGLE (1<<12) /* enable single event capture operation */
+#define EVNT_GPIO_SHIFT (8) /* Event GPIO Connection, value 1-12 */
+#define EVNT_GPIO_MASK (0xf)
+#define EVNT_SEL_SHIFT (1) /* Event Select */
+#define EVNT_SEL_MASK (0x7)
+#define EVNT_WR (1<<0) /* Event Configuration Write */
+
+/* Bit definitions for the PTP_TXCFG0 register */
+#define SYNC_1STEP (1<<15) /* insert timestamp into transmit Sync Messages */
+#define DR_INSERT (1<<13) /* Insert Delay_Req Timestamp in Delay_Resp (dangerous) */
+#define NTP_TS_EN (1<<12) /* Enable Timestamping of NTP Packets */
+#define IGNORE_2STEP (1<<11) /* Ignore Two_Step flag for One-Step operation */
+#define CRC_1STEP (1<<10) /* Disable checking of CRC for One-Step operation */
+#define CHK_1STEP (1<<9) /* Enable UDP Checksum correction for One-Step Operation */
+#define IP1588_EN (1<<8) /* Enable IEEE 1588 defined IP address filter */
+#define TX_L2_EN (1<<7) /* Layer2 Timestamp Enable */
+#define TX_IPV6_EN (1<<6) /* IPv6 Timestamp Enable */
+#define TX_IPV4_EN (1<<5) /* IPv4 Timestamp Enable */
+#define TX_PTP_VER_SHIFT (1) /* Enable Timestamp capture for IEEE 1588 version X */
+#define TX_PTP_VER_MASK (0xf)
+#define TX_TS_EN (1<<0) /* Transmit Timestamp Enable */
+
+/* Bit definitions for the PTP_TXCFG1 register */
+#define BYTE0_MASK_SHIFT (8) /* Bit mask to be used for matching Byte0 of the PTP Message */
+#define BYTE0_MASK_MASK (0xff)
+#define BYTE0_DATA_SHIFT (0) /* Data to be used for matching Byte0 of the PTP Message */
+#define BYTE0_DATA_MASK (0xff)
+
+/* Bit definitions for the PSF_CFG0 register */
+#define MAC_SRC_ADD_SHIFT (11) /* Status Frame Mac Source Address */
+#define MAC_SRC_ADD_MASK (0x3)
+#define MIN_PRE_SHIFT (8) /* Status Frame Minimum Preamble */
+#define MIN_PRE_MASK (0x7)
+#define PSF_ENDIAN (1<<7) /* Status Frame Endian Control */
+#define PSF_IPV4 (1<<6) /* Status Frame IPv4 Enable */
+#define PSF_PCF_RD (1<<5) /* Control Frame Read PHY Status Frame Enable */
+#define PSF_ERR_EN (1<<4) /* Error PHY Status Frame Enable */
+#define PSF_TXTS_EN (1<<3) /* Transmit Timestamp PHY Status Frame Enable */
+#define PSF_RXTS_EN (1<<2) /* Receive Timestamp PHY Status Frame Enable */
+#define PSF_TRIG_EN (1<<1) /* Trigger PHY Status Frame Enable */
+#define PSF_EVNT_EN (1<<0) /* Event PHY Status Frame Enable */
+
+/* Bit definitions for the PTP_RXCFG0 register */
+#define DOMAIN_EN (1<<15) /* Domain Match Enable */
+#define ALT_MAST_DIS (1<<14) /* Alternate Master Timestamp Disable */
+#define USER_IP_SEL (1<<13) /* Selects portion of IP address accessible thru PTP_RXCFG2 */
+#define USER_IP_EN (1<<12) /* Enable User-programmed IP address filter */
+#define RX_SLAVE (1<<11) /* Receive Slave Only */
+#define IP1588_EN_SHIFT (8) /* Enable IEEE 1588 defined IP address filters */
+#define IP1588_EN_MASK (0xf)
+#define RX_L2_EN (1<<7) /* Layer2 Timestamp Enable */
+#define RX_IPV6_EN (1<<6) /* IPv6 Timestamp Enable */
+#define RX_IPV4_EN (1<<5) /* IPv4 Timestamp Enable */
+#define RX_PTP_VER_SHIFT (1) /* Enable Timestamp capture for IEEE 1588 version X */
+#define RX_PTP_VER_MASK (0xf)
+#define RX_TS_EN (1<<0) /* Receive Timestamp Enable */
+
+/* Bit definitions for the PTP_RXCFG1 register */
+#define BYTE0_MASK_SHIFT (8) /* Bit mask to be used for matching Byte0 of the PTP Message */
+#define BYTE0_MASK_MASK (0xff)
+#define BYTE0_DATA_SHIFT (0) /* Data to be used for matching Byte0 of the PTP Message */
+#define BYTE0_DATA_MASK (0xff)
+
+/* Bit definitions for the PTP_RXCFG3 register */
+#define TS_MIN_IFG_SHIFT (12) /* Minimum Inter-frame Gap */
+#define TS_MIN_IFG_MASK (0xf)
+#define ACC_UDP (1<<11) /* Record Timestamp if UDP Checksum Error */
+#define ACC_CRC (1<<10) /* Record Timestamp if CRC Error */
+#define TS_APPEND (1<<9) /* Append Timestamp for L2 */
+#define TS_INSERT (1<<8) /* Enable Timestamp Insertion */
+#define PTP_DOMAIN_SHIFT (0) /* PTP Message domainNumber field */
+#define PTP_DOMAIN_MASK (0xff)
+
+/* Bit definitions for the PTP_RXCFG4 register */
+#define IPV4_UDP_MOD (1<<15) /* Enable IPV4 UDP Modification */
+#define TS_SEC_EN (1<<14) /* Enable Timestamp Seconds */
+#define TS_SEC_LEN_SHIFT (12) /* Inserted Timestamp Seconds Length */
+#define TS_SEC_LEN_MASK (0x3)
+#define RXTS_NS_OFF_SHIFT (6) /* Receive Timestamp Nanoseconds offset */
+#define RXTS_NS_OFF_MASK (0x3f)
+#define RXTS_SEC_OFF_SHIFT (0) /* Receive Timestamp Seconds offset */
+#define RXTS_SEC_OFF_MASK (0x3f)
+
+/* Bit definitions for the PTP_COC register */
+#define PTP_CLKOUT_EN (1<<15) /* PTP Clock Output Enable */
+#define PTP_CLKOUT_SEL (1<<14) /* PTP Clock Output Source Select */
+#define PTP_CLKOUT_SPEEDSEL (1<<13) /* PTP Clock Output I/O Speed Select */
+#define PTP_CLKDIV_SHIFT (0) /* PTP Clock Divide-by Value */
+#define PTP_CLKDIV_MASK (0xff)
+
+/* Bit definitions for the PSF_CFG1 register */
+#define PTPRESERVED_SHIFT (12) /* PTP v2 reserved field */
+#define PTPRESERVED_MASK (0xf)
+#define VERSIONPTP_SHIFT (8) /* PTP v2 versionPTP field */
+#define VERSIONPTP_MASK (0xf)
+#define TRANSPORT_SPECIFIC_SHIFT (4) /* PTP v2 Header transportSpecific field */
+#define TRANSPORT_SPECIFIC_MASK (0xf)
+#define MESSAGETYPE_SHIFT (0) /* PTP v2 messageType field */
+#define MESSAGETYPE_MASK (0xf)
+
+/* Bit definitions for the PTP_SFDCFG register */
+#define TX_SFD_GPIO_SHIFT (4) /* TX SFD GPIO Select, value 1-12 */
+#define TX_SFD_GPIO_MASK (0xf)
+#define RX_SFD_GPIO_SHIFT (0) /* RX SFD GPIO Select, value 1-12 */
+#define RX_SFD_GPIO_MASK (0xf)
+
+/* Bit definitions for the PTP_INTCTL register */
+#define PTP_INT_GPIO_SHIFT (0) /* PTP Interrupt GPIO Select */
+#define PTP_INT_GPIO_MASK (0xf)
+
+/* Bit definitions for the PTP_CLKSRC register */
+#define CLK_SRC_SHIFT (14) /* PTP Clock Source Select */
+#define CLK_SRC_MASK (0x3)
+#define CLK_SRC_PER_SHIFT (0) /* PTP Clock Source Period */
+#define CLK_SRC_PER_MASK (0x7f)
+
+/* Bit definitions for the PTP_OFF register */
+#define PTP_OFFSET_SHIFT (0) /* PTP Message offset from preceding header */
+#define PTP_OFFSET_MASK (0xff)
+
+#endif
--
1.7.0.4
^ permalink raw reply related [flat|nested] 56+ messages in thread