diff mbox

[19/29] ath10k: add pci.c

Message ID 20130515144528.4492.86463.stgit@localhost6.localdomain6 (mailing list archive)
State Not Applicable, archived
Headers show

Commit Message

Kalle Valo May 15, 2013, 2:45 p.m. UTC
Signed-off-by: Kalle Valo <kvalo@qca.qualcomm.com>
---
 drivers/net/wireless/ath/ath10k/pci.c | 2506 +++++++++++++++++++++++++++++++++
 1 file changed, 2506 insertions(+)
 create mode 100644 drivers/net/wireless/ath/ath10k/pci.c


--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/net/wireless/ath/ath10k/pci.c b/drivers/net/wireless/ath/ath10k/pci.c
new file mode 100644
index 0000000..8e4e832
--- /dev/null
+++ b/drivers/net/wireless/ath/ath10k/pci.c
@@ -0,0 +1,2506 @@ 
+/*
+ * Copyright (c) 2005-2011 Atheros Communications Inc.
+ * Copyright (c) 2011-2013 Qualcomm Atheros, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <linux/pci.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+
+#include "core.h"
+#include "debug.h"
+
+#include "targaddrs.h"
+#include "bmi.h"
+
+#include "hif.h"
+#include "htc.h"
+
+#include "ce.h"
+#include "pci.h"
+
+unsigned int ath10k_target_ps;
+module_param(ath10k_target_ps, uint, 0644);
+MODULE_PARM_DESC(ath10k_target_ps, "Enable ath10k Target (SoC) PS option");
+
+#define QCA988X_1_0_DEVICE_ID	(0xabcd)
+#define QCA988X_2_0_DEVICE_ID	(0x003c)
+
+static DEFINE_PCI_DEVICE_TABLE(ath10k_pci_id_table) = {
+	{ PCI_VDEVICE(ATHEROS, QCA988X_1_0_DEVICE_ID) }, /* PCI-E QCA988X V1 */
+	{ PCI_VDEVICE(ATHEROS, QCA988X_2_0_DEVICE_ID) }, /* PCI-E QCA988X V2 */
+	{0}
+};
+
+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 hif_ce_pipe_info *pipe_info,
+					     int num);
+static void ath10k_pci_rx_pipe_cleanup(struct hif_ce_pipe_info *pipe_info);
+static void ath10k_pci_stop_ce(struct ath10k *ar);
+
+static const struct ce_attr host_ce_config_wlan[] = {
+	/* host->target HTC control and raw streams */
+	{ /* CE0 */ CE_ATTR_FLAGS, 0, 16, 256, 0, NULL,},
+	/* could be moved to share CE3 */
+	/* target->host HTT + HTC control */
+	{ /* CE1 */ CE_ATTR_FLAGS, 0, 0, 512, 512, NULL,},
+	/* target->host WMI */
+	{ /* CE2 */ CE_ATTR_FLAGS, 0, 0, 2048, 32, NULL,},
+	/* host->target WMI */
+	{ /* CE3 */ CE_ATTR_FLAGS, 0, 32, 2048, 0, NULL,},
+	/* host->target HTT */
+	{ /* CE4 */ CE_ATTR_FLAGS | CE_ATTR_DIS_INTR, 0,
+		    CE_HTT_H2T_MSG_SRC_NENTRIES, 256, 0, NULL,},
+	/* unused */
+	{ /* CE5 */ CE_ATTR_FLAGS, 0, 0, 0, 0, NULL,},
+	/* Target autonomous hif_memcpy */
+	{ /* CE6 */ CE_ATTR_FLAGS, 0, 0, 0, 0, NULL,},
+	/* ce_diag, the Diagnostic Window */
+	{ /* CE7 */ CE_ATTR_FLAGS, 0, 2, DIAG_TRANSFER_LIMIT, 2, NULL,},
+};
+
+/* Target firmware's Copy Engine configuration. */
+static const struct ce_pipe_config target_ce_config_wlan[] = {
+	/* host->target HTC control and raw streams */
+	{ /* CE0 */ 0, PIPEDIR_OUT, 32, 256, CE_ATTR_FLAGS, 0,},
+	/* target->host HTT + HTC control */
+	{ /* CE1 */ 1, PIPEDIR_IN, 32, 512, CE_ATTR_FLAGS, 0,},
+	/* target->host WMI */
+	{ /* CE2 */ 2, PIPEDIR_IN, 32, 2048, CE_ATTR_FLAGS, 0,},
+	/* host->target WMI */
+	{ /* CE3 */ 3, PIPEDIR_OUT, 32, 2048, CE_ATTR_FLAGS, 0,},
+	/* host->target HTT */
+	{ /* CE4 */ 4, PIPEDIR_OUT, 256, 256, CE_ATTR_FLAGS, 0,},
+	/* NB: 50% of src nentries, since tx has 2 frags */
+	/* unused */
+	{ /* CE5 */ 5, PIPEDIR_OUT, 32, 2048, CE_ATTR_FLAGS, 0,},
+	/* Reserved for target autonomous hif_memcpy */
+	{ /* CE6 */ 6, PIPEDIR_INOUT, 32, 4096, CE_ATTR_FLAGS, 0,},
+	/* CE7 used only by Host */
+};
+
+/*
+ * Diagnostic read/write access is provided for startup/config/debug usage.
+ * Caller must guarantee proper alignment, when applicable, and single user
+ * at any moment.
+ */
+static int ath10k_pci_diag_read_mem(struct ath10k *ar, u32 address, void *data,
+				    int nbytes)
+{
+	struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+	int ret = 0;
+	u32 buf;
+	unsigned int completed_nbytes, orig_nbytes, remaining_bytes;
+	unsigned int id;
+	unsigned int flags;
+	struct ce_state *ce_diag;
+	/* Host buffer address in CE space */
+	u32 ce_data;
+	dma_addr_t ce_data_base = 0;
+	void *data_buf = NULL;
+	int i;
+
+	/*
+	 * This code cannot handle reads to non-memory space. Redirect to the
+	 * register read fn but preserve the multi word read capability of
+	 * this fn
+	 */
+	if (address < DRAM_BASE_ADDRESS) {
+		if (!IS_ALIGNED(address, 4) ||
+		    !IS_ALIGNED((unsigned long)data, 4))
+			return -EIO;
+
+		while ((nbytes >= 4) &&  ((ret = ath10k_pci_diag_read_access(
+					   ar, address, (u32 *)data)) == 0)) {
+			nbytes -= sizeof(u32);
+			address += sizeof(u32);
+			data += sizeof(u32);
+		}
+		return ret;
+	}
+
+	ce_diag = ar_pci->ce_diag;
+
+	/*
+	 * Allocate a temporary bounce buffer to hold caller's data
+	 * to be DMA'ed from Target. This guarantees
+	 *   1) 4-byte alignment
+	 *   2) Buffer in DMA-able space
+	 */
+	orig_nbytes = nbytes;
+	data_buf = (unsigned char *)pci_alloc_consistent(ar_pci->pdev,
+							 orig_nbytes,
+							 &ce_data_base);
+
+	if (!data_buf) {
+		ret = -ENOMEM;
+		goto done;
+	}
+	memset(data_buf, 0, orig_nbytes);
+
+	remaining_bytes = orig_nbytes;
+	ce_data = ce_data_base;
+	while (remaining_bytes) {
+		nbytes = min_t(unsigned int, remaining_bytes,
+			       DIAG_TRANSFER_LIMIT);
+
+		ret = ath10k_ce_recv_buf_enqueue(ce_diag, NULL, ce_data);
+		if (ret != 0)
+			goto done;
+
+		/* Request CE to send from Target(!) address to Host buffer */
+		/*
+		 * The address supplied by the caller is in the
+		 * Target CPU virtual address space.
+		 *
+		 * In order to use this address with the diagnostic CE,
+		 * convert it from Target CPU virtual address space
+		 * to CE address space
+		 */
+		ath10k_pci_wake(ar);
+		address = TARG_CPU_SPACE_TO_CE_SPACE(ar, ar_pci->mem,
+						     address);
+		ath10k_pci_sleep(ar);
+
+		ret = ath10k_ce_send(ce_diag, NULL, (u32)address, nbytes, 0,
+				 0);
+		if (ret)
+			goto done;
+
+		i = 0;
+		while (ath10k_ce_completed_send_next(ce_diag, NULL, &buf,
+						     &completed_nbytes,
+						     &id) != 0) {
+			mdelay(1);
+			if (i++ > DIAG_ACCESS_CE_TIMEOUT_MS) {
+				ret = -EBUSY;
+				goto done;
+			}
+		}
+
+		if (nbytes != completed_nbytes) {
+			ret = -EIO;
+			goto done;
+		}
+
+		if (buf != (u32) address) {
+			ret = -EIO;
+			goto done;
+		}
+
+		i = 0;
+		while (ath10k_ce_completed_recv_next(ce_diag, NULL, &buf,
+						     &completed_nbytes,
+						     &id, &flags) != 0) {
+			mdelay(1);
+
+			if (i++ > DIAG_ACCESS_CE_TIMEOUT_MS) {
+				ret = -EBUSY;
+				goto done;
+			}
+		}
+
+		if (nbytes != completed_nbytes) {
+			ret = -EIO;
+			goto done;
+		}
+
+		if (buf != ce_data) {
+			ret = -EIO;
+			goto done;
+		}
+
+		remaining_bytes -= nbytes;
+		address += nbytes;
+		ce_data += nbytes;
+	}
+
+done:
+	if (ret == 0) {
+		/* Copy data from allocated DMA buf to caller's buf */
+		WARN_ON_ONCE(orig_nbytes & 3);
+		for (i = 0; i < orig_nbytes / sizeof(__le32); i++) {
+			((u32 *)data)[i] =
+				__le32_to_cpu(((__le32 *)data_buf)[i]);
+		}
+	} else
+		ath10k_dbg(ATH10K_DBG_PCI, "%s failure (0x%x)\n",
+			   __func__, address);
+
+	if (data_buf)
+		pci_free_consistent(ar_pci->pdev, orig_nbytes,
+				    data_buf, ce_data_base);
+
+	return ret;
+}
+
+/* Read 4-byte aligned data from Target memory or register */
+static int ath10k_pci_diag_read_access(struct ath10k *ar, u32 address,
+				       u32 *data)
+{
+	/* Assume range doesn't cross this boundary */
+	if (address >= DRAM_BASE_ADDRESS)
+		return ath10k_pci_diag_read_mem(ar, address, data, sizeof(u32));
+
+	ath10k_pci_wake(ar);
+	*data = ath10k_pci_read32(ar, address);
+	ath10k_pci_sleep(ar);
+	return 0;
+}
+
+static int ath10k_pci_diag_write_mem(struct ath10k *ar, u32 address,
+				     const void *data, int nbytes)
+{
+	struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+	int ret = 0;
+	u32 buf;
+	unsigned int completed_nbytes, orig_nbytes, remaining_bytes;
+	unsigned int id;
+	unsigned int flags;
+	struct ce_state *ce_diag;
+	void *data_buf = NULL;
+	u32 ce_data;	/* Host buffer address in CE space */
+	dma_addr_t ce_data_base = 0;
+	int i;
+
+	ce_diag = ar_pci->ce_diag;
+
+	/*
+	 * Allocate a temporary bounce buffer to hold caller's data
+	 * to be DMA'ed to Target. This guarantees
+	 *   1) 4-byte alignment
+	 *   2) Buffer in DMA-able space
+	 */
+	orig_nbytes = nbytes;
+	data_buf = (unsigned char *)pci_alloc_consistent(ar_pci->pdev,
+							 orig_nbytes,
+							 &ce_data_base);
+	if (!data_buf) {
+		ret = -ENOMEM;
+		goto done;
+	}
+
+	/* Copy caller's data to allocated DMA buf */
+	WARN_ON_ONCE(orig_nbytes & 3);
+	for (i = 0; i < orig_nbytes / sizeof(__le32); i++)
+		((__le32 *)data_buf)[i] = __cpu_to_le32(((u32 *)data)[i]);
+
+	/*
+	 * The address supplied by the caller is in the
+	 * Target CPU virtual address space.
+	 *
+	 * In order to use this address with the diagnostic CE,
+	 * convert it from
+	 *    Target CPU virtual address space
+	 * to
+	 *    CE address space
+	 */
+	ath10k_pci_wake(ar);
+	address = TARG_CPU_SPACE_TO_CE_SPACE(ar, ar_pci->mem, address);
+	ath10k_pci_sleep(ar);
+
+	remaining_bytes = orig_nbytes;
+	ce_data = ce_data_base;
+	while (remaining_bytes) {
+		/* FIXME: check cast */
+		nbytes = min_t(int, remaining_bytes, DIAG_TRANSFER_LIMIT);
+
+		/* Set up to receive directly into Target(!) address */
+		ret = ath10k_ce_recv_buf_enqueue(ce_diag, NULL, address);
+		if (ret != 0)
+			goto done;
+
+		/*
+		 * Request CE to send caller-supplied data that
+		 * was copied to bounce buffer to Target(!) address.
+		 */
+		ret = ath10k_ce_send(ce_diag, NULL, (u32) ce_data,
+				     nbytes, 0, 0);
+		if (ret != 0)
+			goto done;
+
+		i = 0;
+		while (ath10k_ce_completed_send_next(ce_diag, NULL, &buf,
+						     &completed_nbytes,
+						     &id) != 0) {
+			mdelay(1);
+
+			if (i++ > DIAG_ACCESS_CE_TIMEOUT_MS) {
+				ret = -EBUSY;
+				goto done;
+			}
+		}
+
+		if (nbytes != completed_nbytes) {
+			ret = -EIO;
+			goto done;
+		}
+
+		if (buf != ce_data) {
+			ret = -EIO;
+			goto done;
+		}
+
+		i = 0;
+		while (ath10k_ce_completed_recv_next(ce_diag, NULL, &buf,
+						     &completed_nbytes,
+						     &id, &flags) != 0) {
+			mdelay(1);
+
+			if (i++ > DIAG_ACCESS_CE_TIMEOUT_MS) {
+				ret = -EBUSY;
+				goto done;
+			}
+		}
+
+		if (nbytes != completed_nbytes) {
+			ret = -EIO;
+			goto done;
+		}
+
+		if (buf != address) {
+			ret = -EIO;
+			goto done;
+		}
+
+		remaining_bytes -= nbytes;
+		address += nbytes;
+		ce_data += nbytes;
+	}
+
+done:
+	if (data_buf) {
+		pci_free_consistent(ar_pci->pdev, orig_nbytes, data_buf,
+				    ce_data_base);
+	}
+
+	if (ret != 0)
+		ath10k_dbg(ATH10K_DBG_PCI, "%s failure (0x%x)\n", __func__,
+			   address);
+
+	return ret;
+}
+
+/* Write 4B data to Target memory or register */
+static int ath10k_pci_diag_write_access(struct ath10k *ar, u32 address,
+					u32 data)
+{
+	/* Assume range doesn't cross this boundary */
+	if (address >= DRAM_BASE_ADDRESS)
+		return ath10k_pci_diag_write_mem(ar, address, &data,
+						 sizeof(u32));
+
+	ath10k_pci_wake(ar);
+	ath10k_pci_write32(ar, address, data);
+	ath10k_pci_sleep(ar);
+	return 0;
+}
+
+static bool ath10k_pci_target_is_awake(struct ath10k *ar)
+{
+	void __iomem *mem = ath10k_pci_priv(ar)->mem;
+	u32 val;
+	val = ioread32(mem + PCIE_LOCAL_BASE_ADDRESS +
+		       RTC_STATE_ADDRESS);
+	return (RTC_STATE_V_GET(val) == RTC_STATE_V_ON);
+}
+
+static void ath10k_pci_wait(struct ath10k *ar)
+{
+	int n = 100;
+
+	while (n-- && !ath10k_pci_target_is_awake(ar))
+		msleep(10);
+
+	if (n < 0)
+		ath10k_warn("Unable to wakeup target\n");
+}
+
+void ath10k_do_pci_wake(struct ath10k *ar)
+{
+	struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+	void __iomem *pci_addr = ar_pci->mem;
+	int tot_delay = 0;
+	int curr_delay = 5;
+
+	if (atomic_read(&ar_pci->keep_awake_count) == 0) {
+		/* Force AWAKE */
+		iowrite32(PCIE_SOC_WAKE_V_MASK,
+			  pci_addr + PCIE_LOCAL_BASE_ADDRESS +
+			  PCIE_SOC_WAKE_ADDRESS);
+	}
+	atomic_inc(&ar_pci->keep_awake_count);
+
+	if (ar_pci->verified_awake)
+		return;
+
+	for (;;) {
+		if (ath10k_pci_target_is_awake(ar)) {
+			ar_pci->verified_awake = true;
+			break;
+		}
+
+		if (tot_delay > PCIE_WAKE_TIMEOUT) {
+			ath10k_warn("target takes too long to wake up (awake count %d)\n",
+				    atomic_read(&ar_pci->keep_awake_count));
+			break;
+		}
+
+		udelay(curr_delay);
+		tot_delay += curr_delay;
+
+		if (curr_delay < 50)
+			curr_delay += 5;
+	}
+}
+
+void ath10k_do_pci_sleep(struct ath10k *ar)
+{
+	struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+	void __iomem *pci_addr = ar_pci->mem;
+
+	if (atomic_dec_and_test(&ar_pci->keep_awake_count)) {
+		/* Allow sleep */
+		ar_pci->verified_awake = false;
+		iowrite32(PCIE_SOC_WAKE_RESET,
+			  pci_addr + PCIE_LOCAL_BASE_ADDRESS +
+			  PCIE_SOC_WAKE_ADDRESS);
+	}
+}
+
+/*
+ * FIXME: Handle OOM properly.
+ */
+static inline
+struct ath10k_pci_compl *get_free_compl(struct hif_ce_pipe_info *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 ce_state *ce_state,
+				    void *transfer_context,
+				    u32 ce_data,
+				    unsigned int nbytes,
+				    unsigned int transfer_id)
+{
+	struct ath10k *ar = ce_state->ar;
+	struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+	struct hif_ce_pipe_info *pipe_info =  &ar_pci->pipe_info[ce_state->id];
+	struct ath10k_pci_compl *compl;
+	bool process = false;
+
+	do {
+		/*
+		 * For the send completion of an item in sendlist, just
+		 * increment num_sends_allowed. The upper layer callback will
+		 * be triggered when last fragment is done with send.
+		 */
+		if (transfer_context == CE_SENDLIST_ITEM_CTXT) {
+			spin_lock_bh(&pipe_info->pipe_lock);
+			pipe_info->num_sends_allowed++;
+			spin_unlock_bh(&pipe_info->pipe_lock);
+			continue;
+		}
+
+		compl = get_free_compl(pipe_info);
+		if (!compl)
+			break;
+
+		compl->send_or_recv = HIF_CE_COMPLETE_SEND;
+		compl->ce_state = ce_state;
+		compl->pipe_info = pipe_info;
+		compl->transfer_context = transfer_context;
+		compl->nbytes = nbytes;
+		compl->transfer_id = transfer_id;
+		compl->flags = 0;
+
+		/*
+		 * 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);
+
+		process = true;
+	} while (ath10k_ce_completed_send_next(ce_state,
+							   &transfer_context,
+							   &ce_data, &nbytes,
+							   &transfer_id) == 0);
+
+	/*
+	 * If only some of the items within a sendlist have completed,
+	 * don't invoke completion processing until the entire sendlist
+	 * has been sent.
+	 */
+	if (!process)
+		return;
+
+	ath10k_pci_process_ce(ar);
+}
+
+/* Called by lower (CE) layer when data is received from the Target. */
+static void ath10k_pci_ce_recv_data(struct ce_state *ce_state,
+				    void *transfer_context, u32 ce_data,
+				    unsigned int nbytes,
+				    unsigned int transfer_id,
+				    unsigned int flags)
+{
+	struct ath10k *ar = ce_state->ar;
+	struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+	struct hif_ce_pipe_info *pipe_info =  &ar_pci->pipe_info[ce_state->id];
+	struct ath10k_pci_compl *compl;
+	struct sk_buff *skb;
+
+	do {
+		compl = get_free_compl(pipe_info);
+		if (!compl)
+			break;
+
+		compl->send_or_recv = HIF_CE_COMPLETE_RECV;
+		compl->ce_state = ce_state;
+		compl->pipe_info = pipe_info;
+		compl->transfer_context = transfer_context;
+		compl->nbytes = nbytes;
+		compl->transfer_id = transfer_id;
+		compl->flags = flags;
+
+		skb = transfer_context;
+		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);
+
+	} while (ath10k_ce_completed_recv_next(ce_state,
+							   &transfer_context,
+							   &ce_data, &nbytes,
+							   &transfer_id,
+							   &flags) == 0);
+
+	ath10k_pci_process_ce(ar);
+}
+
+/* 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)
+{
+	struct ath10k_skb_cb *skb_cb = ATH10K_SKB_CB(nbuf);
+	struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+	struct hif_ce_pipe_info *pipe_info = &(ar_pci->pipe_info[pipe_id]);
+	struct ce_state *ce_hdl = pipe_info->ce_hdl;
+	struct ce_sendlist sendlist;
+	unsigned int len;
+	u32 flags = 0;
+	int ret;
+
+	memset(&sendlist, 0, sizeof(struct ce_sendlist));
+
+	len = min(bytes, nbuf->len);
+	bytes -= len;
+
+	if (len & 3)
+		ath10k_warn("skb not aligned to 4-byte boundary (%d)\n", len);
+
+	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);
+
+	ath10k_ce_sendlist_buf_add(&sendlist, skb_cb->paddr, len, flags);
+
+	/* Make sure we have resources to handle this request */
+	spin_lock_bh(&pipe_info->pipe_lock);
+	if (!pipe_info->num_sends_allowed) {
+		ath10k_warn("Pipe: %d is full\n", pipe_id);
+		spin_unlock_bh(&pipe_info->pipe_lock);
+		return -ENOSR;
+	}
+	pipe_info->num_sends_allowed--;
+	spin_unlock_bh(&pipe_info->pipe_lock);
+
+	ret = ath10k_ce_sendlist_send(ce_hdl, nbuf, &sendlist, transfer_id);
+	if (ret)
+		ath10k_warn("CE send failed: %p\n", nbuf);
+
+	return ret;
+}
+
+static u16 ath10k_pci_hif_get_free_queue_number(struct ath10k *ar, u8 pipe)
+{
+	struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+	struct hif_ce_pipe_info *pipe_info = &(ar_pci->pipe_info[pipe]);
+	int ret;
+
+	spin_lock_bh(&pipe_info->pipe_lock);
+	ret = pipe_info->num_sends_allowed;
+	spin_unlock_bh(&pipe_info->pipe_lock);
+
+	return ret;
+}
+
+static void ath10k_pci_hif_dump_area(struct ath10k *ar)
+{
+	u32 reg_dump_area = 0;
+	u32 reg_dump_values[REG_DUMP_COUNT_QCA988X] = {};
+	u32 host_addr;
+	int ret;
+	u32 i;
+
+	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);
+
+	host_addr = host_interest_item_address(HI_ITEM(hi_failure_state));
+	if (ath10k_pci_diag_read_mem(ar, host_addr,
+				     &reg_dump_area, sizeof(u32)) != 0) {
+		ath10k_warn("could not read hi_failure_state\n");
+		return;
+	}
+
+	ath10k_err("target register Dump Location: 0x%08X\n", reg_dump_area);
+
+	ret = ath10k_pci_diag_read_mem(ar, reg_dump_area,
+				       &reg_dump_values[0],
+				       REG_DUMP_COUNT_QCA988X * sizeof(u32));
+	if (ret != 0) {
+		ath10k_err("could not dump FW Dump Area\n");
+		return;
+	}
+
+	BUILD_BUG_ON(REG_DUMP_COUNT_QCA988X % 4);
+
+	ath10k_err("target Register Dump\n");
+	for (i = 0; i < REG_DUMP_COUNT_QCA988X; i += 4)
+		ath10k_err("[%02d]: 0x%08X 0x%08X 0x%08X 0x%08X\n",
+			   i,
+			   reg_dump_values[i],
+			   reg_dump_values[i + 1],
+			   reg_dump_values[i + 2],
+			   reg_dump_values[i + 3]);
+}
+
+static void ath10k_pci_hif_send_complete_check(struct ath10k *ar, u8 pipe,
+					       int force)
+{
+	if (!force) {
+		int resources;
+		/*
+		 * Decide whether to actually poll for completions, or just
+		 * wait for a later chance.
+		 * If there seem to be plenty of resources left, then just wait
+		 * since checking involves reading a CE register, which is a
+		 * relatively expensive operation.
+		 */
+		resources = ath10k_pci_hif_get_free_queue_number(ar, pipe);
+
+		/*
+		 * If at least 50% of the total resources are still available,
+		 * don't bother checking again yet.
+		 */
+		if (resources > (host_ce_config_wlan[pipe].src_nentries >> 1))
+			return;
+	}
+	ath10k_ce_per_engine_service(ar, pipe);
+}
+
+static void ath10k_pci_hif_post_init(struct ath10k *ar,
+				     struct ath10k_hif_cb *callbacks)
+{
+	struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+
+	ath10k_dbg(ATH10K_DBG_PCI, "%s\n", __func__);
+
+	memcpy(&ar_pci->msg_callbacks_current, callbacks,
+	       sizeof(ar_pci->msg_callbacks_current));
+}
+
+static int ath10k_pci_start_ce(struct ath10k *ar)
+{
+	struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+	struct ce_state *ce_diag = ar_pci->ce_diag;
+	const struct ce_attr *attr;
+	struct hif_ce_pipe_info *pipe_info;
+	struct ath10k_pci_compl *compl;
+	int i, pipe_num, completions, disable_interrupts;
+
+	spin_lock_init(&ar_pci->compl_lock);
+	INIT_LIST_HEAD(&ar_pci->compl_process);
+
+	for (pipe_num = 0; pipe_num < ar_pci->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 == ce_diag)
+			continue;
+
+		attr = &host_ce_config_wlan[pipe_num];
+		completions = 0;
+
+		if (attr->src_nentries) {
+			disable_interrupts = attr->flags & CE_ATTR_DIS_INTR;
+			ath10k_ce_send_cb_register(pipe_info->ce_hdl,
+						   ath10k_pci_ce_send_done,
+						   disable_interrupts);
+			completions += attr->src_nentries;
+			pipe_info->num_sends_allowed = attr->src_nentries - 1;
+		}
+
+		if (attr->dest_nentries) {
+			ath10k_ce_recv_cb_register(pipe_info->ce_hdl,
+						   ath10k_pci_ce_recv_data);
+			completions += attr->dest_nentries;
+		}
+
+		if (completions == 0)
+			continue;
+
+		for (i = 0; i < completions; i++) {
+			compl = kmalloc(sizeof(struct ath10k_pci_compl),
+					GFP_KERNEL);
+			if (!compl) {
+				ath10k_warn("No memory for completion state\n");
+				ath10k_pci_stop_ce(ar);
+				return -ENOMEM;
+			}
+
+			compl->send_or_recv = HIF_CE_COMPLETE_FREE;
+			list_add_tail(&compl->list, &pipe_info->compl_free);
+		}
+	}
+
+	return 0;
+}
+
+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;
+	int i;
+
+	ath10k_ce_disable_interrupts(ar);
+
+	/* Cancel the pending tasklet */
+	tasklet_kill(&ar_pci->intr_tq);
+
+	for (i = 0; i < CE_COUNT; i++)
+		tasklet_kill(&ar_pci->pipe_info[i].intr);
+
+	/* 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 = (struct sk_buff *)compl->transfer_context;
+		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 hif_ce_pipe_info *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 = (struct sk_buff *)compl->transfer_context;
+		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 < ar_pci->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);
+
+		if (compl->send_or_recv == HIF_CE_COMPLETE_SEND) {
+			cb->tx_completion(ar,
+					  compl->transfer_context,
+					  compl->transfer_id);
+			send_done = 1;
+		} else {
+			ret = ath10k_pci_post_rx_pipe(compl->pipe_info, 1);
+			if (ret) {
+				ath10k_warn("Unable to post recv buffer for pipe: %d\n",
+					    compl->pipe_info->pipe_num);
+				break;
+			}
+
+			skb = (struct sk_buff *)compl->transfer_context;
+			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));
+			}
+		}
+
+		compl->send_or_recv = HIF_CE_COMPLETE_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);
+		compl->pipe_info->num_sends_allowed += send_done;
+		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,
+					      u8 *dl_pipe, int *ul_is_polled,
+					      int *dl_is_polled)
+{
+	int ret = 0;
+
+	/* polling for received messages not supported */
+	*dl_is_polled = 0;
+
+	switch (service_id) {
+	case ATH10K_HTC_SVC_ID_HTT_DATA_MSG:
+		/*
+		 * Host->target HTT gets its own pipe, so it can be polled
+		 * while other pipes are interrupt driven.
+		 */
+		*ul_pipe = 4;
+		/*
+		 * Use the same target->host pipe for HTC ctrl, HTC raw
+		 * streams, and HTT.
+		 */
+		*dl_pipe = 1;
+		break;
+
+	case ATH10K_HTC_SVC_ID_RSVD_CTRL:
+	case ATH10K_HTC_SVC_ID_TEST_RAW_STREAMS:
+		/*
+		 * Note: HTC_RAW_STREAMS_SVC is currently unused, and
+		 * HTC_CTRL_RSVD_SVC could share the same pipe as the
+		 * WMI services.  So, if another CE is needed, change
+		 * this to *ul_pipe = 3, which frees up CE 0.
+		 */
+		/* *ul_pipe = 3; */
+		*ul_pipe = 0;
+		*dl_pipe = 1;
+		break;
+
+	case ATH10K_HTC_SVC_ID_WMI_DATA_BK:
+	case ATH10K_HTC_SVC_ID_WMI_DATA_BE:
+	case ATH10K_HTC_SVC_ID_WMI_DATA_VI:
+	case ATH10K_HTC_SVC_ID_WMI_DATA_VO:
+
+	case ATH10K_HTC_SVC_ID_WMI_CONTROL:
+		*ul_pipe = 3;
+		*dl_pipe = 2;
+		break;
+
+		/* pipe 5 unused   */
+		/* pipe 6 reserved */
+		/* pipe 7 reserved */
+
+	default:
+		ret = -1;
+		break;
+	}
+	*ul_is_polled =
+		(host_ce_config_wlan[*ul_pipe].flags & CE_ATTR_DIS_INTR) != 0;
+
+	return ret;
+}
+
+static void ath10k_pci_hif_get_default_pipe(struct ath10k *ar,
+						u8 *ul_pipe, u8 *dl_pipe)
+{
+	int ul_is_polled, dl_is_polled;
+
+	(void)ath10k_pci_hif_map_service_to_pipe(ar,
+						 ATH10K_HTC_SVC_ID_RSVD_CTRL,
+						 ul_pipe,
+						 dl_pipe,
+						 &ul_is_polled,
+						 &dl_is_polled);
+}
+
+static int ath10k_pci_post_rx_pipe(struct hif_ce_pipe_info *pipe_info,
+				   int num)
+{
+	struct ath10k *ar = pipe_info->hif_ce_state;
+	struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+	struct ce_state *ce_state = pipe_info->ce_hdl;
+	struct sk_buff *skb;
+	dma_addr_t ce_data;
+	int i, ret = 0;
+
+	if (pipe_info->buf_sz == 0)
+		return 0;
+
+	for (i = 0; i < num; i++) {
+		skb = dev_alloc_skb(pipe_info->buf_sz);
+		if (!skb) {
+			ath10k_warn("could not allocate skbuff for pipe %d\n",
+				    num);
+			ret = -ENOMEM;
+			goto err;
+		}
+
+		WARN_ONCE((unsigned long)skb->data & 3, "unaligned skb");
+
+		ce_data = dma_map_single(ar->dev, skb->data,
+					 skb->len + skb_tailroom(skb),
+					 DMA_FROM_DEVICE);
+
+		if (unlikely(dma_mapping_error(ar->dev, ce_data))) {
+			ath10k_warn("could not dma map skbuff\n");
+			dev_kfree_skb_any(skb);
+			ret = -EIO;
+			goto err;
+		}
+
+		ATH10K_SKB_CB(skb)->paddr = ce_data;
+
+		pci_dma_sync_single_for_device(ar_pci->pdev, ce_data,
+					       pipe_info->buf_sz,
+					       PCI_DMA_FROMDEVICE);
+
+		ret = ath10k_ce_recv_buf_enqueue(ce_state, (void *)skb,
+						 ce_data);
+		if (ret) {
+			ath10k_warn("could not enqueue to pipe %d (%d)\n",
+				    num, ret);
+			goto err;
+		}
+	}
+
+	return ret;
+
+err:
+	ath10k_pci_rx_pipe_cleanup(pipe_info);
+	return ret;
+}
+
+static int ath10k_pci_post_rx(struct ath10k *ar)
+{
+	struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+	struct hif_ce_pipe_info *pipe_info;
+	const struct ce_attr *attr;
+	int pipe_num, ret = 0;
+
+	for (pipe_num = 0; pipe_num < ar_pci->ce_count; pipe_num++) {
+		pipe_info = &ar_pci->pipe_info[pipe_num];
+		attr = &host_ce_config_wlan[pipe_num];
+
+		if (attr->dest_nentries == 0)
+			continue;
+
+		ret = ath10k_pci_post_rx_pipe(pipe_info,
+					      attr->dest_nentries - 1);
+		if (ret) {
+			ath10k_warn("Unable to replenish recv buffers for pipe: %d\n",
+				    pipe_num);
+
+			for (; pipe_num >= 0; pipe_num--) {
+				pipe_info = &ar_pci->pipe_info[pipe_num];
+				ath10k_pci_rx_pipe_cleanup(pipe_info);
+			}
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int ath10k_pci_hif_start(struct ath10k *ar)
+{
+	struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+	int ret;
+
+	ret = ath10k_pci_start_ce(ar);
+	if (ret) {
+		ath10k_warn("could not start CE (%d)\n", ret);
+		return ret;
+	}
+
+	/* Post buffers once to start things off. */
+	ret = ath10k_pci_post_rx(ar);
+	if (ret) {
+		ath10k_warn("could not post rx pipes (%d)\n", ret);
+		return ret;
+	}
+
+	ar_pci->started = 1;
+	return 0;
+}
+
+static void ath10k_pci_rx_pipe_cleanup(struct hif_ce_pipe_info *pipe_info)
+{
+	struct ath10k *ar;
+	struct ath10k_pci *ar_pci;
+	struct ce_state *ce_hdl;
+	u32 buf_sz;
+	struct sk_buff *netbuf;
+	u32 ce_data;
+
+	buf_sz = pipe_info->buf_sz;
+
+	/* Unused Copy Engine */
+	if (buf_sz == 0)
+		return;
+
+	ar = pipe_info->hif_ce_state;
+	ar_pci = ath10k_pci_priv(ar);
+
+	if (!ar_pci->started)
+		return;
+
+	ce_hdl = pipe_info->ce_hdl;
+
+	while (ath10k_ce_revoke_recv_next(ce_hdl, (void **)&netbuf,
+					  &ce_data) == 0) {
+		dma_unmap_single(ar->dev, ATH10K_SKB_CB(netbuf)->paddr,
+				 netbuf->len + skb_tailroom(netbuf),
+				 DMA_FROM_DEVICE);
+		dev_kfree_skb_any(netbuf);
+	}
+}
+
+static void ath10k_pci_tx_pipe_cleanup(struct hif_ce_pipe_info *pipe_info)
+{
+	struct ath10k *ar;
+	struct ath10k_pci *ar_pci;
+	struct ce_state *ce_hdl;
+	struct sk_buff *netbuf;
+	u32 ce_data;
+	unsigned int nbytes;
+	unsigned int id;
+	u32 buf_sz;
+
+	buf_sz = pipe_info->buf_sz;
+
+	/* Unused Copy Engine */
+	if (buf_sz == 0)
+		return;
+
+	ar = pipe_info->hif_ce_state;
+	ar_pci = ath10k_pci_priv(ar);
+
+	if (!ar_pci->started)
+		return;
+
+	ce_hdl = pipe_info->ce_hdl;
+
+	while (ath10k_ce_cancel_send_next(ce_hdl, (void **)&netbuf,
+					  &ce_data, &nbytes, &id) == 0) {
+		if (netbuf != CE_SENDLIST_ITEM_CTXT)
+			/*
+			 * Indicate the completion to higer layer to free
+			 * the buffer
+			 */
+			ATH10K_SKB_CB(netbuf)->is_aborted = true;
+			ar_pci->msg_callbacks_current.tx_completion(ar,
+								    netbuf,
+								    id);
+	}
+}
+
+/*
+ * Cleanup residual buffers for device shutdown:
+ *    buffers that were enqueued for receive
+ *    buffers that were to be sent
+ * Note: Buffers that had completed but which were
+ * not yet processed are on a completion queue. They
+ * are handled when the completion thread shuts down.
+ */
+static void ath10k_pci_buffer_cleanup(struct ath10k *ar)
+{
+	struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+	int pipe_num;
+
+	for (pipe_num = 0; pipe_num < ar_pci->ce_count; pipe_num++) {
+		struct hif_ce_pipe_info *pipe_info;
+
+		pipe_info = &ar_pci->pipe_info[pipe_num];
+		ath10k_pci_rx_pipe_cleanup(pipe_info);
+		ath10k_pci_tx_pipe_cleanup(pipe_info);
+	}
+}
+
+static void ath10k_pci_ce_deinit(struct ath10k *ar)
+{
+	struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+	struct hif_ce_pipe_info *pipe_info;
+	int pipe_num;
+
+	for (pipe_num = 0; pipe_num < ar_pci->ce_count; pipe_num++) {
+		pipe_info = &ar_pci->pipe_info[pipe_num];
+		if (pipe_info->ce_hdl) {
+			ath10k_ce_deinit(pipe_info->ce_hdl);
+			pipe_info->ce_hdl = NULL;
+			pipe_info->buf_sz = 0;
+		}
+	}
+}
+
+static void ath10k_pci_hif_stop(struct ath10k *ar)
+{
+	ath10k_dbg(ATH10K_DBG_PCI, "%s\n", __func__);
+
+	ath10k_pci_stop_ce(ar);
+
+	/* At this point, asynchronous threads are stopped, the target should
+	 * 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);
+	ath10k_pci_ce_deinit(ar);
+}
+
+static int ath10k_pci_hif_exchange_bmi_msg(struct ath10k *ar,
+					   void *req, u32 req_len,
+					   void *resp, u32 *resp_len)
+{
+	struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+	struct ce_state *ce_tx = ar_pci->pipe_info[BMI_CE_NUM_TO_TARG].ce_hdl;
+	struct ce_state *ce_rx = ar_pci->pipe_info[BMI_CE_NUM_TO_HOST].ce_hdl;
+	dma_addr_t req_paddr = 0;
+	dma_addr_t resp_paddr = 0;
+	struct bmi_xfer xfer = {};
+	void *treq, *tresp = NULL;
+	int ret = 0;
+
+	if (resp && !resp_len)
+		return -EINVAL;
+
+	if (resp && resp_len && *resp_len == 0)
+		return -EINVAL;
+
+	treq = kmemdup(req, req_len, GFP_KERNEL);
+	if (!treq)
+		return -ENOMEM;
+
+	req_paddr = dma_map_single(ar->dev, treq, req_len, DMA_TO_DEVICE);
+	ret = dma_mapping_error(ar->dev, req_paddr);
+	if (ret)
+		goto err_dma;
+
+	if (resp && resp_len) {
+		tresp = kzalloc(*resp_len, GFP_KERNEL);
+		if (!tresp) {
+			ret = -ENOMEM;
+			goto err_req;
+		}
+
+		resp_paddr = dma_map_single(ar->dev, tresp, *resp_len,
+					    DMA_FROM_DEVICE);
+		ret = dma_mapping_error(ar->dev, resp_paddr);
+		if (ret)
+			goto err_req;
+
+		xfer.wait_for_resp = true;
+		xfer.resp_len = 0;
+
+		ath10k_ce_recv_buf_enqueue(ce_rx, &xfer, resp_paddr);
+	}
+
+	init_completion(&xfer.done);
+
+	ret = ath10k_ce_send(ce_tx, &xfer, req_paddr, req_len, -1, 0);
+	if (ret)
+		goto err_resp;
+
+	ret = wait_for_completion_timeout(&xfer.done,
+					  BMI_COMMUNICATION_TIMEOUT_HZ);
+	if (ret <= 0) {
+		u32 unused_buffer;
+		unsigned int unused_nbytes;
+		unsigned int unused_id;
+
+		ret = -ETIMEDOUT;
+		ath10k_ce_cancel_send_next(ce_tx, NULL, &unused_buffer,
+					   &unused_nbytes, &unused_id);
+	} else {
+		/* non-zero means we did not time out */
+		ret = 0;
+	}
+
+err_resp:
+	if (resp) {
+		u32 unused_buffer;
+
+		ath10k_ce_revoke_recv_next(ce_rx, NULL, &unused_buffer);
+		dma_unmap_single(ar->dev, resp_paddr,
+				 *resp_len, DMA_FROM_DEVICE);
+	}
+err_req:
+	dma_unmap_single(ar->dev, req_paddr, req_len, DMA_TO_DEVICE);
+
+	if (ret == 0 && resp_len) {
+		*resp_len = min(*resp_len, xfer.resp_len);
+		memcpy(resp, tresp, xfer.resp_len);
+	}
+err_dma:
+	kfree(treq);
+	kfree(tresp);
+
+	return ret;
+}
+
+static void ath10k_pci_bmi_send_done(struct ce_state *ce_state,
+				     void *transfer_context,
+				     u32 data,
+				     unsigned int nbytes,
+				     unsigned int transfer_id)
+{
+	struct bmi_xfer *xfer = transfer_context;
+
+	if (xfer->wait_for_resp)
+		return;
+
+	complete(&xfer->done);
+}
+
+static void ath10k_pci_bmi_recv_data(struct ce_state *ce_state,
+				     void *transfer_context,
+				     u32 data,
+				     unsigned int nbytes,
+				     unsigned int transfer_id,
+				     unsigned int flags)
+{
+	struct bmi_xfer *xfer = transfer_context;
+
+	if (!xfer->wait_for_resp) {
+		ath10k_warn("unexpected: BMI data received; ignoring\n");
+		return;
+	}
+
+	xfer->resp_len = nbytes;
+	complete(&xfer->done);
+}
+
+/*
+ * Map from service/endpoint to Copy Engine.
+ * This table is derived from the CE_PCI TABLE, above.
+ * It is passed to the Target at startup for use by firmware.
+ */
+static const struct service_to_pipe target_service_to_ce_map_wlan[] = {
+	{
+		 ATH10K_HTC_SVC_ID_WMI_DATA_VO,
+		 PIPEDIR_OUT,		/* out = UL = host -> target */
+		 3,
+	},
+	{
+		 ATH10K_HTC_SVC_ID_WMI_DATA_VO,
+		 PIPEDIR_IN,		/* in = DL = target -> host */
+		 2,
+	},
+	{
+		 ATH10K_HTC_SVC_ID_WMI_DATA_BK,
+		 PIPEDIR_OUT,		/* out = UL = host -> target */
+		 3,
+	},
+	{
+		 ATH10K_HTC_SVC_ID_WMI_DATA_BK,
+		 PIPEDIR_IN,		/* in = DL = target -> host */
+		 2,
+	},
+	{
+		 ATH10K_HTC_SVC_ID_WMI_DATA_BE,
+		 PIPEDIR_OUT,		/* out = UL = host -> target */
+		 3,
+	},
+	{
+		 ATH10K_HTC_SVC_ID_WMI_DATA_BE,
+		 PIPEDIR_IN,		/* in = DL = target -> host */
+		 2,
+	},
+	{
+		 ATH10K_HTC_SVC_ID_WMI_DATA_VI,
+		 PIPEDIR_OUT,		/* out = UL = host -> target */
+		 3,
+	},
+	{
+		 ATH10K_HTC_SVC_ID_WMI_DATA_VI,
+		 PIPEDIR_IN,		/* in = DL = target -> host */
+		 2,
+	},
+	{
+		 ATH10K_HTC_SVC_ID_WMI_CONTROL,
+		 PIPEDIR_OUT,		/* out = UL = host -> target */
+		 3,
+	},
+	{
+		 ATH10K_HTC_SVC_ID_WMI_CONTROL,
+		 PIPEDIR_IN,		/* in = DL = target -> host */
+		 2,
+	},
+	{
+		 ATH10K_HTC_SVC_ID_RSVD_CTRL,
+		 PIPEDIR_OUT,		/* out = UL = host -> target */
+		 0,		/* could be moved to 3 (share with WMI) */
+	},
+	{
+		 ATH10K_HTC_SVC_ID_RSVD_CTRL,
+		 PIPEDIR_IN,		/* in = DL = target -> host */
+		 1,
+	},
+	{
+		 ATH10K_HTC_SVC_ID_TEST_RAW_STREAMS,	/* not currently used */
+		 PIPEDIR_OUT,		/* out = UL = host -> target */
+		 0,
+	},
+	{
+		 ATH10K_HTC_SVC_ID_TEST_RAW_STREAMS,	/* not currently used */
+		 PIPEDIR_IN,		/* in = DL = target -> host */
+		 1,
+	},
+	{
+		 ATH10K_HTC_SVC_ID_HTT_DATA_MSG,
+		 PIPEDIR_OUT,		/* out = UL = host -> target */
+		 4,
+	},
+	{
+		 ATH10K_HTC_SVC_ID_HTT_DATA_MSG,
+		 PIPEDIR_IN,		/* in = DL = target -> host */
+		 1,
+	},
+
+	/* (Additions here) */
+
+	{				/* Must be last */
+		 0,
+		 0,
+		 0,
+	},
+};
+
+/*
+ * Send an interrupt to the device to wake up the Target CPU
+ * so it has an opportunity to notice any changed state.
+ */
+static int ath10k_pci_wake_target_cpu(struct ath10k *ar)
+{
+	int ret;
+	u32 core_ctrl;
+
+	ret = ath10k_pci_diag_read_access(ar, SOC_CORE_BASE_ADDRESS |
+					      CORE_CTRL_ADDRESS,
+					  &core_ctrl);
+	if (ret) {
+		ath10k_warn("Unable to read core ctrl\n");
+		return ret;
+	}
+
+	/* A_INUM_FIRMWARE interrupt to Target CPU */
+	core_ctrl |= CORE_CTRL_CPU_INTR_MASK;
+
+	ret = ath10k_pci_diag_write_access(ar, SOC_CORE_BASE_ADDRESS |
+					       CORE_CTRL_ADDRESS,
+					   core_ctrl);
+	if (ret)
+		ath10k_warn("Unable to set interrupt mask\n");
+
+	return ret;
+}
+
+static int ath10k_pci_init_config(struct ath10k *ar)
+{
+	u32 interconnect_targ_addr;
+	u32 pcie_state_targ_addr = 0;
+	u32 pipe_cfg_targ_addr = 0;
+	u32 svc_to_pipe_map = 0;
+	u32 pcie_config_flags = 0;
+	u32 ealloc_value;
+	u32 ealloc_targ_addr;
+	u32 flag2_value;
+	u32 flag2_targ_addr;
+	int ret = 0;
+
+	/* Download to Target the CE Config and the service-to-CE map */
+	interconnect_targ_addr =
+		host_interest_item_address(HI_ITEM(hi_interconnect_state));
+
+	/* Supply Target-side CE configuration */
+	ret = ath10k_pci_diag_read_access(ar, interconnect_targ_addr,
+					  &pcie_state_targ_addr);
+	if (ret != 0) {
+		ath10k_err("Failed to get pcie state addr: %d\n", ret);
+		return ret;
+	}
+
+	if (pcie_state_targ_addr == 0) {
+		ret = -EIO;
+		ath10k_err("Invalid pcie state addr\n");
+		return ret;
+	}
+
+	ret = ath10k_pci_diag_read_access(ar, pcie_state_targ_addr +
+					  offsetof(struct pcie_state,
+						   pipe_cfg_addr),
+					  &pipe_cfg_targ_addr);
+	if (ret != 0) {
+		ath10k_err("Failed to get pipe cfg addr: %d\n", ret);
+		return ret;
+	}
+
+	if (pipe_cfg_targ_addr == 0) {
+		ret = -EIO;
+		ath10k_err("Invalid pipe cfg addr\n");
+		return ret;
+	}
+
+	ret = ath10k_pci_diag_write_mem(ar, pipe_cfg_targ_addr,
+				 target_ce_config_wlan,
+				 sizeof(target_ce_config_wlan));
+
+	if (ret != 0) {
+		ath10k_err("Failed to write pipe cfg: %d\n", ret);
+		return ret;
+	}
+
+	ret = ath10k_pci_diag_read_access(ar, pcie_state_targ_addr +
+					  offsetof(struct pcie_state,
+						   svc_to_pipe_map),
+					  &svc_to_pipe_map);
+	if (ret != 0) {
+		ath10k_err("Failed to get svc/pipe map: %d\n", ret);
+		return ret;
+	}
+
+	if (svc_to_pipe_map == 0) {
+		ret = -EIO;
+		ath10k_err("Invalid svc_to_pipe map\n");
+		return ret;
+	}
+
+	ret = ath10k_pci_diag_write_mem(ar, svc_to_pipe_map,
+				 target_service_to_ce_map_wlan,
+				 sizeof(target_service_to_ce_map_wlan));
+	if (ret != 0) {
+		ath10k_err("Failed to write svc/pipe map: %d\n", ret);
+		return ret;
+	}
+
+	ret = ath10k_pci_diag_read_access(ar, pcie_state_targ_addr +
+					  offsetof(struct pcie_state,
+						   config_flags),
+					  &pcie_config_flags);
+	if (ret != 0) {
+		ath10k_err("Failed to get pcie config_flags: %d\n", ret);
+		return ret;
+	}
+
+	pcie_config_flags &= ~PCIE_CONFIG_FLAG_ENABLE_L1;
+
+	ret = ath10k_pci_diag_write_mem(ar, pcie_state_targ_addr +
+				 offsetof(struct pcie_state, config_flags),
+				 &pcie_config_flags,
+				 sizeof(pcie_config_flags));
+	if (ret != 0) {
+		ath10k_err("Failed to write pcie config_flags: %d\n", ret);
+		return ret;
+	}
+
+	/* configure early allocation */
+	ealloc_targ_addr = host_interest_item_address(HI_ITEM(hi_early_alloc));
+
+	ret = ath10k_pci_diag_read_access(ar, ealloc_targ_addr, &ealloc_value);
+	if (ret != 0) {
+		ath10k_err("Faile to get early alloc val: %d\n", ret);
+		return ret;
+	}
+
+	/* first bank is switched to IRAM */
+	ealloc_value |= ((HI_EARLY_ALLOC_MAGIC << HI_EARLY_ALLOC_MAGIC_SHIFT) &
+			 HI_EARLY_ALLOC_MAGIC_MASK);
+	ealloc_value |= ((1 << HI_EARLY_ALLOC_IRAM_BANKS_SHIFT) &
+			 HI_EARLY_ALLOC_IRAM_BANKS_MASK);
+
+	ret = ath10k_pci_diag_write_access(ar, ealloc_targ_addr, ealloc_value);
+	if (ret != 0) {
+		ath10k_err("Failed to set early alloc val: %d\n", ret);
+		return ret;
+	}
+
+	/* Tell Target to proceed with initialization */
+	flag2_targ_addr = host_interest_item_address(HI_ITEM(hi_option_flag2));
+
+	ret = ath10k_pci_diag_read_access(ar, flag2_targ_addr, &flag2_value);
+	if (ret != 0) {
+		ath10k_err("Failed to get option val: %d\n", ret);
+		return ret;
+	}
+
+	flag2_value |= HI_OPTION_EARLY_CFG_DONE;
+
+	ret = ath10k_pci_diag_write_access(ar, flag2_targ_addr, flag2_value);
+	if (ret != 0) {
+		ath10k_err("Failed to set option val: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+
+
+static int ath10k_pci_ce_init(struct ath10k *ar)
+{
+	struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+	struct hif_ce_pipe_info *pipe_info;
+	const struct ce_attr *attr;
+	int pipe_num;
+
+	for (pipe_num = 0; pipe_num < ar_pci->ce_count; pipe_num++) {
+		pipe_info = &ar_pci->pipe_info[pipe_num];
+		pipe_info->pipe_num = pipe_num;
+		pipe_info->hif_ce_state = ar;
+		attr = &host_ce_config_wlan[pipe_num];
+
+		pipe_info->ce_hdl = ath10k_ce_init(ar, pipe_num, attr);
+		if (pipe_info->ce_hdl == NULL) {
+			ath10k_err("Unable to initialize CE for pipe: %d\n",
+				   pipe_num);
+
+			/* It is safe to call it here. It checks if ce_hdl is
+			 * valid for each pipe */
+			ath10k_pci_ce_deinit(ar);
+			return -1;
+		}
+
+		if (pipe_num == ar_pci->ce_count - 1) {
+			/*
+			 * Reserve the ultimate CE for
+			 * diagnostic Window support
+			 */
+			ar_pci->ce_diag =
+			ar_pci->pipe_info[ar_pci->ce_count - 1].ce_hdl;
+			continue;
+		}
+
+		pipe_info->buf_sz = (size_t) (attr->src_sz_max);
+	}
+
+	/*
+	 * Initially, establish CE completion handlers for use with BMI.
+	 * These are overwritten with generic handlers after we exit BMI phase.
+	 */
+	pipe_info = &ar_pci->pipe_info[BMI_CE_NUM_TO_TARG];
+	ath10k_ce_send_cb_register(pipe_info->ce_hdl,
+				   ath10k_pci_bmi_send_done, 0);
+
+	pipe_info = &ar_pci->pipe_info[BMI_CE_NUM_TO_HOST];
+	ath10k_ce_recv_cb_register(pipe_info->ce_hdl,
+				   ath10k_pci_bmi_recv_data);
+
+	return 0;
+}
+
+static void ath10k_pci_fw_interrupt_handler(struct ath10k *ar)
+{
+	struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+	u32 fw_indicator_address, fw_indicator;
+
+	ath10k_pci_wake(ar);
+
+	fw_indicator_address = ar_pci->fw_indicator_address;
+	fw_indicator = ath10k_pci_read32(ar, fw_indicator_address);
+
+	if (fw_indicator & FW_IND_EVENT_PENDING) {
+		/* ACK: clear Target-side pending event */
+		ath10k_pci_write32(ar, fw_indicator_address,
+				   fw_indicator & ~FW_IND_EVENT_PENDING);
+
+		if (ar_pci->started) {
+			ath10k_pci_hif_dump_area(ar);
+		} else {
+			/*
+			 * Probable Target failure before we're prepared
+			 * to handle it.  Generally unexpected.
+			 */
+			ath10k_warn("early firmware event indicated\n");
+		}
+	}
+
+	ath10k_pci_sleep(ar);
+}
+
+static const struct ath10k_hif_ops ath10k_pci_hif_ops = {
+	.send_head		= ath10k_pci_hif_send_head,
+	.exchange_bmi_msg	= ath10k_pci_hif_exchange_bmi_msg,
+	.start			= ath10k_pci_hif_start,
+	.stop			= ath10k_pci_hif_stop,
+	.map_service_to_pipe	= ath10k_pci_hif_map_service_to_pipe,
+	.get_default_pipe	= ath10k_pci_hif_get_default_pipe,
+	.send_complete_check	= ath10k_pci_hif_send_complete_check,
+	.init			= ath10k_pci_hif_post_init,
+	.get_free_queue_number	= ath10k_pci_hif_get_free_queue_number,
+};
+
+static void ath10k_pci_ce_tasklet(unsigned long ptr)
+{
+	struct hif_ce_pipe_info *pipe = (struct hif_ce_pipe_info *)ptr;
+	struct ath10k_pci *ar_pci = pipe->ar_pci;
+
+	ath10k_ce_per_engine_service(ar_pci->ar, pipe->pipe_num);
+}
+
+static void ath10k_msi_err_tasklet(unsigned long data)
+{
+	struct ath10k *ar = (struct ath10k *)data;
+
+	ath10k_pci_fw_interrupt_handler(ar);
+}
+
+/*
+ * Handler for a per-engine interrupt on a PARTICULAR CE.
+ * This is used in cases where each CE has a private MSI interrupt.
+ */
+static irqreturn_t ath10k_pci_per_engine_handler(int irq, void *arg)
+{
+	struct ath10k *ar = arg;
+	struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+	int ce_id = irq - ar_pci->pdev->irq - MSI_ASSIGN_CE_INITIAL;
+
+	if (ce_id < 0 || ce_id > ARRAY_SIZE(ar_pci->pipe_info)) {
+		ath10k_warn("unexpected/invalid irq %d ce_id %d\n", irq, ce_id);
+		return IRQ_HANDLED;
+	}
+
+	/*
+	 * NOTE: We are able to derive ce_id from irq because we
+	 * use a one-to-one mapping for CE's 0..5.
+	 * CE's 6 & 7 do not use interrupts at all.
+	 *
+	 * This mapping must be kept in sync with the mapping
+	 * used by firmware.
+	 */
+	tasklet_schedule(&ar_pci->pipe_info[ce_id].intr);
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t ath10k_pci_msi_fw_handler(int irq, void *arg)
+{
+	struct ath10k *ar = arg;
+	struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+
+	tasklet_schedule(&ar_pci->msi_fw_err);
+	return IRQ_HANDLED;
+}
+
+/*
+ * Top-level interrupt handler for all PCI interrupts from a Target.
+ * When a block of MSI interrupts is allocated, this top-level handler
+ * is not used; instead, we directly call the correct sub-handler.
+ */
+static irqreturn_t ath10k_pci_interrupt_handler(int irq, void *arg)
+{
+	struct ath10k *ar = arg;
+	struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+
+	if (ar_pci->num_msi_intrs == 0) {
+		/*
+		 * IMPORTANT: INTR_CLR regiser has to be set after
+		 * INTR_ENABLE is set to 0, otherwise interrupt can not be
+		 * really cleared.
+		 */
+		iowrite32(0, ar_pci->mem +
+			  (SOC_CORE_BASE_ADDRESS |
+			   PCIE_INTR_ENABLE_ADDRESS));
+		iowrite32(PCIE_INTR_FIRMWARE_MASK |
+			  PCIE_INTR_CE_MASK_ALL,
+			  ar_pci->mem + (SOC_CORE_BASE_ADDRESS |
+					 PCIE_INTR_CLR_ADDRESS));
+		/*
+		 * IMPORTANT: this extra read transaction is required to
+		 * flush the posted write buffer.
+		 */
+		(void) ioread32(ar_pci->mem +
+				(SOC_CORE_BASE_ADDRESS |
+				 PCIE_INTR_ENABLE_ADDRESS));
+	}
+
+	tasklet_schedule(&ar_pci->intr_tq);
+
+	return IRQ_HANDLED;
+}
+
+static void ath10k_pci_tasklet(unsigned long data)
+{
+	struct ath10k *ar = (struct ath10k *)data;
+	struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+
+	ath10k_pci_fw_interrupt_handler(ar); /* FIXME: Handle FW error */
+	ath10k_ce_per_engine_service_any(ar);
+
+	if (ar_pci->num_msi_intrs == 0) {
+		/* Enable Legacy PCI line interrupts */
+		iowrite32(PCIE_INTR_FIRMWARE_MASK |
+			  PCIE_INTR_CE_MASK_ALL,
+			  ar_pci->mem + (SOC_CORE_BASE_ADDRESS |
+					 PCIE_INTR_ENABLE_ADDRESS));
+		/*
+		 * IMPORTANT: this extra read transaction is required to
+		 * flush the posted write buffer
+		 */
+		(void) ioread32(ar_pci->mem +
+				(SOC_CORE_BASE_ADDRESS |
+				 PCIE_INTR_ENABLE_ADDRESS));
+	}
+}
+
+static int ath10k_pci_start_intr_msix(struct ath10k *ar, int num)
+{
+	struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+	int ret;
+	int i;
+
+	ret = pci_enable_msi_block(ar_pci->pdev, num);
+	if (ret)
+		return ret;
+
+	ret = request_irq(ar_pci->pdev->irq + MSI_ASSIGN_FW,
+			  ath10k_pci_msi_fw_handler,
+			  IRQF_SHARED, "ath10k_pci", ar);
+	if (ret)
+		return ret;
+
+	for (i = MSI_ASSIGN_CE_INITIAL; i <= MSI_ASSIGN_CE_MAX; i++) {
+		ret = request_irq(ar_pci->pdev->irq + i,
+				  ath10k_pci_per_engine_handler,
+				  IRQF_SHARED, "ath10k_pci", ar);
+		if (ret) {
+			ath10k_warn("request_irq(%d) failed %d\n",
+				    ar_pci->pdev->irq + i, ret);
+
+			for (; i >= MSI_ASSIGN_CE_INITIAL; i--)
+				free_irq(ar_pci->pdev->irq, ar);
+
+			pci_disable_msi(ar_pci->pdev);
+			return ret;
+		}
+	}
+
+	ath10k_info("MSI-X interrupt handling (%d intrs)\n", num);
+	return 0;
+}
+
+static int ath10k_pci_start_intr_msi(struct ath10k *ar)
+{
+	struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+	int ret;
+
+	ret = pci_enable_msi(ar_pci->pdev);
+	if (ret < 0)
+		return ret;
+
+	ret = request_irq(ar_pci->pdev->irq,
+			  ath10k_pci_interrupt_handler,
+			  IRQF_SHARED, "ath10k_pci", ar);
+	if (ret < 0) {
+		pci_disable_msi(ar_pci->pdev);
+		return ret;
+	}
+
+	ath10k_info("MSI interrupt handling\n");
+	return 0;
+}
+
+static int ath10k_pci_start_intr_legacy(struct ath10k *ar)
+{
+	struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+	int ret;
+
+	ret = request_irq(ar_pci->pdev->irq,
+			  ath10k_pci_interrupt_handler,
+			  IRQF_SHARED, "ath10k_pci", ar);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * Make sure to wake the Target before enabling Legacy
+	 * Interrupt.
+	 */
+	iowrite32(PCIE_SOC_WAKE_V_MASK,
+		  ar_pci->mem + PCIE_LOCAL_BASE_ADDRESS +
+		  PCIE_SOC_WAKE_ADDRESS);
+
+	ath10k_pci_wait(ar);
+
+	/*
+	 * A potential race occurs here: The CORE_BASE write
+	 * depends on target correctly decoding AXI address but
+	 * host won't know when target writes BAR to CORE_CTRL.
+	 * This write might get lost if target has NOT written BAR.
+	 * For now, fix the race by repeating the write in below
+	 * synchronization checking.
+	 */
+	iowrite32(PCIE_INTR_FIRMWARE_MASK |
+		  PCIE_INTR_CE_MASK_ALL,
+		  ar_pci->mem + (SOC_CORE_BASE_ADDRESS |
+				 PCIE_INTR_ENABLE_ADDRESS));
+	iowrite32(PCIE_SOC_WAKE_RESET,
+		  ar_pci->mem + PCIE_LOCAL_BASE_ADDRESS +
+		  PCIE_SOC_WAKE_ADDRESS);
+
+	ath10k_info("legacy interrupt handling\n");
+	return 0;
+}
+
+static int ath10k_pci_start_intr(struct ath10k *ar)
+{
+	struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+	int num = MSI_NUM_REQUEST;
+	int ret;
+	int i;
+
+	tasklet_init(&ar_pci->intr_tq, ath10k_pci_tasklet, (unsigned long) ar);
+	tasklet_init(&ar_pci->msi_fw_err, ath10k_msi_err_tasklet,
+		     (unsigned long) ar);
+
+	for (i = 0; i < CE_COUNT; i++) {
+		ar_pci->pipe_info[i].ar_pci = ar_pci;
+		tasklet_init(&ar_pci->pipe_info[i].intr,
+			     ath10k_pci_ce_tasklet,
+			     (unsigned long)&ar_pci->pipe_info[i]);
+	}
+
+	if (!test_bit(ATH10K_PCI_FEATURE_MSI_X, ar_pci->features))
+		num = 1;
+
+	if (num > 1) {
+		ret = ath10k_pci_start_intr_msix(ar, num);
+		if (ret == 0)
+			goto exit;
+
+		ath10k_warn("MSI-X didn't succeed (%d), trying MSI\n", ret);
+		num = 1;
+	}
+
+	if (num == 1) {
+		ret = ath10k_pci_start_intr_msi(ar);
+		if (ret == 0)
+			goto exit;
+
+		ath10k_warn("MSI didn't succeed (%d), trying legacy INTR\n",
+			    ret);
+		num = 0;
+	}
+
+	ret = ath10k_pci_start_intr_legacy(ar);
+
+exit:
+	ar_pci->num_msi_intrs = num;
+	ar_pci->ce_count = CE_COUNT;
+	return ret;
+}
+
+static void ath10k_pci_stop_intr(struct ath10k *ar)
+{
+	struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+	int i;
+
+	/* There's at least one interrupt irregardless whether its legacy INTR
+	 * or MSI or MSI-X */
+	for (i = 0; i < max(1, ar_pci->num_msi_intrs); i++)
+		free_irq(ar_pci->pdev->irq + i, ar);
+
+	if (ar_pci->num_msi_intrs > 0)
+		pci_disable_msi(ar_pci->pdev);
+}
+
+static int ath10k_pci_reset_target(struct ath10k *ar)
+{
+	struct ath10k_pci *ar_pci = ath10k_pci_priv(ar);
+	int wait_limit = 300; /* 3 sec */
+
+	/* Wait for Target to finish initialization before we proceed. */
+	iowrite32(PCIE_SOC_WAKE_V_MASK,
+		  ar_pci->mem + PCIE_LOCAL_BASE_ADDRESS +
+		  PCIE_SOC_WAKE_ADDRESS);
+
+	ath10k_pci_wait(ar);
+
+	while (wait_limit-- &&
+	       !(ioread32(ar_pci->mem + FW_INDICATOR_ADDRESS) &
+		 FW_IND_INITIALIZED)) {
+		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));
+		mdelay(10);
+	}
+
+	if (wait_limit < 0) {
+		ath10k_err("Target stalled\n");
+		iowrite32(PCIE_SOC_WAKE_RESET,
+			  ar_pci->mem + PCIE_LOCAL_BASE_ADDRESS +
+			  PCIE_SOC_WAKE_ADDRESS);
+		return -EIO;
+	}
+
+	iowrite32(PCIE_SOC_WAKE_RESET,
+		  ar_pci->mem + PCIE_LOCAL_BASE_ADDRESS +
+		  PCIE_SOC_WAKE_ADDRESS);
+
+	return 0;
+}
+
+static void ath10k_pci_device_reset(struct ath10k_pci *ar_pci)
+{
+	struct ath10k *ar = ar_pci->ar;
+	void __iomem *mem = ar_pci->mem;
+	int i;
+	u32 val;
+
+	if (!SOC_GLOBAL_RESET_ADDRESS)
+		return;
+
+	if (!mem)
+		return;
+
+	ath10k_pci_reg_write32(mem, PCIE_SOC_WAKE_ADDRESS,
+			       PCIE_SOC_WAKE_V_MASK);
+	for (i = 0; i < ATH_PCI_RESET_WAIT_MAX; i++) {
+		if (ath10k_pci_target_is_awake(ar))
+			break;
+		msleep(1);
+	}
+
+	/* Put Target, including PCIe, into RESET. */
+	val = ath10k_pci_reg_read32(mem, SOC_GLOBAL_RESET_ADDRESS);
+	val |= 1;
+	ath10k_pci_reg_write32(mem, SOC_GLOBAL_RESET_ADDRESS, val);
+
+	for (i = 0; i < ATH_PCI_RESET_WAIT_MAX; i++) {
+		if (ath10k_pci_reg_read32(mem, RTC_STATE_ADDRESS) &
+					  RTC_STATE_COLD_RESET_MASK)
+			break;
+		msleep(1);
+	}
+
+	/* Pull Target, including PCIe, out of RESET. */
+	val &= ~1;
+	ath10k_pci_reg_write32(mem, SOC_GLOBAL_RESET_ADDRESS, val);
+
+	for (i = 0; i < ATH_PCI_RESET_WAIT_MAX; i++) {
+		if (!(ath10k_pci_reg_read32(mem, RTC_STATE_ADDRESS) &
+					    RTC_STATE_COLD_RESET_MASK))
+			break;
+		msleep(1);
+	}
+
+	ath10k_pci_reg_write32(mem, PCIE_SOC_WAKE_ADDRESS, PCIE_SOC_WAKE_RESET);
+}
+
+static void ath10k_pci_dump_features(struct ath10k_pci *ar_pci)
+{
+	int i;
+
+	for (i = 0; i < ATH10K_PCI_FEATURE_COUNT; i++) {
+		if (!test_bit(i, ar_pci->features))
+			continue;
+
+		switch (i) {
+		case ATH10K_PCI_FEATURE_MSI_X:
+			ath10k_dbg(ATH10K_DBG_PCI, "device supports MSI-X\n");
+			break;
+		case ATH10K_PCI_FEATURE_HW_1_0_WARKAROUND:
+			ath10k_dbg(ATH10K_DBG_PCI, "QCA988X_1.0 workaround enabled\n");
+			break;
+		}
+	}
+}
+
+static int ath10k_pci_probe(struct pci_dev *pdev,
+			    const struct pci_device_id *pci_dev)
+{
+	void __iomem *mem;
+	int ret = 0;
+	struct ath10k *ar;
+	struct ath10k_pci *ar_pci;
+	u32 lcr_val;
+
+	ath10k_dbg(ATH10K_DBG_PCI, "%s\n", __func__);
+
+	ar_pci = kzalloc(sizeof(*ar_pci), GFP_KERNEL);
+	if (ar_pci == NULL)
+		return -ENOMEM;
+
+	ar_pci->pdev = pdev;
+	ar_pci->dev = &pdev->dev;
+
+	switch (pci_dev->device) {
+	case QCA988X_1_0_DEVICE_ID:
+		set_bit(ATH10K_PCI_FEATURE_HW_1_0_WARKAROUND, ar_pci->features);
+		break;
+	case QCA988X_2_0_DEVICE_ID:
+		set_bit(ATH10K_PCI_FEATURE_MSI_X, ar_pci->features);
+		break;
+	default:
+		ret = -ENODEV;
+		ath10k_err("Unkown device ID: %d\n", pci_dev->device);
+		goto err_ar_pci;
+	}
+
+	ath10k_pci_dump_features(ar_pci);
+
+	ar = ath10k_core_create(ar_pci, ar_pci->dev, ATH10K_BUS_PCI,
+				&ath10k_pci_hif_ops);
+	if (!ar) {
+		ath10k_err("ath10k_core_create failed!\n");
+		ret = -EINVAL;
+		goto err_ar_pci;
+	}
+
+	/* Enable QCA988X_1.0 HW workarounds */
+	if (test_bit(ATH10K_PCI_FEATURE_HW_1_0_WARKAROUND, ar_pci->features))
+		spin_lock_init(&ar_pci->hw_v1_workaround_lock);
+
+	ar_pci->ar = ar;
+	ar_pci->fw_indicator_address = FW_INDICATOR_ADDRESS;
+	atomic_set(&ar_pci->keep_awake_count, 0);
+
+	pci_set_drvdata(pdev, ar);
+
+	/*
+	 * Without any knowledge of the Host, the Target may have been reset or
+	 * power cycled and its Config Space may no longer reflect the PCI
+	 * address space that was assigned earlier by the PCI infrastructure.
+	 * Refresh it now.
+	 */
+	ret = pci_assign_resource(pdev, BAR_NUM);
+	if (ret) {
+		ath10k_err("cannot assign PCI space: %d\n", ret);
+		goto err_ar;
+	}
+
+	ret = pci_enable_device(pdev);
+	if (ret) {
+		ath10k_err("cannot enable PCI device: %d\n", ret);
+		goto err_ar;
+	}
+
+	/* Request MMIO resources */
+	ret = pci_request_region(pdev, BAR_NUM, "ath");
+	if (ret) {
+		ath10k_err("PCI MMIO reservation error: %d\n", ret);
+		goto err_device;
+	}
+
+	/*
+	 * Target structures have a limit of 32 bit DMA pointers.
+	 * DMA pointers can be wider than 32 bits by default on some systems.
+	 */
+	ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));
+	if (ret) {
+		ath10k_err("32-bit DMA not available: %d\n", ret);
+		goto err_region;
+	}
+
+	ret = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32));
+	if (ret) {
+		ath10k_err("cannot enable 32-bit consistent DMA\n");
+		goto err_region;
+	}
+
+	/* Set bus master bit in PCI_COMMAND to enable DMA */
+	pci_set_master(pdev);
+
+	/*
+	 * Temporary FIX: disable ASPM
+	 * Will be removed after the OTP is programmed
+	 */
+	pci_read_config_dword(pdev, 0x80, &lcr_val);
+	pci_write_config_dword(pdev, 0x80, (lcr_val & 0xffffff00));
+
+	/* Arrange for access to Target SoC registers. */
+	mem = pci_iomap(pdev, BAR_NUM, 0);
+	if (!mem) {
+		ath10k_err("PCI iomap error\n");
+		ret = -EIO;
+		goto err_master;
+	}
+
+	ar_pci->mem = mem;
+
+	spin_lock_init(&ar_pci->ce_lock);
+
+	ar_pci->cacheline_sz = dma_get_cache_alignment();
+
+	ret = ath10k_pci_start_intr(ar);
+	if (ret) {
+		ath10k_err("could not start interrupt handling (%d)\n", ret);
+		goto err_iomap;
+	}
+
+	/*
+	 * Bring the target up cleanly.
+	 *
+	 * The target may be in an undefined state with an AUX-powered Target
+	 * and a Host in WoW mode. If the Host crashes, loses power, or is
+	 * restarted (without unloading the driver) then the Target is left
+	 * (aux) powered and running. On a subsequent driver load, the Target
+	 * is in an unexpected state. We try to catch that here in order to
+	 * reset the Target and retry the probe.
+	 */
+	ath10k_pci_device_reset(ar_pci);
+
+	ret = ath10k_pci_reset_target(ar);
+	if (ret)
+		goto err_intr;
+
+	if (ath10k_target_ps) {
+		ath10k_dbg(ATH10K_DBG_PCI, "on-chip power save enabled\n");
+	} else {
+		/* Force AWAKE forever */
+		ath10k_dbg(ATH10K_DBG_PCI, "on-chip power save disabled\n");
+		ath10k_do_pci_wake(ar);
+	}
+
+	ret = ath10k_pci_ce_init(ar);
+	if (ret)
+		goto err_intr;
+
+	ret = ath10k_pci_init_config(ar);
+	if (ret)
+		goto err_ce;
+
+	ret = ath10k_pci_wake_target_cpu(ar);
+	if (ret) {
+		ath10k_err("could not wake up target CPU (%d)\n", ret);
+		goto err_ce;
+	}
+
+	ret = ath10k_core_register(ar);
+	if (ret) {
+		ath10k_err("could not register driver core (%d)\n", ret);
+		goto err_ce;
+	}
+
+	return 0;
+
+err_ce:
+	ath10k_pci_ce_deinit(ar);
+err_intr:
+	ath10k_pci_stop_intr(ar);
+err_iomap:
+	pci_iounmap(pdev, mem);
+err_master:
+	pci_clear_master(pdev);
+err_region:
+	pci_release_region(pdev, BAR_NUM);
+err_device:
+	pci_disable_device(pdev);
+err_ar:
+	pci_set_drvdata(pdev, NULL);
+	ath10k_core_destroy(ar);
+err_ar_pci:
+	/* call HIF PCI free here */
+	kfree(ar_pci);
+
+	return ret;
+}
+
+static void ath10k_pci_remove(struct pci_dev *pdev)
+{
+	struct ath10k *ar = pci_get_drvdata(pdev);
+	struct ath10k_pci *ar_pci;
+
+	ath10k_dbg(ATH10K_DBG_PCI, "%s\n", __func__);
+
+	if (!ar)
+		return;
+
+	ar_pci = ath10k_pci_priv(ar);
+
+	if (!ar_pci)
+		return;
+
+	tasklet_kill(&ar_pci->msi_fw_err);
+
+	ath10k_core_unregister(ar);
+	ath10k_pci_stop_intr(ar);
+
+	pci_set_drvdata(pdev, NULL);
+	pci_iounmap(pdev, ar_pci->mem);
+	pci_release_region(pdev, BAR_NUM);
+	pci_clear_master(pdev);
+	pci_disable_device(pdev);
+
+	ath10k_core_destroy(ar);
+	kfree(ar_pci);
+}
+
+#if defined(CONFIG_PM_SLEEP)
+
+#define ATH10K_PCI_PM_CONTROL 0x44
+
+static int ath10k_pci_suspend(struct device *device)
+{
+	struct pci_dev *pdev = to_pci_dev(device);
+	struct ath10k *ar = pci_get_drvdata(pdev);
+	struct ath10k_pci *ar_pci;
+	u32 val;
+	int ret, retval;
+
+	ath10k_dbg(ATH10K_DBG_PCI, "%s\n", __func__);
+
+	if (!ar)
+		return -ENODEV;
+
+	ar_pci = ath10k_pci_priv(ar);
+	if (!ar_pci)
+		return -ENODEV;
+
+	if (ath10k_core_target_suspend(ar))
+		return -EBUSY;
+
+	ret = wait_event_interruptible_timeout(ar->event_queue,
+						ar->is_target_paused == true,
+						1 * HZ);
+	if (ret < 0) {
+		ath10k_warn("suspend interrupted (%d)\n", ret);
+		retval = ret;
+		goto resume;
+	} else if (ret == 0) {
+		ath10k_warn("suspend timed out - target pause event never came\n");
+		retval = EIO;
+		goto resume;
+	}
+
+	/*
+	 * reset is_target_paused and host can check that in next time,
+	 * or it will always be TRUE and host just skip the waiting
+	 * condition, it causes target assert due to host already
+	 * suspend
+	 */
+	ar->is_target_paused = false;
+
+	pci_read_config_dword(pdev, ATH10K_PCI_PM_CONTROL, &val);
+
+	if ((val & 0x000000ff) != 0x3) {
+		pci_save_state(pdev);
+		pci_disable_device(pdev);
+		pci_write_config_dword(pdev, ATH10K_PCI_PM_CONTROL,
+				       (val & 0xffffff00) | 0x03);
+	}
+
+	return 0;
+resume:
+	ret = ath10k_core_target_resume(ar);
+	if (ret)
+		ath10k_warn("could not resume (%d)\n", ret);
+
+	return retval;
+}
+
+static int ath10k_pci_resume(struct device *device)
+{
+	struct pci_dev *pdev = to_pci_dev(device);
+	struct ath10k *ar = pci_get_drvdata(pdev);
+	struct ath10k_pci *ar_pci;
+	int ret;
+	u32 val;
+
+	ath10k_dbg(ATH10K_DBG_PCI, "%s\n", __func__);
+
+	if (!ar)
+		return -ENODEV;
+	ar_pci = ath10k_pci_priv(ar);
+
+	if (!ar_pci)
+		return -ENODEV;
+
+	ret = pci_enable_device(pdev);
+	if (ret) {
+		ath10k_warn("cannot enable PCI device: %d\n", ret);
+		return ret;
+	}
+
+	pci_read_config_dword(pdev, ATH10K_PCI_PM_CONTROL, &val);
+
+	if ((val & 0x000000ff) != 0) {
+		pci_restore_state(pdev);
+		pci_write_config_dword(pdev, ATH10K_PCI_PM_CONTROL,
+				       val & 0xffffff00);
+		/*
+		 * Suspend/Resume resets the PCI configuration space,
+		 * so we have to re-disable the RETRY_TIMEOUT register (0x41)
+		 * to keep PCI Tx retries from interfering with C3 CPU state
+		 */
+		pci_read_config_dword(pdev, 0x40, &val);
+
+		if ((val & 0x0000ff00) != 0)
+			pci_write_config_dword(pdev, 0x40, val & 0xffff00ff);
+	}
+
+	ret = ath10k_core_target_resume(ar);
+	if (ret)
+		ath10k_warn("target resume failed: %d\n", ret);
+
+	return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(ath10k_dev_pm_ops,
+			 ath10k_pci_suspend,
+			 ath10k_pci_resume);
+
+#define ATH10K_PCI_PM_OPS (&ath10k_dev_pm_ops)
+
+#else
+
+#define ATH10K_PCI_PM_OPS NULL
+
+#endif /* CONFIG_PM_SLEEP */
+
+MODULE_DEVICE_TABLE(pci, ath10k_pci_id_table);
+
+static struct pci_driver ath10k_pci_driver = {
+	.name = "ath10k_pci",
+	.id_table = ath10k_pci_id_table,
+	.probe = ath10k_pci_probe,
+	.remove = ath10k_pci_remove,
+	.driver.pm = ATH10K_PCI_PM_OPS,
+};
+
+static int __init ath10k_pci_init(void)
+{
+	int ret;
+
+	ret = pci_register_driver(&ath10k_pci_driver);
+	if (ret)
+		ath10k_err("pci_register_driver failed [%d]\n", ret);
+
+	return ret;
+}
+module_init(ath10k_pci_init);
+
+static void __exit ath10k_pci_exit(void)
+{
+	pci_unregister_driver(&ath10k_pci_driver);
+}
+
+module_exit(ath10k_pci_exit);
+
+MODULE_AUTHOR("Qualcomm Atheros");
+MODULE_DESCRIPTION("Driver support for Atheros QCA988X PCIe devices");
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_FIRMWARE(QCA988X_HW_1_0_FW_DIR "/" QCA988X_HW_1_0_FW_FILE);
+MODULE_FIRMWARE(QCA988X_HW_1_0_FW_DIR "/" QCA988X_HW_1_0_OTP_FILE);
+MODULE_FIRMWARE(QCA988X_HW_1_0_FW_DIR "/" QCA988X_HW_1_0_BOARD_DATA_FILE);
+MODULE_FIRMWARE(QCA988X_HW_2_0_FW_DIR "/" QCA988X_HW_2_0_FW_FILE);
+MODULE_FIRMWARE(QCA988X_HW_2_0_FW_DIR "/" QCA988X_HW_2_0_OTP_FILE);
+MODULE_FIRMWARE(QCA988X_HW_2_0_FW_DIR "/" QCA988X_HW_2_0_BOARD_DATA_FILE);