#include <linux/if_ether.h>
 #include <linux/etherdevice.h>
 #include <linux/netdevice.h>
+#include <linux/net_tstamp.h>
 #include <linux/phy.h>
 #include <linux/workqueue.h>
 #include <linux/delay.h>
 #include <linux/platform_data/cpsw.h>
 
 #include "cpsw_ale.h"
+#include "cpts.h"
 #include "davinci_cpdma.h"
 
 #define CPSW_DEBUG     (NETIF_MSG_HW           | NETIF_MSG_WOL         | \
 /* The PTP event messages - Sync, Delay_Req, Pdelay_Req, and Pdelay_Resp. */
 #define EVENT_MSG_BITS ((1<<0) | (1<<1) | (1<<2) | (1<<3))
 
+/* Bit definitions for the CPSW1_TS_CTL register */
+#define CPSW_V1_TS_RX_EN               BIT(0)
+#define CPSW_V1_TS_TX_EN               BIT(4)
+#define CPSW_V1_MSG_TYPE_OFS           16
+
+/* Bit definitions for the CPSW1_TS_SEQ_LTYPE register */
+#define CPSW_V1_SEQ_ID_OFS_SHIFT       16
+
 struct cpsw_host_regs {
        u32     max_blks;
        u32     blk_cnt;
        /* snapshot of IRQ numbers */
        u32 irqs_table[4];
        u32 num_irqs;
+       struct cpts cpts;
 };
 
 #define napi_to_priv(napi)     container_of(napi, struct cpsw_priv, napi)
 
        if (unlikely(netif_queue_stopped(ndev)))
                netif_start_queue(ndev);
+       cpts_tx_timestamp(&priv->cpts, skb);
        priv->stats.tx_packets++;
        priv->stats.tx_bytes += len;
        dev_kfree_skb_any(skb);
        }
        if (likely(status >= 0)) {
                skb_put(skb, len);
+               cpts_rx_timestamp(&priv->cpts, skb);
                skb->protocol = eth_type_trans(skb, ndev);
                netif_receive_skb(skb);
                priv->stats.rx_bytes += len;
                return NETDEV_TX_OK;
        }
 
+       if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP && priv->cpts.tx_enable)
+               skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
+
+       skb_tx_timestamp(skb);
+
        ret = cpdma_chan_submit(priv->txch, skb, skb->data,
                                skb->len, GFP_KERNEL);
        if (unlikely(ret != 0)) {
                dev_err(&ndev->dev, "multicast traffic cannot be filtered!\n");
 }
 
+#ifdef CONFIG_TI_CPTS
+
+static void cpsw_hwtstamp_v1(struct cpsw_priv *priv)
+{
+       struct cpsw_slave *slave = &priv->slaves[priv->data.cpts_active_slave];
+       u32 ts_en, seq_id;
+
+       if (!priv->cpts.tx_enable && !priv->cpts.rx_enable) {
+               slave_write(slave, 0, CPSW1_TS_CTL);
+               return;
+       }
+
+       seq_id = (30 << CPSW_V1_SEQ_ID_OFS_SHIFT) | ETH_P_1588;
+       ts_en = EVENT_MSG_BITS << CPSW_V1_MSG_TYPE_OFS;
+
+       if (priv->cpts.tx_enable)
+               ts_en |= CPSW_V1_TS_TX_EN;
+
+       if (priv->cpts.rx_enable)
+               ts_en |= CPSW_V1_TS_RX_EN;
+
+       slave_write(slave, ts_en, CPSW1_TS_CTL);
+       slave_write(slave, seq_id, CPSW1_TS_SEQ_LTYPE);
+}
+
+static void cpsw_hwtstamp_v2(struct cpsw_priv *priv)
+{
+       struct cpsw_slave *slave = &priv->slaves[priv->data.cpts_active_slave];
+       u32 ctrl, mtype;
+
+       ctrl = slave_read(slave, CPSW2_CONTROL);
+       ctrl &= ~CTRL_ALL_TS_MASK;
+
+       if (priv->cpts.tx_enable)
+               ctrl |= CTRL_TX_TS_BITS;
+
+       if (priv->cpts.rx_enable)
+               ctrl |= CTRL_RX_TS_BITS;
+
+       mtype = (30 << TS_SEQ_ID_OFFSET_SHIFT) | EVENT_MSG_BITS;
+
+       slave_write(slave, mtype, CPSW2_TS_SEQ_MTYPE);
+       slave_write(slave, ctrl, CPSW2_CONTROL);
+       __raw_writel(ETH_P_1588, &priv->regs->ts_ltype);
+}
+
+static int cpsw_hwtstamp_ioctl(struct cpsw_priv *priv, struct ifreq *ifr)
+{
+       struct cpts *cpts = &priv->cpts;
+       struct hwtstamp_config cfg;
+
+       if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg)))
+               return -EFAULT;
+
+       /* reserved for future extensions */
+       if (cfg.flags)
+               return -EINVAL;
+
+       switch (cfg.tx_type) {
+       case HWTSTAMP_TX_OFF:
+               cpts->tx_enable = 0;
+               break;
+       case HWTSTAMP_TX_ON:
+               cpts->tx_enable = 1;
+               break;
+       default:
+               return -ERANGE;
+       }
+
+       switch (cfg.rx_filter) {
+       case HWTSTAMP_FILTER_NONE:
+               cpts->rx_enable = 0;
+               break;
+       case HWTSTAMP_FILTER_ALL:
+       case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
+       case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
+       case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
+               return -ERANGE;
+       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:
+               cpts->rx_enable = 1;
+               cfg.rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT;
+               break;
+       default:
+               return -ERANGE;
+       }
+
+       switch (priv->version) {
+       case CPSW_VERSION_1:
+               cpsw_hwtstamp_v1(priv);
+               break;
+       case CPSW_VERSION_2:
+               cpsw_hwtstamp_v2(priv);
+               break;
+       default:
+               return -ENOTSUPP;
+       }
+
+       return copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg)) ? -EFAULT : 0;
+}
+
+#endif /*CONFIG_TI_CPTS*/
+
+static int cpsw_ndo_ioctl(struct net_device *dev, struct ifreq *req, int cmd)
+{
+       struct cpsw_priv *priv = netdev_priv(dev);
+
+       if (!netif_running(dev))
+               return -EINVAL;
+
+#ifdef CONFIG_TI_CPTS
+       if (cmd == SIOCSHWTSTAMP)
+               return cpsw_hwtstamp_ioctl(priv, req);
+#endif
+       return -ENOTSUPP;
+}
+
 static void cpsw_ndo_tx_timeout(struct net_device *ndev)
 {
        struct cpsw_priv *priv = netdev_priv(ndev);
        .ndo_stop               = cpsw_ndo_stop,
        .ndo_start_xmit         = cpsw_ndo_start_xmit,
        .ndo_change_rx_flags    = cpsw_ndo_change_rx_flags,
+       .ndo_do_ioctl           = cpsw_ndo_ioctl,
        .ndo_validate_addr      = eth_validate_addr,
        .ndo_change_mtu         = eth_change_mtu,
        .ndo_tx_timeout         = cpsw_ndo_tx_timeout,
        priv->msg_enable = value;
 }
 
+static int cpsw_get_ts_info(struct net_device *ndev,
+                           struct ethtool_ts_info *info)
+{
+#ifdef CONFIG_TI_CPTS
+       struct cpsw_priv *priv = netdev_priv(ndev);
+
+       info->so_timestamping =
+               SOF_TIMESTAMPING_TX_HARDWARE |
+               SOF_TIMESTAMPING_TX_SOFTWARE |
+               SOF_TIMESTAMPING_RX_HARDWARE |
+               SOF_TIMESTAMPING_RX_SOFTWARE |
+               SOF_TIMESTAMPING_SOFTWARE |
+               SOF_TIMESTAMPING_RAW_HARDWARE;
+       info->phc_index = priv->cpts.phc_index;
+       info->tx_types =
+               (1 << HWTSTAMP_TX_OFF) |
+               (1 << HWTSTAMP_TX_ON);
+       info->rx_filters =
+               (1 << HWTSTAMP_FILTER_NONE) |
+               (1 << HWTSTAMP_FILTER_PTP_V2_EVENT);
+#else
+       info->so_timestamping =
+               SOF_TIMESTAMPING_TX_SOFTWARE |
+               SOF_TIMESTAMPING_RX_SOFTWARE |
+               SOF_TIMESTAMPING_SOFTWARE;
+       info->phc_index = -1;
+       info->tx_types = 0;
+       info->rx_filters = 0;
+#endif
+       return 0;
+}
+
 static const struct ethtool_ops cpsw_ethtool_ops = {
        .get_drvinfo    = cpsw_get_drvinfo,
        .get_msglevel   = cpsw_get_msglevel,
        .set_msglevel   = cpsw_set_msglevel,
        .get_link       = ethtool_op_get_link,
+       .get_ts_info    = cpsw_get_ts_info,
 };
 
 static void cpsw_slave_init(struct cpsw_slave *slave, struct cpsw_priv *priv)
        priv->regs = regs;
        priv->host_port = data->host_port_num;
        priv->host_port_regs = regs + data->host_port_reg_ofs;
+       priv->cpts.reg = regs + data->cpts_reg_ofs;
 
        priv->cpsw_ss_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
        if (!priv->cpsw_ss_res) {
                goto clean_irq_ret;
        }
 
+       if (cpts_register(&pdev->dev, &priv->cpts,
+                         data->cpts_clock_mult, data->cpts_clock_shift))
+               dev_err(priv->dev, "error registering cpts device\n");
+
        cpsw_notice(priv, probe, "initialized device (regs %x, irq %d)\n",
                  priv->cpsw_res->start, ndev->irq);
 
        pr_info("removing device");
        platform_set_drvdata(pdev, NULL);
 
+       cpts_unregister(&priv->cpts);
        free_irq(ndev->irq, priv);
        cpsw_ale_destroy(priv->ale);
        cpdma_chan_destroy(priv->txch);