stmmac: add IEEE PTPv1 and PTPv2 support.
authorRayagond Kokatanur <rayagond@vayavyalabs.com>
Tue, 26 Mar 2013 04:43:10 +0000 (04:43 +0000)
committerDavid S. Miller <davem@davemloft.net>
Tue, 26 Mar 2013 16:53:37 +0000 (12:53 -0400)
This patch enhances the stmmac driver to support IEEE 1588-2002
PTP (Precision Time Protocol) version 1 and IEEE 1588-2008 PPT
version 2.

Precision Time Protocol(PTP),which enables precise synchronization
of clocks in measurement and control systems implemented with
technologies such as network communication,local computing,
& distributed objects.

Both PTPv1 and PTPv2 is selected at run-time using the HW capability
register.

The PTPv1 TimeStamp support can be used on chips that have the normal
descriptor structures and PTPv2 TimeStamp support can be used on chips
that have the Extended descriptors(DES4-5-6-7). All such sanity checks
are done and verified by using HW capability register.

V2: in this version the ethtool support has been included in this patch;
Koptions have been completely removed (previously added to select
PTP and PTPv2). PTPv1 and PTPv2 is now added in a single patch instead of
two patches.
get_timestamp() and get_systemtime() L/H have been combined into single APIs.

Signed-off-by: Rayagond Kokatanur <rayagond@vayavyalabs.com>
Signed-off-by: Giuseppe Cavallaro <peppe.cavallaro@st.com>
Cc: Richard Cochran <richardcochran@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/stmicro/stmmac/Makefile
drivers/net/ethernet/stmicro/stmmac/chain_mode.c
drivers/net/ethernet/stmicro/stmmac/common.h
drivers/net/ethernet/stmicro/stmmac/enh_desc.c
drivers/net/ethernet/stmicro/stmmac/norm_desc.c
drivers/net/ethernet/stmicro/stmmac/ring_mode.c
drivers/net/ethernet/stmicro/stmmac/stmmac.h
drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c
drivers/net/ethernet/stmicro/stmmac/stmmac_hwtstamp.c [new file with mode: 0644]
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.h [new file with mode: 0644]

index ae995a367c9115ed454e09add6a494e9eba1d23d..1aca0e6881bda465bdb580f4e36058f00878c20c 100644 (file)
@@ -4,4 +4,4 @@ stmmac-$(CONFIG_STMMAC_PCI) += stmmac_pci.o
 stmmac-objs:= stmmac_main.o stmmac_ethtool.o stmmac_mdio.o ring_mode.o \
              chain_mode.o dwmac_lib.o dwmac1000_core.o  dwmac1000_dma.o \
              dwmac100_core.o dwmac100_dma.o enh_desc.o  norm_desc.o \
-             mmc_core.o $(stmmac-y)
+             mmc_core.o stmmac_hwtstamp.o $(stmmac-y)
index 63b6031e304a54af305621017d560858d2db67a9..37a3f93b487deac9c4320c0ef277eaa5298ff257 100644 (file)
@@ -122,8 +122,40 @@ static void stmmac_init_dma_chain(void *des, dma_addr_t phy_addr,
        }
 }
 
+static void stmmac_refill_desc3(void *priv_ptr, struct dma_desc *p)
+{
+       struct stmmac_priv *priv = (struct stmmac_priv *)priv_ptr;
+
+       if (priv->hwts_rx_en && !priv->extend_desc)
+               /* NOTE: Device will overwrite des3 with timestamp value if
+                * 1588-2002 time stamping is enabled, hence reinitialize it
+                * to keep explicit chaining in the descriptor.
+                */
+               p->des3 = (unsigned int)(priv->dma_rx_phy +
+                                        (((priv->dirty_rx) + 1) %
+                                        priv->dma_rx_size) *
+                                        sizeof(struct dma_desc));
+}
+
+static void stmmac_clean_desc3(void *priv_ptr, struct dma_desc *p)
+{
+       struct stmmac_priv *priv = (struct stmmac_priv *)priv_ptr;
+
+       if (priv->hw->desc->get_tx_ls(p) && !priv->extend_desc)
+               /* NOTE: Device will overwrite des3 with timestamp value if
+                * 1588-2002 time stamping is enabled, hence reinitialize it
+                * to keep explicit chaining in the descriptor.
+                */
+               p->des3 = (unsigned int)(priv->dma_tx_phy +
+                                        (((priv->dirty_tx + 1) %
+                                        priv->dma_tx_size) *
+                                        sizeof(struct dma_desc)));
+}
+
 const struct stmmac_chain_mode_ops chain_mode_ops = {
        .init = stmmac_init_dma_chain,
        .is_jumbo_frm = stmmac_is_jumbo_frm,
        .jumbo_frm = stmmac_jumbo_frm,
+       .refill_desc3 = stmmac_refill_desc3,
+       .clean_desc3 = stmmac_clean_desc3,
 };
index 942fdce3ab930b9bb06c0e7ef0701e1a2a80a437..6fa975c5523442d4e0fcf19c1fbdced6886b03ca 100644 (file)
@@ -339,6 +339,14 @@ struct stmmac_desc_ops {
                          struct dma_desc *p);
        void (*rx_extended_status) (void *data, struct stmmac_extra_stats *x,
                                    struct dma_extended_desc *p);
+       /* Set tx timestamp enable bit */
+       void (*enable_tx_timestamp) (struct dma_desc *p);
+       /* get tx timestamp status */
+       int (*get_tx_timestamp_status) (struct dma_desc *p);
+       /* get timestamp value */
+       u64 (*get_timestamp) (void *desc, u32 ats);
+       /* get rx timestamp status */
+       int (*get_rx_timestamp_status) (void *desc, u32 ats);
 };
 
 struct stmmac_dma_ops {
@@ -398,6 +406,13 @@ struct stmmac_ops {
        void (*get_adv) (void __iomem *ioaddr, struct rgmii_adv *adv);
 };
 
+struct stmmac_hwtimestamp {
+       void (*config_hw_tstamping) (void __iomem *ioaddr, u32 data);
+       void (*config_sub_second_increment) (void __iomem *ioaddr);
+       int (*init_systime) (void __iomem *ioaddr, u32 sec, u32 nsec);
+       int (*config_addend)(void __iomem *ioaddr, u32 addend);
+};
+
 struct mac_link {
        int port;
        int duplex;
@@ -412,9 +427,9 @@ struct mii_regs {
 struct stmmac_ring_mode_ops {
        unsigned int (*is_jumbo_frm) (int len, int ehn_desc);
        unsigned int (*jumbo_frm) (void *priv, struct sk_buff *skb, int csum);
-       void (*refill_desc3) (int bfsize, struct dma_desc *p);
+       void (*refill_desc3) (void *priv, struct dma_desc *p);
        void (*init_desc3) (struct dma_desc *p);
-       void (*clean_desc3) (struct dma_desc *p);
+       void (*clean_desc3) (void *priv, struct dma_desc *p);
        int (*set_16kib_bfsize) (int mtu);
 };
 
@@ -423,6 +438,8 @@ struct stmmac_chain_mode_ops {
                      unsigned int extend_desc);
        unsigned int (*is_jumbo_frm) (int len, int ehn_desc);
        unsigned int (*jumbo_frm) (void *priv, struct sk_buff *skb, int csum);
+       void (*refill_desc3) (void *priv, struct dma_desc *p);
+       void (*clean_desc3) (void *priv, struct dma_desc *p);
 };
 
 struct mac_device_info {
@@ -431,6 +448,7 @@ struct mac_device_info {
        const struct stmmac_dma_ops     *dma;
        const struct stmmac_ring_mode_ops       *ring;
        const struct stmmac_chain_mode_ops      *chain;
+       const struct stmmac_hwtimestamp *ptp;
        struct mii_regs mii;    /* MII register Addresses */
        struct mac_link link;
        unsigned int synopsys_uid;
index c1b9ab23b3c547d7e8827ac288f72d93cf418470..0fbc8fafa7060a1a5b1c68725b4e9a2cd1b23e84 100644 (file)
@@ -378,6 +378,49 @@ static int enh_desc_get_rx_frame_len(struct dma_desc *p, int rx_coe_type)
                return p->des01.erx.frame_length;
 }
 
+static void enh_desc_enable_tx_timestamp(struct dma_desc *p)
+{
+       p->des01.etx.time_stamp_enable = 1;
+}
+
+static int enh_desc_get_tx_timestamp_status(struct dma_desc *p)
+{
+       return p->des01.etx.time_stamp_status;
+}
+
+static u64 enh_desc_get_timestamp(void *desc, u32 ats)
+{
+       u64 ns;
+
+       if (ats) {
+               struct dma_extended_desc *p = (struct dma_extended_desc *)desc;
+               ns = p->des6;
+               /* convert high/sec time stamp value to nanosecond */
+               ns += p->des7 * 1000000000ULL;
+       } else {
+               struct dma_desc *p = (struct dma_desc *)desc;
+               ns = p->des2;
+               ns += p->des3 * 1000000000ULL;
+       }
+
+       return ns;
+}
+
+static int enh_desc_get_rx_timestamp_status(void *desc, u32 ats)
+{
+       if (ats) {
+               struct dma_extended_desc *p = (struct dma_extended_desc *)desc;
+               return p->basic.des01.erx.ipc_csum_error;
+       } else {
+               struct dma_desc *p = (struct dma_desc *)desc;
+               if ((p->des2 == 0xffffffff) && (p->des3 == 0xffffffff))
+                       /* timestamp is corrupted, hence don't store it */
+                       return 0;
+               else
+                       return 1;
+       }
+}
+
 const struct stmmac_desc_ops enh_desc_ops = {
        .tx_status = enh_desc_get_tx_status,
        .rx_status = enh_desc_get_rx_status,
@@ -395,4 +438,8 @@ const struct stmmac_desc_ops enh_desc_ops = {
        .set_rx_owner = enh_desc_set_rx_owner,
        .get_rx_frame_len = enh_desc_get_rx_frame_len,
        .rx_extended_status = enh_desc_get_ext_status,
+       .enable_tx_timestamp = enh_desc_enable_tx_timestamp,
+       .get_tx_timestamp_status = enh_desc_get_tx_timestamp_status,
+       .get_timestamp = enh_desc_get_timestamp,
+       .get_rx_timestamp_status = enh_desc_get_rx_timestamp_status,
 };
index 47d509435ebbbcc6b2585d32ca9326e19f11713b..7cbcea348c3d98d5e9a5cd625ed28266bf71f239 100644 (file)
@@ -219,6 +219,39 @@ static int ndesc_get_rx_frame_len(struct dma_desc *p, int rx_coe_type)
                return p->des01.rx.frame_length;
 }
 
+static void ndesc_enable_tx_timestamp(struct dma_desc *p)
+{
+       p->des01.tx.time_stamp_enable = 1;
+}
+
+static int ndesc_get_tx_timestamp_status(struct dma_desc *p)
+{
+       return p->des01.tx.time_stamp_status;
+}
+
+static u64 ndesc_get_timestamp(void *desc, u32 ats)
+{
+       struct dma_desc *p = (struct dma_desc *)desc;
+       u64 ns;
+
+       ns = p->des2;
+       /* convert high/sec time stamp value to nanosecond */
+       ns += p->des3 * 1000000000ULL;
+
+       return ns;
+}
+
+static int ndesc_get_rx_timestamp_status(void *desc, u32 ats)
+{
+       struct dma_desc *p = (struct dma_desc *)desc;
+
+       if ((p->des2 == 0xffffffff) && (p->des3 == 0xffffffff))
+               /* timestamp is corrupted, hence don't store it */
+               return 0;
+       else
+               return 1;
+}
+
 const struct stmmac_desc_ops ndesc_ops = {
        .tx_status = ndesc_get_tx_status,
        .rx_status = ndesc_get_rx_status,
@@ -235,4 +268,8 @@ const struct stmmac_desc_ops ndesc_ops = {
        .set_tx_owner = ndesc_set_tx_owner,
        .set_rx_owner = ndesc_set_rx_owner,
        .get_rx_frame_len = ndesc_get_rx_frame_len,
+       .enable_tx_timestamp = ndesc_enable_tx_timestamp,
+       .get_tx_timestamp_status = ndesc_get_tx_timestamp_status,
+       .get_timestamp = ndesc_get_timestamp,
+       .get_rx_timestamp_status = ndesc_get_rx_timestamp_status,
 };
index 43fc699b21cc6bd09ad02e2a4308e6a2c1b1940e..d0265a7d5a54162c8a43eb961ed4f1f16f38c691 100644 (file)
@@ -87,11 +87,14 @@ static unsigned int stmmac_is_jumbo_frm(int len, int enh_desc)
        return ret;
 }
 
-static void stmmac_refill_desc3(int bfsize, struct dma_desc *p)
+static void stmmac_refill_desc3(void *priv_ptr, struct dma_desc *p)
 {
-       /* Fill DES3 in case of RING mode */
-       if (bfsize >= BUF_SIZE_8KiB)
-               p->des3 = p->des2 + BUF_SIZE_8KiB;
+       struct stmmac_priv *priv = (struct stmmac_priv *)priv_ptr;
+
+       if (unlikely(priv->plat->has_gmac))
+               /* Fill DES3 in case of RING mode */
+               if (priv->dma_buf_sz >= BUF_SIZE_8KiB)
+                       p->des3 = p->des2 + BUF_SIZE_8KiB;
 }
 
 /* In ring mode we need to fill the desc3 because it is used as buffer */
@@ -100,7 +103,7 @@ static void stmmac_init_desc3(struct dma_desc *p)
        p->des3 = p->des2 + BUF_SIZE_8KiB;
 }
 
-static void stmmac_clean_desc3(struct dma_desc *p)
+static void stmmac_clean_desc3(void *priv_ptr, struct dma_desc *p)
 {
        if (unlikely(p->des3))
                p->des3 = 0;
index 5176cae44b038dfbb7b3c314f283ff0471c823a7..a21d1b9c90943db0201806cb56f43ef364f2c88f 100644 (file)
@@ -99,6 +99,10 @@ struct stmmac_priv {
        unsigned int mode;
        int extend_desc;
        int pcs;
+       int hwts_tx_en;
+       int hwts_rx_en;
+       unsigned int default_addend;
+       u32 adv_ts;
 };
 
 extern int phyaddr;
@@ -108,6 +112,7 @@ extern int stmmac_mdio_register(struct net_device *ndev);
 extern void stmmac_set_ethtool_ops(struct net_device *netdev);
 extern const struct stmmac_desc_ops enh_desc_ops;
 extern const struct stmmac_desc_ops ndesc_ops;
+extern const struct stmmac_hwtimestamp stmmac_ptp;
 int stmmac_freeze(struct net_device *ndev);
 int stmmac_restore(struct net_device *ndev);
 int stmmac_resume(struct net_device *ndev);
index 54e15db516fe5ee558f9a2f9c222ea4350b881b9..5b340c23fd6bbbe48a8b846d79efe725f90ca079 100644 (file)
@@ -27,6 +27,7 @@
 #include <linux/interrupt.h>
 #include <linux/mii.h>
 #include <linux/phy.h>
+#include <linux/net_tstamp.h>
 #include <asm/io.h>
 
 #include "stmmac.h"
@@ -725,6 +726,35 @@ static int stmmac_set_coalesce(struct net_device *dev,
        return 0;
 }
 
+static int stmmac_get_ts_info(struct net_device *dev,
+                             struct ethtool_ts_info *info)
+{
+       struct stmmac_priv *priv = netdev_priv(dev);
+
+       if ((priv->hwts_tx_en) && (priv->hwts_rx_en)) {
+
+               info->so_timestamping = SOF_TIMESTAMPING_TX_HARDWARE |
+                                       SOF_TIMESTAMPING_RX_HARDWARE |
+                                       SOF_TIMESTAMPING_RAW_HARDWARE;
+
+               info->tx_types = (1 << HWTSTAMP_TX_OFF) | (1 << HWTSTAMP_TX_ON);
+
+               info->rx_filters = ((1 << HWTSTAMP_FILTER_NONE) |
+                                   (1 << HWTSTAMP_FILTER_PTP_V1_L4_EVENT) |
+                                   (1 << HWTSTAMP_FILTER_PTP_V1_L4_SYNC) |
+                                   (1 << HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ) |
+                                   (1 << HWTSTAMP_FILTER_PTP_V2_L4_EVENT) |
+                                   (1 << HWTSTAMP_FILTER_PTP_V2_L4_SYNC) |
+                                   (1 << HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ) |
+                                   (1 << HWTSTAMP_FILTER_PTP_V2_EVENT) |
+                                   (1 << HWTSTAMP_FILTER_PTP_V2_SYNC) |
+                                   (1 << HWTSTAMP_FILTER_PTP_V2_DELAY_REQ) |
+                                   (1 << HWTSTAMP_FILTER_ALL));
+               return 0;
+       } else
+               return ethtool_op_get_ts_info(dev, info);
+}
+
 static const struct ethtool_ops stmmac_ethtool_ops = {
        .begin = stmmac_check_if_running,
        .get_drvinfo = stmmac_ethtool_getdrvinfo,
@@ -744,7 +774,7 @@ static const struct ethtool_ops stmmac_ethtool_ops = {
        .get_eee = stmmac_ethtool_op_get_eee,
        .set_eee = stmmac_ethtool_op_set_eee,
        .get_sset_count = stmmac_get_sset_count,
-       .get_ts_info = ethtool_op_get_ts_info,
+       .get_ts_info = stmmac_get_ts_info,
        .get_coalesce = stmmac_get_coalesce,
        .set_coalesce = stmmac_set_coalesce,
 };
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_hwtstamp.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_hwtstamp.c
new file mode 100644 (file)
index 0000000..380baeb
--- /dev/null
@@ -0,0 +1,108 @@
+/*******************************************************************************
+  Copyright (C) 2013  Vayavya Labs Pvt Ltd
+
+  This implements all the API for managing HW timestamp & PTP.
+
+  This program is free software; you can redistribute it and/or modify it
+  under the terms and conditions of the GNU General Public License,
+  version 2, as published by the Free Software Foundation.
+
+  This program is distributed in the hope 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.,
+  51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+
+  The full GNU General Public License is included in this distribution in
+  the file called "COPYING".
+
+  Author: Rayagond Kokatanur <rayagond@vayavyalabs.com>
+  Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+*******************************************************************************/
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include "common.h"
+#include "stmmac_ptp.h"
+
+static void stmmac_config_hw_tstamping(void __iomem *ioaddr, u32 data)
+{
+       writel(data, ioaddr + PTP_TCR);
+}
+
+static void stmmac_config_sub_second_increment(void __iomem *ioaddr)
+{
+       u32 value = readl(ioaddr + PTP_TCR);
+       unsigned long data;
+
+       /* Convert the ptp_clock to nano second
+        * formula = (1/ptp_clock) * 1000000000
+        * where, ptp_clock = 50MHz.
+        */
+       data = (1000000000ULL / 50000000);
+
+       /* 0.465ns accuracy */
+       if (value & PTP_TCR_TSCTRLSSR)
+               data = (data * 100) / 465;
+
+       writel(data, ioaddr + PTP_SSIR);
+}
+
+static int stmmac_init_systime(void __iomem *ioaddr, u32 sec, u32 nsec)
+{
+       int limit;
+       u32 value;
+
+       writel(sec, ioaddr + PTP_STSUR);
+       writel(nsec, ioaddr + PTP_STNSUR);
+       /* issue command to initialize the system time value */
+       value = readl(ioaddr + PTP_TCR);
+       value |= PTP_TCR_TSINIT;
+       writel(value, ioaddr + PTP_TCR);
+
+       /* wait for present system time initialize to complete */
+       limit = 10;
+       while (limit--) {
+               if (!(readl(ioaddr + PTP_TCR) & PTP_TCR_TSINIT))
+                       break;
+               mdelay(10);
+       }
+       if (limit < 0)
+               return -EBUSY;
+
+       return 0;
+}
+
+static int stmmac_config_addend(void __iomem *ioaddr, u32 addend)
+{
+       u32 value;
+       int limit;
+
+       writel(addend, ioaddr + PTP_TAR);
+       /* issue command to update the addend value */
+       value = readl(ioaddr + PTP_TCR);
+       value |= PTP_TCR_TSADDREG;
+       writel(value, ioaddr + PTP_TCR);
+
+       /* wait for present addend update to complete */
+       limit = 10;
+       while (limit--) {
+               if (!(readl(ioaddr + PTP_TCR) & PTP_TCR_TSADDREG))
+                       break;
+               mdelay(10);
+       }
+       if (limit < 0)
+               return -EBUSY;
+
+       return 0;
+}
+
+const struct stmmac_hwtimestamp stmmac_ptp = {
+       .config_hw_tstamping = stmmac_config_hw_tstamping,
+       .init_systime = stmmac_init_systime,
+       .config_sub_second_increment = stmmac_config_sub_second_increment,
+       .config_addend = stmmac_config_addend,
+};
index 180eed7168c14b3c0641a5b8e91b5601c535a173..6906772069e33a7aed97b1323861a3c58dec4847 100644 (file)
@@ -47,6 +47,8 @@
 #include <linux/debugfs.h>
 #include <linux/seq_file.h>
 #endif
+#include <linux/net_tstamp.h>
+#include "stmmac_ptp.h"
 #include "stmmac.h"
 
 #undef STMMAC_DEBUG
@@ -311,6 +313,327 @@ static void stmmac_eee_adjust(struct stmmac_priv *priv)
                priv->hw->mac->set_eee_pls(priv->ioaddr, priv->phydev->link);
 }
 
+/* stmmac_get_tx_hwtstamp:
+ * @priv : pointer to private device structure.
+ * @entry : descriptor index to be used.
+ * @skb : the socket buffer
+ * Description :
+ * This function will read timestamp from the descriptor & pass it to stack.
+ * and also perform some sanity checks.
+ */
+static void stmmac_get_tx_hwtstamp(struct stmmac_priv *priv,
+                                  unsigned int entry,
+                                  struct sk_buff *skb)
+{
+       struct skb_shared_hwtstamps shhwtstamp;
+       u64 ns;
+       void *desc = NULL;
+
+       if (!priv->hwts_tx_en)
+               return;
+
+       /* if skb doesn't support hw tstamp */
+       if (likely(!(skb_shinfo(skb)->tx_flags & SKBTX_IN_PROGRESS)))
+               return;
+
+       if (priv->adv_ts)
+               desc = (priv->dma_etx + entry);
+       else
+               desc = (priv->dma_tx + entry);
+
+       /* check tx tstamp status */
+       if (!priv->hw->desc->get_tx_timestamp_status((struct dma_desc *)desc))
+               return;
+
+       /* get the valid tstamp */
+       ns = priv->hw->desc->get_timestamp(desc, priv->adv_ts);
+
+       memset(&shhwtstamp, 0, sizeof(struct skb_shared_hwtstamps));
+       shhwtstamp.hwtstamp = ns_to_ktime(ns);
+       /* pass tstamp to stack */
+       skb_tstamp_tx(skb, &shhwtstamp);
+
+       return;
+}
+
+/* stmmac_get_rx_hwtstamp:
+ * @priv : pointer to private device structure.
+ * @entry : descriptor index to be used.
+ * @skb : the socket buffer
+ * Description :
+ * This function will read received packet's timestamp from the descriptor
+ * and pass it to stack. It also perform some sanity checks.
+ */
+static void stmmac_get_rx_hwtstamp(struct stmmac_priv *priv,
+                                  unsigned int entry,
+                                  struct sk_buff *skb)
+{
+       struct skb_shared_hwtstamps *shhwtstamp = NULL;
+       u64 ns;
+       void *desc = NULL;
+
+       if (!priv->hwts_rx_en)
+               return;
+
+       if (priv->adv_ts)
+               desc = (priv->dma_erx + entry);
+       else
+               desc = (priv->dma_rx + entry);
+
+       /* if rx tstamp is not valid */
+       if (!priv->hw->desc->get_rx_timestamp_status(desc, priv->adv_ts))
+               return;
+
+       /* get valid tstamp */
+       ns = priv->hw->desc->get_timestamp(desc, priv->adv_ts);
+       shhwtstamp = skb_hwtstamps(skb);
+       memset(shhwtstamp, 0, sizeof(struct skb_shared_hwtstamps));
+       shhwtstamp->hwtstamp = ns_to_ktime(ns);
+}
+
+/**
+ *  stmmac_hwtstamp_ioctl - control hardware timestamping.
+ *  @dev: device pointer.
+ *  @ifr: An IOCTL specefic structure, that can contain a pointer to
+ *  a proprietary structure used to pass information to the driver.
+ *  Description:
+ *  This function configures the MAC to enable/disable both outgoing(TX)
+ *  and incoming(RX) packets time stamping based on user input.
+ *  Return Value:
+ *  0 on success and an appropriate -ve integer on failure.
+ */
+static int stmmac_hwtstamp_ioctl(struct net_device *dev, struct ifreq *ifr)
+{
+       struct stmmac_priv *priv = netdev_priv(dev);
+       struct hwtstamp_config config;
+       struct timespec now;
+       u64 temp = 0;
+       u32 ptp_v2 = 0;
+       u32 tstamp_all = 0;
+       u32 ptp_over_ipv4_udp = 0;
+       u32 ptp_over_ipv6_udp = 0;
+       u32 ptp_over_ethernet = 0;
+       u32 snap_type_sel = 0;
+       u32 ts_master_en = 0;
+       u32 ts_event_en = 0;
+       u32 value = 0;
+
+       if (!(priv->dma_cap.time_stamp || priv->adv_ts)) {
+               netdev_alert(priv->dev, "No support for HW time stamping\n");
+               priv->hwts_tx_en = 0;
+               priv->hwts_rx_en = 0;
+
+               return -EOPNOTSUPP;
+       }
+
+       if (copy_from_user(&config, ifr->ifr_data,
+               sizeof(struct hwtstamp_config)))
+               return -EFAULT;
+
+       pr_debug("%s config flags:0x%x, tx_type:0x%x, rx_filter:0x%x\n",
+                __func__, config.flags, config.tx_type, config.rx_filter);
+
+       /* reserved for future extensions */
+       if (config.flags)
+               return -EINVAL;
+
+       switch (config.tx_type) {
+       case HWTSTAMP_TX_OFF:
+               priv->hwts_tx_en = 0;
+               break;
+       case HWTSTAMP_TX_ON:
+               priv->hwts_tx_en = 1;
+               break;
+       default:
+               return -ERANGE;
+       }
+
+       if (priv->adv_ts) {
+               switch (config.rx_filter) {
+               /* time stamp no incoming packet at all */
+               case HWTSTAMP_FILTER_NONE:
+                       config.rx_filter = HWTSTAMP_FILTER_NONE;
+                       break;
+
+               /* PTP v1, UDP, any kind of event packet */
+               case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
+                       config.rx_filter = HWTSTAMP_FILTER_PTP_V1_L4_EVENT;
+                       /* take time stamp for all event messages */
+                       snap_type_sel = PTP_TCR_SNAPTYPSEL_1;
+
+                       ptp_over_ipv4_udp = PTP_TCR_TSIPV4ENA;
+                       ptp_over_ipv6_udp = PTP_TCR_TSIPV6ENA;
+                       break;
+
+               /* PTP v1, UDP, Sync packet */
+               case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
+                       config.rx_filter = HWTSTAMP_FILTER_PTP_V1_L4_SYNC;
+                       /* take time stamp for SYNC messages only */
+                       ts_event_en = PTP_TCR_TSEVNTENA;
+
+                       ptp_over_ipv4_udp = PTP_TCR_TSIPV4ENA;
+                       ptp_over_ipv6_udp = PTP_TCR_TSIPV6ENA;
+                       break;
+
+               /* PTP v1, UDP, Delay_req packet */
+               case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
+                       config.rx_filter = HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ;
+                       /* take time stamp for Delay_Req messages only */
+                       ts_master_en = PTP_TCR_TSMSTRENA;
+                       ts_event_en = PTP_TCR_TSEVNTENA;
+
+                       ptp_over_ipv4_udp = PTP_TCR_TSIPV4ENA;
+                       ptp_over_ipv6_udp = PTP_TCR_TSIPV6ENA;
+                       break;
+
+               /* PTP v2, UDP, any kind of event packet */
+               case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
+                       config.rx_filter = HWTSTAMP_FILTER_PTP_V2_L4_EVENT;
+                       ptp_v2 = PTP_TCR_TSVER2ENA;
+                       /* take time stamp for all event messages */
+                       snap_type_sel = PTP_TCR_SNAPTYPSEL_1;
+
+                       ptp_over_ipv4_udp = PTP_TCR_TSIPV4ENA;
+                       ptp_over_ipv6_udp = PTP_TCR_TSIPV6ENA;
+                       break;
+
+               /* PTP v2, UDP, Sync packet */
+               case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
+                       config.rx_filter = HWTSTAMP_FILTER_PTP_V2_L4_SYNC;
+                       ptp_v2 = PTP_TCR_TSVER2ENA;
+                       /* take time stamp for SYNC messages only */
+                       ts_event_en = PTP_TCR_TSEVNTENA;
+
+                       ptp_over_ipv4_udp = PTP_TCR_TSIPV4ENA;
+                       ptp_over_ipv6_udp = PTP_TCR_TSIPV6ENA;
+                       break;
+
+               /* PTP v2, UDP, Delay_req packet */
+               case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
+                       config.rx_filter = HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ;
+                       ptp_v2 = PTP_TCR_TSVER2ENA;
+                       /* take time stamp for Delay_Req messages only */
+                       ts_master_en = PTP_TCR_TSMSTRENA;
+                       ts_event_en = PTP_TCR_TSEVNTENA;
+
+                       ptp_over_ipv4_udp = PTP_TCR_TSIPV4ENA;
+                       ptp_over_ipv6_udp = PTP_TCR_TSIPV6ENA;
+                       break;
+
+               /* PTP v2/802.AS1, any layer, any kind of event packet */
+               case HWTSTAMP_FILTER_PTP_V2_EVENT:
+                       config.rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT;
+                       ptp_v2 = PTP_TCR_TSVER2ENA;
+                       /* take time stamp for all event messages */
+                       snap_type_sel = PTP_TCR_SNAPTYPSEL_1;
+
+                       ptp_over_ipv4_udp = PTP_TCR_TSIPV4ENA;
+                       ptp_over_ipv6_udp = PTP_TCR_TSIPV6ENA;
+                       ptp_over_ethernet = PTP_TCR_TSIPENA;
+                       break;
+
+               /* PTP v2/802.AS1, any layer, Sync packet */
+               case HWTSTAMP_FILTER_PTP_V2_SYNC:
+                       config.rx_filter = HWTSTAMP_FILTER_PTP_V2_SYNC;
+                       ptp_v2 = PTP_TCR_TSVER2ENA;
+                       /* take time stamp for SYNC messages only */
+                       ts_event_en = PTP_TCR_TSEVNTENA;
+
+                       ptp_over_ipv4_udp = PTP_TCR_TSIPV4ENA;
+                       ptp_over_ipv6_udp = PTP_TCR_TSIPV6ENA;
+                       ptp_over_ethernet = PTP_TCR_TSIPENA;
+                       break;
+
+               /* PTP v2/802.AS1, any layer, Delay_req packet */
+               case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
+                       config.rx_filter = HWTSTAMP_FILTER_PTP_V2_DELAY_REQ;
+                       ptp_v2 = PTP_TCR_TSVER2ENA;
+                       /* take time stamp for Delay_Req messages only */
+                       ts_master_en = PTP_TCR_TSMSTRENA;
+                       ts_event_en = PTP_TCR_TSEVNTENA;
+
+                       ptp_over_ipv4_udp = PTP_TCR_TSIPV4ENA;
+                       ptp_over_ipv6_udp = PTP_TCR_TSIPV6ENA;
+                       ptp_over_ethernet = PTP_TCR_TSIPENA;
+                       break;
+
+               /* time stamp any incoming packet */
+               case HWTSTAMP_FILTER_ALL:
+                       config.rx_filter = HWTSTAMP_FILTER_ALL;
+                       tstamp_all = PTP_TCR_TSENALL;
+                       break;
+
+               default:
+                       return -ERANGE;
+               }
+       } else {
+               switch (config.rx_filter) {
+               case HWTSTAMP_FILTER_NONE:
+                       config.rx_filter = HWTSTAMP_FILTER_NONE;
+                       break;
+               default:
+                       /* PTP v1, UDP, any kind of event packet */
+                       config.rx_filter = HWTSTAMP_FILTER_PTP_V1_L4_EVENT;
+                       break;
+               }
+       }
+       priv->hwts_rx_en = ((config.rx_filter == HWTSTAMP_FILTER_NONE) ? 0 : 1);
+
+       if (!priv->hwts_tx_en && !priv->hwts_rx_en)
+               priv->hw->ptp->config_hw_tstamping(priv->ioaddr, 0);
+       else {
+               value = (PTP_TCR_TSENA | PTP_TCR_TSCFUPDT | PTP_TCR_TSCTRLSSR |
+                       tstamp_all | ptp_v2 | ptp_over_ethernet |
+                       ptp_over_ipv6_udp | ptp_over_ipv4_udp | ts_event_en |
+                       ts_master_en | snap_type_sel);
+
+               priv->hw->ptp->config_hw_tstamping(priv->ioaddr, value);
+
+               /* program Sub Second Increment reg */
+               priv->hw->ptp->config_sub_second_increment(priv->ioaddr);
+
+               /* calculate default added value:
+                * formula is :
+                * addend = (2^32)/freq_div_ratio;
+                * where, freq_div_ratio = STMMAC_SYSCLOCK/50MHz
+                * hence, addend = ((2^32) * 50MHz)/STMMAC_SYSCLOCK;
+                * NOTE: STMMAC_SYSCLOCK should be >= 50MHz to
+                *       achive 20ns accuracy.
+                *
+                * 2^x * y == (y << x), hence
+                * 2^32 * 50000000 ==> (50000000 << 32)
+                */
+               temp = (u64)(50000000ULL << 32);
+               priv->default_addend = div_u64(temp, STMMAC_SYSCLOCK);
+               priv->hw->ptp->config_addend(priv->ioaddr,
+                                            priv->default_addend);
+
+               /* initialize system time */
+               getnstimeofday(&now);
+               priv->hw->ptp->init_systime(priv->ioaddr, now.tv_sec,
+                                           now.tv_nsec);
+       }
+
+       return copy_to_user(ifr->ifr_data, &config,
+                           sizeof(struct hwtstamp_config)) ? -EFAULT : 0;
+}
+
+static void stmmac_init_ptp(struct stmmac_priv *priv)
+{
+       if (priv->dma_cap.time_stamp) {
+               pr_debug("IEEE 1588-2002 Time Stamp supported\n");
+               priv->adv_ts = 0;
+       }
+       if (priv->dma_cap.atime_stamp && priv->extend_desc) {
+               pr_debug("IEEE 1588-2008 Advanced Time Stamp supported\n");
+               priv->adv_ts = 1;
+       }
+
+       priv->hw->ptp = &stmmac_ptp;
+       priv->hwts_tx_en = 0;
+       priv->hwts_rx_en = 0;
+}
+
 /**
  * stmmac_adjust_link
  * @dev: net device structure
@@ -856,6 +1179,8 @@ static void stmmac_tx_clean(struct stmmac_priv *priv)
                                priv->xstats.tx_pkt_n++;
                        } else
                                priv->dev->stats.tx_errors++;
+
+                       stmmac_get_tx_hwtstamp(priv, entry, skb);
                }
                TX_DBG("%s: curr %d, dirty %d\n", __func__,
                        priv->cur_tx, priv->dirty_tx);
@@ -867,8 +1192,7 @@ static void stmmac_tx_clean(struct stmmac_priv *priv)
                                         DMA_TO_DEVICE);
                        priv->tx_skbuff_dma[entry] = 0;
                }
-               if (priv->mode == STMMAC_RING_MODE)
-                       priv->hw->ring->clean_desc3(p);
+               priv->hw->ring->clean_desc3(priv, p);
 
                if (likely(skb != NULL)) {
                        dev_kfree_skb(skb);
@@ -1243,6 +1567,8 @@ static int stmmac_open(struct net_device *dev)
 
        stmmac_mmc_setup(priv);
 
+       stmmac_init_ptp(priv);
+
 #ifdef CONFIG_STMMAC_DEBUG_FS
        ret = stmmac_init_fs(dev);
        if (ret < 0)
@@ -1507,7 +1833,15 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev)
 
        dev->stats.tx_bytes += skb->len;
 
-       skb_tx_timestamp(skb);
+       if (unlikely((skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) &&
+                    priv->hwts_tx_en)) {
+               /* declare that device is doing timestamping */
+               skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
+               priv->hw->desc->enable_tx_timestamp(first);
+       }
+
+       if (!priv->hwts_tx_en)
+               skb_tx_timestamp(skb);
 
        priv->hw->dma->enable_dma_transmission(priv->ioaddr);
 
@@ -1545,9 +1879,7 @@ static inline void stmmac_rx_refill(struct stmmac_priv *priv)
 
                        p->des2 = priv->rx_skbuff_dma[entry];
 
-                       if (unlikely((priv->mode == STMMAC_RING_MODE) &&
-                                    (priv->plat->has_gmac)))
-                               priv->hw->ring->refill_desc3(bfsize, p);
+                       priv->hw->ring->refill_desc3(priv, p);
 
                        RX_DBG(KERN_INFO "\trefill entry #%d\n", entry);
                }
@@ -1604,9 +1936,20 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit)
                                                           &priv->xstats,
                                                           priv->dma_erx +
                                                           entry);
-               if (unlikely(status == discard_frame))
+               if (unlikely(status == discard_frame)) {
                        priv->dev->stats.rx_errors++;
-               else {
+                       if (priv->hwts_rx_en && !priv->extend_desc) {
+                               /* DESC2 & DESC3 will be overwitten by device
+                                * with timestamp value, hence reinitialize
+                                * them in stmmac_rx_refill() function so that
+                                * device can reuse it.
+                                */
+                               priv->rx_skbuff[entry] = NULL;
+                               dma_unmap_single(priv->device,
+                                       priv->rx_skbuff_dma[entry],
+                                       priv->dma_buf_sz, DMA_FROM_DEVICE);
+                       }
+               } else {
                        struct sk_buff *skb;
                        int frame_len;
 
@@ -1635,6 +1978,8 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit)
                        prefetch(skb->data - NET_IP_ALIGN);
                        priv->rx_skbuff[entry] = NULL;
 
+                       stmmac_get_rx_hwtstamp(priv, entry, skb);
+
                        skb_put(skb, frame_len);
                        dma_unmap_single(priv->device,
                                         priv->rx_skbuff_dma[entry],
@@ -1855,21 +2200,30 @@ static void stmmac_poll_controller(struct net_device *dev)
  *  a proprietary structure used to pass information to the driver.
  *  @cmd: IOCTL command
  *  Description:
- *  Currently there are no special functionality supported in IOCTL, just the
- *  phy_mii_ioctl(...) can be invoked.
+ *  Currently it supports just the phy_mii_ioctl(...) and HW time stamping.
  */
 static int stmmac_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
 {
        struct stmmac_priv *priv = netdev_priv(dev);
-       int ret;
+       int ret = -EOPNOTSUPP;
 
        if (!netif_running(dev))
                return -EINVAL;
 
-       if (!priv->phydev)
-               return -EINVAL;
-
-       ret = phy_mii_ioctl(priv->phydev, rq, cmd);
+       switch (cmd) {
+       case SIOCGMIIPHY:
+       case SIOCGMIIREG:
+       case SIOCSMIIREG:
+               if (!priv->phydev)
+                       return -EINVAL;
+               ret = phy_mii_ioctl(priv->phydev, rq, cmd);
+               break;
+       case SIOCSHWTSTAMP:
+               ret = stmmac_hwtstamp_ioctl(dev, rq);
+               break;
+       default:
+               break;
+       }
 
        return ret;
 }
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.h b/drivers/net/ethernet/stmicro/stmmac/stmmac_ptp.h
new file mode 100644 (file)
index 0000000..3dbc047
--- /dev/null
@@ -0,0 +1,74 @@
+/******************************************************************************
+  PTP Header file
+
+  Copyright (C) 2013  Vayavya Labs Pvt Ltd
+
+  This program is free software; you can redistribute it and/or modify it
+  under the terms and conditions of the GNU General Public License,
+  version 2, as published by the Free Software Foundation.
+
+  This program is distributed in the hope 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.,
+  51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+
+  The full GNU General Public License is included in this distribution in
+  the file called "COPYING".
+
+  Author: Rayagond Kokatanur <rayagond@vayavyalabs.com>
+******************************************************************************/
+
+#ifndef __STMMAC_PTP_H__
+#define __STMMAC_PTP_H__
+
+#define STMMAC_SYSCLOCK 62500000
+
+/* IEEE 1588 PTP register offsets */
+#define PTP_TCR                0x0700  /* Timestamp Control Reg */
+#define PTP_SSIR       0x0704  /* Sub-Second Increment Reg */
+#define PTP_STSR       0x0708  /* System Time – Seconds Regr */
+#define PTP_STNSR      0x070C  /* System Time – Nanoseconds Reg */
+#define PTP_STSUR      0x0710  /* System Time – Seconds Update Reg */
+#define PTP_STNSUR     0x0714  /* System Time – Nanoseconds Update Reg */
+#define PTP_TAR                0x0718  /* Timestamp Addend Reg */
+#define PTP_TTSR       0x071C  /* Target Time Seconds Reg */
+#define PTP_TTNSR      0x0720  /* Target Time Nanoseconds Reg */
+#define        PTP_STHWSR      0x0724  /* System Time - Higher Word Seconds Reg */
+#define PTP_TSR                0x0728  /* Timestamp Status */
+
+#define PTP_STNSUR_ADDSUB_SHIFT 31
+
+/* PTP TCR defines */
+#define PTP_TCR_TSENA          0x00000001 /* Timestamp Enable */
+#define PTP_TCR_TSCFUPDT       0x00000002 /* Timestamp Fine/Coarse Update */
+#define PTP_TCR_TSINIT         0x00000004 /* Timestamp Initialize */
+#define PTP_TCR_TSUPDT         0x00000008 /* Timestamp Update */
+/* Timestamp Interrupt Trigger Enable */
+#define PTP_TCR_TSTRIG         0x00000010
+#define PTP_TCR_TSADDREG       0x00000020 /* Addend Reg Update */
+#define PTP_TCR_TSENALL                0x00000100 /* Enable Timestamp for All Frames */
+/* Timestamp Digital or Binary Rollover Control */
+#define PTP_TCR_TSCTRLSSR      0x00000200
+
+/* Enable PTP packet Processing for Version 2 Format */
+#define PTP_TCR_TSVER2ENA      0x00000400
+/* Enable Processing of PTP over Ethernet Frames */
+#define PTP_TCR_TSIPENA                0x00000800
+/* Enable Processing of PTP Frames Sent over IPv6-UDP */
+#define PTP_TCR_TSIPV6ENA      0x00001000
+/* Enable Processing of PTP Frames Sent over IPv4-UDP */
+#define PTP_TCR_TSIPV4ENA      0x00002000
+/* Enable Timestamp Snapshot for Event Messages */
+#define PTP_TCR_TSEVNTENA      0x00004000
+/* Enable Snapshot for Messages Relevant to Master */
+#define PTP_TCR_TSMSTRENA      0x00008000
+/* Select PTP packets for Taking Snapshots */
+#define PTP_TCR_SNAPTYPSEL_1   0x00010000
+/* Enable MAC address for PTP Frame Filtering */
+#define PTP_TCR_TSENMACADDR    0x00040000
+
+#endif /* __STMMAC_PTP_H__ */