diff --git a/drivers/net/wireless/quantenna/qtnfmac/bus.h b/drivers/net/wireless/quantenna/qtnfmac/bus.h
index 87d048df09d1..7811833a84c9 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/bus.h
+++ b/drivers/net/wireless/quantenna/qtnfmac/bus.h
@@ -67,6 +67,7 @@ struct qtnf_bus {
 	struct mutex bus_lock; /* lock during command/event processing */
 	struct dentry *dbg_dir;
 	struct notifier_block netdev_nb;
+	struct net_device *svc_ndev;
 	u8 hw_id[ETH_ALEN];
 	/* bus private data */
 	char bus_priv[0] __aligned(sizeof(void *));
diff --git a/drivers/net/wireless/quantenna/qtnfmac/commands.c b/drivers/net/wireless/quantenna/qtnfmac/commands.c
index f40d8c3c3d9e..984300ac2f30 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/commands.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/commands.c
@@ -980,6 +980,7 @@ qtnf_cmd_resp_proc_hw_info(struct qtnf_bus *bus,
 
 	strlcpy(hwinfo->fw_version, bld_label, sizeof(hwinfo->fw_version));
 	hwinfo->hw_version = hw_ver;
+	hwinfo->svc_mode = qtnf_hwcap_is_set(hwinfo, QLINK_HW_CAPAB_SVC_MODE);
 
 	return 0;
 }
diff --git a/drivers/net/wireless/quantenna/qtnfmac/core.c b/drivers/net/wireless/quantenna/qtnfmac/core.c
index eea777f8acea..0b93ab7cca61 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/core.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/core.c
@@ -244,6 +244,49 @@ const struct net_device_ops qtnf_netdev_ops = {
 	.ndo_get_port_parent_id = qtnf_netdev_port_parent_id,
 };
 
+static netdev_tx_t
+qtnf_svc_ndev_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+	struct qtnf_bus *bus = *((void **)netdev_priv(ndev));
+
+	if (unlikely(!bus)) {
+		pr_err_ratelimited("invalid ndev settings");
+		dev_kfree_skb_any(skb);
+		return 0;
+	}
+
+	if (unlikely(skb->dev != ndev)) {
+		pr_err_ratelimited("invalid skb->dev");
+		dev_kfree_skb_any(skb);
+		return 0;
+	}
+
+	if (!skb->len || skb->len > ETH_FRAME_LEN) {
+		pr_err_ratelimited("%s: invalid skb len %d\n", ndev->name,
+				   skb->len);
+		dev_kfree_skb_any(skb);
+		ndev->stats.tx_dropped++;
+		return 0;
+	}
+
+	return qtnf_bus_data_tx(bus, skb, 0, 0);
+}
+
+static const struct net_device_ops svc_netdev_ops = {
+	.ndo_start_xmit = qtnf_svc_ndev_xmit,
+	.ndo_set_mac_address = eth_mac_addr,
+	.ndo_validate_addr = eth_validate_addr,
+};
+
+static void svc_ndev_setup(struct net_device *dev)
+{
+	dev->netdev_ops = &svc_netdev_ops;
+	dev->needs_free_netdev = true;
+	ether_setup(dev);
+	dev->priv_flags |= IFF_NO_QUEUE;
+	eth_hw_addr_random(dev);
+}
+
 static int qtnf_mac_init_single_band(struct wiphy *wiphy,
 				     struct qtnf_wmac *mac,
 				     enum nl80211_band band)
@@ -736,6 +779,7 @@ static int qtnf_core_netdevice_event(struct notifier_block *nb,
 int qtnf_core_attach(struct qtnf_bus *bus)
 {
 	unsigned int i;
+	void *priv;
 	int ret;
 
 	qtnf_trans_init(bus);
@@ -779,6 +823,38 @@ int qtnf_core_attach(struct qtnf_bus *bus)
 		goto error;
 	}
 
+	if (bus->hw_info.svc_mode) {
+		bus->svc_ndev = alloc_netdev(sizeof(struct qtnf_bus *),
+					     "eth%d", NET_NAME_UNKNOWN,
+					     svc_ndev_setup);
+		if (!bus->svc_ndev) {
+			ret = -ENOMEM;
+			goto error;
+		}
+
+		rtnl_lock();
+		ret = dev_alloc_name(bus->svc_ndev, bus->svc_ndev->name);
+		if (ret < 0) {
+			rtnl_unlock();
+			free_netdev(bus->svc_ndev);
+			bus->svc_ndev = NULL;
+			goto error;
+		}
+
+		priv = netdev_priv(bus->svc_ndev);
+		*((void **)priv) = bus;
+
+		ret = register_netdevice(bus->svc_ndev);
+		if (ret < 0) {
+			rtnl_unlock();
+			free_netdev(bus->svc_ndev);
+			bus->svc_ndev = NULL;
+			goto error;
+		}
+		rtnl_unlock();
+		goto done;
+	}
+
 	if (qtnf_hwcap_is_set(&bus->hw_info, QLINK_HW_CAPAB_HW_BRIDGE) &&
 	    bus->bus_ops->data_tx_use_meta_set)
 		bus->bus_ops->data_tx_use_meta_set(bus, true);
@@ -806,6 +882,7 @@ int qtnf_core_attach(struct qtnf_bus *bus)
 		goto error;
 	}
 
+done:
 	bus->fw_state = QTNF_FW_STATE_RUNNING;
 	return 0;
 
@@ -822,6 +899,9 @@ void qtnf_core_detach(struct qtnf_bus *bus)
 	unregister_netdevice_notifier(&bus->netdev_nb);
 	qtnf_bus_data_rx_stop(bus);
 
+	if (bus->svc_ndev)
+		unregister_netdev(bus->svc_ndev);
+
 	for (macid = 0; macid < QTNF_MAX_MAC; macid++)
 		qtnf_core_mac_detach(bus, macid);
 
@@ -862,6 +942,9 @@ struct net_device *qtnf_classify_skb(struct qtnf_bus *bus, struct sk_buff *skb)
 	if (unlikely(bus->fw_state != QTNF_FW_STATE_RUNNING))
 		return NULL;
 
+	if (unlikely(bus->svc_ndev))
+		return bus->svc_ndev;
+
 	meta = (struct qtnf_frame_meta_info *)
 		(skb_tail_pointer(skb) - sizeof(*meta));
 
diff --git a/drivers/net/wireless/quantenna/qtnfmac/core.h b/drivers/net/wireless/quantenna/qtnfmac/core.h
index 269ce12cf8bf..9745b27379ba 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/core.h
+++ b/drivers/net/wireless/quantenna/qtnfmac/core.h
@@ -116,6 +116,7 @@ struct qtnf_hw_info {
 	u8 num_mac;
 	u8 mac_bitmap;
 	u32 fw_ver;
+	u8 svc_mode;
 	u8 total_tx_chain;
 	u8 total_rx_chain;
 	char fw_version[ETHTOOL_FWVERS_LEN];
diff --git a/drivers/net/wireless/quantenna/qtnfmac/pcie/pcie.c b/drivers/net/wireless/quantenna/qtnfmac/pcie/pcie.c
index 5337e67092ca..8e025a27a4df 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/pcie/pcie.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/pcie/pcie.c
@@ -41,6 +41,10 @@ static u8 flashboot = 1;
 module_param(flashboot, byte, 0644);
 MODULE_PARM_DESC(flashboot, "set to 0 to use FW binary file on FS");
 
+static u8 force_svcmode;
+module_param(force_svcmode, byte, 0644);
+MODULE_PARM_DESC(force_svcmode, "set to 1 to force boot into service mode");
+
 static unsigned int fw_blksize_param = QTN_PCIE_MAX_FW_BUFSZ;
 module_param(fw_blksize_param, uint, 0644);
 MODULE_PARM_DESC(fw_blksize_param, "firmware loading block size in bytes");
@@ -342,6 +346,7 @@ static int qtnf_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 	pcie_priv->pdev = pdev;
 	pcie_priv->tx_stopped = 0;
 	pcie_priv->flashboot = flashboot;
+	pcie_priv->force_svcmode = force_svcmode;
 
 	if (fw_blksize_param > QTN_PCIE_MAX_FW_BUFSZ)
 		pcie_priv->fw_blksize =  QTN_PCIE_MAX_FW_BUFSZ;
diff --git a/drivers/net/wireless/quantenna/qtnfmac/pcie/pcie_priv.h b/drivers/net/wireless/quantenna/qtnfmac/pcie/pcie_priv.h
index 2a6a928e13bd..93cb651a3236 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/pcie/pcie_priv.h
+++ b/drivers/net/wireless/quantenna/qtnfmac/pcie/pcie_priv.h
@@ -66,7 +66,8 @@ struct qtnf_pcie_bus_priv {
 
 	u8 msi_enabled;
 	u8 tx_stopped;
-	bool flashboot;
+	u8 flashboot;
+	u8 force_svcmode;
 };
 
 int qtnf_pcie_control_tx(struct qtnf_bus *bus, struct sk_buff *skb);
diff --git a/drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie.c b/drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie.c
index d1b850aa4657..f3f517733f99 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/pcie/topaz_pcie.c
@@ -866,6 +866,9 @@ static int qtnf_pre_init_ep(struct qtnf_bus *bus)
 	/* notify card about driver type and boot mode */
 	flags = readl(&bda->bda_flags) | QTN_BDA_HOST_QLINK_DRV;
 
+	if (ts->base.force_svcmode)
+		flags |= QTN_BDA_HOST_CALCMD;
+
 	if (ts->base.flashboot)
 		flags |= QTN_BDA_FLASH_BOOT;
 	else
diff --git a/drivers/net/wireless/quantenna/qtnfmac/qlink.h b/drivers/net/wireless/quantenna/qtnfmac/qlink.h
index 4d22a54c034f..3abbe0b6e32b 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/qlink.h
+++ b/drivers/net/wireless/quantenna/qtnfmac/qlink.h
@@ -73,6 +73,7 @@ struct qlink_msg_header {
  *	Randomization in probe requests.
  * @QLINK_HW_CAPAB_OBSS_SCAN: device can perform OBSS scanning.
  * @QLINK_HW_CAPAB_HW_BRIDGE: device has hardware switch capabilities.
+ * @QLINK_HW_CAPAB_SVC_MODE: device is in service mode (used for calibration).
  */
 enum qlink_hw_capab {
 	QLINK_HW_CAPAB_REG_UPDATE = 0,
@@ -84,6 +85,7 @@ enum qlink_hw_capab {
 	QLINK_HW_CAPAB_SCAN_DWELL,
 	QLINK_HW_CAPAB_SAE,
 	QLINK_HW_CAPAB_HW_BRIDGE,
+	QLINK_HW_CAPAB_SVC_MODE,
 	QLINK_HW_CAPAB_NUM
 };
 
