/* runtime link query interval */
 #define LIQUIDIO_LINK_QUERY_INTERVAL_MS         1000
+/* update localtime to octeon firmware every 60 seconds.
+ * make firmware to use same time reference, so that it will be easy to
+ * correlate firmware logged events/errors with host events, for debugging.
+ */
+#define LIO_SYNC_OCTEON_TIME_INTERVAL_MS 60000
 
 struct liquidio_if_cfg_context {
        int octeon_id;
        }
 }
 
+/**
+ * lio_sync_octeon_time_cb - callback that is invoked when soft command
+ * sent by lio_sync_octeon_time() has completed successfully or failed
+ *
+ * @oct - octeon device structure
+ * @status - indicates success or failure
+ * @buf - pointer to the command that was sent to firmware
+ **/
+static void lio_sync_octeon_time_cb(struct octeon_device *oct,
+                                   u32 status, void *buf)
+{
+       struct octeon_soft_command *sc = (struct octeon_soft_command *)buf;
+
+       if (status)
+               dev_err(&oct->pci_dev->dev,
+                       "Failed to sync time to octeon; error=%d\n", status);
+
+       octeon_free_soft_command(oct, sc);
+}
+
+/**
+ * lio_sync_octeon_time - send latest localtime to octeon firmware so that
+ * firmware will correct it's time, in case there is a time skew
+ *
+ * @work: work scheduled to send time update to octeon firmware
+ **/
+static void lio_sync_octeon_time(struct work_struct *work)
+{
+       struct cavium_wk *wk = (struct cavium_wk *)work;
+       struct lio *lio = (struct lio *)wk->ctxptr;
+       struct octeon_device *oct = lio->oct_dev;
+       struct octeon_soft_command *sc;
+       struct timespec64 ts;
+       struct lio_time *lt;
+       int ret;
+
+       sc = octeon_alloc_soft_command(oct, sizeof(struct lio_time), 0, 0);
+       if (!sc) {
+               dev_err(&oct->pci_dev->dev,
+                       "Failed to sync time to octeon: soft command allocation failed\n");
+               return;
+       }
+
+       lt = (struct lio_time *)sc->virtdptr;
+
+       /* Get time of the day */
+       getnstimeofday64(&ts);
+       lt->sec = ts.tv_sec;
+       lt->nsec = ts.tv_nsec;
+       octeon_swap_8B_data((u64 *)lt, (sizeof(struct lio_time)) / 8);
+
+       sc->iq_no = lio->linfo.txpciq[0].s.q_no;
+       octeon_prepare_soft_command(oct, sc, OPCODE_NIC,
+                                   OPCODE_NIC_SYNC_OCTEON_TIME, 0, 0, 0);
+
+       sc->callback = lio_sync_octeon_time_cb;
+       sc->callback_arg = sc;
+       sc->wait_time = 1000;
+
+       ret = octeon_send_soft_command(oct, sc);
+       if (ret == IQ_SEND_FAILED) {
+               dev_err(&oct->pci_dev->dev,
+                       "Failed to sync time to octeon: failed to send soft command\n");
+               octeon_free_soft_command(oct, sc);
+       }
+
+       queue_delayed_work(lio->sync_octeon_time_wq.wq,
+                          &lio->sync_octeon_time_wq.wk.work,
+                          msecs_to_jiffies(LIO_SYNC_OCTEON_TIME_INTERVAL_MS));
+}
+
+/**
+ * setup_sync_octeon_time_wq - Sets up the work to periodically update
+ * local time to octeon firmware
+ *
+ * @netdev - network device which should send time update to firmware
+ **/
+static inline int setup_sync_octeon_time_wq(struct net_device *netdev)
+{
+       struct lio *lio = GET_LIO(netdev);
+       struct octeon_device *oct = lio->oct_dev;
+
+       lio->sync_octeon_time_wq.wq =
+               alloc_workqueue("update-octeon-time", WQ_MEM_RECLAIM, 0);
+       if (!lio->sync_octeon_time_wq.wq) {
+               dev_err(&oct->pci_dev->dev, "Unable to create wq to update octeon time\n");
+               return -1;
+       }
+       INIT_DELAYED_WORK(&lio->sync_octeon_time_wq.wk.work,
+                         lio_sync_octeon_time);
+       lio->sync_octeon_time_wq.wk.ctxptr = lio;
+       queue_delayed_work(lio->sync_octeon_time_wq.wq,
+                          &lio->sync_octeon_time_wq.wk.work,
+                          msecs_to_jiffies(LIO_SYNC_OCTEON_TIME_INTERVAL_MS));
+
+       return 0;
+}
+
+/**
+ * cleanup_sync_octeon_time_wq - stop scheduling and destroy the work created
+ * to periodically update local time to octeon firmware
+ *
+ * @netdev - network device which should send time update to firmware
+ **/
+static inline void cleanup_sync_octeon_time_wq(struct net_device *netdev)
+{
+       struct lio *lio = GET_LIO(netdev);
+       struct cavium_wq *time_wq = &lio->sync_octeon_time_wq;
+
+       if (time_wq->wq) {
+               cancel_delayed_work_sync(&time_wq->wk.work);
+               destroy_workqueue(time_wq->wq);
+       }
+}
+
 static struct octeon_device *get_other_octeon_device(struct octeon_device *oct)
 {
        struct octeon_device *other_oct;
        if (atomic_read(&lio->ifstate) & LIO_IFSTATE_REGISTERED)
                unregister_netdev(netdev);
 
+       cleanup_sync_octeon_time_wq(netdev);
        cleanup_link_status_change_wq(netdev);
 
        cleanup_rx_oom_poll_fn(netdev);
                if (setup_link_status_change_wq(netdev))
                        goto setup_nic_dev_fail;
 
+               if ((octeon_dev->fw_info.app_cap_flags &
+                    LIQUIDIO_TIME_SYNC_CAP) &&
+                   setup_sync_octeon_time_wq(netdev))
+                       goto setup_nic_dev_fail;
+
                if (setup_rx_oom_poll_fn(netdev))
                        goto setup_nic_dev_fail;
 
 
 }
 
 #define FBUF_SIZE      (4 * 1024 * 1024)
+#define MAX_BOOTTIME_SIZE    80
 
 int octeon_download_firmware(struct octeon_device *oct, const u8 *data,
                             size_t size)
 {
-       int ret = 0;
+       struct octeon_firmware_file_header *h;
+       char boottime[MAX_BOOTTIME_SIZE];
+       struct timespec64 ts;
        u32 crc32_result;
        u64 load_addr;
        u32 image_len;
-       struct octeon_firmware_file_header *h;
+       int ret = 0;
        u32 i, rem;
 
        if (size < sizeof(struct octeon_firmware_file_header)) {
                        load_addr += size;
                }
        }
+
+       /* Pass date and time information to NIC at the time of loading
+        * firmware and periodically update the host time to NIC firmware.
+        * This is to make NIC firmware use the same time reference as Host,
+        * so that it is easy to correlate logs from firmware and host for
+        * debugging.
+        *
+        * Octeon always uses UTC time. so timezone information is not sent.
+        */
+       getnstimeofday64(&ts);
+       ret = snprintf(boottime, MAX_BOOTTIME_SIZE,
+                      " time_sec=%lld time_nsec=%ld",
+                      (s64)ts.tv_sec, ts.tv_nsec);
+       if ((sizeof(h->bootcmd) - strnlen(h->bootcmd, sizeof(h->bootcmd))) <
+               ret) {
+               dev_err(&oct->pci_dev->dev, "Boot command buffer too small\n");
+               return -EINVAL;
+       }
+       strncat(h->bootcmd, boottime,
+               sizeof(h->bootcmd) - strnlen(h->bootcmd, sizeof(h->bootcmd)));
+
        dev_info(&oct->pci_dev->dev, "Writing boot command: %s\n",
                 h->bootcmd);
 
        /* Invoke the bootcmd */
        ret = octeon_console_send_cmd(oct, h->bootcmd, 50);
+       if (ret)
+               dev_info(&oct->pci_dev->dev, "Boot command send failed\n");
 
-       return 0;
+       return ret;
 }