]> rtime.felk.cvut.cz Git - zynq/linux.git/commitdiff
drivers: net: ethernet: TSN PTP module
authorSaurabh Sengar <saurabh.singh@xilinx.com>
Thu, 16 Mar 2017 14:56:13 +0000 (20:26 +0530)
committerMichal Simek <michal.simek@xilinx.com>
Thu, 15 Mar 2018 14:18:01 +0000 (15:18 +0100)
Adding TSN IP ptp module apis

Signed-off-by: Saurabh Sengar <saurabhs@xilinx.com>
Acked-by: Kedareswara rao Appana <appanad@xilinx.com>
Signed-off-by: Michal Simek <michal.simek@xilinx.com>
drivers/net/ethernet/xilinx/xilinx_tsn_ptp.h [new file with mode: 0644]
drivers/net/ethernet/xilinx/xilinx_tsn_ptp_clock.c [new file with mode: 0644]
drivers/net/ethernet/xilinx/xilinx_tsn_ptp_xmit.c [new file with mode: 0644]
drivers/net/ethernet/xilinx/xilinx_tsn_timer.h [new file with mode: 0644]

diff --git a/drivers/net/ethernet/xilinx/xilinx_tsn_ptp.h b/drivers/net/ethernet/xilinx/xilinx_tsn_ptp.h
new file mode 100644 (file)
index 0000000..f89e5f1
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * Xilinx TSN PTP header
+ *
+ * Copyright (C) 2017 Xilinx, Inc.
+ *
+ * Author: Syed S <syeds@xilinx.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ */
+
+#ifndef _TSN_PTP_H_
+#define _TSN_PTP_H_
+
+#define PTP_HW_TSTAMP_SIZE  8   /* 64 bit timestamp */
+#define PTP_RX_HWBUF_SIZE   256
+#define PTP_RX_FRAME_SIZE   252
+#define PTP_HW_TSTAMP_OFFSET (PTP_RX_HWBUF_SIZE - PTP_HW_TSTAMP_SIZE)
+
+#define PTP_TYPE_SYNC                                   0x0
+#define PTP_TYPE_FOLLOW_UP                              0x8
+#define PTP_TYPE_PDELAYREQ                              0x2
+#define PTP_TYPE_PDELAYRESP                             0x3
+#define PTP_TYPE_PDELAYRESP_FOLLOW_UP                   0xA
+#define PTP_TYPE_ANNOUNCE                               0xB
+#define PTP_TYPE_SIGNALING                              0xC
+
+#define PTP_TX_CONTROL_OFFSET          0x00012000 /**< Tx PTP Control Reg */
+#define PTP_RX_CONTROL_OFFSET          0x00012004 /**< Rx PTP Control Reg */
+#define RX_FILTER_CONTROL              0x00012008 /**< Rx Filter Ctrl Reg */
+
+#define PTP_RX_BASE_OFFSET             0x00010000
+#define PTP_RX_CONTROL_OFFSET          0x00012004 /**< Rx PTP Control Reg */
+#define PTP_RX_PACKET_FIELD_MASK       0x00000F00
+#define PTP_RX_PACKET_CLEAR            0x00000001
+
+#define PTP_TX_BUFFER_OFFSET(index)       (0x00011000 + (index) * 0x100)
+
+#define PTP_TX_SYNC_OFFSET                 0x00011000
+#define PTP_TX_FOLLOW_UP_OFFSET            0x00011100
+#define PTP_TX_PDELAYREQ_OFFSET            0x00011200
+#define PTP_TX_PDELAYRESP_OFFSET           0x00011300
+#define PTP_TX_PDELAYRESP_FOLLOW_UP_OFFSET 0x00011400
+#define PTP_TX_ANNOUNCE_OFFSET             0x00011500
+#define PTP_TX_SIGNALING_OFFSET                   0x00011600
+#define PTP_TX_GENERIC_OFFSET             0x00011700
+#define PTP_TX_SEND_SYNC_FRAME_MASK                     0x00000001
+#define PTP_TX_SEND_FOLLOWUP_FRAME_MASK                 0x00000002
+#define PTP_TX_SEND_PDELAYREQ_FRAME_MASK                0x00000004
+#define PTP_TX_SEND_PDELAYRESP_FRAME_MASK               0x00000008
+#define PTP_TX_SEND_PDELAYRESPFOLLOWUP_FRAME_MASK       0x00000010
+#define PTP_TX_SEND_ANNOUNCE_FRAME_MASK                 0x00000020
+#define PTP_TX_SEND_FRAME6_BIT_MASK                     0x00000040
+#define PTP_TX_SEND_FRAME7_BIT_MASK                     0x00000080
+#define PTP_TX_FRAME_WAITING_MASK                      0x0000ff00
+#define PTP_TX_FRAME_WAITING_SHIFT                     8
+#define PTP_TX_WAIT_SYNC_FRAME_MASK                     0x00000100
+#define PTP_TX_WAIT_FOLLOWUP_FRAME_MASK                 0x00000200
+#define PTP_TX_WAIT_PDELAYREQ_FRAME_MASK                0x00000400
+#define PTP_TX_WAIT_PDELAYRESP_FRAME_MASK               0x00000800
+#define PTP_TX_WAIT_PDELAYRESPFOLLOWUP_FRAME_MASK       0x00001000
+#define PTP_TX_WAIT_ANNOUNCE_FRAME_MASK                 0x00002000
+#define PTP_TX_WAIT_FRAME6_BIT_MASK                     0x00004000
+#define PTP_TX_WAIT_FRAME7_BIT_MASK                     0x00008000
+#define PTP_TX_WAIT_ALL_FRAMES_MASK                     0x0000FF00
+#define PTP_TX_PACKET_FIELD_MASK                        0x00070000
+#define PTP_TX_PACKET_FIELD_SHIFT                       16
+
+int axienet_ptp_xmit(struct sk_buff *skb, struct net_device *ndev);
+irqreturn_t axienet_ptp_rx_irq(int irq, void *_ndev);
+irqreturn_t axienet_ptp_tx_irq(int irq, void *_ndev);
+
+#endif
diff --git a/drivers/net/ethernet/xilinx/xilinx_tsn_ptp_clock.c b/drivers/net/ethernet/xilinx/xilinx_tsn_ptp_clock.c
new file mode 100644 (file)
index 0000000..3ee815e
--- /dev/null
@@ -0,0 +1,314 @@
+/*
+ * Xilinx FPGA Xilinx TSN PTP protocol clock Controller module.
+ *
+ * Copyright (c) 2017 Xilinx Pvt., Ltd
+ *
+ * Author: Syed S <syeds@xilinx.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/ptp_clock_kernel.h>
+#include <linux/platform_device.h>
+#include <linux/of_irq.h>
+#include "xilinx_tsn_timer.h"
+
+struct xlnx_ptp_timer {
+       struct                 device *dev;
+       void __iomem          *baseaddr;
+       struct ptp_clock      *ptp_clock;
+       struct ptp_clock_info  ptp_clock_info;
+       spinlock_t             reg_lock; /* ptp timer lock */
+       int                    irq;
+       int                    pps_enable;
+       int                    countpulse;
+};
+
+static void xlnx_tod_read(struct xlnx_ptp_timer *timer, struct timespec64 *ts)
+{
+       u32 sec, nsec;
+
+       nsec = in_be32(timer->baseaddr + XTIMER1588_CURRENT_RTC_NS);
+       sec = in_be32(timer->baseaddr + XTIMER1588_CURRENT_RTC_SEC_L);
+
+       ts->tv_sec = sec;
+       ts->tv_nsec = nsec;
+}
+
+static void xlnx_rtc_offset_write(struct xlnx_ptp_timer *timer,
+                                 const struct timespec64 *ts)
+{
+       pr_debug("%s: sec: %ld nsec: %ld\n", __func__, ts->tv_sec, ts->tv_nsec);
+
+       out_be32((timer->baseaddr + XTIMER1588_RTC_OFFSET_SEC_H), 0);
+       out_be32((timer->baseaddr + XTIMER1588_RTC_OFFSET_SEC_L),
+                (ts->tv_sec));
+       out_be32((timer->baseaddr + XTIMER1588_RTC_OFFSET_NS), ts->tv_nsec);
+}
+
+static void xlnx_rtc_offset_read(struct xlnx_ptp_timer *timer,
+                                struct timespec64 *ts)
+{
+       ts->tv_sec = in_be32(timer->baseaddr + XTIMER1588_RTC_OFFSET_SEC_L);
+       ts->tv_nsec = in_be32(timer->baseaddr + XTIMER1588_RTC_OFFSET_NS);
+}
+
+/* PTP clock operations
+ */
+static int xlnx_ptp_adjfreq(struct ptp_clock_info *ptp, s32 ppb)
+{
+       struct xlnx_ptp_timer *timer = container_of(ptp, struct xlnx_ptp_timer,
+                                                   ptp_clock_info);
+
+       int neg_adj = 0;
+       u64 freq;
+       u32 diff, incval;
+
+       /* This number should be replaced by a call to get the frequency
+        * from the device-tree. Currently assumes 125MHz
+        */
+       incval = 0x800000;
+       /* for 156.25 MHZ Ref clk the value is  incval = 0x800000; */
+
+       if (ppb < 0) {
+               neg_adj = 1;
+               ppb = -ppb;
+       }
+
+       freq = incval;
+       freq *= ppb;
+       diff = div_u64(freq, 1000000000ULL);
+
+       pr_debug("%s: adj: %d ppb: %d\n", __func__, diff, ppb);
+
+       incval = neg_adj ? (incval - diff) : (incval + diff);
+       out_be32((timer->baseaddr + XTIMER1588_RTC_INCREMENT), incval);
+       return 0;
+}
+
+static int xlnx_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
+{
+       unsigned long flags;
+       struct xlnx_ptp_timer *timer = container_of(ptp, struct xlnx_ptp_timer,
+                                                   ptp_clock_info);
+       struct timespec64 now, then = ns_to_timespec64(delta);
+
+       spin_lock_irqsave(&timer->reg_lock, flags);
+
+       xlnx_rtc_offset_read(timer, &now);
+
+       now = timespec64_add(now, then);
+
+       xlnx_rtc_offset_write(timer, (const struct timespec64 *)&now);
+       spin_unlock_irqrestore(&timer->reg_lock, flags);
+
+       return 0;
+}
+
+static int xlnx_ptp_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts)
+{
+       unsigned long flags;
+       struct xlnx_ptp_timer *timer = container_of(ptp, struct xlnx_ptp_timer,
+                                                   ptp_clock_info);
+       spin_lock_irqsave(&timer->reg_lock, flags);
+
+       xlnx_tod_read(timer, ts);
+
+       spin_unlock_irqrestore(&timer->reg_lock, flags);
+       return 0;
+}
+
+/**
+ * xlnx_ptp_settime - Set the current time on the hardware clock
+ * @ptp: ptp clock structure
+ * @ts: timespec64 containing the new time for the cycle counter
+ *
+ * Return: 0 in all cases.
+ *
+ * The seconds register is written first, then the nanosecond
+ * The hardware loads the entire new value when a nanosecond register
+ * is written
+ **/
+static int xlnx_ptp_settime(struct ptp_clock_info *ptp,
+                           const struct timespec64 *ts)
+{
+       struct xlnx_ptp_timer *timer = container_of(ptp, struct xlnx_ptp_timer,
+                                                   ptp_clock_info);
+       struct timespec64 delta, tod;
+       struct timespec64 offset;
+       unsigned long flags;
+
+       spin_lock_irqsave(&timer->reg_lock, flags);
+
+       /* First zero the offset */
+       offset.tv_sec = 0;
+       offset.tv_nsec = 0;
+       xlnx_rtc_offset_write(timer, &offset);
+
+       /* Get the current timer value */
+       xlnx_tod_read(timer, &tod);
+
+       /* Subtract the current reported time from our desired time */
+       delta = timespec64_sub(*ts, tod);
+
+       /* Don't write a negative offset */
+       if (delta.tv_sec <= 0) {
+               delta.tv_sec = 0;
+               if (delta.tv_nsec < 0)
+                       delta.tv_nsec = 0;
+       }
+
+       xlnx_rtc_offset_write(timer, &delta);
+       spin_unlock_irqrestore(&timer->reg_lock, flags);
+       return 0;
+}
+
+static int xlnx_ptp_enable(struct ptp_clock_info *ptp,
+                          struct ptp_clock_request *rq, int on)
+{
+       struct xlnx_ptp_timer *timer = container_of(ptp, struct xlnx_ptp_timer,
+                                                   ptp_clock_info);
+
+       switch (rq->type) {
+       case PTP_CLK_REQ_PPS:
+               timer->pps_enable = 1;
+               return 0;
+       default:
+               break;
+       }
+
+       return -EOPNOTSUPP;
+}
+
+static struct ptp_clock_info xlnx_ptp_clock_info = {
+       .owner    = THIS_MODULE,
+       .name     = "Xilinx Timer",
+       .max_adj  = 999999999,
+       .n_ext_ts       = 0,
+       .pps      = 1,
+       .adjfreq  = xlnx_ptp_adjfreq,
+       .adjtime  = xlnx_ptp_adjtime,
+       .gettime64  = xlnx_ptp_gettime,
+       .settime64 = xlnx_ptp_settime,
+       .enable   = xlnx_ptp_enable,
+};
+
+/* module operations */
+
+/**
+ * xlnx_ptp_timer_isr - Interrupt Service Routine
+ * @irq:               IRQ number
+ * @priv:              pointer to the timer structure
+ *
+ * Returns: IRQ_HANDLED for all cases
+ *
+ * Handles the timer interrupt. The timer interrupt fires 128 times per
+ * secound. When our count reaches 128 emit a PTP_CLOCK_PPS event
+ */
+static irqreturn_t xlnx_ptp_timer_isr(int irq, void *priv)
+{
+       struct xlnx_ptp_timer *timer = (struct xlnx_ptp_timer *)priv;
+       struct ptp_clock_event event;
+
+       event.type = PTP_CLOCK_PPS;
+       ++timer->countpulse;
+       if (timer->countpulse >= PULSESIN1PPS) {
+               timer->countpulse = 0;
+               if ((timer->ptp_clock) && (timer->pps_enable))
+                       ptp_clock_event(timer->ptp_clock, &event);
+       }
+       out_be32((timer->baseaddr + XTIMER1588_INTERRUPT),
+                (1 << XTIMER1588_INT_SHIFT));
+
+       return IRQ_HANDLED;
+}
+
+int axienet_ptp_timer_remove(void *priv)
+{
+       struct xlnx_ptp_timer *timer = (struct xlnx_ptp_timer *)priv;
+
+       free_irq(timer->irq, (void *)timer);
+
+       axienet_phc_index = -1;
+       if (timer->ptp_clock) {
+               ptp_clock_unregister(timer->ptp_clock);
+               timer->ptp_clock = NULL;
+       }
+       kfree(timer);
+       return 0;
+}
+
+int axienet_get_phc_index(void *priv)
+{
+       struct xlnx_ptp_timer *timer = (struct xlnx_ptp_timer *)priv;
+
+       if (timer->ptp_clock)
+               return ptp_clock_index(timer->ptp_clock);
+       else
+               return -1;
+}
+
+void *axienet_ptp_timer_probe(void __iomem *base, struct platform_device *pdev)
+{
+       struct xlnx_ptp_timer *timer;
+       struct timespec64 ts;
+       int err = 0;
+
+       timer = kzalloc(sizeof(*timer), GFP_KERNEL);
+       if (!timer)
+               return NULL;
+
+       timer->baseaddr = base;
+
+       timer->irq = platform_get_irq_byname(pdev, "rtc_irq");
+
+       spin_lock_init(&timer->reg_lock);
+
+       timer->ptp_clock_info = xlnx_ptp_clock_info;
+
+       timer->ptp_clock = ptp_clock_register(&timer->ptp_clock_info,
+                                             &pdev->dev);
+
+       if (IS_ERR(timer->ptp_clock)) {
+               err = PTR_ERR(timer->ptp_clock);
+               pr_debug("Failed to register ptp clock\n");
+               goto out;
+       }
+
+       axienet_phc_index = ptp_clock_index(timer->ptp_clock);
+
+       ts = ktime_to_timespec64(ktime_get_real());
+
+       xlnx_ptp_settime(&timer->ptp_clock_info, &ts);
+
+       /* Enable interrupts */
+       err = request_irq(timer->irq,
+                         xlnx_ptp_timer_isr,
+                         0,
+                         "ptp_rtc",
+                         (void *)timer);
+       if (err)
+               goto err_irq;
+
+       return timer;
+
+err_irq:
+       ptp_clock_unregister(timer->ptp_clock);
+out:
+       timer->ptp_clock = NULL;
+       return NULL;
+}
diff --git a/drivers/net/ethernet/xilinx/xilinx_tsn_ptp_xmit.c b/drivers/net/ethernet/xilinx/xilinx_tsn_ptp_xmit.c
new file mode 100644 (file)
index 0000000..55cdd8f
--- /dev/null
@@ -0,0 +1,324 @@
+/*
+ * Xilinx FPGA Xilinx TSN PTP transfer protocol module.
+ *
+ * Copyright (c) 2017 Xilinx Pvt., Ltd
+ *
+ * Author: Syed S <syeds@xilinx.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ */
+
+#include "xilinx_axienet.h"
+#include "xilinx_tsn_ptp.h"
+#include "xilinx_tsn_timer.h"
+#include <linux/ptp_classify.h>
+
+#define PTP_ONE_SECOND            1000000000    /**< Value in ns */
+
+#define msg_type_string(type) \
+       ((type) == PTP_TYPE_SYNC) ? "SYNC" : \
+       ((type) == PTP_TYPE_FOLLOW_UP)            ? "FOLLOW_UP" : \
+       ((type) == PTP_TYPE_PDELAYREQ)            ? "PDELAY_REQ" : \
+       ((type) == PTP_TYPE_PDELAYRESP)           ? "PDELAY_RESP" : \
+       ((type) == PTP_TYPE_PDELAYRESP_FOLLOW_UP) ? "PDELAY_RESP_FOLLOW_UP" : \
+       ((type) == PTP_TYPE_ANNOUNCE)             ? "ANNOUNCE" : \
+       "UNKNOWN"
+
+/**
+ * memcpy_fromio_32 - copy ptp buffer from HW
+ * @lp:                Pointer to axienet local structure
+ * @offset:    Offset in the PTP buffer
+ * @data:      Destination buffer
+ * @len:       Len to copy
+ *
+ * This functions copies the data from PTP buffer to destination data buffer
+ */
+static void memcpy_fromio_32(struct axienet_local *lp,
+                            unsigned long offset, u8 *data, size_t len)
+{
+       while (len >= 4) {
+               *(u32 *)data = axienet_ior(lp, offset);
+               len -= 4;
+               offset += 4;
+               data += 4;
+       }
+
+       if (len > 0) {
+               u32 leftover = axienet_ior(lp, offset);
+               u8 *src = (u8 *)&leftover;
+
+               while (len) {
+                       *data++ = *src++;
+                       len--;
+               }
+       }
+}
+
+/**
+ * memcpy_toio_32 - copy ptp buffer from HW
+ * @lp:                Pointer to axienet local structure
+ * @offset:    Offset in the PTP buffer
+ * @data:      Source data
+ * @len:       Len to copy
+ *
+ * This functions copies the source data to desination ptp buffer
+ */
+static void memcpy_toio_32(struct axienet_local *lp,
+                          unsigned long offset, u8 *data, size_t len)
+{
+       while (len >= 4) {
+               axienet_iow(lp, offset, *(u32 *)data);
+               len -= 4;
+               offset += 4;
+               data += 4;
+       }
+
+       if (len > 0) {
+               u32 leftover = 0;
+               u8 *dest = (u8 *)&leftover;
+
+               while (len) {
+                       *dest++ = *data++;
+                       len--;
+               }
+               axienet_iow(lp, offset, leftover);
+       }
+}
+
+/**
+ * axienet_ptp_xmit - xmit skb using PTP HW
+ * @skb:       sk_buff pointer that contains data to be Txed.
+ * @ndev:      Pointer to net_device structure.
+ *
+ * Return: NETDEV_TX_OK, on success
+ *         NETDEV_TX_BUSY, if any of the descriptors are not free
+ *
+ * This function is called to transmit a PTP skb. The function uses
+ * the free PTP TX buffer entry and sends the frame
+ */
+int axienet_ptp_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+       u8 msg_type;
+       struct axienet_local *lp = netdev_priv(ndev);
+       unsigned long flags;
+       u8 tx_frame_waiting;
+       u8 free_index;
+
+       msg_type  = *(u8 *)(skb->data + ETH_HLEN);
+
+       pr_debug("  -->XMIT: protocol: %x message: %s frame_len: %d\n",
+                skb->protocol,
+                msg_type_string(msg_type & 0xf), skb->len);
+
+       tx_frame_waiting =  (axienet_ior(lp, PTP_TX_CONTROL_OFFSET) &
+                               PTP_TX_FRAME_WAITING_MASK) >>
+                               PTP_TX_FRAME_WAITING_SHIFT;
+
+       /* we reached last frame */
+       if (tx_frame_waiting & (1 << 7)) {
+               if (!netif_queue_stopped(ndev))
+                       netif_stop_queue(ndev);
+               pr_debug("tx_frame_waiting: %d\n", tx_frame_waiting);
+               return NETDEV_TX_BUSY;
+       }
+
+       /* go to next available slot */
+       free_index  = fls(tx_frame_waiting);
+
+       /* write the len */
+       axienet_iow(lp, PTP_TX_BUFFER_OFFSET(free_index), skb->len);
+       memcpy_toio_32(lp, (PTP_TX_BUFFER_OFFSET(free_index) + 8),
+                      skb->data, skb->len);
+
+       /* send the frame */
+       axienet_iow(lp, PTP_TX_CONTROL_OFFSET, (1 << free_index));
+
+       spin_lock_irqsave(&lp->ptp_tx_lock, flags);
+       skb->cb[0] = free_index;
+       skb_queue_tail(&lp->ptp_txq, skb_get(skb));
+       skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
+       skb_tx_timestamp(skb);
+       spin_unlock_irqrestore(&lp->ptp_tx_lock, flags);
+
+       dev_kfree_skb_any(skb);
+
+       return NETDEV_TX_OK;
+}
+
+/**
+ * axienet_set_timestamp - timestamp skb with HW timestamp
+ * @lp:                Pointer to axienet local structure
+ * @hwtstamps:  Pointer to skb timestamp structure
+ * @offset:    offset of the timestamp in the PTP buffer
+ *
+ * Return:     None.
+ *
+ */
+static void axienet_set_timestamp(struct axienet_local *lp,
+                                 struct skb_shared_hwtstamps *hwtstamps,
+                                 unsigned int offset)
+{
+       u32 captured_ns;
+       u32 captured_sec;
+
+       captured_ns = axienet_ior(lp, offset + 4);
+       captured_sec = axienet_ior(lp, offset);
+
+       /* Upper 32 bits contain s, lower 32 bits contain ns. */
+       hwtstamps->hwtstamp = ktime_set(captured_sec,
+                                               captured_ns);
+}
+
+/**
+ * axienet_ptp_recv - receive ptp buffer in skb from HW
+ * @ndev:      Pointer to net_device structure.
+ *
+ * This function is called from the ptp rx isr. It allocates skb, and
+ * copies the ptp rx buffer data to it and calls netif_rx for further
+ * processing.
+ *
+ */
+static void axienet_ptp_recv(struct net_device *ndev)
+{
+       struct axienet_local *lp = netdev_priv(ndev);
+       unsigned long ptp_frame_base_addr = 0;
+       struct sk_buff *skb;
+       u16 msg_len;
+       u8 msg_type;
+
+       pr_debug("%s:\n ", __func__);
+
+       while (((lp->ptp_rx_hw_pointer & 0xf) !=
+                (lp->ptp_rx_sw_pointer & 0xf))) {
+               skb = netdev_alloc_skb(ndev, PTP_RX_FRAME_SIZE);
+
+               lp->ptp_rx_sw_pointer += 1;
+
+               ptp_frame_base_addr = PTP_RX_BASE_OFFSET +
+                                  ((lp->ptp_rx_sw_pointer & 0xf) *
+                                                       PTP_RX_HWBUF_SIZE);
+
+               memset(skb->data, 0x0, PTP_RX_FRAME_SIZE);
+
+               memcpy_fromio_32(lp, ptp_frame_base_addr, skb->data,
+                                PTP_RX_FRAME_SIZE);
+
+               msg_type  = *(u8 *)(skb->data + ETH_HLEN) & 0xf;
+               msg_len  = *(u16 *)(skb->data + ETH_HLEN + 2);
+
+               skb_put(skb, ntohs(msg_len) + ETH_HLEN);
+
+               skb->protocol = eth_type_trans(skb, ndev);
+               skb->ip_summed = CHECKSUM_UNNECESSARY;
+
+               pr_debug("  -->RECV: protocol: %x message: %s frame_len: %d\n",
+                        skb->protocol, msg_type_string(msg_type & 0xf),
+                        skb->len);
+               axienet_set_timestamp(lp, skb_hwtstamps(skb),
+                                     (ptp_frame_base_addr
+                                     + PTP_HW_TSTAMP_OFFSET));
+
+               netif_rx(skb);
+       }
+}
+
+/**
+ * axienet_ptp_rx_irq - PTP RX ISR handler
+ * @irq:               irq number
+ * @_ndev:     net_device pointer
+ *
+ * Return:     IRQ_HANDLED for all cases.
+ */
+irqreturn_t axienet_ptp_rx_irq(int irq, void *_ndev)
+{
+       struct net_device *ndev = _ndev;
+       struct axienet_local *lp = netdev_priv(ndev);
+
+       pr_debug("%s: received\n ", __func__);
+       lp->ptp_rx_hw_pointer = (axienet_ior(lp, PTP_RX_CONTROL_OFFSET)
+                                       & PTP_RX_PACKET_FIELD_MASK)  >> 8;
+
+       axienet_ptp_recv(ndev);
+
+       return IRQ_HANDLED;
+}
+
+/**
+ * axienet_tx_tstamp - timestamp skb on trasmit path
+ * @work:      Pointer to work_struct structure
+ *
+ * This adds TX timestamp to skb
+ */
+void axienet_tx_tstamp(struct work_struct *work)
+{
+       struct axienet_local *lp = container_of(work, struct axienet_local,
+                       tx_tstamp_work);
+       struct skb_shared_hwtstamps hwtstamps;
+       struct sk_buff *skb;
+       unsigned long ts_reg_offset;
+       unsigned long flags;
+       u8 tx_packet;
+       u8 index;
+
+       memset(&hwtstamps, 0, sizeof(struct skb_shared_hwtstamps));
+
+       spin_lock_irqsave(&lp->ptp_tx_lock, flags);
+
+       tx_packet =  (axienet_ior(lp, PTP_TX_CONTROL_OFFSET) &
+                               PTP_TX_PACKET_FIELD_MASK) >>
+                               PTP_TX_PACKET_FIELD_SHIFT;
+
+       while ((skb = __skb_dequeue(&lp->ptp_txq)) != NULL) {
+               index = skb->cb[0];
+
+               /* dequeued packet yet to be xmited? */
+               if (index > tx_packet) {
+                       /* enqueue it back and break */
+                       skb_queue_tail(&lp->ptp_txq, skb);
+                       break;
+               }
+               /* time stamp reg offset */
+               ts_reg_offset = PTP_TX_BUFFER_OFFSET(index) +
+                                       PTP_HW_TSTAMP_OFFSET;
+
+               axienet_set_timestamp(lp, &hwtstamps, ts_reg_offset);
+
+               skb_tstamp_tx(skb, &hwtstamps);
+
+               dev_kfree_skb_any(skb);
+       }
+
+       spin_unlock_irqrestore(&lp->ptp_tx_lock, flags);
+}
+
+/**
+ * axienet_ptp_tx_irq - PTP TX irq handler
+ * @irq:               irq number
+ * @_ndev:     net_device pointer
+ *
+ * Return:     IRQ_HANDLED for all cases.
+ *
+ */
+irqreturn_t axienet_ptp_tx_irq(int irq, void *_ndev)
+{
+       struct net_device *ndev = _ndev;
+       struct axienet_local *lp = netdev_priv(ndev);
+
+       pr_debug("%s: got tx interrupt\n", __func__);
+
+       /* read ctrl register to clear the interrupt */
+       axienet_ior(lp, PTP_TX_CONTROL_OFFSET);
+
+       schedule_work(&lp->tx_tstamp_work);
+
+       netif_wake_queue(ndev);
+
+       return IRQ_HANDLED;
+}
diff --git a/drivers/net/ethernet/xilinx/xilinx_tsn_timer.h b/drivers/net/ethernet/xilinx/xilinx_tsn_timer.h
new file mode 100644 (file)
index 0000000..4bb74e7
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Xilinx FPGA Xilinx TSN timer module header.
+ *
+ * Copyright (c) 2017 Xilinx Pvt., Ltd
+ *
+ * Author: Syed S <syeds@xilinx.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ */
+
+#ifndef _XILINX_TSN_H_
+#define _XILINX_TSN_H_
+
+#include <linux/platform_device.h>
+
+#define XAE_RTC_OFFSET                 0x12800
+/* RTC Nanoseconds Field Offset Register */
+#define XTIMER1588_RTC_OFFSET_NS       0x00000
+/* RTC Seconds Field Offset Register - Low */
+#define XTIMER1588_RTC_OFFSET_SEC_L    0x00008
+/* RTC Seconds Field Offset Register - High */
+#define XTIMER1588_RTC_OFFSET_SEC_H    0x0000C
+/* RTC Increment */
+#define XTIMER1588_RTC_INCREMENT       0x00010
+/* Current TOD Nanoseconds - RO */
+#define XTIMER1588_CURRENT_RTC_NS      0x00014
+/* Current TOD Seconds -Low RO  */
+#define XTIMER1588_CURRENT_RTC_SEC_L   0x00018
+/* Current TOD Seconds -High RO */
+#define XTIMER1588_CURRENT_RTC_SEC_H   0x0001C
+#define XTIMER1588_SYNTONIZED_NS       0x0002C
+#define XTIMER1588_SYNTONIZED_SEC_L    0x00030
+#define XTIMER1588_SYNTONIZED_SEC_H    0x00034
+/* Write to Bit 0 to clear the interrupt */
+#define XTIMER1588_INTERRUPT           0x00020
+/* 8kHz Pulse Offset Register */
+#define XTIMER1588_8KPULSE             0x00024
+/* Correction Field - Low */
+#define XTIMER1588_CF_L                        0x0002C
+/* Correction Field - Low */
+#define XTIMER1588_CF_H                        0x00030
+
+#define XTIMER1588_RTC_MASK  ((1 << 26) - 1)
+#define XTIMER1588_INT_SHIFT 0
+#define NANOSECOND_BITS 20
+#define NANOSECOND_MASK ((1 << NANOSECOND_BITS) - 1)
+#define SECOND_MASK ((1 << (32 - NANOSECOND_BITS)) - 1)
+#define XTIMER1588_RTC_INCREMENT_SHIFT 20
+#define PULSESIN1PPS 128
+
+/* Read/Write access to the registers */
+#ifndef out_be32
+#if defined(CONFIG_ARCH_ZYNQ) || defined(CONFIG_ARCH_ZYNQMP)
+#define in_be32(offset)                __raw_readl(offset)
+#define out_be32(offset, val)  __raw_writel(val, offset)
+#endif
+#endif
+
+/* The tsn ptp module will set this variable */
+extern int axienet_phc_index;
+
+void *axienet_ptp_timer_probe(void __iomem *base,
+                             struct platform_device *pdev);
+int axienet_ptp_timer_remove(void *priv);
+int axienet_get_phc_index(void *priv);
+#endif