ath10k: add module parameter to disable cold reset
[cascardo/linux.git] / drivers / net / wireless / ath / ath10k / pci.c
index 29fd197..9dab859 100644 (file)
@@ -39,8 +39,14 @@ enum ath10k_pci_irq_mode {
        ATH10K_PCI_IRQ_MSI = 2,
 };
 
+enum ath10k_pci_reset_mode {
+       ATH10K_PCI_RESET_AUTO = 0,
+       ATH10K_PCI_RESET_WARM_ONLY = 1,
+};
+
 static unsigned int ath10k_target_ps;
 static unsigned int ath10k_pci_irq_mode = ATH10K_PCI_IRQ_AUTO;
+static unsigned int ath10k_pci_reset_mode = ATH10K_PCI_RESET_AUTO;
 
 module_param(ath10k_target_ps, uint, 0644);
 MODULE_PARM_DESC(ath10k_target_ps, "Enable ath10k Target (SoC) PS option");
@@ -48,6 +54,12 @@ MODULE_PARM_DESC(ath10k_target_ps, "Enable ath10k Target (SoC) PS option");
 module_param_named(irq_mode, ath10k_pci_irq_mode, uint, 0644);
 MODULE_PARM_DESC(irq_mode, "0: auto, 1: legacy, 2: msi (default: 0)");
 
+module_param_named(reset_mode, ath10k_pci_reset_mode, uint, 0644);
+MODULE_PARM_DESC(reset_mode, "0: auto, 1: warm only (default: 0)");
+
+/* how long wait to wait for target to initialise, in ms */
+#define ATH10K_PCI_TARGET_WAIT 3000
+
 #define QCA988X_2_0_DEVICE_ID  (0x003c)
 
 static DEFINE_PCI_DEVICE_TABLE(ath10k_pci_id_table) = {
@@ -58,13 +70,12 @@ static DEFINE_PCI_DEVICE_TABLE(ath10k_pci_id_table) = {
 static int ath10k_pci_diag_read_access(struct ath10k *ar, u32 address,
                                       u32 *data);
 
-static void ath10k_pci_process_ce(struct ath10k *ar);
 static int ath10k_pci_post_rx(struct ath10k *ar);
 static int ath10k_pci_post_rx_pipe(struct ath10k_pci_pipe *pipe_info,
                                             int num);
 static void ath10k_pci_rx_pipe_cleanup(struct ath10k_pci_pipe *pipe_info);
-static void ath10k_pci_stop_ce(struct ath10k *ar);
-static int ath10k_pci_device_reset(struct ath10k *ar);
+static int ath10k_pci_cold_reset(struct ath10k *ar);
+static int ath10k_pci_warm_reset(struct ath10k *ar);
 static int ath10k_pci_wait_for_target_init(struct ath10k *ar);
 static int ath10k_pci_init_irq(struct ath10k *ar);
 static int ath10k_pci_deinit_irq(struct ath10k *ar);
@@ -73,7 +84,6 @@ static void ath10k_pci_free_irq(struct ath10k *ar);
 static int ath10k_pci_bmi_wait(struct ath10k_ce_pipe *tx_pipe,
                               struct ath10k_ce_pipe *rx_pipe,
                               struct bmi_xfer *xfer);
-static void ath10k_pci_cleanup_ce(struct ath10k *ar);
 
 static const struct ce_attr host_ce_config_wlan[] = {
        /* CE0: host->target HTC control and raw streams */
@@ -678,34 +688,12 @@ void ath10k_do_pci_sleep(struct ath10k *ar)
        }
 }
 
-/*
- * FIXME: Handle OOM properly.
- */
-static inline
-struct ath10k_pci_compl *get_free_compl(struct ath10k_pci_pipe *pipe_info)
-{
-       struct ath10k_pci_compl *compl = NULL;
-
-       spin_lock_bh(&pipe_info->pipe_lock);
-       if (list_empty(&pipe_info->compl_free)) {
-               ath10k_warn("Completion buffers are full\n");
-               goto exit;
-       }
-       compl = list_first_entry(&pipe_info->compl_free,
-                                struct ath10k_pci_compl, list);
-       list_del(&compl->list);
-exit:
-       spin_unlock_bh(&pipe_info->pipe_lock);
-       return compl;
-}
-
 /* Called by lower (CE) layer when a send to Target completes. */
 static void ath10k_pci_ce_send_done(struct ath10k_ce_pipe *ce_state)
 {
        struct ath10k *ar = ce_state->ar;
        struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
-       struct ath10k_pci_pipe *pipe_info =  &ar_pci->pipe_info[ce_state->id];
-       struct ath10k_pci_compl *compl;
+       struct ath10k_hif_cb *cb = &ar_pci->msg_callbacks_current;
        void *transfer_context;
        u32 ce_data;
        unsigned int nbytes;
@@ -714,27 +702,12 @@ static void ath10k_pci_ce_send_done(struct ath10k_ce_pipe *ce_state)
        while (ath10k_ce_completed_send_next(ce_state, &transfer_context,
                                             &ce_data, &nbytes,
                                             &transfer_id) == 0) {
-               compl = get_free_compl(pipe_info);
-               if (!compl)
-                       break;
-
-               compl->state = ATH10K_PCI_COMPL_SEND;
-               compl->ce_state = ce_state;
-               compl->pipe_info = pipe_info;
-               compl->skb = transfer_context;
-               compl->nbytes = nbytes;
-               compl->transfer_id = transfer_id;
-               compl->flags = 0;
+               /* no need to call tx completion for NULL pointers */
+               if (transfer_context == NULL)
+                       continue;
 
-               /*
-                * Add the completion to the processing queue.
-                */
-               spin_lock_bh(&ar_pci->compl_lock);
-               list_add_tail(&compl->list, &ar_pci->compl_process);
-               spin_unlock_bh(&ar_pci->compl_lock);
+               cb->tx_completion(ar, transfer_context, transfer_id);
        }
-
-       ath10k_pci_process_ce(ar);
 }
 
 /* Called by lower (CE) layer when data is received from the Target. */
@@ -743,77 +716,100 @@ static void ath10k_pci_ce_recv_data(struct ath10k_ce_pipe *ce_state)
        struct ath10k *ar = ce_state->ar;
        struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
        struct ath10k_pci_pipe *pipe_info =  &ar_pci->pipe_info[ce_state->id];
-       struct ath10k_pci_compl *compl;
+       struct ath10k_hif_cb *cb = &ar_pci->msg_callbacks_current;
        struct sk_buff *skb;
        void *transfer_context;
        u32 ce_data;
-       unsigned int nbytes;
+       unsigned int nbytes, max_nbytes;
        unsigned int transfer_id;
        unsigned int flags;
+       int err;
 
        while (ath10k_ce_completed_recv_next(ce_state, &transfer_context,
                                             &ce_data, &nbytes, &transfer_id,
                                             &flags) == 0) {
-               compl = get_free_compl(pipe_info);
-               if (!compl)
-                       break;
-
-               compl->state = ATH10K_PCI_COMPL_RECV;
-               compl->ce_state = ce_state;
-               compl->pipe_info = pipe_info;
-               compl->skb = transfer_context;
-               compl->nbytes = nbytes;
-               compl->transfer_id = transfer_id;
-               compl->flags = flags;
+               err = ath10k_pci_post_rx_pipe(pipe_info, 1);
+               if (unlikely(err)) {
+                       /* FIXME: retry */
+                       ath10k_warn("failed to replenish CE rx ring %d: %d\n",
+                                   pipe_info->pipe_num, err);
+               }
 
                skb = transfer_context;
+               max_nbytes = skb->len + skb_tailroom(skb);
                dma_unmap_single(ar->dev, ATH10K_SKB_CB(skb)->paddr,
-                                skb->len + skb_tailroom(skb),
-                                DMA_FROM_DEVICE);
-               /*
-                * Add the completion to the processing queue.
-                */
-               spin_lock_bh(&ar_pci->compl_lock);
-               list_add_tail(&compl->list, &ar_pci->compl_process);
-               spin_unlock_bh(&ar_pci->compl_lock);
-       }
+                                max_nbytes, DMA_FROM_DEVICE);
+
+               if (unlikely(max_nbytes < nbytes)) {
+                       ath10k_warn("rxed more than expected (nbytes %d, max %d)",
+                                   nbytes, max_nbytes);
+                       dev_kfree_skb_any(skb);
+                       continue;
+               }
 
-       ath10k_pci_process_ce(ar);
+               skb_put(skb, nbytes);
+               cb->rx_completion(ar, skb, pipe_info->pipe_num);
+       }
 }
 
-/* Send the first nbytes bytes of the buffer */
-static int ath10k_pci_hif_send_head(struct ath10k *ar, u8 pipe_id,
-                                   unsigned int transfer_id,
-                                   unsigned int bytes, struct sk_buff *nbuf)
+static int ath10k_pci_hif_tx_sg(struct ath10k *ar, u8 pipe_id,
+                               struct ath10k_hif_sg_item *items, int n_items)
 {
-       struct ath10k_skb_cb *skb_cb = ATH10K_SKB_CB(nbuf);
        struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
-       struct ath10k_pci_pipe *pipe_info = &(ar_pci->pipe_info[pipe_id]);
-       struct ath10k_ce_pipe *ce_hdl = pipe_info->ce_hdl;
-       unsigned int len;
-       u32 flags = 0;
-       int ret;
+       struct ath10k_pci_pipe *pci_pipe = &ar_pci->pipe_info[pipe_id];
+       struct ath10k_ce_pipe *ce_pipe = pci_pipe->ce_hdl;
+       struct ath10k_ce_ring *src_ring = ce_pipe->src_ring;
+       unsigned int nentries_mask = src_ring->nentries_mask;
+       unsigned int sw_index = src_ring->sw_index;
+       unsigned int write_index = src_ring->write_index;
+       int err, i;
 
-       len = min(bytes, nbuf->len);
-       bytes -= len;
+       spin_lock_bh(&ar_pci->ce_lock);
 
-       if (len & 3)
-               ath10k_warn("skb not aligned to 4-byte boundary (%d)\n", len);
+       if (unlikely(CE_RING_DELTA(nentries_mask,
+                                  write_index, sw_index - 1) < n_items)) {
+               err = -ENOBUFS;
+               goto unlock;
+       }
 
-       ath10k_dbg(ATH10K_DBG_PCI,
-                  "pci send data vaddr %p paddr 0x%llx len %d as %d bytes\n",
-                  nbuf->data, (unsigned long long) skb_cb->paddr,
-                  nbuf->len, len);
-       ath10k_dbg_dump(ATH10K_DBG_PCI_DUMP, NULL,
-                       "ath10k tx: data: ",
-                       nbuf->data, nbuf->len);
-
-       ret = ath10k_ce_send(ce_hdl, nbuf, skb_cb->paddr, len, transfer_id,
-                            flags);
-       if (ret)
-               ath10k_warn("failed to send sk_buff to CE: %p\n", nbuf);
+       for (i = 0; i < n_items - 1; i++) {
+               ath10k_dbg(ATH10K_DBG_PCI,
+                          "pci tx item %d paddr 0x%08x len %d n_items %d\n",
+                          i, items[i].paddr, items[i].len, n_items);
+               ath10k_dbg_dump(ATH10K_DBG_PCI_DUMP, NULL, "item data: ",
+                               items[i].vaddr, items[i].len);
 
-       return ret;
+               err = ath10k_ce_send_nolock(ce_pipe,
+                                           items[i].transfer_context,
+                                           items[i].paddr,
+                                           items[i].len,
+                                           items[i].transfer_id,
+                                           CE_SEND_FLAG_GATHER);
+               if (err)
+                       goto unlock;
+       }
+
+       /* `i` is equal to `n_items -1` after for() */
+
+       ath10k_dbg(ATH10K_DBG_PCI,
+                  "pci tx item %d paddr 0x%08x len %d n_items %d\n",
+                  i, items[i].paddr, items[i].len, n_items);
+       ath10k_dbg_dump(ATH10K_DBG_PCI_DUMP, NULL, "item data: ",
+                       items[i].vaddr, items[i].len);
+
+       err = ath10k_ce_send_nolock(ce_pipe,
+                                   items[i].transfer_context,
+                                   items[i].paddr,
+                                   items[i].len,
+                                   items[i].transfer_id,
+                                   0);
+       if (err)
+               goto unlock;
+
+       err = 0;
+unlock:
+       spin_unlock_bh(&ar_pci->ce_lock);
+       return err;
 }
 
 static u16 ath10k_pci_hif_get_free_queue_number(struct ath10k *ar, u8 pipe)
@@ -833,9 +829,7 @@ static void ath10k_pci_hif_dump_area(struct ath10k *ar)
        ath10k_err("firmware crashed!\n");
        ath10k_err("hardware name %s version 0x%x\n",
                   ar->hw_params.name, ar->target_version);
-       ath10k_err("firmware version: %u.%u.%u.%u\n", ar->fw_version_major,
-                  ar->fw_version_minor, ar->fw_version_release,
-                  ar->fw_version_build);
+       ath10k_err("firmware version: %s\n", ar->hw->wiphy->fw_version);
 
        host_addr = host_interest_item_address(HI_ITEM(hi_failure_state));
        ret = ath10k_pci_diag_read_mem(ar, host_addr,
@@ -904,52 +898,6 @@ static void ath10k_pci_hif_set_callbacks(struct ath10k *ar,
               sizeof(ar_pci->msg_callbacks_current));
 }
 
-static int ath10k_pci_alloc_compl(struct ath10k *ar)
-{
-       struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
-       const struct ce_attr *attr;
-       struct ath10k_pci_pipe *pipe_info;
-       struct ath10k_pci_compl *compl;
-       int i, pipe_num, completions;
-
-       spin_lock_init(&ar_pci->compl_lock);
-       INIT_LIST_HEAD(&ar_pci->compl_process);
-
-       for (pipe_num = 0; pipe_num < CE_COUNT; pipe_num++) {
-               pipe_info = &ar_pci->pipe_info[pipe_num];
-
-               spin_lock_init(&pipe_info->pipe_lock);
-               INIT_LIST_HEAD(&pipe_info->compl_free);
-
-               /* Handle Diagnostic CE specially */
-               if (pipe_info->ce_hdl == ar_pci->ce_diag)
-                       continue;
-
-               attr = &host_ce_config_wlan[pipe_num];
-               completions = 0;
-
-               if (attr->src_nentries)
-                       completions += attr->src_nentries;
-
-               if (attr->dest_nentries)
-                       completions += attr->dest_nentries;
-
-               for (i = 0; i < completions; i++) {
-                       compl = kmalloc(sizeof(*compl), GFP_KERNEL);
-                       if (!compl) {
-                               ath10k_warn("No memory for completion state\n");
-                               ath10k_pci_cleanup_ce(ar);
-                               return -ENOMEM;
-                       }
-
-                       compl->state = ATH10K_PCI_COMPL_FREE;
-                       list_add_tail(&compl->list, &pipe_info->compl_free);
-               }
-       }
-
-       return 0;
-}
-
 static int ath10k_pci_setup_ce_irq(struct ath10k *ar)
 {
        struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
@@ -994,147 +942,6 @@ static void ath10k_pci_kill_tasklet(struct ath10k *ar)
                tasklet_kill(&ar_pci->pipe_info[i].intr);
 }
 
-static void ath10k_pci_stop_ce(struct ath10k *ar)
-{
-       struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
-       struct ath10k_pci_compl *compl;
-       struct sk_buff *skb;
-
-       /* Mark pending completions as aborted, so that upper layers free up
-        * their associated resources */
-       spin_lock_bh(&ar_pci->compl_lock);
-       list_for_each_entry(compl, &ar_pci->compl_process, list) {
-               skb = compl->skb;
-               ATH10K_SKB_CB(skb)->is_aborted = true;
-       }
-       spin_unlock_bh(&ar_pci->compl_lock);
-}
-
-static void ath10k_pci_cleanup_ce(struct ath10k *ar)
-{
-       struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
-       struct ath10k_pci_compl *compl, *tmp;
-       struct ath10k_pci_pipe *pipe_info;
-       struct sk_buff *netbuf;
-       int pipe_num;
-
-       /* Free pending completions. */
-       spin_lock_bh(&ar_pci->compl_lock);
-       if (!list_empty(&ar_pci->compl_process))
-               ath10k_warn("pending completions still present! possible memory leaks.\n");
-
-       list_for_each_entry_safe(compl, tmp, &ar_pci->compl_process, list) {
-               list_del(&compl->list);
-               netbuf = compl->skb;
-               dev_kfree_skb_any(netbuf);
-               kfree(compl);
-       }
-       spin_unlock_bh(&ar_pci->compl_lock);
-
-       /* Free unused completions for each pipe. */
-       for (pipe_num = 0; pipe_num < CE_COUNT; pipe_num++) {
-               pipe_info = &ar_pci->pipe_info[pipe_num];
-
-               spin_lock_bh(&pipe_info->pipe_lock);
-               list_for_each_entry_safe(compl, tmp,
-                                        &pipe_info->compl_free, list) {
-                       list_del(&compl->list);
-                       kfree(compl);
-               }
-               spin_unlock_bh(&pipe_info->pipe_lock);
-       }
-}
-
-static void ath10k_pci_process_ce(struct ath10k *ar)
-{
-       struct ath10k_pci *ar_pci = ar->hif.priv;
-       struct ath10k_hif_cb *cb = &ar_pci->msg_callbacks_current;
-       struct ath10k_pci_compl *compl;
-       struct sk_buff *skb;
-       unsigned int nbytes;
-       int ret, send_done = 0;
-
-       /* Upper layers aren't ready to handle tx/rx completions in parallel so
-        * we must serialize all completion processing. */
-
-       spin_lock_bh(&ar_pci->compl_lock);
-       if (ar_pci->compl_processing) {
-               spin_unlock_bh(&ar_pci->compl_lock);
-               return;
-       }
-       ar_pci->compl_processing = true;
-       spin_unlock_bh(&ar_pci->compl_lock);
-
-       for (;;) {
-               spin_lock_bh(&ar_pci->compl_lock);
-               if (list_empty(&ar_pci->compl_process)) {
-                       spin_unlock_bh(&ar_pci->compl_lock);
-                       break;
-               }
-               compl = list_first_entry(&ar_pci->compl_process,
-                                        struct ath10k_pci_compl, list);
-               list_del(&compl->list);
-               spin_unlock_bh(&ar_pci->compl_lock);
-
-               switch (compl->state) {
-               case ATH10K_PCI_COMPL_SEND:
-                       cb->tx_completion(ar,
-                                         compl->skb,
-                                         compl->transfer_id);
-                       send_done = 1;
-                       break;
-               case ATH10K_PCI_COMPL_RECV:
-                       ret = ath10k_pci_post_rx_pipe(compl->pipe_info, 1);
-                       if (ret) {
-                               ath10k_warn("failed to post RX buffer for pipe %d: %d\n",
-                                           compl->pipe_info->pipe_num, ret);
-                               break;
-                       }
-
-                       skb = compl->skb;
-                       nbytes = compl->nbytes;
-
-                       ath10k_dbg(ATH10K_DBG_PCI,
-                                  "ath10k_pci_ce_recv_data netbuf=%p  nbytes=%d\n",
-                                  skb, nbytes);
-                       ath10k_dbg_dump(ATH10K_DBG_PCI_DUMP, NULL,
-                                       "ath10k rx: ", skb->data, nbytes);
-
-                       if (skb->len + skb_tailroom(skb) >= nbytes) {
-                               skb_trim(skb, 0);
-                               skb_put(skb, nbytes);
-                               cb->rx_completion(ar, skb,
-                                                 compl->pipe_info->pipe_num);
-                       } else {
-                               ath10k_warn("rxed more than expected (nbytes %d, max %d)",
-                                           nbytes,
-                                           skb->len + skb_tailroom(skb));
-                       }
-                       break;
-               case ATH10K_PCI_COMPL_FREE:
-                       ath10k_warn("free completion cannot be processed\n");
-                       break;
-               default:
-                       ath10k_warn("invalid completion state (%d)\n",
-                                   compl->state);
-                       break;
-               }
-
-               compl->state = ATH10K_PCI_COMPL_FREE;
-
-               /*
-                * Add completion back to the pipe's free list.
-                */
-               spin_lock_bh(&compl->pipe_info->pipe_lock);
-               list_add_tail(&compl->list, &compl->pipe_info->compl_free);
-               spin_unlock_bh(&compl->pipe_info->pipe_lock);
-       }
-
-       spin_lock_bh(&ar_pci->compl_lock);
-       ar_pci->compl_processing = false;
-       spin_unlock_bh(&ar_pci->compl_lock);
-}
-
 /* TODO - temporary mapping while we have too few CE's */
 static int ath10k_pci_hif_map_service_to_pipe(struct ath10k *ar,
                                              u16 service_id, u8 *ul_pipe,
@@ -1306,17 +1113,11 @@ static int ath10k_pci_hif_start(struct ath10k *ar)
        ath10k_pci_free_early_irq(ar);
        ath10k_pci_kill_tasklet(ar);
 
-       ret = ath10k_pci_alloc_compl(ar);
-       if (ret) {
-               ath10k_warn("failed to allocate CE completions: %d\n", ret);
-               goto err_early_irq;
-       }
-
        ret = ath10k_pci_request_irq(ar);
        if (ret) {
                ath10k_warn("failed to post RX buffers for all pipes: %d\n",
                            ret);
-               goto err_free_compl;
+               goto err_early_irq;
        }
 
        ret = ath10k_pci_setup_ce_irq(ar);
@@ -1340,10 +1141,6 @@ err_stop:
        ath10k_ce_disable_interrupts(ar);
        ath10k_pci_free_irq(ar);
        ath10k_pci_kill_tasklet(ar);
-       ath10k_pci_stop_ce(ar);
-       ath10k_pci_process_ce(ar);
-err_free_compl:
-       ath10k_pci_cleanup_ce(ar);
 err_early_irq:
        /* Though there should be no interrupts (device was reset)
         * power_down() expects the early IRQ to be installed as per the
@@ -1414,18 +1211,10 @@ static void ath10k_pci_tx_pipe_cleanup(struct ath10k_pci_pipe *pipe_info)
 
        while (ath10k_ce_cancel_send_next(ce_hdl, (void **)&netbuf,
                                          &ce_data, &nbytes, &id) == 0) {
-               /*
-                * Indicate the completion to higer layer to free
-                * the buffer
-                */
-
-               if (!netbuf) {
-                       ath10k_warn("invalid sk_buff on CE %d - NULL pointer. firmware crashed?\n",
-                                   ce_hdl->id);
+               /* no need to call tx completion for NULL pointers */
+               if (!netbuf)
                        continue;
-               }
 
-               ATH10K_SKB_CB(netbuf)->is_aborted = true;
                ar_pci->msg_callbacks_current.tx_completion(ar,
                                                            netbuf,
                                                            id);
@@ -1483,7 +1272,6 @@ static void ath10k_pci_hif_stop(struct ath10k *ar)
 
        ath10k_pci_free_irq(ar);
        ath10k_pci_kill_tasklet(ar);
-       ath10k_pci_stop_ce(ar);
 
        ret = ath10k_pci_request_early_irq(ar);
        if (ret)
@@ -1493,8 +1281,6 @@ static void ath10k_pci_hif_stop(struct ath10k *ar)
         * not DMA nor interrupt. We process the leftovers and then free
         * everything else up. */
 
-       ath10k_pci_process_ce(ar);
-       ath10k_pci_cleanup_ce(ar);
        ath10k_pci_buffer_cleanup(ar);
 
        /* Make the sure the device won't access any structures on the host by
@@ -1502,7 +1288,7 @@ static void ath10k_pci_hif_stop(struct ath10k *ar)
         * configuration during init. If ringbuffers are freed and the device
         * were to access them this could lead to memory corruption on the
         * host. */
-       ath10k_pci_device_reset(ar);
+       ath10k_pci_warm_reset(ar);
 
        ar_pci->started = 0;
 }
@@ -1993,7 +1779,94 @@ static void ath10k_pci_fw_interrupt_handler(struct ath10k *ar)
        ath10k_pci_sleep(ar);
 }
 
-static int ath10k_pci_hif_power_up(struct ath10k *ar)
+static int ath10k_pci_warm_reset(struct ath10k *ar)
+{
+       struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+       int ret = 0;
+       u32 val;
+
+       ath10k_dbg(ATH10K_DBG_BOOT, "boot performing warm chip reset\n");
+
+       ret = ath10k_do_pci_wake(ar);
+       if (ret) {
+               ath10k_err("failed to wake up target: %d\n", ret);
+               return ret;
+       }
+
+       /* debug */
+       val = ath10k_pci_read32(ar, SOC_CORE_BASE_ADDRESS +
+                               PCIE_INTR_CAUSE_ADDRESS);
+       ath10k_dbg(ATH10K_DBG_BOOT, "boot host cpu intr cause: 0x%08x\n", val);
+
+       val = ath10k_pci_read32(ar, SOC_CORE_BASE_ADDRESS +
+                               CPU_INTR_ADDRESS);
+       ath10k_dbg(ATH10K_DBG_BOOT, "boot target cpu intr cause: 0x%08x\n",
+                  val);
+
+       /* disable pending irqs */
+       ath10k_pci_write32(ar, SOC_CORE_BASE_ADDRESS +
+                          PCIE_INTR_ENABLE_ADDRESS, 0);
+
+       ath10k_pci_write32(ar, SOC_CORE_BASE_ADDRESS +
+                          PCIE_INTR_CLR_ADDRESS, ~0);
+
+       msleep(100);
+
+       /* clear fw indicator */
+       ath10k_pci_write32(ar, ar_pci->fw_indicator_address, 0);
+
+       /* clear target LF timer interrupts */
+       val = ath10k_pci_read32(ar, RTC_SOC_BASE_ADDRESS +
+                               SOC_LF_TIMER_CONTROL0_ADDRESS);
+       ath10k_pci_write32(ar, RTC_SOC_BASE_ADDRESS +
+                          SOC_LF_TIMER_CONTROL0_ADDRESS,
+                          val & ~SOC_LF_TIMER_CONTROL0_ENABLE_MASK);
+
+       /* reset CE */
+       val = ath10k_pci_read32(ar, RTC_SOC_BASE_ADDRESS +
+                               SOC_RESET_CONTROL_ADDRESS);
+       ath10k_pci_write32(ar, RTC_SOC_BASE_ADDRESS + SOC_RESET_CONTROL_ADDRESS,
+                          val | SOC_RESET_CONTROL_CE_RST_MASK);
+       val = ath10k_pci_read32(ar, RTC_SOC_BASE_ADDRESS +
+                               SOC_RESET_CONTROL_ADDRESS);
+       msleep(10);
+
+       /* unreset CE */
+       ath10k_pci_write32(ar, RTC_SOC_BASE_ADDRESS + SOC_RESET_CONTROL_ADDRESS,
+                          val & ~SOC_RESET_CONTROL_CE_RST_MASK);
+       val = ath10k_pci_read32(ar, RTC_SOC_BASE_ADDRESS +
+                               SOC_RESET_CONTROL_ADDRESS);
+       msleep(10);
+
+       /* debug */
+       val = ath10k_pci_read32(ar, SOC_CORE_BASE_ADDRESS +
+                               PCIE_INTR_CAUSE_ADDRESS);
+       ath10k_dbg(ATH10K_DBG_BOOT, "boot host cpu intr cause: 0x%08x\n", val);
+
+       val = ath10k_pci_read32(ar, SOC_CORE_BASE_ADDRESS +
+                               CPU_INTR_ADDRESS);
+       ath10k_dbg(ATH10K_DBG_BOOT, "boot target cpu intr cause: 0x%08x\n",
+                  val);
+
+       /* CPU warm reset */
+       val = ath10k_pci_read32(ar, RTC_SOC_BASE_ADDRESS +
+                               SOC_RESET_CONTROL_ADDRESS);
+       ath10k_pci_write32(ar, RTC_SOC_BASE_ADDRESS + SOC_RESET_CONTROL_ADDRESS,
+                          val | SOC_RESET_CONTROL_CPU_WARM_RST_MASK);
+
+       val = ath10k_pci_read32(ar, RTC_SOC_BASE_ADDRESS +
+                               SOC_RESET_CONTROL_ADDRESS);
+       ath10k_dbg(ATH10K_DBG_BOOT, "boot target reset state: 0x%08x\n", val);
+
+       msleep(100);
+
+       ath10k_dbg(ATH10K_DBG_BOOT, "boot warm reset complete\n");
+
+       ath10k_do_pci_sleep(ar);
+       return ret;
+}
+
+static int __ath10k_pci_hif_power_up(struct ath10k *ar, bool cold_reset)
 {
        struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
        const char *irq_mode;
@@ -2009,7 +1882,11 @@ static int ath10k_pci_hif_power_up(struct ath10k *ar)
         * is in an unexpected state. We try to catch that here in order to
         * reset the Target and retry the probe.
         */
-       ret = ath10k_pci_device_reset(ar);
+       if (cold_reset)
+               ret = ath10k_pci_cold_reset(ar);
+       else
+               ret = ath10k_pci_warm_reset(ar);
+
        if (ret) {
                ath10k_err("failed to reset target: %d\n", ret);
                goto err;
@@ -2079,7 +1956,7 @@ err_deinit_irq:
        ath10k_pci_deinit_irq(ar);
 err_ce:
        ath10k_pci_ce_deinit(ar);
-       ath10k_pci_device_reset(ar);
+       ath10k_pci_warm_reset(ar);
 err_ps:
        if (!test_bit(ATH10K_PCI_FEATURE_SOC_POWER_SAVE, ar_pci->features))
                ath10k_do_pci_sleep(ar);
@@ -2087,6 +1964,39 @@ err:
        return ret;
 }
 
+static int ath10k_pci_hif_power_up(struct ath10k *ar)
+{
+       int ret;
+
+       /*
+        * Hardware CUS232 version 2 has some issues with cold reset and the
+        * preferred (and safer) way to perform a device reset is through a
+        * warm reset.
+        *
+        * Warm reset doesn't always work though (notably after a firmware
+        * crash) so fall back to cold reset if necessary.
+        */
+       ret = __ath10k_pci_hif_power_up(ar, false);
+       if (ret) {
+               ath10k_warn("failed to power up target using warm reset: %d\n",
+                           ret);
+
+               if (ath10k_pci_reset_mode == ATH10K_PCI_RESET_WARM_ONLY)
+                       return ret;
+
+               ath10k_warn("trying cold reset\n");
+
+               ret = __ath10k_pci_hif_power_up(ar, true);
+               if (ret) {
+                       ath10k_err("failed to power up target using cold reset too (%d)\n",
+                                  ret);
+                       return ret;
+               }
+       }
+
+       return 0;
+}
+
 static void ath10k_pci_hif_power_down(struct ath10k *ar)
 {
        struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
@@ -2094,7 +2004,7 @@ static void ath10k_pci_hif_power_down(struct ath10k *ar)
        ath10k_pci_free_early_irq(ar);
        ath10k_pci_kill_tasklet(ar);
        ath10k_pci_deinit_irq(ar);
-       ath10k_pci_device_reset(ar);
+       ath10k_pci_warm_reset(ar);
 
        ath10k_pci_ce_deinit(ar);
        if (!test_bit(ATH10K_PCI_FEATURE_SOC_POWER_SAVE, ar_pci->features))
@@ -2151,7 +2061,7 @@ static int ath10k_pci_hif_resume(struct ath10k *ar)
 #endif
 
 static const struct ath10k_hif_ops ath10k_pci_hif_ops = {
-       .send_head              = ath10k_pci_hif_send_head,
+       .tx_sg                  = ath10k_pci_hif_tx_sg,
        .exchange_bmi_msg       = ath10k_pci_hif_exchange_bmi_msg,
        .start                  = ath10k_pci_hif_start,
        .stop                   = ath10k_pci_hif_stop,
@@ -2411,11 +2321,10 @@ static int ath10k_pci_init_irq(struct ath10k *ar)
        /* Try MSI-X */
        if (ath10k_pci_irq_mode == ATH10K_PCI_IRQ_AUTO && msix_supported) {
                ar_pci->num_msi_intrs = MSI_NUM_REQUEST;
-               ret = pci_enable_msi_block(ar_pci->pdev, ar_pci->num_msi_intrs);
-               if (ret == 0)
-                       return 0;
+               ret = pci_enable_msi_range(ar_pci->pdev, ar_pci->num_msi_intrs,
+                                                        ar_pci->num_msi_intrs);
                if (ret > 0)
-                       pci_disable_msi(ar_pci->pdev);
+                       return 0;
 
                /* fall-through */
        }
@@ -2482,6 +2391,8 @@ static int ath10k_pci_deinit_irq(struct ath10k *ar)
        case MSI_NUM_REQUEST:
                pci_disable_msi(ar_pci->pdev);
                return 0;
+       default:
+               pci_disable_msi(ar_pci->pdev);
        }
 
        ath10k_warn("unknown irq configuration upon deinit\n");
@@ -2491,30 +2402,41 @@ static int ath10k_pci_deinit_irq(struct ath10k *ar)
 static int ath10k_pci_wait_for_target_init(struct ath10k *ar)
 {
        struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
-       int wait_limit = 300; /* 3 sec */
+       unsigned long timeout;
        int ret;
+       u32 val;
 
        ret = ath10k_pci_wake(ar);
        if (ret) {
-               ath10k_err("failed to wake up target: %d\n", ret);
+               ath10k_err("failed to wake up target for init: %d\n", ret);
                return ret;
        }
 
-       while (wait_limit-- &&
-              !(ioread32(ar_pci->mem + FW_INDICATOR_ADDRESS) &
-                FW_IND_INITIALIZED)) {
+       timeout = jiffies + msecs_to_jiffies(ATH10K_PCI_TARGET_WAIT);
+
+       do {
+               val = ath10k_pci_read32(ar, FW_INDICATOR_ADDRESS);
+
+               /* target should never return this */
+               if (val == 0xffffffff)
+                       continue;
+
+               if (val & FW_IND_INITIALIZED)
+                       break;
+
                if (ar_pci->num_msi_intrs == 0)
                        /* Fix potential race by repeating CORE_BASE writes */
-                       iowrite32(PCIE_INTR_FIRMWARE_MASK |
-                                 PCIE_INTR_CE_MASK_ALL,
-                                 ar_pci->mem + (SOC_CORE_BASE_ADDRESS |
-                                                PCIE_INTR_ENABLE_ADDRESS));
+                       ath10k_pci_soc_write32(ar, PCIE_INTR_ENABLE_ADDRESS,
+                                              PCIE_INTR_FIRMWARE_MASK |
+                                              PCIE_INTR_CE_MASK_ALL);
+
                mdelay(10);
-       }
+       } while (time_before(jiffies, timeout));
 
-       if (wait_limit < 0) {
-               ath10k_err("target stalled\n");
-               ret = -EIO;
+       if (val == 0xffffffff || !(val & FW_IND_INITIALIZED)) {
+               ath10k_err("failed to receive initialized event from target: %08x\n",
+                          val);
+               ret = -ETIMEDOUT;
                goto out;
        }
 
@@ -2523,7 +2445,7 @@ out:
        return ret;
 }
 
-static int ath10k_pci_device_reset(struct ath10k *ar)
+static int ath10k_pci_cold_reset(struct ath10k *ar)
 {
        int i, ret;
        u32 val;