diff mbox series

[5/5] qtnfmac: add support for Topaz chipsets

Message ID 20181008095551.18651-6-sergey.matyukevich.os@quantenna.com (mailing list archive)
State Changes Requested
Delegated to: Kalle Valo
Headers show
Series qtnfmac: add support for QSR1000/QSR2000 (aka Topaz) chipsets | expand

Commit Message

Sergey Matyukevich Oct. 8, 2018, 9:56 a.m. UTC
This patch adds support for QSR1000/QSR2000 family of chipsets
to qtnfmac_pcie platform driver.

QSR1000/QSR2000 (aka Topaz) is a family of 80MHz, 11ac Wave2,
4x4/2x4/2x2 chips, including single and dual band devices.
Depending on specific chip model and firmware in use, either
STA or both STA and AP modes are supported.

Patch adds Topaz support to qtnfmac_pcie driver. It is possible
to enable both Topaz and Pearl support in kernel configuration.
In that case proper platform bus will be selected on probing
based on chip ID.

Signed-off-by: Igor Mitsyanko <igor.mitsyanko.os@quantenna.com>
Signed-off-by: Sergey Matyukevich <sergey.matyukevich.os@quantenna.com>
Signed-off-by: Andrey Shevchenko <ashevchenko@quantenna.com>
---
 drivers/net/wireless/quantenna/qtnfmac/Kconfig     |   27 +-
 drivers/net/wireless/quantenna/qtnfmac/Makefile    |    1 +
 drivers/net/wireless/quantenna/qtnfmac/pcie/pcie.c |    5 +
 .../wireless/quantenna/qtnfmac/pcie/pcie_priv.h    |    1 +
 .../wireless/quantenna/qtnfmac/pcie/topaz_pcie.c   | 1219 ++++++++++++++++++++
 .../quantenna/qtnfmac/pcie/topaz_pcie_ipc.h        |   94 ++
 .../quantenna/qtnfmac/pcie/topaz_pcie_regs.h       |   45 +
 .../net/wireless/quantenna/qtnfmac/qtn_hw_ids.h    |    3 +
 drivers/net/wireless/quantenna/qtnfmac/util.c      |    2 +
 9 files changed, 1391 insertions(+), 6 deletions(-)
 create mode 100644 drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie.c
 create mode 100644 drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie_ipc.h
 create mode 100644 drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie_regs.h

Comments

Kalle Valo Oct. 8, 2018, 11:36 a.m. UTC | #1
Sergey Matyukevich <sergey.matyukevich.os@quantenna.com> writes:

> This patch adds support for QSR1000/QSR2000 family of chipsets
> to qtnfmac_pcie platform driver.
>
> QSR1000/QSR2000 (aka Topaz) is a family of 80MHz, 11ac Wave2,
> 4x4/2x4/2x2 chips, including single and dual band devices.
> Depending on specific chip model and firmware in use, either
> STA or both STA and AP modes are supported.
>
> Patch adds Topaz support to qtnfmac_pcie driver. It is possible
> to enable both Topaz and Pearl support in kernel configuration.
> In that case proper platform bus will be selected on probing
> based on chip ID.
>
> Signed-off-by: Igor Mitsyanko <igor.mitsyanko.os@quantenna.com>
> Signed-off-by: Sergey Matyukevich <sergey.matyukevich.os@quantenna.com>
> Signed-off-by: Andrey Shevchenko <ashevchenko@quantenna.com>

[...]

> --- a/drivers/net/wireless/quantenna/qtnfmac/qtn_hw_ids.h
> +++ b/drivers/net/wireless/quantenna/qtnfmac/qtn_hw_ids.h
> @@ -24,6 +24,7 @@
>  /* PCIE Device IDs */
>  
>  #define	PCIE_DEVICE_ID_QTN_PEARL	(0x0008)
> +#define	PCIE_DEVICE_ID_QTN_TOPAZ	(0x0008)

Same ids for both, is this really correct?
Sergey Matyukevich Oct. 8, 2018, 1:36 p.m. UTC | #2
> > This patch adds support for QSR1000/QSR2000 family of chipsets
> > to qtnfmac_pcie platform driver.
> >
> > QSR1000/QSR2000 (aka Topaz) is a family of 80MHz, 11ac Wave2,
> > 4x4/2x4/2x2 chips, including single and dual band devices.
> > Depending on specific chip model and firmware in use, either
> > STA or both STA and AP modes are supported.
> >
> > Patch adds Topaz support to qtnfmac_pcie driver. It is possible
> > to enable both Topaz and Pearl support in kernel configuration.
> > In that case proper platform bus will be selected on probing
> > based on chip ID.
> >
> > Signed-off-by: Igor Mitsyanko <igor.mitsyanko.os@quantenna.com>
> > Signed-off-by: Sergey Matyukevich <sergey.matyukevich.os@quantenna.com>
> > Signed-off-by: Andrey Shevchenko <ashevchenko@quantenna.com>
> 
> [...]
> 
> > --- a/drivers/net/wireless/quantenna/qtnfmac/qtn_hw_ids.h
> > +++ b/drivers/net/wireless/quantenna/qtnfmac/qtn_hw_ids.h
> > @@ -24,6 +24,7 @@
> >  /* PCIE Device IDs */
> >
> >  #define      PCIE_DEVICE_ID_QTN_PEARL        (0x0008)
> > +#define      PCIE_DEVICE_ID_QTN_TOPAZ        (0x0008)
> 
> Same ids for both, is this really correct?

Yes, that is correct. This is one of the reasons why we switched
to chip ID for card identification. In fact, it can be configured
in firmware, but default PCI ID values are the same for both chipsets.

Regards,
Sergey
Kalle Valo Oct. 13, 2018, 12:14 p.m. UTC | #3
Sergey Matyukevich <sergey.matyukevich.os@quantenna.com> writes:

> This patch adds support for QSR1000/QSR2000 family of chipsets
> to qtnfmac_pcie platform driver.
>
> QSR1000/QSR2000 (aka Topaz) is a family of 80MHz, 11ac Wave2,
> 4x4/2x4/2x2 chips, including single and dual band devices.
> Depending on specific chip model and firmware in use, either
> STA or both STA and AP modes are supported.
>
> Patch adds Topaz support to qtnfmac_pcie driver. It is possible
> to enable both Topaz and Pearl support in kernel configuration.
> In that case proper platform bus will be selected on probing
> based on chip ID.
>
> Signed-off-by: Igor Mitsyanko <igor.mitsyanko.os@quantenna.com>
> Signed-off-by: Sergey Matyukevich <sergey.matyukevich.os@quantenna.com>
> Signed-off-by: Andrey Shevchenko <ashevchenko@quantenna.com>

[...]

> +config QTNFMAC_TOPAZ_PCIE
> +	tristate "Quantenna QSR1000/QSR2000 PCIe support"
> +	default n
> +	depends on PCI && CFG80211
> +	select QTNFMAC
> +	select FW_LOADER
> +	select CRC32
> +	help
> +	  This option adds support for wireless adapters based on Quantenna
> +	  802.11ac QSR1000/QSR2000 (aka Topaz) FullMAC chipset
> +	  running over PCIe.
> +
> +	  If you choose to build it as a module, two modules will be built:
> +	  qtnfmac.ko and qtnfmac_pcie.ko.

I'm not really fond of adding a Kconfig option for every supported
hardware version unless there are very good reasons (memory savings
etc). So is this really needed?

A much better approach would be to have a generic QTNFMAC_PCIE option
which can be used to include or exclude all PCI code.
Sergey Matyukevich Oct. 15, 2018, 7:46 p.m. UTC | #4
> > +config QTNFMAC_TOPAZ_PCIE
> > +     tristate "Quantenna QSR1000/QSR2000 PCIe support"
> > +     default n
> > +     depends on PCI && CFG80211
> > +     select QTNFMAC
> > +     select FW_LOADER
> > +     select CRC32
> > +     help
> > +       This option adds support for wireless adapters based on Quantenna
> > +       802.11ac QSR1000/QSR2000 (aka Topaz) FullMAC chipset
> > +       running over PCIe.
> > +
> > +       If you choose to build it as a module, two modules will be built:
> > +       qtnfmac.ko and qtnfmac_pcie.ko.

> I'm not really fond of adding a Kconfig option for every supported
> hardware version unless there are very good reasons (memory savings
> etc). So is this really needed?

Yes, the idea was to save some memory building pcie backend for
the requested chip family only.
 
> A much better approach would be to have a generic QTNFMAC_PCIE option
> which can be used to include or exclude all PCI code.

Ok, sounds reasonable. Three Kconfig options for the single qtnfmac_pcie
module is way too much. We will drop chipset specific Kconfig knobs in
v2, keeping single Kconfig option for qtnfmac_pcie backend as a whole.

Thanks,
Sergey
Kalle Valo Oct. 29, 2018, 2:56 p.m. UTC | #5
Sergey Matyukevich <sergey.matyukevich.os@quantenna.com> writes:

>> > +config QTNFMAC_TOPAZ_PCIE
>> > +     tristate "Quantenna QSR1000/QSR2000 PCIe support"
>> > +     default n
>> > +     depends on PCI && CFG80211
>> > +     select QTNFMAC
>> > +     select FW_LOADER
>> > +     select CRC32
>> > +     help
>> > +       This option adds support for wireless adapters based on Quantenna
>> > +       802.11ac QSR1000/QSR2000 (aka Topaz) FullMAC chipset
>> > +       running over PCIe.
>> > +
>> > +       If you choose to build it as a module, two modules will be built:
>> > +       qtnfmac.ko and qtnfmac_pcie.ko.
>
>> I'm not really fond of adding a Kconfig option for every supported
>> hardware version unless there are very good reasons (memory savings
>> etc). So is this really needed?
>
> Yes, the idea was to save some memory building pcie backend for
> the requested chip family only.
>  
>> A much better approach would be to have a generic QTNFMAC_PCIE option
>> which can be used to include or exclude all PCI code.
>
> Ok, sounds reasonable. Three Kconfig options for the single qtnfmac_pcie
> module is way too much. We will drop chipset specific Kconfig knobs in
> v2, keeping single Kconfig option for qtnfmac_pcie backend as a whole.

Great, thanks.
diff mbox series

Patch

diff --git a/drivers/net/wireless/quantenna/qtnfmac/Kconfig b/drivers/net/wireless/quantenna/qtnfmac/Kconfig
index f43b236b7c0b..df2fd0a1ab5c 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/Kconfig
+++ b/drivers/net/wireless/quantenna/qtnfmac/Kconfig
@@ -1,14 +1,14 @@ 
 config QTNFMAC
 	tristate
-	depends on QTNFMAC_PEARL_PCIE
-	default m if QTNFMAC_PEARL_PCIE=m
-	default y if QTNFMAC_PEARL_PCIE=y
+	depends on QTNFMAC_PEARL_PCIE || QTNFMAC_TOPAZ_PCIE
+	default m if QTNFMAC_PEARL_PCIE=m || QTNFMAC_TOPAZ_PCIE=m
+	default y if QTNFMAC_PEARL_PCIE=y || QTNFMAC_TOPAZ_PCIE=y
 
 config QTNFMAC_PCIE
 	tristate
-	depends on QTNFMAC_PEARL_PCIE
-	default m if QTNFMAC_PEARL_PCIE=m
-	default y if QTNFMAC_PEARL_PCIE=y
+	depends on QTNFMAC_PEARL_PCIE || QTNFMAC_TOPAZ_PCIE
+	default m if QTNFMAC_PEARL_PCIE=m || QTNFMAC_TOPAZ_PCIE=m
+	default y if QTNFMAC_PEARL_PCIE=y || QTNFMAC_TOPAZ_PCIE=y
 
 config QTNFMAC_PEARL_PCIE
 	tristate "Quantenna QSR10g PCIe support"
@@ -23,3 +23,18 @@  config QTNFMAC_PEARL_PCIE
 
 	  If you choose to build it as a module, two modules will be built:
 	  qtnfmac.ko and qtnfmac_pcie.ko.
+
+config QTNFMAC_TOPAZ_PCIE
+	tristate "Quantenna QSR1000/QSR2000 PCIe support"
+	default n
+	depends on PCI && CFG80211
+	select QTNFMAC
+	select FW_LOADER
+	select CRC32
+	help
+	  This option adds support for wireless adapters based on Quantenna
+	  802.11ac QSR1000/QSR2000 (aka Topaz) FullMAC chipset
+	  running over PCIe.
+
+	  If you choose to build it as a module, two modules will be built:
+	  qtnfmac.ko and qtnfmac_pcie.ko.
diff --git a/drivers/net/wireless/quantenna/qtnfmac/Makefile b/drivers/net/wireless/quantenna/qtnfmac/Makefile
index e295aa55619f..4de95330e793 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/Makefile
+++ b/drivers/net/wireless/quantenna/qtnfmac/Makefile
@@ -27,3 +27,4 @@  qtnfmac_pcie-objs += \
 
 qtnfmac_pcie-$(CONFIG_DEBUG_FS) += debug.o
 qtnfmac_pcie-$(CONFIG_QTNFMAC_PEARL_PCIE) += pcie/pearl_pcie.o
+qtnfmac_pcie-$(CONFIG_QTNFMAC_TOPAZ_PCIE) += pcie/topaz_pcie.o
diff --git a/drivers/net/wireless/quantenna/qtnfmac/pcie/pcie.c b/drivers/net/wireless/quantenna/qtnfmac/pcie/pcie.c
index b589c01d9bbe..a139db398fd4 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/pcie/pcie.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/pcie/pcie.c
@@ -332,6 +332,11 @@  static int qtnf_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 		bus = qtnf_pcie_pearl_alloc(pdev);
 		break;
 #endif
+#if IS_ENABLED(CONFIG_QTNFMAC_TOPAZ_PCIE)
+	case QTN_CHIP_ID_TOPAZ:
+		bus = qtnf_pcie_topaz_alloc(pdev);
+		break;
+#endif
 	default:
 		pr_err("unsupported chip ID 0x%x\n", chipid);
 		return -ENOTSUPP;
diff --git a/drivers/net/wireless/quantenna/qtnfmac/pcie/pcie_priv.h b/drivers/net/wireless/quantenna/qtnfmac/pcie/pcie_priv.h
index 7c742c56efaf..bbc074e1f34d 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/pcie/pcie_priv.h
+++ b/drivers/net/wireless/quantenna/qtnfmac/pcie/pcie_priv.h
@@ -76,6 +76,7 @@  void qtnf_pcie_init_shm_ipc(struct qtnf_pcie_bus_priv *priv,
 			    struct qtnf_shm_ipc_region __iomem *ipc_rx_reg,
 			    const struct qtnf_shm_ipc_int *ipc_int);
 struct qtnf_bus *qtnf_pcie_pearl_alloc(struct pci_dev *pdev);
+struct qtnf_bus *qtnf_pcie_topaz_alloc(struct pci_dev *pdev);
 
 static inline void qtnf_non_posted_write(u32 val, void __iomem *basereg)
 {
diff --git a/drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie.c b/drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie.c
new file mode 100644
index 000000000000..598edb814421
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie.c
@@ -0,0 +1,1219 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (c) 2018 Quantenna Communications */
+
+#include <linux/kernel.h>
+#include <linux/firmware.h>
+#include <linux/pci.h>
+#include <linux/vmalloc.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/crc32.h>
+#include <linux/completion.h>
+#include <linux/spinlock.h>
+#include <linux/circ_buf.h>
+
+#include "pcie_priv.h"
+#include "topaz_pcie_regs.h"
+#include "topaz_pcie_ipc.h"
+#include "qtn_hw_ids.h"
+#include "core.h"
+#include "bus.h"
+#include "shm_ipc.h"
+#include "debug.h"
+
+#define TOPAZ_TX_BD_SIZE_DEFAULT	128
+
+struct qtnf_topaz_tx_bd {
+	__le32 addr;
+	__le32 info;
+} __packed;
+
+struct qtnf_topaz_rx_bd {
+	__le32 addr;
+	__le32 info;
+} __packed;
+
+struct qtnf_extra_bd_params {
+	__le32 param1;
+	__le32 param2;
+	__le32 param3;
+	__le32 param4;
+} __packed;
+
+#define QTNF_BD_PARAM_OFFSET(n)	offsetof(struct qtnf_extra_bd_params, param##n)
+
+struct vmac_pkt_info {
+	__le32 addr;
+	__le32 info;
+};
+
+struct qtnf_topaz_bda {
+	__le16	bda_len;
+	__le16	bda_version;
+	__le32	bda_bootstate;
+	__le32	bda_dma_mask;
+	__le32	bda_dma_offset;
+	__le32	bda_flags;
+	__le32	bda_img;
+	__le32	bda_img_size;
+	__le32	bda_ep2h_irqstatus;
+	__le32	bda_h2ep_irqstatus;
+	__le32	bda_msi_addr;
+	u8	reserved1[56];
+	__le32	bda_flashsz;
+	u8	bda_boardname[PCIE_BDA_NAMELEN];
+	__le32	bda_pci_pre_status;
+	__le32	bda_pci_endian;
+	__le32	bda_pci_post_status;
+	__le32	bda_h2ep_txd_budget;
+	__le32	bda_ep2h_txd_budget;
+	__le32	bda_rc_rx_bd_base;
+	__le32	bda_rc_rx_bd_num;
+	__le32	bda_rc_tx_bd_base;
+	__le32	bda_rc_tx_bd_num;
+	u8	bda_ep_link_state;
+	u8	bda_rc_link_state;
+	u8	bda_rc_msi_enabled;
+	u8	reserved2;
+	__le32	bda_ep_next_pkt;
+	struct vmac_pkt_info request[QTN_PCIE_RC_TX_QUEUE_LEN];
+	struct qtnf_shm_ipc_region bda_shm_reg1 __aligned(4096);
+	struct qtnf_shm_ipc_region bda_shm_reg2 __aligned(4096);
+} __packed;
+
+struct qtnf_pcie_topaz_state {
+	struct qtnf_pcie_bus_priv base;
+	struct qtnf_topaz_bda __iomem *bda;
+
+	dma_addr_t dma_msi_dummy;
+	u32 dma_msi_imwr;
+
+	struct qtnf_topaz_tx_bd *tx_bd_vbase;
+	struct qtnf_topaz_rx_bd *rx_bd_vbase;
+
+	__le32 __iomem *ep_next_rx_pkt;
+	__le32 __iomem *txqueue_wake;
+	__le32 __iomem *ep_pmstate;
+
+	unsigned long rx_pkt_count;
+};
+
+static void qtnf_deassert_intx(struct qtnf_pcie_topaz_state *ts)
+{
+	void __iomem *reg = ts->base.sysctl_bar + TOPAZ_PCIE_CFG0_OFFSET;
+	u32 cfg;
+
+	cfg = readl(reg);
+	cfg &= ~TOPAZ_ASSERT_INTX;
+	qtnf_non_posted_write(cfg, reg);
+}
+
+static inline int qtnf_topaz_intx_asserted(struct qtnf_pcie_topaz_state *ts)
+{
+	void __iomem *reg = ts->base.sysctl_bar + TOPAZ_PCIE_CFG0_OFFSET;
+	u32 cfg = readl(reg);
+
+	return !!(cfg & TOPAZ_ASSERT_INTX);
+}
+
+static void qtnf_topaz_reset_ep(struct qtnf_pcie_topaz_state *ts)
+{
+	writel(TOPAZ_IPC_IRQ_WORD(TOPAZ_RC_RST_EP_IRQ),
+	       TOPAZ_LH_IPC4_INT(ts->base.sysctl_bar));
+	msleep(QTN_EP_RESET_WAIT_MS);
+	pci_restore_state(ts->base.pdev);
+}
+
+static void setup_rx_irqs(struct qtnf_pcie_topaz_state *ts)
+{
+	void __iomem *reg = PCIE_DMA_WR_DONE_IMWR_ADDR_LOW(ts->base.dmareg_bar);
+
+	ts->dma_msi_imwr = readl(reg);
+}
+
+static void enable_rx_irqs(struct qtnf_pcie_topaz_state *ts)
+{
+	void __iomem *reg = PCIE_DMA_WR_DONE_IMWR_ADDR_LOW(ts->base.dmareg_bar);
+
+	qtnf_non_posted_write(ts->dma_msi_imwr, reg);
+}
+
+static void disable_rx_irqs(struct qtnf_pcie_topaz_state *ts)
+{
+	void __iomem *reg = PCIE_DMA_WR_DONE_IMWR_ADDR_LOW(ts->base.dmareg_bar);
+
+	qtnf_non_posted_write(QTN_HOST_LO32(ts->dma_msi_dummy), reg);
+}
+
+static void qtnf_topaz_ipc_gen_ep_int(void *arg)
+{
+	struct qtnf_pcie_topaz_state *ts = arg;
+
+	writel(TOPAZ_IPC_IRQ_WORD(TOPAZ_RC_CTRL_IRQ),
+	       TOPAZ_CTL_M2L_INT(ts->base.sysctl_bar));
+}
+
+static int qtnf_is_state(__le32 __iomem *reg, u32 state)
+{
+	u32 s = readl(reg);
+
+	return (s == state);
+}
+
+static void qtnf_set_state(__le32 __iomem *reg, u32 state)
+{
+	qtnf_non_posted_write(state, reg);
+}
+
+static int qtnf_poll_state(__le32 __iomem *reg, u32 state, u32 delay_in_ms)
+{
+	u32 timeout = 0;
+
+	while ((qtnf_is_state(reg, state) == 0)) {
+		usleep_range(1000, 1200);
+		if (++timeout > delay_in_ms)
+			return -1;
+	}
+
+	return 0;
+}
+
+static int topaz_alloc_bd_table(struct qtnf_pcie_topaz_state *ts,
+				struct qtnf_topaz_bda __iomem *bda)
+{
+	struct qtnf_extra_bd_params __iomem *extra_params;
+	struct qtnf_pcie_bus_priv *priv = &ts->base;
+	dma_addr_t paddr;
+	void *vaddr;
+	int len;
+	int i;
+
+	/* bd table */
+
+	len = priv->tx_bd_num * sizeof(struct qtnf_topaz_tx_bd) +
+		priv->rx_bd_num * sizeof(struct qtnf_topaz_rx_bd) +
+			sizeof(struct qtnf_extra_bd_params);
+
+	vaddr = dmam_alloc_coherent(&priv->pdev->dev, len, &paddr, GFP_KERNEL);
+	if (!vaddr)
+		return -ENOMEM;
+
+	memset(vaddr, 0, len);
+
+	/* tx bd */
+
+	ts->tx_bd_vbase = vaddr;
+	qtnf_non_posted_write(paddr, &bda->bda_rc_tx_bd_base);
+
+	for (i = 0; i < priv->tx_bd_num; i++)
+		ts->tx_bd_vbase[i].info |= cpu_to_le32(QTN_BD_EMPTY);
+
+	pr_debug("TX descriptor table: vaddr=0x%p paddr=%pad\n", vaddr, &paddr);
+
+	priv->tx_bd_r_index = 0;
+	priv->tx_bd_w_index = 0;
+
+	/* rx bd */
+
+	vaddr = ((struct qtnf_topaz_tx_bd *)vaddr) + priv->tx_bd_num;
+	paddr += priv->tx_bd_num * sizeof(struct qtnf_topaz_tx_bd);
+
+	ts->rx_bd_vbase = vaddr;
+	qtnf_non_posted_write(paddr, &bda->bda_rc_rx_bd_base);
+
+	pr_debug("RX descriptor table: vaddr=0x%p paddr=%pad\n", vaddr, &paddr);
+
+	/* extra shared params */
+
+	vaddr = ((struct qtnf_topaz_rx_bd *)vaddr) + priv->rx_bd_num;
+	paddr += priv->rx_bd_num * sizeof(struct qtnf_topaz_rx_bd);
+
+	extra_params = (struct qtnf_extra_bd_params __iomem *)vaddr;
+
+	ts->ep_next_rx_pkt = &extra_params->param1;
+	qtnf_non_posted_write(paddr + QTNF_BD_PARAM_OFFSET(1),
+			      &bda->bda_ep_next_pkt);
+	ts->txqueue_wake = &extra_params->param2;
+	ts->ep_pmstate = &extra_params->param3;
+	ts->dma_msi_dummy = paddr + QTNF_BD_PARAM_OFFSET(4);
+
+	return 0;
+}
+
+static int
+topaz_skb2rbd_attach(struct qtnf_pcie_topaz_state *ts, u16 index, u32 wrap)
+{
+	struct qtnf_topaz_rx_bd *rxbd = &ts->rx_bd_vbase[index];
+	struct sk_buff *skb;
+	dma_addr_t paddr;
+
+	skb = __netdev_alloc_skb_ip_align(NULL, SKB_BUF_SIZE, GFP_ATOMIC);
+	if (!skb) {
+		ts->base.rx_skb[index] = NULL;
+		return -ENOMEM;
+	}
+
+	ts->base.rx_skb[index] = skb;
+
+	paddr = pci_map_single(ts->base.pdev, skb->data,
+			       SKB_BUF_SIZE, PCI_DMA_FROMDEVICE);
+	if (pci_dma_mapping_error(ts->base.pdev, paddr)) {
+		pr_err("skb mapping error: %pad\n", &paddr);
+		return -ENOMEM;
+	}
+
+	rxbd->addr = cpu_to_le32(QTN_HOST_LO32(paddr));
+	rxbd->info = cpu_to_le32(QTN_BD_EMPTY | wrap);
+
+	ts->base.rx_bd_w_index = index;
+
+	return 0;
+}
+
+static int topaz_alloc_rx_buffers(struct qtnf_pcie_topaz_state *ts)
+{
+	u16 i;
+	int ret = 0;
+
+	memset(ts->rx_bd_vbase, 0x0,
+	       ts->base.rx_bd_num * sizeof(struct qtnf_topaz_rx_bd));
+
+	for (i = 0; i < ts->base.rx_bd_num; i++) {
+		ret = topaz_skb2rbd_attach(ts, i, 0);
+		if (ret)
+			break;
+	}
+
+	ts->rx_bd_vbase[ts->base.rx_bd_num - 1].info |=
+						cpu_to_le32(QTN_BD_WRAP);
+
+	return ret;
+}
+
+/* all rx/tx activity should have ceased before calling this function */
+static void qtnf_topaz_free_xfer_buffers(struct qtnf_pcie_topaz_state *ts)
+{
+	struct qtnf_pcie_bus_priv *priv = &ts->base;
+	struct qtnf_topaz_rx_bd *rxbd;
+	struct qtnf_topaz_tx_bd *txbd;
+	struct sk_buff *skb;
+	dma_addr_t paddr;
+	int i;
+
+	/* free rx buffers */
+	for (i = 0; i < priv->rx_bd_num; i++) {
+		if (priv->rx_skb && priv->rx_skb[i]) {
+			rxbd = &ts->rx_bd_vbase[i];
+			skb = priv->rx_skb[i];
+			paddr = QTN_HOST_ADDR(0x0, le32_to_cpu(rxbd->addr));
+			pci_unmap_single(priv->pdev, paddr, SKB_BUF_SIZE,
+					 PCI_DMA_FROMDEVICE);
+			dev_kfree_skb_any(skb);
+			priv->rx_skb[i] = NULL;
+			rxbd->addr = 0;
+			rxbd->info = 0;
+		}
+	}
+
+	/* free tx buffers */
+	for (i = 0; i < priv->tx_bd_num; i++) {
+		if (priv->tx_skb && priv->tx_skb[i]) {
+			txbd = &ts->tx_bd_vbase[i];
+			skb = priv->tx_skb[i];
+			paddr = QTN_HOST_ADDR(0x0, le32_to_cpu(txbd->addr));
+			pci_unmap_single(priv->pdev, paddr, SKB_BUF_SIZE,
+					 PCI_DMA_TODEVICE);
+			dev_kfree_skb_any(skb);
+			priv->tx_skb[i] = NULL;
+			txbd->addr = 0;
+			txbd->info = 0;
+		}
+	}
+}
+
+static int qtnf_pcie_topaz_init_xfer(struct qtnf_pcie_topaz_state *ts,
+				     unsigned int tx_bd_size)
+{
+	struct qtnf_topaz_bda __iomem *bda = ts->bda;
+	struct qtnf_pcie_bus_priv *priv = &ts->base;
+	int ret;
+
+	if (tx_bd_size == 0)
+		tx_bd_size = TOPAZ_TX_BD_SIZE_DEFAULT;
+
+	/* check TX BD queue max length according to struct qtnf_topaz_bda */
+	if (tx_bd_size > QTN_PCIE_RC_TX_QUEUE_LEN) {
+		pr_warn("TX BD queue cannot exceed %d\n",
+			QTN_PCIE_RC_TX_QUEUE_LEN);
+		tx_bd_size = QTN_PCIE_RC_TX_QUEUE_LEN;
+	}
+
+	priv->tx_bd_num = tx_bd_size;
+	qtnf_non_posted_write(priv->tx_bd_num, &bda->bda_rc_tx_bd_num);
+	qtnf_non_posted_write(priv->rx_bd_num, &bda->bda_rc_rx_bd_num);
+
+	priv->rx_bd_w_index = 0;
+	priv->rx_bd_r_index = 0;
+
+	ret = qtnf_pcie_alloc_skb_array(priv);
+	if (ret) {
+		pr_err("failed to allocate skb array\n");
+		return ret;
+	}
+
+	ret = topaz_alloc_bd_table(ts, bda);
+	if (ret) {
+		pr_err("failed to allocate bd table\n");
+		return ret;
+	}
+
+	ret = topaz_alloc_rx_buffers(ts);
+	if (ret) {
+		pr_err("failed to allocate rx buffers\n");
+		return ret;
+	}
+
+	return ret;
+}
+
+static void qtnf_topaz_data_tx_reclaim(struct qtnf_pcie_topaz_state *ts)
+{
+	struct qtnf_pcie_bus_priv *priv = &ts->base;
+	struct qtnf_topaz_tx_bd *txbd;
+	struct sk_buff *skb;
+	unsigned long flags;
+	dma_addr_t paddr;
+	u32 tx_done_index;
+	int count = 0;
+	int i;
+
+	spin_lock_irqsave(&priv->tx_reclaim_lock, flags);
+
+	tx_done_index = readl(ts->ep_next_rx_pkt);
+	i = priv->tx_bd_r_index;
+
+	if (CIRC_CNT(priv->tx_bd_w_index, tx_done_index, priv->tx_bd_num))
+		writel(TOPAZ_IPC_IRQ_WORD(TOPAZ_RC_TX_DONE_IRQ),
+		       TOPAZ_LH_IPC4_INT(priv->sysctl_bar));
+
+	while (CIRC_CNT(tx_done_index, i, priv->tx_bd_num)) {
+		skb = priv->tx_skb[i];
+
+		if (likely(skb)) {
+			txbd = &ts->tx_bd_vbase[i];
+			paddr = QTN_HOST_ADDR(0x0, le32_to_cpu(txbd->addr));
+			pci_unmap_single(priv->pdev, paddr, skb->len,
+					 PCI_DMA_TODEVICE);
+
+			if (skb->dev) {
+				qtnf_update_tx_stats(skb->dev, skb);
+				if (unlikely(priv->tx_stopped)) {
+					qtnf_wake_all_queues(skb->dev);
+					priv->tx_stopped = 0;
+				}
+			}
+
+			dev_kfree_skb_any(skb);
+		}
+
+		priv->tx_skb[i] = NULL;
+		count++;
+
+		if (++i >= priv->tx_bd_num)
+			i = 0;
+	}
+
+	priv->tx_reclaim_done += count;
+	priv->tx_reclaim_req++;
+	priv->tx_bd_r_index = i;
+
+	spin_unlock_irqrestore(&priv->tx_reclaim_lock, flags);
+}
+
+static void qtnf_try_stop_xmit(struct qtnf_bus *bus, struct net_device *ndev)
+{
+	struct qtnf_pcie_topaz_state *ts = (void *)get_bus_priv(bus);
+
+	if (ndev) {
+		netif_tx_stop_all_queues(ndev);
+		ts->base.tx_stopped = 1;
+	}
+
+	writel(0x0, ts->txqueue_wake);
+
+	/* sync up tx queue status before generating interrupt */
+	dma_wmb();
+
+	/* send irq to card: tx stopped */
+	writel(TOPAZ_IPC_IRQ_WORD(TOPAZ_RC_TX_STOP_IRQ),
+	       TOPAZ_LH_IPC4_INT(ts->base.sysctl_bar));
+
+	/* schedule reclaim attempt */
+	tasklet_hi_schedule(&ts->base.reclaim_tq);
+}
+
+static void qtnf_try_wake_xmit(struct qtnf_bus *bus, struct net_device *ndev)
+{
+	struct qtnf_pcie_topaz_state *ts = get_bus_priv(bus);
+	int ready;
+
+	ready = readl(ts->txqueue_wake);
+	if (ready) {
+		netif_wake_queue(ndev);
+	} else {
+		/* re-send irq to card: tx stopped */
+		writel(TOPAZ_IPC_IRQ_WORD(TOPAZ_RC_TX_STOP_IRQ),
+		       TOPAZ_LH_IPC4_INT(ts->base.sysctl_bar));
+	}
+}
+
+static int qtnf_tx_queue_ready(struct qtnf_pcie_topaz_state *ts)
+{
+	struct qtnf_pcie_bus_priv *priv = &ts->base;
+
+	if (!CIRC_SPACE(priv->tx_bd_w_index, priv->tx_bd_r_index,
+			priv->tx_bd_num)) {
+		qtnf_topaz_data_tx_reclaim(ts);
+
+		if (!CIRC_SPACE(priv->tx_bd_w_index, priv->tx_bd_r_index,
+				priv->tx_bd_num)) {
+			priv->tx_full_count++;
+			return 0;
+		}
+	}
+
+	return 1;
+}
+
+static int qtnf_pcie_data_tx(struct qtnf_bus *bus, struct sk_buff *skb)
+{
+	struct qtnf_pcie_topaz_state *ts = (void *)get_bus_priv(bus);
+	struct qtnf_pcie_bus_priv *priv = &ts->base;
+	struct qtnf_topaz_bda __iomem *bda = ts->bda;
+	struct qtnf_topaz_tx_bd *txbd;
+	dma_addr_t skb_paddr;
+	unsigned long flags;
+	int ret = 0;
+	int len;
+	int i;
+
+	spin_lock_irqsave(&priv->tx_lock, flags);
+
+	if (!qtnf_tx_queue_ready(ts)) {
+		qtnf_try_stop_xmit(bus, skb->dev);
+		spin_unlock_irqrestore(&priv->tx_lock, flags);
+		return NETDEV_TX_BUSY;
+	}
+
+	i = priv->tx_bd_w_index;
+	priv->tx_skb[i] = skb;
+	len = skb->len;
+
+	skb_paddr = pci_map_single(priv->pdev, skb->data,
+				   skb->len, PCI_DMA_TODEVICE);
+	if (pci_dma_mapping_error(priv->pdev, skb_paddr)) {
+		ret = -ENOMEM;
+		goto tx_done;
+	}
+
+	txbd = &ts->tx_bd_vbase[i];
+	txbd->addr = cpu_to_le32(QTN_HOST_LO32(skb_paddr));
+
+	writel(QTN_HOST_LO32(skb_paddr), &bda->request[i].addr);
+	writel(len | QTN_PCIE_TX_VALID_PKT, &bda->request[i].info);
+
+	/* sync up descriptor updates before generating interrupt */
+	dma_wmb();
+
+	/* generate irq to card: tx done */
+	writel(TOPAZ_IPC_IRQ_WORD(TOPAZ_RC_TX_DONE_IRQ),
+	       TOPAZ_LH_IPC4_INT(priv->sysctl_bar));
+
+	if (++i >= priv->tx_bd_num)
+		i = 0;
+
+	priv->tx_bd_w_index = i;
+
+tx_done:
+	if (ret) {
+		if (skb->dev)
+			skb->dev->stats.tx_dropped++;
+		dev_kfree_skb_any(skb);
+	}
+
+	priv->tx_done_count++;
+	spin_unlock_irqrestore(&priv->tx_lock, flags);
+
+	qtnf_topaz_data_tx_reclaim(ts);
+
+	return NETDEV_TX_OK;
+}
+
+static irqreturn_t qtnf_pcie_topaz_interrupt(int irq, void *data)
+{
+	struct qtnf_bus *bus = (struct qtnf_bus *)data;
+	struct qtnf_pcie_topaz_state *ts = (void *)get_bus_priv(bus);
+	struct qtnf_pcie_bus_priv *priv = &ts->base;
+
+	if (!priv->msi_enabled && !qtnf_topaz_intx_asserted(ts))
+		return IRQ_NONE;
+
+	priv->pcie_irq_count++;
+
+	qtnf_shm_ipc_irq_handler(&priv->shm_ipc_ep_in);
+	qtnf_shm_ipc_irq_handler(&priv->shm_ipc_ep_out);
+
+	if (napi_schedule_prep(&bus->mux_napi)) {
+		disable_rx_irqs(ts);
+		__napi_schedule(&bus->mux_napi);
+	}
+
+	tasklet_hi_schedule(&priv->reclaim_tq);
+
+	if (!priv->msi_enabled)
+		qtnf_deassert_intx(ts);
+
+	return IRQ_HANDLED;
+}
+
+static int qtnf_rx_data_ready(struct qtnf_pcie_topaz_state *ts)
+{
+	u16 index = ts->base.rx_bd_r_index;
+	struct qtnf_topaz_rx_bd *rxbd;
+	u32 descw;
+
+	rxbd = &ts->rx_bd_vbase[index];
+	descw = le32_to_cpu(rxbd->info);
+
+	if (descw & QTN_BD_EMPTY)
+		return 0;
+
+	return 1;
+}
+
+static int qtnf_topaz_rx_poll(struct napi_struct *napi, int budget)
+{
+	struct qtnf_bus *bus = container_of(napi, struct qtnf_bus, mux_napi);
+	struct qtnf_pcie_topaz_state *ts = (void *)get_bus_priv(bus);
+	struct qtnf_pcie_bus_priv *priv = &ts->base;
+	struct net_device *ndev = NULL;
+	struct sk_buff *skb = NULL;
+	int processed = 0;
+	struct qtnf_topaz_rx_bd *rxbd;
+	dma_addr_t skb_paddr;
+	int consume;
+	u32 descw;
+	u32 poffset;
+	u32 psize;
+	u16 r_idx;
+	u16 w_idx;
+	int ret;
+
+	while (processed < budget) {
+		if (!qtnf_rx_data_ready(ts))
+			goto rx_out;
+
+		r_idx = priv->rx_bd_r_index;
+		rxbd = &ts->rx_bd_vbase[r_idx];
+		descw = le32_to_cpu(rxbd->info);
+
+		skb = priv->rx_skb[r_idx];
+		poffset = QTN_GET_OFFSET(descw);
+		psize = QTN_GET_LEN(descw);
+		consume = 1;
+
+		if (descw & QTN_BD_EMPTY) {
+			pr_warn("skip invalid rxbd[%d]\n", r_idx);
+			consume = 0;
+		}
+
+		if (!skb) {
+			pr_warn("skip missing rx_skb[%d]\n", r_idx);
+			consume = 0;
+		}
+
+		if (skb && (skb_tailroom(skb) <  psize)) {
+			pr_err("skip packet with invalid length: %u > %u\n",
+			       psize, skb_tailroom(skb));
+			consume = 0;
+		}
+
+		if (skb) {
+			skb_paddr = QTN_HOST_ADDR(0x0, le32_to_cpu(rxbd->addr));
+			pci_unmap_single(priv->pdev, skb_paddr, SKB_BUF_SIZE,
+					 PCI_DMA_FROMDEVICE);
+		}
+
+		if (consume) {
+			skb_reserve(skb, poffset);
+			skb_put(skb, psize);
+			ndev = qtnf_classify_skb(bus, skb);
+			if (likely(ndev)) {
+				qtnf_update_rx_stats(ndev, skb);
+				skb->protocol = eth_type_trans(skb, ndev);
+				netif_receive_skb(skb);
+			} else {
+				pr_debug("drop untagged skb\n");
+				bus->mux_dev.stats.rx_dropped++;
+				dev_kfree_skb_any(skb);
+			}
+		} else {
+			if (skb) {
+				bus->mux_dev.stats.rx_dropped++;
+				dev_kfree_skb_any(skb);
+			}
+		}
+
+		/* notify card about recv packets once per several packets */
+		if (((++ts->rx_pkt_count) & RX_DONE_INTR_MSK) == 0)
+			writel(TOPAZ_IPC_IRQ_WORD(TOPAZ_RC_RX_DONE_IRQ),
+			       TOPAZ_LH_IPC4_INT(priv->sysctl_bar));
+
+		priv->rx_skb[r_idx] = NULL;
+		if (++r_idx >= priv->rx_bd_num)
+			r_idx = 0;
+
+		priv->rx_bd_r_index = r_idx;
+
+		/* repalce processed buffer by a new one */
+		w_idx = priv->rx_bd_w_index;
+		while (CIRC_SPACE(priv->rx_bd_w_index, priv->rx_bd_r_index,
+				  priv->rx_bd_num) > 0) {
+			if (++w_idx >= priv->rx_bd_num)
+				w_idx = 0;
+
+			ret = topaz_skb2rbd_attach(ts, w_idx,
+						   descw & QTN_BD_WRAP);
+			if (ret) {
+				pr_err("failed to allocate new rx_skb[%d]\n",
+				       w_idx);
+				break;
+			}
+		}
+
+		processed++;
+	}
+
+rx_out:
+	if (processed < budget) {
+		napi_complete(napi);
+		enable_rx_irqs(ts);
+	}
+
+	return processed;
+}
+
+static void
+qtnf_pcie_data_tx_timeout(struct qtnf_bus *bus, struct net_device *ndev)
+{
+	struct qtnf_pcie_topaz_state *ts = get_bus_priv(bus);
+
+	qtnf_try_wake_xmit(bus, ndev);
+	tasklet_hi_schedule(&ts->base.reclaim_tq);
+}
+
+static void qtnf_pcie_data_rx_start(struct qtnf_bus *bus)
+{
+	struct qtnf_pcie_topaz_state *ts = get_bus_priv(bus);
+
+	napi_enable(&bus->mux_napi);
+	enable_rx_irqs(ts);
+}
+
+static void qtnf_pcie_data_rx_stop(struct qtnf_bus *bus)
+{
+	struct qtnf_pcie_topaz_state *ts = get_bus_priv(bus);
+
+	disable_rx_irqs(ts);
+	napi_disable(&bus->mux_napi);
+}
+
+static const struct qtnf_bus_ops qtnf_pcie_topaz_bus_ops = {
+	/* control path methods */
+	.control_tx	= qtnf_pcie_control_tx,
+
+	/* data path methods */
+	.data_tx		= qtnf_pcie_data_tx,
+	.data_tx_timeout	= qtnf_pcie_data_tx_timeout,
+	.data_rx_start		= qtnf_pcie_data_rx_start,
+	.data_rx_stop		= qtnf_pcie_data_rx_stop,
+};
+
+static int qtnf_dbg_irq_stats(struct seq_file *s, void *data)
+{
+	struct qtnf_bus *bus = dev_get_drvdata(s->private);
+	struct qtnf_pcie_topaz_state *ts = get_bus_priv(bus);
+
+	seq_printf(s, "pcie_irq_count(%u)\n", ts->base.pcie_irq_count);
+
+	return 0;
+}
+
+static int qtnf_dbg_pkt_stats(struct seq_file *s, void *data)
+{
+	struct qtnf_bus *bus = dev_get_drvdata(s->private);
+	struct qtnf_pcie_topaz_state *ts = get_bus_priv(bus);
+	struct qtnf_pcie_bus_priv *priv = &ts->base;
+	u32 tx_done_index = readl(ts->ep_next_rx_pkt);
+
+	seq_printf(s, "tx_full_count(%u)\n", priv->tx_full_count);
+	seq_printf(s, "tx_done_count(%u)\n", priv->tx_done_count);
+	seq_printf(s, "tx_reclaim_done(%u)\n", priv->tx_reclaim_done);
+	seq_printf(s, "tx_reclaim_req(%u)\n", priv->tx_reclaim_req);
+
+	seq_printf(s, "tx_bd_r_index(%u)\n", priv->tx_bd_r_index);
+	seq_printf(s, "tx_done_index(%u)\n", tx_done_index);
+	seq_printf(s, "tx_bd_w_index(%u)\n", priv->tx_bd_w_index);
+
+	seq_printf(s, "tx host queue len(%u)\n",
+		   CIRC_CNT(priv->tx_bd_w_index, priv->tx_bd_r_index,
+			    priv->tx_bd_num));
+	seq_printf(s, "tx reclaim queue len(%u)\n",
+		   CIRC_CNT(tx_done_index, priv->tx_bd_r_index,
+			    priv->tx_bd_num));
+	seq_printf(s, "tx card queue len(%u)\n",
+		   CIRC_CNT(priv->tx_bd_w_index, tx_done_index,
+			    priv->tx_bd_num));
+
+	seq_printf(s, "rx_bd_r_index(%u)\n", priv->rx_bd_r_index);
+	seq_printf(s, "rx_bd_w_index(%u)\n", priv->rx_bd_w_index);
+	seq_printf(s, "rx alloc queue len(%u)\n",
+		   CIRC_SPACE(priv->rx_bd_w_index, priv->rx_bd_r_index,
+			      priv->rx_bd_num));
+
+	return 0;
+}
+
+static void qtnf_reset_dma_offset(struct qtnf_pcie_topaz_state *ts)
+{
+	struct qtnf_topaz_bda __iomem *bda = ts->bda;
+	u32 offset = readl(&bda->bda_dma_offset);
+
+	if ((offset & PCIE_DMA_OFFSET_ERROR_MASK) != PCIE_DMA_OFFSET_ERROR)
+		return;
+
+	writel(0x0, &bda->bda_dma_offset);
+}
+
+static int qtnf_pcie_endian_detect(struct qtnf_pcie_topaz_state *ts)
+{
+	struct qtnf_topaz_bda __iomem *bda = ts->bda;
+	u32 timeout = 0;
+	u32 endian;
+	int ret = 0;
+
+	writel(QTN_PCI_ENDIAN_DETECT_DATA, &bda->bda_pci_endian);
+
+	/* flush endian modifications before status update */
+	dma_wmb();
+
+	writel(QTN_PCI_ENDIAN_VALID_STATUS, &bda->bda_pci_pre_status);
+
+	while (readl(&bda->bda_pci_post_status) !=
+	       QTN_PCI_ENDIAN_VALID_STATUS) {
+		usleep_range(1000, 1200);
+		if (++timeout > QTN_FW_DL_TIMEOUT_MS) {
+			pr_err("card endianness detection timed out\n");
+			ret = -ETIMEDOUT;
+			goto endian_out;
+		}
+	}
+
+	/* do not read before status is updated */
+	dma_rmb();
+
+	endian = readl(&bda->bda_pci_endian);
+	WARN(endian != QTN_PCI_LITTLE_ENDIAN,
+	     "%s: unexpected card endianness", __func__);
+
+endian_out:
+	writel(0, &bda->bda_pci_pre_status);
+	writel(0, &bda->bda_pci_post_status);
+	writel(0, &bda->bda_pci_endian);
+
+	return ret;
+}
+
+static int qtnf_pre_init_ep(struct qtnf_bus *bus)
+{
+	struct qtnf_pcie_topaz_state *ts = (void *)get_bus_priv(bus);
+	struct qtnf_topaz_bda __iomem *bda = ts->bda;
+	u32 flags;
+	int ret;
+
+	ret = qtnf_pcie_endian_detect(ts);
+	if (ret < 0) {
+		pr_err("failed to detect card endianness\n");
+		return ret;
+	}
+
+	writeb(ts->base.msi_enabled, &ts->bda->bda_rc_msi_enabled);
+	qtnf_reset_dma_offset(ts);
+
+	/* notify card about driver type and boot mode */
+	flags = readl(&bda->bda_flags) | QTN_BDA_HOST_QLINK_DRV;
+
+	if (ts->base.flashboot)
+		flags |= QTN_BDA_FLASH_BOOT;
+	else
+		flags &= ~QTN_BDA_FLASH_BOOT;
+
+	writel(flags, &bda->bda_flags);
+
+	qtnf_set_state(&ts->bda->bda_bootstate, QTN_BDA_FW_HOST_RDY);
+	if (qtnf_poll_state(&ts->bda->bda_bootstate, QTN_BDA_FW_TARGET_RDY,
+			    QTN_FW_DL_TIMEOUT_MS)) {
+		pr_err("card is not ready to boot...\n");
+		return -ETIMEDOUT;
+	}
+
+	return ret;
+}
+
+static int qtnf_post_init_ep(struct qtnf_pcie_topaz_state *ts)
+{
+	struct pci_dev *pdev = ts->base.pdev;
+
+	setup_rx_irqs(ts);
+	disable_rx_irqs(ts);
+
+	if (qtnf_poll_state(&ts->bda->bda_bootstate, QTN_BDA_FW_QLINK_DONE,
+			    QTN_FW_QLINK_TIMEOUT_MS))
+		return -ETIMEDOUT;
+
+	enable_irq(pdev->irq);
+	return 0;
+}
+
+static int
+qtnf_ep_fw_load(struct qtnf_pcie_topaz_state *ts, const u8 *fw, u32 fw_size)
+{
+	struct qtnf_topaz_bda __iomem *bda = ts->bda;
+	struct pci_dev *pdev = ts->base.pdev;
+	u32 remaining = fw_size;
+	u8 *curr = (u8 *)fw;
+	u32 blksize;
+	u32 nblocks;
+	u32 offset;
+	u32 count;
+	u32 size;
+	dma_addr_t paddr;
+	void *data;
+	int ret = 0;
+
+	pr_debug("FW upload started: fw_addr = 0x%p, size=%d\n", fw, fw_size);
+
+	blksize = ts->base.fw_blksize;
+
+	if (blksize < PAGE_SIZE)
+		blksize = PAGE_SIZE;
+
+	while (blksize >= PAGE_SIZE) {
+		pr_debug("allocating %u bytes to upload FW\n", blksize);
+		data = dma_alloc_coherent(&pdev->dev, blksize,
+					  &paddr, GFP_KERNEL);
+		if (data)
+			break;
+		blksize /= 2;
+	}
+
+	if (!data) {
+		pr_err("failed to allocate DMA buffer for FW upload\n");
+		ret = -ENOMEM;
+		goto fw_load_out;
+	}
+
+	nblocks = NBLOCKS(fw_size, blksize);
+	offset = readl(&bda->bda_dma_offset);
+
+	qtnf_set_state(&ts->bda->bda_bootstate, QTN_BDA_FW_HOST_LOAD);
+	if (qtnf_poll_state(&ts->bda->bda_bootstate, QTN_BDA_FW_EP_RDY,
+			    QTN_FW_DL_TIMEOUT_MS)) {
+		pr_err("card is not ready to download FW\n");
+		ret = -ETIMEDOUT;
+		goto fw_load_map;
+	}
+
+	for (count = 0 ; count < nblocks; count++) {
+		size = (remaining > blksize) ? blksize : remaining;
+
+		memcpy(data, curr, size);
+		qtnf_non_posted_write(paddr + offset, &bda->bda_img);
+		qtnf_non_posted_write(size, &bda->bda_img_size);
+
+		pr_debug("chunk[%u] VA[0x%p] PA[%pad] sz[%u]\n",
+			 count, (void *)curr, &paddr, size);
+
+		qtnf_set_state(&ts->bda->bda_bootstate, QTN_BDA_FW_BLOCK_RDY);
+		if (qtnf_poll_state(&ts->bda->bda_bootstate,
+				    QTN_BDA_FW_BLOCK_DONE,
+				    QTN_FW_DL_TIMEOUT_MS)) {
+			pr_err("confirmation for block #%d timed out\n", count);
+			ret = -ETIMEDOUT;
+			goto fw_load_map;
+		}
+
+		remaining = (remaining < size) ? remaining : (remaining - size);
+		curr += size;
+	}
+
+	/* upload completion mark: zero-sized block */
+	qtnf_non_posted_write(0, &bda->bda_img);
+	qtnf_non_posted_write(0, &bda->bda_img_size);
+
+	qtnf_set_state(&ts->bda->bda_bootstate, QTN_BDA_FW_BLOCK_RDY);
+	if (qtnf_poll_state(&ts->bda->bda_bootstate, QTN_BDA_FW_BLOCK_DONE,
+			    QTN_FW_DL_TIMEOUT_MS)) {
+		pr_err("confirmation for the last block timed out\n");
+		ret = -ETIMEDOUT;
+		goto fw_load_map;
+	}
+
+	/* RC is done */
+	qtnf_set_state(&ts->bda->bda_bootstate, QTN_BDA_FW_BLOCK_END);
+	if (qtnf_poll_state(&ts->bda->bda_bootstate, QTN_BDA_FW_LOAD_DONE,
+			    QTN_FW_DL_TIMEOUT_MS)) {
+		pr_err("confirmation for FW upload completion timed out\n");
+		ret = -ETIMEDOUT;
+		goto fw_load_map;
+	}
+
+	pr_debug("FW upload completed: totally sent %d blocks\n", count);
+
+fw_load_map:
+	dma_free_coherent(&pdev->dev, blksize, data, paddr);
+
+fw_load_out:
+	return ret;
+}
+
+static int qtnf_topaz_fw_upload(struct qtnf_pcie_topaz_state *ts,
+				const char *fwname)
+{
+	const struct firmware *fw;
+	struct pci_dev *pdev = ts->base.pdev;
+	int ret;
+
+	if (qtnf_poll_state(&ts->bda->bda_bootstate,
+			    QTN_BDA_FW_LOAD_RDY,
+			    QTN_FW_DL_TIMEOUT_MS)) {
+		pr_err("%s: card is not ready\n", fwname);
+		return -1;
+	}
+
+	pr_info("starting firmware upload: %s\n", fwname);
+
+	ret = request_firmware(&fw, fwname, &pdev->dev);
+	if (ret < 0) {
+		pr_err("%s: request_firmware error %d\n", fwname, ret);
+		return -1;
+	}
+
+	ret = qtnf_ep_fw_load(ts, fw->data, fw->size);
+	release_firmware(fw);
+
+	if (ret)
+		pr_err("%s: FW upload error\n", fwname);
+
+	return ret;
+}
+
+static void qtnf_topaz_fw_work_handler(struct work_struct *work)
+{
+	struct qtnf_bus *bus = container_of(work, struct qtnf_bus, fw_work);
+	struct qtnf_pcie_topaz_state *ts = (void *)get_bus_priv(bus);
+	int ret;
+	int bootloader_needed = readl(&ts->bda->bda_flags) & QTN_BDA_XMIT_UBOOT;
+
+	qtnf_set_state(&ts->bda->bda_bootstate, QTN_BDA_FW_TARGET_BOOT);
+
+	if (bootloader_needed) {
+		ret = qtnf_topaz_fw_upload(ts, QTN_PCI_TOPAZ_BOOTLD_NAME);
+		if (ret)
+			goto fw_load_exit;
+
+		ret = qtnf_pre_init_ep(bus);
+		if (ret)
+			goto fw_load_exit;
+
+		qtnf_set_state(&ts->bda->bda_bootstate,
+			       QTN_BDA_FW_TARGET_BOOT);
+	}
+
+	if (ts->base.flashboot) {
+		pr_info("booting firmware from flash\n");
+
+		ret = qtnf_poll_state(&ts->bda->bda_bootstate,
+				      QTN_BDA_FW_FLASH_BOOT,
+				      QTN_FW_DL_TIMEOUT_MS);
+		if (ret)
+			goto fw_load_exit;
+	} else {
+		ret = qtnf_topaz_fw_upload(ts, QTN_PCI_TOPAZ_FW_NAME);
+		if (ret)
+			goto fw_load_exit;
+
+		qtnf_set_state(&ts->bda->bda_bootstate, QTN_BDA_FW_START);
+		ret = qtnf_poll_state(&ts->bda->bda_bootstate,
+				      QTN_BDA_FW_CONFIG,
+				      QTN_FW_QLINK_TIMEOUT_MS);
+		if (ret) {
+			pr_err("FW bringup timed out\n");
+			goto fw_load_exit;
+		}
+
+		qtnf_set_state(&ts->bda->bda_bootstate, QTN_BDA_FW_RUN);
+		ret = qtnf_poll_state(&ts->bda->bda_bootstate,
+				      QTN_BDA_FW_RUNNING,
+				      QTN_FW_QLINK_TIMEOUT_MS);
+		if (ret) {
+			pr_err("card bringup timed out\n");
+			goto fw_load_exit;
+		}
+	}
+
+	pr_info("firmware is up and running\n");
+
+	ret = qtnf_post_init_ep(ts);
+	if (ret)
+		pr_err("FW runtime failure\n");
+
+fw_load_exit:
+	qtnf_pcie_fw_boot_done(bus, ret ? false : true);
+
+	if (ret == 0) {
+		qtnf_debugfs_add_entry(bus, "pkt_stats", qtnf_dbg_pkt_stats);
+		qtnf_debugfs_add_entry(bus, "irq_stats", qtnf_dbg_irq_stats);
+	}
+}
+
+static void qtnf_reclaim_tasklet_fn(unsigned long data)
+{
+	struct qtnf_pcie_topaz_state *ts = (void *)data;
+
+	qtnf_topaz_data_tx_reclaim(ts);
+}
+
+static u64 qtnf_topaz_dma_mask_get(void)
+{
+	return DMA_BIT_MASK(32);
+}
+
+static int qtnf_pcie_topaz_probe(struct qtnf_bus *bus, unsigned int tx_bd_num)
+{
+	struct qtnf_pcie_topaz_state *ts = get_bus_priv(bus);
+	struct pci_dev *pdev = ts->base.pdev;
+	struct qtnf_shm_ipc_int ipc_int;
+	unsigned long irqflags;
+	int ret;
+
+	bus->bus_ops = &qtnf_pcie_topaz_bus_ops;
+	INIT_WORK(&bus->fw_work, qtnf_topaz_fw_work_handler);
+	ts->bda = ts->base.epmem_bar;
+
+	/* assign host msi irq before card init */
+	if (ts->base.msi_enabled)
+		irqflags = IRQF_NOBALANCING;
+	else
+		irqflags = IRQF_NOBALANCING | IRQF_SHARED;
+
+	ret = devm_request_irq(&pdev->dev, pdev->irq,
+			       &qtnf_pcie_topaz_interrupt,
+			       irqflags, "qtnf_topaz_irq", (void *)bus);
+	if (ret) {
+		pr_err("failed to request pcie irq %d\n", pdev->irq);
+		return ret;
+	}
+
+	disable_irq(pdev->irq);
+
+	ret = qtnf_pre_init_ep(bus);
+	if (ret) {
+		pr_err("failed to init card\n");
+		return ret;
+	}
+
+	ret = qtnf_pcie_topaz_init_xfer(ts, tx_bd_num);
+	if (ret) {
+		pr_err("PCIE xfer init failed\n");
+		return ret;
+	}
+
+	tasklet_init(&ts->base.reclaim_tq, qtnf_reclaim_tasklet_fn,
+		     (unsigned long)ts);
+	netif_napi_add(&bus->mux_dev, &bus->mux_napi,
+		       qtnf_topaz_rx_poll, 10);
+
+	ipc_int.fn = qtnf_topaz_ipc_gen_ep_int;
+	ipc_int.arg = ts;
+	qtnf_pcie_init_shm_ipc(&ts->base, &ts->bda->bda_shm_reg1,
+			       &ts->bda->bda_shm_reg2, &ipc_int);
+
+	return 0;
+}
+
+static void qtnf_pcie_topaz_remove(struct qtnf_bus *bus)
+{
+	struct qtnf_pcie_topaz_state *ts = get_bus_priv(bus);
+
+	qtnf_topaz_reset_ep(ts);
+	qtnf_topaz_free_xfer_buffers(ts);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int qtnf_pcie_topaz_suspend(struct qtnf_bus *bus)
+{
+	struct qtnf_pcie_topaz_state *ts = get_bus_priv(bus);
+	struct pci_dev *pdev = ts->base.pdev;
+
+	writel((u32 __force)PCI_D3hot, ts->ep_pmstate);
+	dma_wmb();
+	writel(TOPAZ_IPC_IRQ_WORD(TOPAZ_RC_PM_EP_IRQ),
+	       TOPAZ_LH_IPC4_INT(ts->base.sysctl_bar));
+
+	pci_save_state(pdev);
+	pci_enable_wake(pdev, PCI_D3hot, 1);
+	pci_set_power_state(pdev, PCI_D3hot);
+
+	return 0;
+}
+
+static int qtnf_pcie_topaz_resume(struct qtnf_bus *bus)
+{
+	struct qtnf_pcie_topaz_state *ts = get_bus_priv(bus);
+	struct pci_dev *pdev = ts->base.pdev;
+
+	pci_set_power_state(pdev, PCI_D0);
+	pci_restore_state(pdev);
+	pci_enable_wake(pdev, PCI_D0, 0);
+
+	writel((u32 __force)PCI_D0, ts->ep_pmstate);
+	dma_wmb();
+	writel(TOPAZ_IPC_IRQ_WORD(TOPAZ_RC_PM_EP_IRQ),
+	       TOPAZ_LH_IPC4_INT(ts->base.sysctl_bar));
+
+	return 0;
+}
+#endif
+
+struct qtnf_bus *qtnf_pcie_topaz_alloc(struct pci_dev *pdev)
+{
+	struct qtnf_bus *bus;
+	struct qtnf_pcie_topaz_state *ts;
+
+	bus = devm_kzalloc(&pdev->dev, sizeof(*bus) + sizeof(*ts), GFP_KERNEL);
+	if (!bus)
+		return NULL;
+
+	ts = get_bus_priv(bus);
+	ts->base.probe_cb = qtnf_pcie_topaz_probe;
+	ts->base.remove_cb = qtnf_pcie_topaz_remove;
+	ts->base.dma_mask_get_cb = qtnf_topaz_dma_mask_get;
+#ifdef CONFIG_PM_SLEEP
+	ts->base.resume_cb = qtnf_pcie_topaz_resume;
+	ts->base.suspend_cb = qtnf_pcie_topaz_suspend;
+#endif
+
+	return bus;
+}
diff --git a/drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie_ipc.h b/drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie_ipc.h
new file mode 100644
index 000000000000..eb30e9d08de2
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie_ipc.h
@@ -0,0 +1,94 @@ 
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (c) 2018 Quantenna Communications */
+
+#ifndef _QTN_FMAC_PCIE_IPC_H_
+#define _QTN_FMAC_PCIE_IPC_H_
+
+#include <linux/types.h>
+
+#include "shm_ipc_defs.h"
+
+/* EP/RC status and flags */
+#define QTN_BDA_PCIE_INIT		0x01
+#define QTN_BDA_PCIE_RDY		0x02
+#define QTN_BDA_FW_LOAD_RDY		0x03
+#define QTN_BDA_FW_LOAD_DONE		0x04
+#define QTN_BDA_FW_START		0x05
+#define QTN_BDA_FW_RUN			0x06
+#define QTN_BDA_FW_HOST_RDY		0x07
+#define QTN_BDA_FW_TARGET_RDY		0x11
+#define QTN_BDA_FW_TARGET_BOOT		0x12
+#define QTN_BDA_FW_FLASH_BOOT		0x13
+#define QTN_BDA_FW_QLINK_DONE		0x14
+#define QTN_BDA_FW_HOST_LOAD		0x08
+#define QTN_BDA_FW_BLOCK_DONE		0x09
+#define QTN_BDA_FW_BLOCK_RDY		0x0A
+#define QTN_BDA_FW_EP_RDY		0x0B
+#define QTN_BDA_FW_BLOCK_END		0x0C
+#define QTN_BDA_FW_CONFIG		0x0D
+#define QTN_BDA_FW_RUNNING		0x0E
+#define QTN_BDA_PCIE_FAIL		0x82
+#define QTN_BDA_FW_LOAD_FAIL		0x85
+
+#define QTN_BDA_RCMODE			BIT(1)
+#define QTN_BDA_MSI			BIT(2)
+#define QTN_BDA_HOST_CALCMD		BIT(3)
+#define QTN_BDA_FLASH_PRESENT		BIT(4)
+#define QTN_BDA_FLASH_BOOT		BIT(5)
+#define QTN_BDA_XMIT_UBOOT		BIT(6)
+#define QTN_BDA_HOST_QLINK_DRV		BIT(7)
+#define QTN_BDA_TARGET_FBOOT_ERR	BIT(8)
+#define QTN_BDA_TARGET_FWLOAD_ERR	BIT(9)
+#define QTN_BDA_HOST_NOFW_ERR		BIT(12)
+#define QTN_BDA_HOST_MEMALLOC_ERR	BIT(13)
+#define QTN_BDA_HOST_MEMMAP_ERR		BIT(14)
+#define QTN_BDA_VER(x)			(((x) >> 4) & 0xFF)
+#define QTN_BDA_ERROR_MASK		0xFF00
+
+/* registers and shmem address macros */
+#if BITS_PER_LONG == 64
+#define QTN_HOST_HI32(a)	((u32)(((u64)a) >> 32))
+#define QTN_HOST_LO32(a)	((u32)(((u64)a) & 0xffffffffUL))
+#define QTN_HOST_ADDR(h, l)	((((u64)h) << 32) | ((u64)l))
+#elif BITS_PER_LONG == 32
+#define QTN_HOST_HI32(a)	0
+#define QTN_HOST_LO32(a)	((u32)(((u32)a) & 0xffffffffUL))
+#define QTN_HOST_ADDR(h, l)	((u32)l)
+#else
+#error Unexpected BITS_PER_LONG value
+#endif
+
+#define QTN_PCIE_BDA_VERSION		0x1001
+
+#define PCIE_BDA_NAMELEN		32
+
+#define QTN_PCIE_RC_TX_QUEUE_LEN	256
+#define QTN_PCIE_TX_VALID_PKT		0x80000000
+#define QTN_PCIE_PKT_LEN_MASK		0xffff
+
+#define QTN_BD_EMPTY		((uint32_t)0x00000001)
+#define QTN_BD_WRAP		((uint32_t)0x00000002)
+#define QTN_BD_MASK_LEN		((uint32_t)0xFFFF0000)
+#define QTN_BD_MASK_OFFSET	((uint32_t)0x0000FF00)
+
+#define QTN_GET_LEN(x)		(((x) >> 16) & 0xFFFF)
+#define QTN_GET_OFFSET(x)	(((x) >> 8) & 0xFF)
+#define QTN_SET_LEN(len)	(((len) & 0xFFFF) << 16)
+#define QTN_SET_OFFSET(of)	(((of) & 0xFF) << 8)
+
+#define RX_DONE_INTR_MSK	((0x1 << 6) - 1)
+
+#define PCIE_DMA_OFFSET_ERROR		0xFFFF
+#define PCIE_DMA_OFFSET_ERROR_MASK	0xFFFF
+
+#define QTN_PCI_ENDIAN_DETECT_DATA	0x12345678
+#define QTN_PCI_ENDIAN_REVERSE_DATA	0x78563412
+#define QTN_PCI_ENDIAN_VALID_STATUS	0x3c3c3c3c
+#define QTN_PCI_ENDIAN_INVALID_STATUS	0
+#define QTN_PCI_LITTLE_ENDIAN		0
+#define QTN_PCI_BIG_ENDIAN		0xffffffff
+
+#define NBLOCKS(size, blksize)		\
+	((size) / (blksize) + (((size) % (blksize) > 0) ? 1 : 0))
+
+#endif /* _QTN_FMAC_PCIE_IPC_H_ */
diff --git a/drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie_regs.h b/drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie_regs.h
new file mode 100644
index 000000000000..4782e1ed3c2c
--- /dev/null
+++ b/drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie_regs.h
@@ -0,0 +1,45 @@ 
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (c) 2018 Quantenna Communications */
+
+#ifndef __TOPAZ_PCIE_H
+#define __TOPAZ_PCIE_H
+
+/* Topaz PCIe DMA registers */
+#define PCIE_DMA_WR_INTR_STATUS(base)		((base) + 0x9bc)
+#define PCIE_DMA_WR_INTR_MASK(base)		((base) + 0x9c4)
+#define PCIE_DMA_WR_INTR_CLR(base)		((base) + 0x9c8)
+#define PCIE_DMA_WR_ERR_STATUS(base)		((base) + 0x9cc)
+#define PCIE_DMA_WR_DONE_IMWR_ADDR_LOW(base)	((base) + 0x9D0)
+#define PCIE_DMA_WR_DONE_IMWR_ADDR_HIGH(base)	((base) + 0x9d4)
+
+#define PCIE_DMA_RD_INTR_STATUS(base)		((base) + 0x310)
+#define PCIE_DMA_RD_INTR_MASK(base)		((base) + 0x319)
+#define PCIE_DMA_RD_INTR_CLR(base)		((base) + 0x31c)
+#define PCIE_DMA_RD_ERR_STATUS_LOW(base)	((base) + 0x324)
+#define PCIE_DMA_RD_ERR_STATUS_HIGH(base)	((base) + 0x328)
+#define PCIE_DMA_RD_DONE_IMWR_ADDR_LOW(base)	((base) + 0x33c)
+#define PCIE_DMA_RD_DONE_IMWR_ADDR_HIGH(base)	((base) + 0x340)
+
+/* Topaz LHost IPC4 interrupt */
+#define TOPAZ_LH_IPC4_INT(base)			((base) + 0x13C)
+#define TOPAZ_LH_IPC4_INT_MASK(base)		((base) + 0x140)
+
+#define TOPAZ_RC_TX_DONE_IRQ			(0)
+#define TOPAZ_RC_RST_EP_IRQ			(1)
+#define TOPAZ_RC_TX_STOP_IRQ			(2)
+#define TOPAZ_RC_RX_DONE_IRQ			(3)
+#define TOPAZ_RC_PM_EP_IRQ			(4)
+
+/* Topaz LHost M2L interrupt */
+#define TOPAZ_CTL_M2L_INT(base)			((base) + 0x2C)
+#define TOPAZ_CTL_M2L_INT_MASK(base)		((base) + 0x30)
+
+#define TOPAZ_RC_CTRL_IRQ			(6)
+
+#define TOPAZ_IPC_IRQ_WORD(irq)			(BIT(irq) | BIT(irq + 16))
+
+/* PCIe legacy INTx */
+#define TOPAZ_PCIE_CFG0_OFFSET	(0x6C)
+#define TOPAZ_ASSERT_INTX	BIT(9)
+
+#endif /* __TOPAZ_PCIE_H */
diff --git a/drivers/net/wireless/quantenna/qtnfmac/qtn_hw_ids.h b/drivers/net/wireless/quantenna/qtnfmac/qtn_hw_ids.h
index 1fe798a9a667..165fcbb41255 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/qtn_hw_ids.h
+++ b/drivers/net/wireless/quantenna/qtnfmac/qtn_hw_ids.h
@@ -24,6 +24,7 @@ 
 /* PCIE Device IDs */
 
 #define	PCIE_DEVICE_ID_QTN_PEARL	(0x0008)
+#define	PCIE_DEVICE_ID_QTN_TOPAZ	(0x0008)
 
 #define QTN_REG_SYS_CTRL_CSR		0x14
 #define QTN_CHIP_ID_MASK		0xF0
@@ -35,6 +36,8 @@ 
 /* FW names */
 
 #define QTN_PCI_PEARL_FW_NAME		"qtn/fmac_qsr10g.img"
+#define QTN_PCI_TOPAZ_FW_NAME		"qtn/fmac_qsr1000.img"
+#define QTN_PCI_TOPAZ_BOOTLD_NAME	"qtn/uboot_qsr1000.img"
 
 static inline unsigned int qtnf_chip_id_get(const void __iomem *regs_base)
 {
diff --git a/drivers/net/wireless/quantenna/qtnfmac/util.c b/drivers/net/wireless/quantenna/qtnfmac/util.c
index dfe3fe8a42c1..3bc96b264769 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/util.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/util.c
@@ -121,6 +121,8 @@  void qtnf_sta_list_free(struct qtnf_sta_list *list)
 const char *qtnf_chipid_to_string(unsigned long chip_id)
 {
 	switch (chip_id) {
+	case QTN_CHIP_ID_TOPAZ:
+		return "Topaz";
 	case QTN_CHIP_ID_PEARL:
 		return "Pearl revA";
 	case QTN_CHIP_ID_PEARL_B: