stmmac: add IEEE PTPv1 and PTPv2 support.
[firefly-linux-kernel-4.4.55.git] / drivers / net / ethernet / stmicro / stmmac / stmmac_main.c
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;
 }