--- v7
+++ v11
@@ -1,28 +1,26 @@
-This patch adds support for adding and removing posix clocks. The
-clock lifetime cycle is patterned after usb devices. Each clock is
-represented by a standard character device. In addition, the driver
-may optionally implemented custom character device operations.
+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.
-The dynamic posix clocks do not yet do anything useful. This patch
-merely provides some needed infrastructure.
+Signed-off-by: Richard Cochran <richard.cochran-3mrvs1K0uXizZXS1Dc/lvw@public.gmane.org>
+Acked-by: John Stultz <johnstul-r/Jw6+rmf7HQT0dZR+AlfA@public.gmane.org>
+---
+ 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
-Signed-off-by: Richard Cochran <richard.cochran@omicron.at>
----
- include/linux/posix-clock.h | 111 ++++++++++++++++++++++
- kernel/time/Makefile | 3 +-
- kernel/time/posix-clock.c | 217 +++++++++++++++++++++++++++++++++++++++++++
- 3 files changed, 330 insertions(+), 1 deletions(-)
- create mode 100644 include/linux/posix-clock.h
- create mode 100644 kernel/time/posix-clock.c
-
-diff --git a/include/linux/posix-clock.h b/include/linux/posix-clock.h
+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..1ce7fb7
+index 0000000..292d55e
--- /dev/null
-+++ b/include/linux/posix-clock.h
-@@ -0,0 +1,111 @@
++++ b/arch/arm/mach-ixp4xx/include/mach/ixp46x_ts.h
+@@ -0,0 +1,78 @@
+/*
-+ * posix-clock.h - support for dynamic clock devices
++ * PTP 1588 clock using the IXP46X
+ *
+ * Copyright (C) 2010 OMICRON electronics GmbH
+ *
@@ -40,117 +38,371 @@
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
-+#ifndef _LINUX_POSIX_CLOCK_H_
-+#define _LINUX_POSIX_CLOCK_H_
-+
-+#include <linux/fs.h>
-+#include <linux/poll.h>
-+#include <linux/posix-timers.h>
-+
-+/**
-+ * struct posix_clock_fops - character device operations
-+ *
-+ * Every posix clock is represented by a character device. Drivers may
-+ * optionally offer extended capabilities by implementing these
-+ * functions. The character device file operations are first handled
-+ * by the clock device layer, then passed on to the driver by calling
-+ * these functions.
-+ *
-+ * The clock device layer already uses fp->private_data, so drivers
-+ * are provided their private data via the 'priv' paramenter.
-+ */
-+void *posix_clock_private(struct file *fp);
-+
-+struct posix_clock_fops {
-+ int (*fasync) (void *priv, int fd, struct file *file, int on);
-+ int (*mmap) (void *priv, struct vm_area_struct *vma);
-+ int (*open) (void *priv, fmode_t f_mode);
-+ int (*release) (void *priv);
-+ long (*ioctl) (void *priv, unsigned int cmd, unsigned long arg);
-+ long (*compat_ioctl) (void *priv, unsigned int cmd, unsigned long arg);
-+ ssize_t (*read) (void *priv, uint flags, char __user *buf, size_t cnt);
-+ unsigned int (*poll) (void *priv, struct file *file, poll_table *wait);
++
++#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 posix_clock_operations - functional interface to the clock
-+ * @owner: The clock driver should set to THIS_MODULE.
-+ * @fops: Optional custom character device operations
-+ * @clock_adjtime: Adjust the clock
-+ * @clock_gettime: Read the current time
-+ * @clock_getres: Get the clock resolution
-+ * @clock_settime: Set the current time value
-+ * @timer_create: Create a new timer
-+ * @timer_delete: Remove a previously created timer
-+ * @timer_gettime: Get remaining time and interval of a timer
-+ * @timer_setttime: Set a timer's initial expiration and interval
-+ */
-+struct posix_clock_operations {
-+ struct module *owner;
-+ struct posix_clock_fops fops;
-+ int (*clock_adjtime)(void *priv, struct timex *tx);
-+ int (*clock_gettime)(void *priv, struct timespec *ts);
-+ int (*clock_getres) (void *priv, struct timespec *ts);
-+ int (*clock_settime)(void *priv, const struct timespec *ts);
-+ int (*timer_create) (void *priv, struct k_itimer *kit);
-+ int (*timer_delete) (void *priv, struct k_itimer *kit);
-+ void (*timer_gettime)(void *priv, struct k_itimer *kit,
-+ struct itimerspec *tsp);
-+ int (*timer_settime)(void *priv, struct k_itimer *kit, int flags,
-+ struct itimerspec *tsp, struct itimerspec *old);
++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];
+};
+
-+/**
-+ * struct posix_clock - an opaque type
-+ */
-+struct posix_clock;
-+
-+/**
-+ * posix_clock_create() - register a new clock
-+ * @cops: Pointer to the clock's interface
-+ * @devid: Allocated device id
-+ * @priv: Private data passed back to the driver via the interface functions
-+ *
-+ * A clock driver calls this function to register itself with the
-+ * clock device subsystem. The 'cops' argument must point to
-+ * persistent data, so the caller should pass a static global.
-+ *
-+ * Returns a pointer to a new clock device, or PTR_ERR on error.
-+ */
-+struct posix_clock *posix_clock_create(struct posix_clock_operations *cops,
-+ dev_t devid, void *priv);
-+
-+/**
-+ * posix_clock_destroy() - unregister a clock
-+ * @clk: Pointer obtained via posix_clock_create()
-+ *
-+ * A clock driver calls this function to remove itself from the clock
-+ * device subsystem. The posix_clock itself will remain (in an
-+ * inactive state) until its reference count drops to zero, at which
-+ * point it will be deallocated.
-+ */
-+void posix_clock_destroy(struct posix_clock *clk);
++/* 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/kernel/time/Makefile b/kernel/time/Makefile
-index ee26662..bb3c9b8 100644
---- a/kernel/time/Makefile
-+++ b/kernel/time/Makefile
-@@ -1,4 +1,5 @@
--obj-y += timekeeping.o ntp.o clocksource.o jiffies.o timer_list.o timecompare.o timeconv.o
-+obj-y += timekeeping.o ntp.o clocksource.o jiffies.o timer_list.o \
-+timecompare.o timeconv.o posix-clock.o
-
- obj-$(CONFIG_GENERIC_CLOCKEVENTS_BUILD) += clockevents.o
- obj-$(CONFIG_GENERIC_CLOCKEVENTS) += tick-common.o
-diff --git a/kernel/time/posix-clock.c b/kernel/time/posix-clock.c
+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;
+ }
+
+@@ -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..e5924c9
+index 0000000..e28b416
--- /dev/null
-+++ b/kernel/time/posix-clock.c
-@@ -0,0 +1,217 @@
++++ b/drivers/ptp/ptp_ixp46x.c
+@@ -0,0 +1,332 @@
+/*
-+ * posix-clock.c - support for dynamic clock devices
++ * PTP 1588 clock using the IXP46X
+ *
+ * Copyright (C) 2010 OMICRON electronics GmbH
+ *
@@ -168,204 +420,318 @@
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
-+#include <linux/cdev.h>
+#include <linux/device.h>
-+#include <linux/mutex.h>
-+#include <linux/posix-clock.h>
-+#include <linux/slab.h>
-+
-+#define MAX_CLKDEV BITS_PER_LONG
-+static DECLARE_BITMAP(clocks_map, MAX_CLKDEV);
-+static DEFINE_MUTEX(clocks_mutex); /* protects 'clocks_map' */
-+
-+struct posix_clock {
-+ struct posix_clock_operations *ops;
-+ struct cdev cdev;
-+ struct kref kref;
-+ struct mutex mux;
-+ void *priv;
-+ int index;
-+ bool zombie;
++#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;
+};
+
-+static void delete_clock(struct kref *kref);
-+
-+static ssize_t posix_clock_read(struct file *fp, char __user *buf,
-+ size_t count, loff_t *ppos)
-+{
-+ struct posix_clock *clk = fp->private_data;
-+
-+ if (clk->ops->fops.read)
-+ return clk->ops->fops.read(clk->priv, fp->f_flags, buf, count);
-+ else
-+ return -EINVAL;
-+}
-+
-+static unsigned int posix_clock_poll(struct file *fp, poll_table *wait)
-+{
-+ struct posix_clock *clk = fp->private_data;
-+
-+ if (clk->ops->fops.poll)
-+ return clk->ops->fops.poll(clk->priv, fp, wait);
-+ else
++DEFINE_SPINLOCK(register_lock);
++
++/*
++ * Register access functions
++ */
++
++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;
-+}
-+
-+static int posix_clock_fasync(int fd, struct file *fp, int on)
-+{
-+ struct posix_clock *clk = fp->private_data;
-+
-+ if (clk->ops->fops.fasync)
-+ return clk->ops->fops.fasync(clk->priv, fd, fp, on);
-+ else
-+ return 0;
-+}
-+
-+static int posix_clock_mmap(struct file *fp, struct vm_area_struct *vma)
-+{
-+ struct posix_clock *clk = fp->private_data;
-+
-+ if (clk->ops->fops.mmap)
-+ return clk->ops->fops.mmap(clk->priv, vma);
-+ else
++ 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,
++};
++
++/* 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;
-+}
-+
-+static long posix_clock_ioctl(struct file *fp,
-+ unsigned int cmd, unsigned long arg)
-+{
-+ struct posix_clock *clk = fp->private_data;
-+
-+ if (clk->ops->fops.ioctl)
-+ return clk->ops->fops.ioctl(clk->priv, cmd, arg);
-+ else
-+ return -ENOTTY;
-+}
-+
-+#ifdef CONFIG_COMPAT
-+static long posix_clock_compat_ioctl(struct file *fp,
-+ unsigned int cmd, unsigned long arg)
-+{
-+ struct posix_clock *clk = fp->private_data;
-+
-+ if (clk->ops->fops.compat_ioctl)
-+ return clk->ops->fops.compat_ioctl(clk->priv, cmd, arg);
-+ else
-+ return -ENOTTY;
-+}
-+#endif
-+
-+static int posix_clock_open(struct inode *inode, struct file *fp)
-+{
-+ struct posix_clock *clk =
-+ container_of(inode->i_cdev, struct posix_clock, cdev);
-+
-+ kref_get(&clk->kref);
-+ fp->private_data = clk;
-+
-+ if (clk->ops->fops.open)
-+ return clk->ops->fops.open(clk->priv, fp->f_mode);
-+ else
-+ return 0;
-+}
-+
-+static int posix_clock_release(struct inode *inode, struct file *fp)
-+{
-+ struct posix_clock *clk = fp->private_data;
-+ int err = 0;
-+
-+ if (clk->ops->fops.release)
-+ err = clk->ops->fops.release(clk->priv);
-+
-+ kref_put(&clk->kref, delete_clock);
-+
-+ return err;
-+}
-+
-+static const struct file_operations posix_clock_file_operations = {
-+ .owner = THIS_MODULE,
-+ .llseek = no_llseek,
-+ .read = posix_clock_read,
-+ .poll = posix_clock_poll,
-+ .unlocked_ioctl = posix_clock_ioctl,
-+ .open = posix_clock_open,
-+ .release = posix_clock_release,
-+ .fasync = posix_clock_fasync,
-+ .mmap = posix_clock_mmap,
-+#ifdef CONFIG_COMPAT
-+ .compat_ioctl = posix_clock_compat_ioctl,
-+#endif
-+};
-+
-+struct posix_clock *posix_clock_create(struct posix_clock_operations *cops,
-+ dev_t devid, void *priv)
-+{
-+ struct posix_clock *clk;
-+ int err;
-+
-+ mutex_lock(&clocks_mutex);
-+
-+ err = -ENOMEM;
-+ clk = kzalloc(sizeof(*clk), GFP_KERNEL);
-+ if (!clk)
-+ goto no_memory;
-+
-+ err = -EBUSY;
-+ clk->index = find_first_zero_bit(clocks_map, MAX_CLKDEV);
-+ if (clk->index < MAX_CLKDEV)
-+ set_bit(clk->index, clocks_map);
-+ else
-+ goto no_index;
-+
-+ clk->ops = cops;
-+ clk->priv = priv;
-+ kref_init(&clk->kref);
-+ mutex_init(&clk->mux);
-+
-+ cdev_init(&clk->cdev, &posix_clock_file_operations);
-+ clk->cdev.owner = clk->ops->owner;
-+ err = cdev_add(&clk->cdev, devid, 1);
-+ if (err)
-+ goto no_cdev;
-+
-+ mutex_unlock(&clocks_mutex);
-+ return clk;
-+
-+no_cdev:
-+ mutex_destroy(&clk->mux);
-+ clear_bit(clk->index, clocks_map);
-+no_index:
-+ kfree(clk);
-+no_memory:
-+ mutex_unlock(&clocks_mutex);
-+ return ERR_PTR(err);
-+}
-+EXPORT_SYMBOL_GPL(posix_clock_create);
-+
-+static void delete_clock(struct kref *kref)
-+{
-+ struct posix_clock *clk =
-+ container_of(kref, struct posix_clock, kref);
-+
-+ mutex_lock(&clocks_mutex);
-+ clear_bit(clk->index, clocks_map);
-+ mutex_unlock(&clocks_mutex);
-+
-+ mutex_destroy(&clk->mux);
-+ kfree(clk);
-+}
-+
-+void posix_clock_destroy(struct posix_clock *clk)
-+{
-+ cdev_del(&clk->cdev);
-+
-+ mutex_lock(&clk->mux);
-+ clk->zombie = true;
-+ mutex_unlock(&clk->mux);
-+
-+ kref_put(&clk->kref, delete_clock);
-+}
-+EXPORT_SYMBOL_GPL(posix_clock_destroy);
++
++ 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
-