--- v6
+++ v12
@@ -1,202 +1,737 @@
-This patch adds a 'timesource' class into sysfs. Each registered POSIX
-clock appears by name under /sys/class/timesource. The idea is to
-expose to user space the dynamic mapping between clock devices and
-clock IDs.
+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>
+Signed-off-by: Richard Cochran <richard.cochran-3mrvs1K0uXizZXS1Dc/lvw@public.gmane.org>
+Acked-by: John Stultz <johnstul-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>
---
- Documentation/ABI/testing/sysfs-timesource | 24 ++++++++++++++++
- drivers/char/mmtimer.c | 1 +
- include/linux/posix-timers.h | 4 +++
- kernel/posix-cpu-timers.c | 2 +
- kernel/posix-timers.c | 40 ++++++++++++++++++++++++++++
- 5 files changed, 71 insertions(+), 0 deletions(-)
- create mode 100644 Documentation/ABI/testing/sysfs-timesource
+ arch/arm/mach-ixp4xx/include/mach/ixp46x_ts.h | 78 ++++++
+ drivers/net/arm/ixp4xx_eth.c | 192 ++++++++++++++-
+ drivers/ptp/Kconfig | 13 +
+ drivers/ptp/Makefile | 1 +
+ drivers/ptp/ptp_ixp46x.c | 332 +++++++++++++++++++++++++
+ 5 files changed, 613 insertions(+), 3 deletions(-)
+ create mode 100644 arch/arm/mach-ixp4xx/include/mach/ixp46x_ts.h
+ create mode 100644 drivers/ptp/ptp_ixp46x.c
-diff --git a/Documentation/ABI/testing/sysfs-timesource b/Documentation/ABI/testing/sysfs-timesource
+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..f991de2
+index 0000000..292d55e
--- /dev/null
-+++ b/Documentation/ABI/testing/sysfs-timesource
-@@ -0,0 +1,24 @@
-+What: /sys/class/timesource/
-+Date: September 2010
-+Contact: Richard Cochran <richardcochran@gmail.com>
-+Description:
-+ This directory contains files and directories
-+ providing a standardized interface to the available
-+ time sources.
-+
-+What: /sys/class/timesource/<name>/
-+Date: September 2010
-+Contact: Richard Cochran <richardcochran@gmail.com>
-+Description:
-+ This directory contains the attributes of a time
-+ source registered with the POSIX clock subsystem.
-+
-+What: /sys/class/timesource/<name>/id
-+Date: September 2010
-+Contact: Richard Cochran <richardcochran@gmail.com>
-+Description:
-+ This file contains the clock ID (a non-negative
-+ integer) of the named time source registered with the
-+ POSIX clock subsystem. This value may be passed as the
-+ first argument to the POSIX clock and timer system
-+ calls. See man CLOCK_GETRES(2) and TIMER_CREATE(2).
-diff --git a/drivers/char/mmtimer.c b/drivers/char/mmtimer.c
-index ea7c99f..e9173e3 100644
---- a/drivers/char/mmtimer.c
-+++ b/drivers/char/mmtimer.c
-@@ -758,6 +758,7 @@ static int sgi_timer_set(struct k_itimer *timr, int flags,
++++ 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 tx_snap_lo; /* 0x48 Transmit Snapshot Low Register */
++ u32 tx_snap_hi; /* 0x4C Transmit Snapshot High Register */
++ u32 rx_snap_lo; /* 0x50 Receive Snapshot Low Register */
++ u32 rx_snap_hi; /* 0x54 Receive Snapshot High Register */
++ u32 src_uuid_lo; /* 0x58 Source UUID0 Low Register */
++ u32 src_uuid_hi; /* 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 rsystime_hi; /* 0x1C RawSystemTime_High Register */
++ u32 systime_lo; /* 0x20 SystemTime_Low Register */
++ u32 systime_hi; /* 0x24 SystemTime_High Register */
++ u32 trgt_lo; /* 0x28 TargetTime_Low Register */
++ u32 trgt_hi; /* 0x2C TargetTime_High Register */
++ u32 asms_lo; /* 0x30 Auxiliary Slave Mode Snapshot Low */
++ u32 asms_hi; /* 0x34 Auxiliary Slave Mode Snapshot High */
++ u32 amms_lo; /* 0x38 Auxiliary Master Mode Snapshot Low */
++ u32 amms_hi; /* 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 9eb9b98..fa08c17 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,10 @@
+ #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) NPE_ID(p->id)
++
+ /* TX Control Registers */
+ #define TX_CNTRL0_TX_EN 0x01
+ #define TX_CNTRL0_HALFDUPLEX 0x02
+@@ -171,6 +178,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 +255,169 @@ 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);
++
++ 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].src_uuid_lo);
++ hi = __raw_readl(®s->channel[ch].src_uuid_hi);
++
++ uid = hi & 0xffff;
++ seq = (hi >> 16) & 0xffff;
++
++ if (!match(skb, htons(uid), htonl(lo), htons(seq)))
++ goto out;
++
++ lo = __raw_readl(®s->channel[ch].rx_snap_lo);
++ hi = __raw_readl(®s->channel[ch].rx_snap_hi);
++ 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)
++{
++ struct skb_shared_hwtstamps shhwtstamps;
++ struct ixp46x_ts_regs *regs;
++ struct skb_shared_info *shtx;
++ u64 ns;
++ u32 ch, cnt, hi, lo, val;
++
++ shtx = skb_shinfo(skb);
++ if (unlikely(shtx->tx_flags & SKBTX_HW_TSTAMP && port->hwts_tx_en))
++ shtx->tx_flags |= SKBTX_IN_PROGRESS;
++ 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->tx_flags &= ~SKBTX_IN_PROGRESS;
++ return;
++ }
++
++ lo = __raw_readl(®s->channel[ch].tx_snap_lo);
++ hi = __raw_readl(®s->channel[ch].tx_snap_hi);
++ 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);
++}
++
++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 +745,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;
+@@ -679,14 +852,12 @@ static int eth_xmit(struct sk_buff *skb, struct net_device *dev)
+ return NETDEV_TX_OK;
+ }
+ memcpy_swab32(mem, (u32 *)((int)skb->data & ~3), bytes / 4);
+- dev_kfree_skb(skb);
+ #endif
+
+ phys = dma_map_single(&dev->dev, mem, bytes, DMA_TO_DEVICE);
+ if (dma_mapping_error(&dev->dev, phys)) {
+-#ifdef __ARMEB__
+ dev_kfree_skb(skb);
+-#else
++#ifndef __ARMEB__
+ kfree(mem);
+ #endif
+ dev->stats.tx_dropped++;
+@@ -728,6 +899,13 @@ 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);
++
++#ifndef __ARMEB__
++ dev_kfree_skb(skb);
++#endif
+ return NETDEV_TX_OK;
}
- static struct k_clock sgi_clock = {
-+ .name = "sgi_cycle",
- .res = 0,
- .clock_set = sgi_clock_set,
- .clock_get = sgi_clock_get,
-diff --git a/include/linux/posix-timers.h b/include/linux/posix-timers.h
-index 08aa4da..64e6fee 100644
---- a/include/linux/posix-timers.h
-+++ b/include/linux/posix-timers.h
-@@ -67,7 +67,11 @@ struct k_itimer {
- } it;
- };
-
-+#define KCLOCK_MAX_NAME 32
-+
- struct k_clock {
-+ char name[KCLOCK_MAX_NAME];
-+ struct device *dev;
- clockid_t id;
- int res; /* in nanoseconds */
- int (*clock_getres) (const clockid_t which_clock, struct timespec *tp);
-diff --git a/kernel/posix-cpu-timers.c b/kernel/posix-cpu-timers.c
-index e1c2e7b..df9cbab 100644
---- a/kernel/posix-cpu-timers.c
-+++ b/kernel/posix-cpu-timers.c
-@@ -1611,6 +1611,7 @@ static long thread_cpu_nsleep_restart(struct restart_block *restart_block)
- static __init int init_posix_cpu_timers(void)
- {
- struct k_clock process = {
-+ .name = "process_cputime",
- .clock_getres = process_cpu_clock_getres,
- .clock_get = process_cpu_clock_get,
- .clock_set = do_posix_clock_nosettime,
-@@ -1619,6 +1620,7 @@ static __init int init_posix_cpu_timers(void)
- .nsleep_restart = process_cpu_nsleep_restart,
- };
- struct k_clock thread = {
-+ .name = "thread_cputime",
- .clock_getres = thread_cpu_clock_getres,
- .clock_get = thread_cpu_clock_get,
- .clock_set = do_posix_clock_nosettime,
-diff --git a/kernel/posix-timers.c b/kernel/posix-timers.c
-index 67fba5c..719aa11 100644
---- a/kernel/posix-timers.c
-+++ b/kernel/posix-timers.c
-@@ -46,6 +46,7 @@
- #include <linux/wait.h>
- #include <linux/workqueue.h>
- #include <linux/module.h>
+@@ -783,6 +961,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 +1352,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 12eb844..fcfafd0 100644
+--- a/drivers/ptp/Kconfig
++++ b/drivers/ptp/Kconfig
+@@ -40,4 +40,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 480e2af..f6933e8 100644
+--- a/drivers/ptp/Makefile
++++ b/drivers/ptp/Makefile
+@@ -4,3 +4,4 @@
+
+ ptp-y := ptp_clock.o ptp_chardev.o ptp_sysfs.o
+ obj-$(CONFIG_PTP_1588_CLOCK) += ptp.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..e78545f
+--- /dev/null
++++ b/drivers/ptp/ptp_ixp46x.c
+@@ -0,0 +1,332 @@
++/*
++ * 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>
-
- /*
- * Management arrays for POSIX timers. Timers are kept in slab memory
-@@ -135,6 +136,8 @@ static struct k_clock posix_clocks[MAX_CLOCKS];
- static DECLARE_BITMAP(clocks_map, MAX_CLOCKS);
- static DEFINE_MUTEX(clocks_mux); /* protects 'posix_clocks' and 'clocks_map' */
-
-+static struct class *timesource_class;
-+
- /*
- * These ones are defined below.
- */
-@@ -271,20 +274,40 @@ static int posix_get_coarse_res(const clockid_t which_clock, struct timespec *tp
- *tp = ktime_to_timespec(KTIME_LOW_RES);
- return 0;
- }
++#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;
++ struct ptp_clock_info caps;
++ int exts0_enabled;
++ int exts1_enabled;
++};
++
++DEFINE_SPINLOCK(register_lock);
+
+/*
-+ * sysfs attributes
++ * Register access functions
+ */
+
-+static ssize_t show_clock_id(struct device *dev,
-+ struct device_attribute *attr, char *page)
-+{
-+ struct k_clock *kc = dev_get_drvdata(dev);
-+ return snprintf(page, PAGE_SIZE-1, "%d\n", kc->id);
-+}
-+
-+static struct device_attribute timesource_dev_attrs[] = {
-+ __ATTR(id, 0444, show_clock_id, NULL),
-+ __ATTR_NULL,
++static u64 sys_time_read(struct ixp46x_ts_regs *regs)
++{
++ u64 ns;
++ u32 lo, hi;
++
++ lo = __raw_readl(®s->systime_lo);
++ hi = __raw_readl(®s->systime_hi);
++
++ 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;
++
++ __raw_writel(lo, ®s->systime_lo);
++ __raw_writel(hi, ®s->systime_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 = __raw_readl(®s->event);
++
++ if (val & TSER_SNS) {
++ ack |= TSER_SNS;
++ if (ixp_clock->exts0_enabled) {
++ hi = __raw_readl(®s->asms_hi);
++ lo = __raw_readl(®s->asms_lo);
++ 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 = __raw_readl(®s->amms_hi);
++ lo = __raw_readl(®s->amms_lo);
++ 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) {
++ __raw_writel(ack, ®s->event);
++ return IRQ_HANDLED;
++ } else
++ return IRQ_NONE;
++}
++
++/*
++ * PTP clock operations
++ */
++
++static int ptp_ixp_adjfreq(struct ptp_clock_info *ptp, s32 ppb)
++{
++ u64 adj;
++ u32 diff, addend;
++ int neg_adj = 0;
++ struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps);
++ struct ixp46x_ts_regs *regs = ixp_clock->regs;
++
++ 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;
++
++ __raw_writel(addend, ®s->addend);
++
++ return 0;
++}
++
++static int ptp_ixp_adjtime(struct ptp_clock_info *ptp, s64 delta)
++{
++ s64 now;
++ unsigned long flags;
++ struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps);
++ struct ixp46x_ts_regs *regs = ixp_clock->regs;
++
++ 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(struct ptp_clock_info *ptp, struct timespec *ts)
++{
++ u64 ns;
++ u32 remainder;
++ unsigned long flags;
++ struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps);
++ 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(struct ptp_clock_info *ptp,
++ const struct timespec *ts)
++{
++ u64 ns;
++ unsigned long flags;
++ struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps);
++ 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_enable(struct ptp_clock_info *ptp,
++ struct ptp_clock_request *rq, int on)
++{
++ struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps);
++
++ switch (rq->type) {
++ case PTP_CLK_REQ_EXTTS:
++ switch (rq->extts.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,
++ .adjfreq = ptp_ixp_adjfreq,
++ .adjtime = ptp_ixp_adjtime,
++ .gettime = ptp_ixp_gettime,
++ .settime = ptp_ixp_settime,
++ .enable = ptp_ixp_enable,
+};
+
- /*
- * Initialize everything, well, just everything in Posix clocks/timers ;)
- */
- static __init int init_posix_timers(void)
- {
- struct k_clock clock_realtime = {
-+ .name = "realtime",
- .clock_getres = hrtimer_get_res,
- };
- struct k_clock clock_monotonic = {
-+ .name = "monotonic",
- .clock_getres = hrtimer_get_res,
- .clock_get = posix_ktime_get_ts,
- .clock_set = do_posix_clock_nosettime,
- };
- struct k_clock clock_monotonic_raw = {
-+ .name = "monotonic_raw",
- .clock_getres = hrtimer_get_res,
- .clock_get = posix_get_monotonic_raw,
- .clock_set = do_posix_clock_nosettime,
-@@ -292,6 +315,7 @@ static __init int init_posix_timers(void)
- .nsleep = no_nsleep,
- };
- struct k_clock clock_realtime_coarse = {
-+ .name = "realtime_coarse",
- .clock_getres = posix_get_coarse_res,
- .clock_get = posix_get_realtime_coarse,
- .clock_set = do_posix_clock_nosettime,
-@@ -299,6 +323,7 @@ static __init int init_posix_timers(void)
- .nsleep = no_nsleep,
- };
- struct k_clock clock_monotonic_coarse = {
-+ .name = "monotonic_coarse",
- .clock_getres = posix_get_coarse_res,
- .clock_get = posix_get_monotonic_coarse,
- .clock_set = do_posix_clock_nosettime,
-@@ -306,6 +331,13 @@ static __init int init_posix_timers(void)
- .nsleep = no_nsleep,
- };
-
-+ timesource_class = class_create(NULL, "timesource");
-+ if (IS_ERR(timesource_class)) {
-+ pr_err("posix-timers: failed to allocate timesource class\n");
-+ return PTR_ERR(timesource_class);
-+ }
-+ timesource_class->dev_attrs = timesource_dev_attrs;
-+
- register_posix_clock(CLOCK_REALTIME, &clock_realtime);
- register_posix_clock(CLOCK_MONOTONIC, &clock_monotonic);
- register_posix_clock(CLOCK_MONOTONIC_RAW, &clock_monotonic_raw);
-@@ -500,6 +532,14 @@ int register_posix_clock(const clockid_t id, struct k_clock *clock)
- kc = &posix_clocks[id];
- *kc = *clock;
- kc->id = id;
-+ kc->dev = device_create(timesource_class, NULL, MKDEV(0, 0),
-+ kc, "%s", kc->name);
-+ if (IS_ERR(kc->dev)) {
-+ pr_err("failed to create device clock_id %d\n", id);
-+ err = PTR_ERR(kc->dev);
-+ goto out;
-+ }
-+ dev_set_drvdata(kc->dev, kc);
- set_bit(id, clocks_map);
- out:
- mutex_unlock(&clocks_mux);
++/* 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)
++{
++ if (!cpu_is_ixp46x())
++ return -ENODEV;
++
++ ixp_clock.regs =
++ (struct ixp46x_ts_regs __iomem *) IXP4XX_TIMESYNC_BASE_VIRT;
++
++ ixp_clock.caps = ptp_ixp_caps;
++
++ ixp_clock.ptp_clock = ptp_clock_register(&ixp_clock.caps);
++
++ if (IS_ERR(ixp_clock.ptp_clock))
++ return PTR_ERR(ixp_clock.ptp_clock);
++
++ __raw_writel(DEFAULT_ADDEND, &ixp_clock.regs->addend);
++ __raw_writel(1, &ixp_clock.regs->trgt_lo);
++ __raw_writel(0, &ixp_clock.regs->trgt_hi);
++ __raw_writel(TTIPEND, &ixp_clock.regs->event);
++
++ 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-3mrvs1K0uXizZXS1Dc/lvw@public.gmane.org>");
++MODULE_DESCRIPTION("PTP clock using the IXP46X timer");
++MODULE_LICENSE("GPL");
--
1.7.0.4