Inter-revision diff: patch 3

Comparing v7 (message) to v11 (message)

--- 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(&regs->channel[ch].ch_event);
++
++	if (!(val & RX_SNAPSHOT_LOCKED))
++		return;
++
++	lo = __raw_readl(&regs->channel[ch].src_uuid_lo);
++	hi = __raw_readl(&regs->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(&regs->channel[ch].rx_snap_lo);
++	hi = __raw_readl(&regs->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, &regs->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(&regs->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(&regs->channel[ch].tx_snap_lo);
++	hi = __raw_readl(&regs->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, &regs->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, &regs->channel[ch].ch_control);
++		break;
++	case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
++		port->hwts_rx_en = PTP_MASTER_MODE;
++		__raw_writel(MASTER_MODE, &regs->channel[ch].ch_control);
++		break;
++	default:
++		return -ERANGE;
++	}
++
++	/* Clear out any old time stamps. */
++	__raw_writel(TX_SNAPSHOT_LOCKED | RX_SNAPSHOT_LOCKED,
++		     &regs->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(&regs->systime_lo);
++	hi = __raw_readl(&regs->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, &regs->systime_lo);
++	__raw_writel(hi, &regs->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(&regs->event);
++
++	if (val & TSER_SNS) {
++		ack |= TSER_SNS;
++		if (ixp_clock->exts0_enabled) {
++			hi = __raw_readl(&regs->asms_hi);
++			lo = __raw_readl(&regs->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(&regs->amms_hi);
++			lo = __raw_readl(&regs->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, &regs->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, &regs->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(&register_lock, flags);
++
++	now = sys_time_read(regs);
++	now += delta;
++	sys_time_write(regs, now);
++
++	spin_unlock_irqrestore(&register_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(&register_lock, flags);
++
++	ns = sys_time_read(regs);
++
++	spin_unlock_irqrestore(&register_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(&register_lock, flags);
++
++	sys_time_write(regs, ns);
++
++	spin_unlock_irqrestore(&register_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
-
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help