tile: support PTP using the tilegx mPIPE (IEEE 1588)

Signed-off-by: Chris Metcalf <cmetcalf@tilera.com>
Acked-by: Richard Cochran <richardcochran@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
diff --git a/drivers/net/ethernet/tile/tilegx.c b/drivers/net/ethernet/tile/tilegx.c
index 0beed7e..907b577 100644
--- a/drivers/net/ethernet/tile/tilegx.c
+++ b/drivers/net/ethernet/tile/tilegx.c
@@ -38,6 +38,8 @@
 #include <linux/ip.h>
 #include <linux/ipv6.h>
 #include <linux/tcp.h>
+#include <linux/net_tstamp.h>
+#include <linux/ptp_clock_kernel.h>
 
 #include <asm/checksum.h>
 #include <asm/homecache.h>
@@ -185,6 +187,10 @@
 	int echannel;
 	/* mPIPE instance, 0 or 1. */
 	int instance;
+#ifdef CONFIG_PTP_1588_CLOCK_TILEGX
+	/* The timestamp config. */
+	struct hwtstamp_config stamp_cfg;
+#endif
 };
 
 static struct mpipe_data {
@@ -223,6 +229,15 @@
 	int first_bucket;
 	int num_buckets;
 
+#ifdef CONFIG_PTP_1588_CLOCK_TILEGX
+	/* PTP-specific data. */
+	struct ptp_clock *ptp_clock;
+	struct ptp_clock_info caps;
+
+	/* Lock for ptp accessors. */
+	struct mutex ptp_lock;
+#endif
+
 } mpipe_data[NR_MPIPE_MAX] = {
 	[0 ... (NR_MPIPE_MAX - 1)] {
 		.ingress_irq = -1,
@@ -432,6 +447,94 @@
 	}
 }
 
+/* Get RX timestamp, and store it in the skb. */
+static void tile_rx_timestamp(struct tile_net_priv *priv, struct sk_buff *skb,
+			      gxio_mpipe_idesc_t *idesc)
+{
+#ifdef CONFIG_PTP_1588_CLOCK_TILEGX
+	if (unlikely(priv->stamp_cfg.rx_filter != HWTSTAMP_FILTER_NONE)) {
+		struct skb_shared_hwtstamps *shhwtstamps = skb_hwtstamps(skb);
+		memset(shhwtstamps, 0, sizeof(*shhwtstamps));
+		shhwtstamps->hwtstamp = ktime_set(idesc->time_stamp_sec,
+						  idesc->time_stamp_ns);
+	}
+#endif
+}
+
+/* Get TX timestamp, and store it in the skb. */
+static void tile_tx_timestamp(struct sk_buff *skb, int instance)
+{
+#ifdef CONFIG_PTP_1588_CLOCK_TILEGX
+	struct skb_shared_info *shtx = skb_shinfo(skb);
+	if (unlikely((shtx->tx_flags & SKBTX_HW_TSTAMP) != 0)) {
+		struct mpipe_data *md = &mpipe_data[instance];
+		struct skb_shared_hwtstamps shhwtstamps;
+		struct timespec ts;
+
+		shtx->tx_flags |= SKBTX_IN_PROGRESS;
+		gxio_mpipe_get_timestamp(&md->context, &ts);
+		memset(&shhwtstamps, 0, sizeof(shhwtstamps));
+		shhwtstamps.hwtstamp = ktime_set(ts.tv_sec, ts.tv_nsec);
+		skb_tstamp_tx(skb, &shhwtstamps);
+	}
+#endif
+}
+
+/* Use ioctl() to enable or disable TX or RX timestamping. */
+static int tile_hwtstamp_ioctl(struct net_device *dev, struct ifreq *rq,
+			       int cmd)
+{
+#ifdef CONFIG_PTP_1588_CLOCK_TILEGX
+	struct hwtstamp_config config;
+	struct tile_net_priv *priv = netdev_priv(dev);
+
+	if (copy_from_user(&config, rq->ifr_data, sizeof(config)))
+		return -EFAULT;
+
+	if (config.flags)  /* reserved for future extensions */
+		return -EINVAL;
+
+	switch (config.tx_type) {
+	case HWTSTAMP_TX_OFF:
+	case HWTSTAMP_TX_ON:
+		break;
+	default:
+		return -ERANGE;
+	}
+
+	switch (config.rx_filter) {
+	case HWTSTAMP_FILTER_NONE:
+		break;
+	case HWTSTAMP_FILTER_ALL:
+	case HWTSTAMP_FILTER_SOME:
+	case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
+	case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
+	case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
+	case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
+	case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
+	case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
+	case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
+	case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
+	case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
+	case HWTSTAMP_FILTER_PTP_V2_EVENT:
+	case HWTSTAMP_FILTER_PTP_V2_SYNC:
+	case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
+		config.rx_filter = HWTSTAMP_FILTER_ALL;
+		break;
+	default:
+		return -ERANGE;
+	}
+
+	if (copy_to_user(rq->ifr_data, &config, sizeof(config)))
+		return -EFAULT;
+
+	priv->stamp_cfg = config;
+	return 0;
+#else
+	return -EOPNOTSUPP;
+#endif
+}
+
 static inline bool filter_packet(struct net_device *dev, void *buf)
 {
 	/* Filter packets received before we're up. */
@@ -451,7 +554,8 @@
 				 gxio_mpipe_idesc_t *idesc, unsigned long len)
 {
 	struct tile_net_info *info = &__get_cpu_var(per_cpu_info);
-	int instance = mpipe_instance(dev);
+	struct tile_net_priv *priv = netdev_priv(dev);
+	int instance = priv->instance;
 
 	/* Encode the actual packet length. */
 	skb_put(skb, len);
@@ -462,6 +566,9 @@
 	if (idesc->cs && idesc->csum_seed_val == 0xFFFF)
 		skb->ip_summed = CHECKSUM_UNNECESSARY;
 
+	/* Get RX timestamp from idesc. */
+	tile_rx_timestamp(priv, skb, idesc);
+
 	napi_gro_receive(&info->mpipe[instance].napi, skb);
 
 	/* Update stats. */
@@ -707,6 +814,103 @@
 	return HRTIMER_NORESTART;
 }
 
+#ifdef CONFIG_PTP_1588_CLOCK_TILEGX
+
+/* PTP clock operations. */
+
+static int ptp_mpipe_adjfreq(struct ptp_clock_info *ptp, s32 ppb)
+{
+	int ret = 0;
+	struct mpipe_data *md = container_of(ptp, struct mpipe_data, caps);
+	mutex_lock(&md->ptp_lock);
+	if (gxio_mpipe_adjust_timestamp_freq(&md->context, ppb))
+		ret = -EINVAL;
+	mutex_unlock(&md->ptp_lock);
+	return ret;
+}
+
+static int ptp_mpipe_adjtime(struct ptp_clock_info *ptp, s64 delta)
+{
+	int ret = 0;
+	struct mpipe_data *md = container_of(ptp, struct mpipe_data, caps);
+	mutex_lock(&md->ptp_lock);
+	if (gxio_mpipe_adjust_timestamp(&md->context, delta))
+		ret = -EBUSY;
+	mutex_unlock(&md->ptp_lock);
+	return ret;
+}
+
+static int ptp_mpipe_gettime(struct ptp_clock_info *ptp, struct timespec *ts)
+{
+	int ret = 0;
+	struct mpipe_data *md = container_of(ptp, struct mpipe_data, caps);
+	mutex_lock(&md->ptp_lock);
+	if (gxio_mpipe_get_timestamp(&md->context, ts))
+		ret = -EBUSY;
+	mutex_unlock(&md->ptp_lock);
+	return ret;
+}
+
+static int ptp_mpipe_settime(struct ptp_clock_info *ptp,
+			     const struct timespec *ts)
+{
+	int ret = 0;
+	struct mpipe_data *md = container_of(ptp, struct mpipe_data, caps);
+	mutex_lock(&md->ptp_lock);
+	if (gxio_mpipe_set_timestamp(&md->context, ts))
+		ret = -EBUSY;
+	mutex_unlock(&md->ptp_lock);
+	return ret;
+}
+
+static int ptp_mpipe_enable(struct ptp_clock_info *ptp,
+			    struct ptp_clock_request *request, int on)
+{
+	return -EOPNOTSUPP;
+}
+
+static struct ptp_clock_info ptp_mpipe_caps = {
+	.owner		= THIS_MODULE,
+	.name		= "mPIPE clock",
+	.max_adj	= 999999999,
+	.n_ext_ts	= 0,
+	.pps		= 0,
+	.adjfreq	= ptp_mpipe_adjfreq,
+	.adjtime	= ptp_mpipe_adjtime,
+	.gettime	= ptp_mpipe_gettime,
+	.settime	= ptp_mpipe_settime,
+	.enable		= ptp_mpipe_enable,
+};
+
+#endif /* CONFIG_PTP_1588_CLOCK_TILEGX */
+
+/* Sync mPIPE's timestamp up with Linux system time and register PTP clock. */
+static void register_ptp_clock(struct net_device *dev, struct mpipe_data *md)
+{
+#ifdef CONFIG_PTP_1588_CLOCK_TILEGX
+	struct timespec ts;
+
+	getnstimeofday(&ts);
+	gxio_mpipe_set_timestamp(&md->context, &ts);
+
+	mutex_init(&md->ptp_lock);
+	md->caps = ptp_mpipe_caps;
+	md->ptp_clock = ptp_clock_register(&md->caps, NULL);
+	if (IS_ERR(md->ptp_clock))
+		netdev_err(dev, "ptp_clock_register failed %ld\n",
+			   PTR_ERR(md->ptp_clock));
+#endif
+}
+
+/* Initialize PTP fields in a new device. */
+static void init_ptp_dev(struct tile_net_priv *priv)
+{
+#ifdef CONFIG_PTP_1588_CLOCK_TILEGX
+	priv->stamp_cfg.rx_filter = HWTSTAMP_FILTER_NONE;
+	priv->stamp_cfg.tx_type = HWTSTAMP_TX_OFF;
+#endif
+}
+
 /* Helper functions for "tile_net_update()". */
 static void enable_ingress_irq(void *irq)
 {
@@ -1149,6 +1353,9 @@
 	if (rc != 0)
 		goto fail;
 
+	/* Register PTP clock and set mPIPE timestamp, if configured. */
+	register_ptp_clock(dev, md);
+
 	return 0;
 
 fail:
@@ -1851,6 +2058,9 @@
 	for (i = 0; i < num_edescs; i++)
 		gxio_mpipe_equeue_put_at(equeue, edescs[i], slot++);
 
+	/* Store TX timestamp if needed. */
+	tile_tx_timestamp(skb, instance);
+
 	/* Add a completion record. */
 	add_comp(equeue, comps, slot - 1, skb);
 
@@ -1885,6 +2095,9 @@
 /* Ioctl commands. */
 static int tile_net_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
 {
+	if (cmd == SIOCSHWTSTAMP)
+		return tile_hwtstamp_ioctl(dev, rq, cmd);
+
 	return -EOPNOTSUPP;
 }
 
@@ -2005,6 +2218,7 @@
 	priv->channel = -1;
 	priv->loopify_channel = -1;
 	priv->echannel = -1;
+	init_ptp_dev(priv);
 
 	/* Get the MAC address and set it in the device struct; this must
 	 * be done before the device is opened.  If the MAC is all zeroes,