Inter-revision diff: patch 3

Comparing v6 (message) to v12 (message)

--- 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(&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;
  }
  
- 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(&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;
++	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
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help