return 0;
 }
 
+static int btintel_boot_wait_d0(struct hci_dev *hdev, ktime_t calltime,
+                               int msec)
+{
+       ktime_t delta, rettime;
+       unsigned long long duration;
+       int err;
+
+       bt_dev_info(hdev, "Waiting for device transition to d0");
+
+       err = btintel_wait_on_flag_timeout(hdev, INTEL_WAIT_FOR_D0,
+                                          TASK_INTERRUPTIBLE,
+                                          msecs_to_jiffies(msec));
+       if (err == -EINTR) {
+               bt_dev_err(hdev, "Device d0 move interrupted");
+               return -EINTR;
+       }
+
+       if (err) {
+               bt_dev_err(hdev, "Device d0 move timeout");
+               return -ETIMEDOUT;
+       }
+
+       rettime = ktime_get();
+       delta = ktime_sub(rettime, calltime);
+       duration = (unsigned long long)ktime_to_ns(delta) >> 10;
+
+       bt_dev_info(hdev, "Device moved to D0 in %llu usecs", duration);
+
+       return 0;
+}
+
 static int btintel_boot(struct hci_dev *hdev, u32 boot_addr)
 {
        ktime_t calltime;
        calltime = ktime_get();
 
        btintel_set_flag(hdev, INTEL_BOOTING);
+       btintel_set_flag(hdev, INTEL_WAIT_FOR_D0);
 
        err = btintel_send_intel_reset(hdev, boot_addr);
        if (err) {
         * is done by the operational firmware sending bootup notification.
         *
         * Booting into operational firmware should not take longer than
-        * 1 second. However if that happens, then just fail the setup
+        * 5 second. However if that happens, then just fail the setup
         * since something went wrong.
         */
-       err = btintel_boot_wait(hdev, calltime, 1000);
-       if (err == -ETIMEDOUT)
+       err = btintel_boot_wait(hdev, calltime, 5000);
+       if (err == -ETIMEDOUT) {
                btintel_reset_to_bootloader(hdev);
+               goto exit_error;
+       }
 
+       if (hdev->bus == HCI_PCI) {
+               /* In case of PCIe, after receiving bootup event, driver performs
+                * D0 entry by writing 0 to sleep control register (check
+                * btintel_pcie_recv_event())
+                * Firmware acks with alive interrupt indicating host is full ready to
+                * perform BT operation. Lets wait here till INTEL_WAIT_FOR_D0
+                * bit is cleared.
+                */
+               calltime = ktime_get();
+               err = btintel_boot_wait_d0(hdev, calltime, 2000);
+       }
+
+exit_error:
        return err;
 }
 
 }
 EXPORT_SYMBOL_GPL(btintel_configure_setup);
 
-static int btintel_diagnostics(struct hci_dev *hdev, struct sk_buff *skb)
+int btintel_diagnostics(struct hci_dev *hdev, struct sk_buff *skb)
 {
        struct intel_tlv *tlv = (void *)&skb->data[5];
 
 recv_frame:
        return hci_recv_frame(hdev, skb);
 }
+EXPORT_SYMBOL_GPL(btintel_diagnostics);
 
 int btintel_recv_event(struct hci_dev *hdev, struct sk_buff *skb)
 {
 
 #define BTINTEL_PCIE_HCI_EVT_PKT       0x00000004
 #define BTINTEL_PCIE_HCI_ISO_PKT       0x00000005
 
+/* Alive interrupt context */
+enum {
+       BTINTEL_PCIE_ROM,
+       BTINTEL_PCIE_FW_DL,
+       BTINTEL_PCIE_HCI_RESET,
+       BTINTEL_PCIE_INTEL_HCI_RESET1,
+       BTINTEL_PCIE_INTEL_HCI_RESET2,
+       BTINTEL_PCIE_D0,
+       BTINTEL_PCIE_D3
+};
+
 static inline void ipc_print_ia_ring(struct hci_dev *hdev, struct ia *ia,
                                     u16 queue_num)
 {
        /* wait for interrupt from the device after booting up to primary
         * bootloader.
         */
+       data->alive_intr_ctxt = BTINTEL_PCIE_ROM;
        err = wait_event_timeout(data->gp0_wait_q, data->gp0_received,
-                                msecs_to_jiffies(BTINTEL_DEFAULT_INTR_TIMEOUT));
+                                msecs_to_jiffies(BTINTEL_DEFAULT_INTR_TIMEOUT_MS));
        if (!err)
                return -ETIME;
 
        return 0;
 }
 
+/* BIT(0) - ROM, BIT(1) - IML and BIT(3) - OP
+ * Sometimes during firmware image switching from ROM to IML or IML to OP image,
+ * the previous image bit is not cleared by firmware when alive interrupt is
+ * received. Driver needs to take care of these sticky bits when deciding the
+ * current image running on controller.
+ * Ex: 0x10 and 0x11 - both represents that controller is running IML
+ */
+static inline bool btintel_pcie_in_rom(struct btintel_pcie_data *data)
+{
+       return data->boot_stage_cache & BTINTEL_PCIE_CSR_BOOT_STAGE_ROM &&
+               !(data->boot_stage_cache & BTINTEL_PCIE_CSR_BOOT_STAGE_IML) &&
+               !(data->boot_stage_cache & BTINTEL_PCIE_CSR_BOOT_STAGE_OPFW);
+}
+
+static inline bool btintel_pcie_in_op(struct btintel_pcie_data *data)
+{
+       return data->boot_stage_cache & BTINTEL_PCIE_CSR_BOOT_STAGE_OPFW;
+}
+
+static inline bool btintel_pcie_in_iml(struct btintel_pcie_data *data)
+{
+       return data->boot_stage_cache & BTINTEL_PCIE_CSR_BOOT_STAGE_IML &&
+               !(data->boot_stage_cache & BTINTEL_PCIE_CSR_BOOT_STAGE_OPFW);
+}
+
+static inline bool btintel_pcie_in_d3(struct btintel_pcie_data *data)
+{
+       return data->boot_stage_cache & BTINTEL_PCIE_CSR_BOOT_STAGE_D3_STATE_READY;
+}
+
+static inline bool btintel_pcie_in_d0(struct btintel_pcie_data *data)
+{
+       return !(data->boot_stage_cache & BTINTEL_PCIE_CSR_BOOT_STAGE_D3_STATE_READY);
+}
+
+static void btintel_pcie_wr_sleep_cntrl(struct btintel_pcie_data *data,
+                                       u32 dxstate)
+{
+       bt_dev_dbg(data->hdev, "writing sleep_ctl_reg: 0x%8.8x", dxstate);
+       btintel_pcie_wr_reg32(data, BTINTEL_PCIE_CSR_IPC_SLEEP_CTL_REG, dxstate);
+}
+
+static inline char *btintel_pcie_alivectxt_state2str(u32 alive_intr_ctxt)
+{
+       switch (alive_intr_ctxt) {
+       case BTINTEL_PCIE_ROM:
+               return "rom";
+       case BTINTEL_PCIE_FW_DL:
+               return "fw_dl";
+       case BTINTEL_PCIE_D0:
+               return "d0";
+       case BTINTEL_PCIE_D3:
+               return "d3";
+       case BTINTEL_PCIE_HCI_RESET:
+               return "hci_reset";
+       case BTINTEL_PCIE_INTEL_HCI_RESET1:
+               return "intel_reset1";
+       case BTINTEL_PCIE_INTEL_HCI_RESET2:
+               return "intel_reset2";
+       default:
+               return "unknown";
+       }
+       return "null";
+}
+
 /* This function handles the MSI-X interrupt for gp0 cause (bit 0 in
  * BTINTEL_PCIE_CSR_MSIX_HW_INT_CAUSES) which is sent for boot stage and image response.
  */
 static void btintel_pcie_msix_gp0_handler(struct btintel_pcie_data *data)
 {
-       u32 reg;
+       bool submit_rx, signal_waitq;
+       u32 reg, old_ctxt;
 
        /* This interrupt is for three different causes and it is not easy to
         * know what causes the interrupt. So, it compares each register value
        if (reg != data->boot_stage_cache)
                data->boot_stage_cache = reg;
 
+       bt_dev_dbg(data->hdev, "Alive context: %s old_boot_stage: 0x%8.8x new_boot_stage: 0x%8.8x",
+                  btintel_pcie_alivectxt_state2str(data->alive_intr_ctxt),
+                  data->boot_stage_cache, reg);
        reg = btintel_pcie_rd_reg32(data, BTINTEL_PCIE_CSR_IMG_RESPONSE_REG);
        if (reg != data->img_resp_cache)
                data->img_resp_cache = reg;
 
        data->gp0_received = true;
 
-       /* If the boot stage is OP or IML, reset IA and start RX again */
-       if (data->boot_stage_cache & BTINTEL_PCIE_CSR_BOOT_STAGE_OPFW ||
-           data->boot_stage_cache & BTINTEL_PCIE_CSR_BOOT_STAGE_IML) {
+       old_ctxt = data->alive_intr_ctxt;
+       submit_rx = false;
+       signal_waitq = false;
+
+       switch (data->alive_intr_ctxt) {
+       case BTINTEL_PCIE_ROM:
+               data->alive_intr_ctxt = BTINTEL_PCIE_FW_DL;
+               signal_waitq = true;
+               break;
+       case BTINTEL_PCIE_FW_DL:
+               /* Error case is already handled. Ideally control shall not
+                * reach here
+                */
+               break;
+       case BTINTEL_PCIE_INTEL_HCI_RESET1:
+               if (btintel_pcie_in_op(data)) {
+                       submit_rx = true;
+                       break;
+               }
+
+               if (btintel_pcie_in_iml(data)) {
+                       submit_rx = true;
+                       data->alive_intr_ctxt = BTINTEL_PCIE_FW_DL;
+                       break;
+               }
+               break;
+       case BTINTEL_PCIE_INTEL_HCI_RESET2:
+               if (btintel_test_and_clear_flag(data->hdev, INTEL_WAIT_FOR_D0)) {
+                       btintel_wake_up_flag(data->hdev, INTEL_WAIT_FOR_D0);
+                       data->alive_intr_ctxt = BTINTEL_PCIE_D0;
+               }
+               break;
+       case BTINTEL_PCIE_D0:
+               if (btintel_pcie_in_d3(data)) {
+                       data->alive_intr_ctxt = BTINTEL_PCIE_D3;
+                       signal_waitq = true;
+                       break;
+               }
+               break;
+       case BTINTEL_PCIE_D3:
+               if (btintel_pcie_in_d0(data)) {
+                       data->alive_intr_ctxt = BTINTEL_PCIE_D0;
+                       submit_rx = true;
+                       signal_waitq = true;
+                       break;
+               }
+               break;
+       case BTINTEL_PCIE_HCI_RESET:
+               data->alive_intr_ctxt = BTINTEL_PCIE_D0;
+               submit_rx = true;
+               signal_waitq = true;
+               break;
+       default:
+               bt_dev_err(data->hdev, "Unknown state: 0x%2.2x",
+                          data->alive_intr_ctxt);
+               break;
+       }
+
+       if (submit_rx) {
                btintel_pcie_reset_ia(data);
                btintel_pcie_start_rx(data);
        }
 
-       wake_up(&data->gp0_wait_q);
+       if (signal_waitq) {
+               bt_dev_dbg(data->hdev, "wake up gp0 wait_q");
+               wake_up(&data->gp0_wait_q);
+       }
+
+       if (old_ctxt != data->alive_intr_ctxt)
+               bt_dev_dbg(data->hdev, "alive context changed: %s  ->  %s",
+                          btintel_pcie_alivectxt_state2str(old_ctxt),
+                          btintel_pcie_alivectxt_state2str(data->alive_intr_ctxt));
 }
 
 /* This function handles the MSX-X interrupt for rx queue 0 which is for TX
        }
 }
 
+static int btintel_pcie_recv_event(struct hci_dev *hdev, struct sk_buff *skb)
+{
+       struct hci_event_hdr *hdr = (void *)skb->data;
+       const char diagnostics_hdr[] = { 0x87, 0x80, 0x03 };
+       struct btintel_pcie_data *data = hci_get_drvdata(hdev);
+
+       if (skb->len > HCI_EVENT_HDR_SIZE && hdr->evt == 0xff &&
+           hdr->plen > 0) {
+               const void *ptr = skb->data + HCI_EVENT_HDR_SIZE + 1;
+               unsigned int len = skb->len - HCI_EVENT_HDR_SIZE - 1;
+
+               if (btintel_test_flag(hdev, INTEL_BOOTLOADER)) {
+                       switch (skb->data[2]) {
+                       case 0x02:
+                               /* When switching to the operational firmware
+                                * the device sends a vendor specific event
+                                * indicating that the bootup completed.
+                                */
+                               btintel_bootup(hdev, ptr, len);
+
+                               /* If bootup event is from operational image,
+                                * driver needs to write sleep control register to
+                                * move into D0 state
+                                */
+                               if (btintel_pcie_in_op(data)) {
+                                       btintel_pcie_wr_sleep_cntrl(data, BTINTEL_PCIE_STATE_D0);
+                                       data->alive_intr_ctxt = BTINTEL_PCIE_INTEL_HCI_RESET2;
+                                       break;
+                               }
+
+                               if (btintel_pcie_in_iml(data)) {
+                                       /* In case of IML, there is no concept
+                                        * of D0 transition. Just mimic as if
+                                        * IML moved to D0 by clearing INTEL_WAIT_FOR_D0
+                                        * bit and waking up the task waiting on
+                                        * INTEL_WAIT_FOR_D0. This is required
+                                        * as intel_boot() is common function for
+                                        * both IML and OP image loading.
+                                        */
+                                       if (btintel_test_and_clear_flag(data->hdev,
+                                                                       INTEL_WAIT_FOR_D0))
+                                               btintel_wake_up_flag(data->hdev,
+                                                                    INTEL_WAIT_FOR_D0);
+                               }
+                               break;
+                       case 0x06:
+                               /* When the firmware loading completes the
+                                * device sends out a vendor specific event
+                                * indicating the result of the firmware
+                                * loading.
+                                */
+                               btintel_secure_send_result(hdev, ptr, len);
+                               break;
+                       }
+               }
+
+               /* Handle all diagnostics events separately. May still call
+                * hci_recv_frame.
+                */
+               if (len >= sizeof(diagnostics_hdr) &&
+                   memcmp(&skb->data[2], diagnostics_hdr,
+                          sizeof(diagnostics_hdr)) == 0) {
+                       return btintel_diagnostics(hdev, skb);
+               }
+
+               /* This is a debug event that comes from IML and OP image when it
+                * starts execution. There is no need pass this event to stack.
+                */
+               if (skb->data[2] == 0x97)
+                       return 0;
+       }
+
+       return hci_recv_frame(hdev, skb);
+}
 /* Process the received rx data
  * It check the frame header to identify the data type and create skb
  * and calling HCI API
        hdev->stat.byte_rx += plen;
 
        if (pcie_pkt_type == BTINTEL_PCIE_HCI_EVT_PKT)
-               ret = btintel_recv_event(hdev, new_skb);
+               ret = btintel_pcie_recv_event(hdev, new_skb);
        else
                ret = hci_recv_frame(hdev, new_skb);
 
                                       struct sk_buff *skb)
 {
        struct btintel_pcie_data *data = hci_get_drvdata(hdev);
+       struct hci_command_hdr *cmd;
+       __u16 opcode = ~0;
        int ret;
        u32 type;
+       u32 old_ctxt;
 
        /* Due to the fw limitation, the type header of the packet should be
         * 4 bytes unlike 1 byte for UART. In UART, the firmware can read
        switch (hci_skb_pkt_type(skb)) {
        case HCI_COMMAND_PKT:
                type = BTINTEL_PCIE_HCI_CMD_PKT;
+               cmd = (void *)skb->data;
+               opcode = le16_to_cpu(cmd->opcode);
                if (btintel_test_flag(hdev, INTEL_BOOTLOADER)) {
                        struct hci_command_hdr *cmd = (void *)skb->data;
                        __u16 opcode = le16_to_cpu(cmd->opcode);
                bt_dev_err(hdev, "Failed to send frame (%d)", ret);
                goto exit_error;
        }
+
+       if (type == BTINTEL_PCIE_HCI_CMD_PKT &&
+           (opcode == HCI_OP_RESET || opcode == 0xfc01)) {
+               old_ctxt = data->alive_intr_ctxt;
+               data->alive_intr_ctxt =
+                       (opcode == 0xfc01 ? BTINTEL_PCIE_INTEL_HCI_RESET1 :
+                               BTINTEL_PCIE_HCI_RESET);
+               bt_dev_dbg(data->hdev, "sent cmd: 0x%4.4x alive context changed: %s  ->  %s",
+                          opcode, btintel_pcie_alivectxt_state2str(old_ctxt),
+                          btintel_pcie_alivectxt_state2str(data->alive_intr_ctxt));
+               if (opcode == HCI_OP_RESET) {
+                       data->gp0_received = false;
+                       ret = wait_event_timeout(data->gp0_wait_q,
+                                                data->gp0_received,
+                                                msecs_to_jiffies(BTINTEL_DEFAULT_INTR_TIMEOUT_MS));
+                       if (!ret) {
+                               hdev->stat.err_tx++;
+                               bt_dev_err(hdev, "No alive interrupt received for %s",
+                                          btintel_pcie_alivectxt_state2str(data->alive_intr_ctxt));
+                               ret = -ETIME;
+                               goto exit_error;
+                       }
+               }
+       }
        hdev->stat.byte_tx += skb->len;
        kfree_skb(skb);