#include "aq_nic.h"
 #include "aq_ptp.h"
 #include "aq_ring.h"
+#include "aq_phy.h"
 #include "aq_filters.h"
 
 #define AQ_PTP_TX_TIMEOUT        (HZ *  10)
 
+#define POLL_SYNC_TIMER_MS 15
+
 enum ptp_speed_offsets {
        ptp_offset_idx_10 = 0,
        ptp_offset_idx_100,
 
        struct aq_rx_filter_l3l4 udp_filter;
        struct aq_rx_filter_l2 eth_type_filter;
+
+       struct delayed_work poll_sync;
+       u32 poll_timeout_ms;
+
+       bool extts_pin_enabled;
+       u64 last_sync1588_ts;
 };
 
 struct ptp_tm_offset {
        hwtstamp->hwtstamp = ns_to_ktime(timestamp);
 }
 
+static int aq_ptp_hw_pin_conf(struct aq_nic_s *aq_nic, u32 pin_index, u64 start,
+                             u64 period)
+{
+       if (period)
+               netdev_dbg(aq_nic->ndev,
+                          "Enable GPIO %d pulsing, start time %llu, period %u\n",
+                          pin_index, start, (u32)period);
+       else
+               netdev_dbg(aq_nic->ndev,
+                          "Disable GPIO %d pulsing, start time %llu, period %u\n",
+                          pin_index, start, (u32)period);
+
+       /* Notify hardware of request to being sending pulses.
+        * If period is ZERO then pulsen is disabled.
+        */
+       mutex_lock(&aq_nic->fwreq_mutex);
+       aq_nic->aq_hw_ops->hw_gpio_pulse(aq_nic->aq_hw, pin_index,
+                                        start, (u32)period);
+       mutex_unlock(&aq_nic->fwreq_mutex);
+
+       return 0;
+}
+
+static int aq_ptp_perout_pin_configure(struct ptp_clock_info *ptp,
+                                      struct ptp_clock_request *rq, int on)
+{
+       struct aq_ptp_s *aq_ptp = container_of(ptp, struct aq_ptp_s, ptp_info);
+       struct ptp_clock_time *t = &rq->perout.period;
+       struct ptp_clock_time *s = &rq->perout.start;
+       struct aq_nic_s *aq_nic = aq_ptp->aq_nic;
+       u64 start, period;
+       u32 pin_index = rq->perout.index;
+
+       /* verify the request channel is there */
+       if (pin_index >= ptp->n_per_out)
+               return -EINVAL;
+
+       /* we cannot support periods greater
+        * than 4 seconds due to reg limit
+        */
+       if (t->sec > 4 || t->sec < 0)
+               return -ERANGE;
+
+       /* convert to unsigned 64b ns,
+        * verify we can put it in a 32b register
+        */
+       period = on ? t->sec * NSEC_PER_SEC + t->nsec : 0;
+
+       /* verify the value is in range supported by hardware */
+       if (period > U32_MAX)
+               return -ERANGE;
+       /* convert to unsigned 64b ns */
+       /* TODO convert to AQ time */
+       start = on ? s->sec * NSEC_PER_SEC + s->nsec : 0;
+
+       aq_ptp_hw_pin_conf(aq_nic, pin_index, start, period);
+
+       return 0;
+}
+
+static int aq_ptp_pps_pin_configure(struct ptp_clock_info *ptp,
+                                   struct ptp_clock_request *rq, int on)
+{
+       struct aq_ptp_s *aq_ptp = container_of(ptp, struct aq_ptp_s, ptp_info);
+       struct aq_nic_s *aq_nic = aq_ptp->aq_nic;
+       u64 start, period;
+       u32 pin_index = 0;
+       u32 rest = 0;
+
+       /* verify the request channel is there */
+       if (pin_index >= ptp->n_per_out)
+               return -EINVAL;
+
+       aq_nic->aq_hw_ops->hw_get_ptp_ts(aq_nic->aq_hw, &start);
+       div_u64_rem(start, NSEC_PER_SEC, &rest);
+       period = on ? NSEC_PER_SEC : 0; /* PPS - pulse per second */
+       start = on ? start - rest + NSEC_PER_SEC *
+               (rest > 990000000LL ? 2 : 1) : 0;
+
+       aq_ptp_hw_pin_conf(aq_nic, pin_index, start, period);
+
+       return 0;
+}
+
+static void aq_ptp_extts_pin_ctrl(struct aq_ptp_s *aq_ptp)
+{
+       struct aq_nic_s *aq_nic = aq_ptp->aq_nic;
+       u32 enable = aq_ptp->extts_pin_enabled;
+
+       if (aq_nic->aq_hw_ops->hw_extts_gpio_enable)
+               aq_nic->aq_hw_ops->hw_extts_gpio_enable(aq_nic->aq_hw, 0,
+                                                       enable);
+}
+
+static int aq_ptp_extts_pin_configure(struct ptp_clock_info *ptp,
+                                     struct ptp_clock_request *rq, int on)
+{
+       struct aq_ptp_s *aq_ptp = container_of(ptp, struct aq_ptp_s, ptp_info);
+
+       u32 pin_index = rq->extts.index;
+
+       if (pin_index >= ptp->n_ext_ts)
+               return -EINVAL;
+
+       aq_ptp->extts_pin_enabled = !!on;
+       if (on) {
+               aq_ptp->poll_timeout_ms = POLL_SYNC_TIMER_MS;
+               cancel_delayed_work_sync(&aq_ptp->poll_sync);
+               schedule_delayed_work(&aq_ptp->poll_sync,
+                                     msecs_to_jiffies(aq_ptp->poll_timeout_ms));
+       }
+
+       aq_ptp_extts_pin_ctrl(aq_ptp);
+       return 0;
+}
+
+/* aq_ptp_gpio_feature_enable
+ * @ptp: the ptp clock structure
+ * @rq: the requested feature to change
+ * @on: whether to enable or disable the feature
+ */
+static int aq_ptp_gpio_feature_enable(struct ptp_clock_info *ptp,
+                                     struct ptp_clock_request *rq, int on)
+{
+       switch (rq->type) {
+       case PTP_CLK_REQ_EXTTS:
+               return aq_ptp_extts_pin_configure(ptp, rq, on);
+       case PTP_CLK_REQ_PEROUT:
+               return aq_ptp_perout_pin_configure(ptp, rq, on);
+       case PTP_CLK_REQ_PPS:
+               return aq_ptp_pps_pin_configure(ptp, rq, on);
+       default:
+               return -EOPNOTSUPP;
+       }
+
+       return 0;
+}
+
+/* aq_ptp_verify
+ * @ptp: the ptp clock structure
+ * @pin: index of the pin in question
+ * @func: the desired function to use
+ * @chan: the function channel index to use
+ */
+static int aq_ptp_verify(struct ptp_clock_info *ptp, unsigned int pin,
+                        enum ptp_pin_function func, unsigned int chan)
+{
+       /* verify the requested pin is there */
+       if (!ptp->pin_config || pin >= ptp->n_pins)
+               return -EINVAL;
+
+       /* enforce locked channels, no changing them */
+       if (chan != ptp->pin_config[pin].chan)
+               return -EINVAL;
+
+       /* we want to keep the functions locked as well */
+       if (func != ptp->pin_config[pin].func)
+               return -EINVAL;
+
+       return 0;
+}
+
 /* aq_ptp_tx_hwtstamp - utility function which checks for TX time stamp
  * @adapter: the private adapter struct
  *
        aq_ptp_skb_ring_release(&aq_ptp->skb_ring);
 }
 
+#define MAX_PTP_GPIO_COUNT 4
+
 static struct ptp_clock_info aq_ptp_clock = {
        .owner          = THIS_MODULE,
        .name           = "atlantic ptp",
        .gettime64      = aq_ptp_gettime,
        .settime64      = aq_ptp_settime,
        .n_per_out      = 0,
+       .enable         = aq_ptp_gpio_feature_enable,
        .n_pins         = 0,
+       .verify         = aq_ptp_verify,
        .pin_config     = NULL,
 };
 
        aq_ptp_offset_init_from_fw(offsets);
 }
 
+static void aq_ptp_gpio_init(struct ptp_clock_info *info,
+                            struct hw_aq_info *hw_info)
+{
+       struct ptp_pin_desc pin_desc[MAX_PTP_GPIO_COUNT];
+       u32 extts_pin_cnt = 0;
+       u32 out_pin_cnt = 0;
+       u32 i;
+
+       memset(pin_desc, 0, sizeof(pin_desc));
+
+       for (i = 0; i < MAX_PTP_GPIO_COUNT - 1; i++) {
+               if (hw_info->gpio_pin[i] ==
+                   (GPIO_PIN_FUNCTION_PTP0 + out_pin_cnt)) {
+                       snprintf(pin_desc[out_pin_cnt].name,
+                                sizeof(pin_desc[out_pin_cnt].name),
+                                "AQ_GPIO%d", i);
+                       pin_desc[out_pin_cnt].index = out_pin_cnt;
+                       pin_desc[out_pin_cnt].chan = out_pin_cnt;
+                       pin_desc[out_pin_cnt++].func = PTP_PF_PEROUT;
+               }
+       }
+
+       info->n_per_out = out_pin_cnt;
+
+       if (hw_info->caps_ex & BIT(CAPS_EX_PHY_CTRL_TS_PIN)) {
+               extts_pin_cnt += 1;
+
+               snprintf(pin_desc[out_pin_cnt].name,
+                        sizeof(pin_desc[out_pin_cnt].name),
+                         "AQ_GPIO%d", out_pin_cnt);
+               pin_desc[out_pin_cnt].index = out_pin_cnt;
+               pin_desc[out_pin_cnt].chan = 0;
+               pin_desc[out_pin_cnt].func = PTP_PF_EXTTS;
+       }
+
+       info->n_pins = out_pin_cnt + extts_pin_cnt;
+       info->n_ext_ts = extts_pin_cnt;
+
+       if (!info->n_pins)
+               return;
+
+       info->pin_config = kcalloc(info->n_pins, sizeof(struct ptp_pin_desc),
+                                  GFP_KERNEL);
+
+       if (!info->pin_config)
+               return;
+
+       memcpy(info->pin_config, &pin_desc,
+              sizeof(struct ptp_pin_desc) * info->n_pins);
+}
+
 void aq_ptp_clock_init(struct aq_nic_s *aq_nic)
 {
        struct aq_ptp_s *aq_ptp = aq_nic->aq_ptp;
        aq_ptp_settime(&aq_ptp->ptp_info, &ts);
 }
 
+static void aq_ptp_poll_sync_work_cb(struct work_struct *w);
+
 int aq_ptp_init(struct aq_nic_s *aq_nic, unsigned int idx_vec)
 {
        struct hw_atl_utils_mbox mbox;
        spin_lock_init(&aq_ptp->ptp_ring_lock);
 
        aq_ptp->ptp_info = aq_ptp_clock;
+       aq_ptp_gpio_init(&aq_ptp->ptp_info, &mbox.info);
        clock = ptp_clock_register(&aq_ptp->ptp_info, &aq_nic->ndev->dev);
        if (!clock || IS_ERR(clock)) {
                netdev_err(aq_nic->ndev, "ptp_clock_register failed\n");
        aq_ptp_clock_init(aq_nic);
        mutex_unlock(&aq_nic->fwreq_mutex);
 
+       INIT_DELAYED_WORK(&aq_ptp->poll_sync, &aq_ptp_poll_sync_work_cb);
        aq_ptp->eth_type_filter.location =
                        aq_nic_reserve_filter(aq_nic, aq_rx_filter_ethertype);
        aq_ptp->udp_filter.location =
        return 0;
 
 err_exit:
+       if (aq_ptp)
+               kfree(aq_ptp->ptp_info.pin_config);
        kfree(aq_ptp);
        aq_nic->aq_ptp = NULL;
        return err;
                              aq_ptp->eth_type_filter.location);
        aq_nic_release_filter(aq_nic, aq_rx_filter_l3l4,
                              aq_ptp->udp_filter.location);
+       cancel_delayed_work_sync(&aq_ptp->poll_sync);
        /* disable ptp */
        mutex_lock(&aq_nic->fwreq_mutex);
        aq_nic->aq_fw_ops->enable_ptp(aq_nic->aq_hw, 0);
        mutex_unlock(&aq_nic->fwreq_mutex);
 
+       kfree(aq_ptp->ptp_info.pin_config);
+
        netif_napi_del(&aq_ptp->napi);
        kfree(aq_ptp);
        aq_nic->aq_ptp = NULL;
 {
        return aq_ptp->ptp_clock;
 }
+
+/* PTP external GPIO nanoseconds count */
+static uint64_t aq_ptp_get_sync1588_ts(struct aq_nic_s *aq_nic)
+{
+       u64 ts = 0;
+
+       if (aq_nic->aq_hw_ops->hw_get_sync_ts)
+               aq_nic->aq_hw_ops->hw_get_sync_ts(aq_nic->aq_hw, &ts);
+
+       return ts;
+}
+
+static void aq_ptp_start_work(struct aq_ptp_s *aq_ptp)
+{
+       if (aq_ptp->extts_pin_enabled) {
+               aq_ptp->poll_timeout_ms = POLL_SYNC_TIMER_MS;
+               aq_ptp->last_sync1588_ts =
+                               aq_ptp_get_sync1588_ts(aq_ptp->aq_nic);
+               schedule_delayed_work(&aq_ptp->poll_sync,
+                                     msecs_to_jiffies(aq_ptp->poll_timeout_ms));
+       }
+}
+
+int aq_ptp_link_change(struct aq_nic_s *aq_nic)
+{
+       struct aq_ptp_s *aq_ptp = aq_nic->aq_ptp;
+
+       if (!aq_ptp)
+               return 0;
+
+       if (aq_nic->aq_hw->aq_link_status.mbps)
+               aq_ptp_start_work(aq_ptp);
+       else
+               cancel_delayed_work_sync(&aq_ptp->poll_sync);
+
+       return 0;
+}
+
+static bool aq_ptp_sync_ts_updated(struct aq_ptp_s *aq_ptp, u64 *new_ts)
+{
+       struct aq_nic_s *aq_nic = aq_ptp->aq_nic;
+       u64 sync_ts2;
+       u64 sync_ts;
+
+       sync_ts = aq_ptp_get_sync1588_ts(aq_nic);
+
+       if (sync_ts != aq_ptp->last_sync1588_ts) {
+               sync_ts2 = aq_ptp_get_sync1588_ts(aq_nic);
+               if (sync_ts != sync_ts2) {
+                       sync_ts = sync_ts2;
+                       sync_ts2 = aq_ptp_get_sync1588_ts(aq_nic);
+                       if (sync_ts != sync_ts2) {
+                               netdev_err(aq_nic->ndev,
+                                          "%s: Unable to get correct GPIO TS",
+                                          __func__);
+                               sync_ts = 0;
+                       }
+               }
+
+               *new_ts = sync_ts;
+               return true;
+       }
+       return false;
+}
+
+static int aq_ptp_check_sync1588(struct aq_ptp_s *aq_ptp)
+{
+       struct aq_nic_s *aq_nic = aq_ptp->aq_nic;
+       u64 sync_ts;
+
+        /* Sync1588 pin was triggered */
+       if (aq_ptp_sync_ts_updated(aq_ptp, &sync_ts)) {
+               if (aq_ptp->extts_pin_enabled) {
+                       struct ptp_clock_event ptp_event;
+                       u64 time = 0;
+
+                       aq_nic->aq_hw_ops->hw_ts_to_sys_clock(aq_nic->aq_hw,
+                                                             sync_ts, &time);
+                       ptp_event.index = aq_ptp->ptp_info.n_pins - 1;
+                       ptp_event.timestamp = time;
+
+                       ptp_event.type = PTP_CLOCK_EXTTS;
+                       ptp_clock_event(aq_ptp->ptp_clock, &ptp_event);
+               }
+
+               aq_ptp->last_sync1588_ts = sync_ts;
+       }
+
+       return 0;
+}
+
+void aq_ptp_poll_sync_work_cb(struct work_struct *w)
+{
+       struct delayed_work *dw = to_delayed_work(w);
+       struct aq_ptp_s *aq_ptp = container_of(dw, struct aq_ptp_s, poll_sync);
+
+       aq_ptp_check_sync1588(aq_ptp);
+
+       if (aq_ptp->extts_pin_enabled) {
+               unsigned long timeout = msecs_to_jiffies(aq_ptp->poll_timeout_ms);
+
+               schedule_delayed_work(&aq_ptp->poll_sync, timeout);
+       }
+}
 
 #include "../aq_hw_utils.h"
 #include "../aq_ring.h"
 #include "../aq_nic.h"
+#include "../aq_phy.h"
 #include "hw_atl_b0.h"
 #include "hw_atl_utils.h"
 #include "hw_atl_llh.h"
        return hw_atl_b0_adj_sys_clock(self, delta);
 }
 
+int hw_atl_b0_ts_to_sys_clock(struct aq_hw_s *self, u64 ts, u64 *time)
+{
+       *time = self->ptp_clk_offset + ts;
+       return 0;
+}
+
 static int hw_atl_b0_adj_clock_freq(struct aq_hw_s *self, s32 ppb)
 {
        struct hw_fw_request_iface fwreq;
        return self->aq_fw_ops->send_fw_request(self, &fwreq, size);
 }
 
+static int hw_atl_b0_gpio_pulse(struct aq_hw_s *self, u32 index,
+                               u64 start, u32 period)
+{
+       struct hw_fw_request_iface fwreq;
+       size_t size;
+
+       memset(&fwreq, 0, sizeof(fwreq));
+
+       fwreq.msg_id = HW_AQ_FW_REQUEST_PTP_GPIO_CTRL;
+       fwreq.ptp_gpio_ctrl.index = index;
+       fwreq.ptp_gpio_ctrl.period = period;
+       /* Apply time offset */
+       fwreq.ptp_gpio_ctrl.start = start - self->ptp_clk_offset;
+
+       size = sizeof(fwreq.msg_id) + sizeof(fwreq.ptp_gpio_ctrl);
+       return self->aq_fw_ops->send_fw_request(self, &fwreq, size);
+}
+
+static int hw_atl_b0_extts_gpio_enable(struct aq_hw_s *self, u32 index,
+                                      u32 enable)
+{
+       /* Enable/disable Sync1588 GPIO Timestamping */
+       aq_phy_write_reg(self, MDIO_MMD_PCS, 0xc611, enable ? 0x71 : 0);
+
+       return 0;
+}
+
+static int hw_atl_b0_get_sync_ts(struct aq_hw_s *self, u64 *ts)
+{
+       u64 sec_l;
+       u64 sec_h;
+       u64 nsec_l;
+       u64 nsec_h;
+
+       if (!ts)
+               return -1;
+
+       /* PTP external GPIO clock seconds count 15:0 */
+       sec_l = aq_phy_read_reg(self, MDIO_MMD_PCS, 0xc914);
+       /* PTP external GPIO clock seconds count 31:16 */
+       sec_h = aq_phy_read_reg(self, MDIO_MMD_PCS, 0xc915);
+       /* PTP external GPIO clock nanoseconds count 15:0 */
+       nsec_l = aq_phy_read_reg(self, MDIO_MMD_PCS, 0xc916);
+       /* PTP external GPIO clock nanoseconds count 31:16 */
+       nsec_h = aq_phy_read_reg(self, MDIO_MMD_PCS, 0xc917);
+
+       *ts = (nsec_h << 16) + nsec_l + ((sec_h << 16) + sec_l) * NSEC_PER_SEC;
+
+       return 0;
+}
+
 static u16 hw_atl_b0_rx_extract_ts(struct aq_hw_s *self, u8 *p,
                                   unsigned int len, u64 *timestamp)
 {
        .hw_get_ptp_ts           = hw_atl_b0_get_ptp_ts,
        .hw_adj_sys_clock        = hw_atl_b0_adj_sys_clock,
        .hw_set_sys_clock        = hw_atl_b0_set_sys_clock,
+       .hw_ts_to_sys_clock      = hw_atl_b0_ts_to_sys_clock,
        .hw_adj_clock_freq       = hw_atl_b0_adj_clock_freq,
-
+       .hw_gpio_pulse           = hw_atl_b0_gpio_pulse,
+       .hw_extts_gpio_enable    = hw_atl_b0_extts_gpio_enable,
+       .hw_get_sync_ts          = hw_atl_b0_get_sync_ts,
        .rx_extract_ts           = hw_atl_b0_rx_extract_ts,
        .extract_hwts            = hw_atl_b0_extract_hwts,
        .hw_set_offload          = hw_atl_b0_hw_offload_set,