diff --git a/doc/guides/rawdevs/cnxk_bphy.rst b/doc/guides/rawdevs/cnxk_bphy.rst
index 4015740f7..2edd814da 100644
--- a/doc/guides/rawdevs/cnxk_bphy.rst
+++ b/doc/guides/rawdevs/cnxk_bphy.rst
@@ -38,7 +38,8 @@ To perform data transfer use standard ``rte_rawdev_enqueue_buffers()`` and
 responses hence dequeueing is not always necessary.
 
 BPHY CGX/RPM PMD accepts ``struct cnxk_bphy_cgx_msg`` messages which differ by type and payload.
-Message types along with description are listed below.
+Message types along with description are listed below. As for the usage examples please refer to
+``cnxk_bphy_cgx_dev_selftest()``.
 
 Get link information
 ~~~~~~~~~~~~~~~~~~~~
@@ -93,3 +94,19 @@ Message is used to start or stop accepting traffic.
 Message must have type set to ``CNXK_BPHY_CGX_MSG_TYPE_START_RXTX`` or
 ``CNXK_BPHY_CGX_MSG_TYPE_STOP_RXTX``. Former will enable traffic while the latter will
 do the opposite.
+
+Self test
+---------
+
+On EAL initialization, BPHY CGX/RPM devices will be probed and populated into
+the raw devices. The rawdev ID of the device can be obtained using invocation
+of ``rte_rawdev_get_dev_id("NAME:x")`` from the test application, where:
+
+- NAME is the desired subsystem: use "BPHY_CGX" for
+  RFOE module,
+- x is the device's bus id specified in "bus:device.func" (BDF) format.
+
+Use this identifier for further rawdev function calls.
+
+The driver's selftest rawdev API can be used to verify the BPHY CGX/RPM
+functionality.
diff --git a/drivers/raw/cnxk_bphy/cnxk_bphy_cgx.c b/drivers/raw/cnxk_bphy/cnxk_bphy_cgx.c
index a8eafae1b..3da224414 100644
--- a/drivers/raw/cnxk_bphy/cnxk_bphy_cgx.c
+++ b/drivers/raw/cnxk_bphy/cnxk_bphy_cgx.c
@@ -9,6 +9,7 @@
 
 #include <roc_api.h>
 
+#include "cnxk_bphy_cgx.h"
 #include "rte_pmd_bphy.h"
 
 struct cnxk_bphy_cgx_queue {
@@ -196,6 +197,7 @@ static const struct rte_rawdev_ops cnxk_bphy_cgx_rawdev_ops = {
 	.enqueue_bufs = cnxk_bphy_cgx_enqueue_bufs,
 	.dequeue_bufs = cnxk_bphy_cgx_dequeue_bufs,
 	.queue_count = cnxk_bphy_cgx_queue_count,
+	.dev_selftest = cnxk_bphy_cgx_dev_selftest,
 };
 
 static void
diff --git a/drivers/raw/cnxk_bphy/cnxk_bphy_cgx.h b/drivers/raw/cnxk_bphy/cnxk_bphy_cgx.h
new file mode 100644
index 000000000..fb6b31bf4
--- /dev/null
+++ b/drivers/raw/cnxk_bphy/cnxk_bphy_cgx.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2021 Marvell.
+ */
+
+#ifndef _CNXK_BPHY_CGX_H_
+#define _CNXK_BPHY_CGX_H_
+
+int cnxk_bphy_cgx_dev_selftest(uint16_t dev_id);
+
+#endif /* _CNXK_BPHY_CGX_H_ */
diff --git a/drivers/raw/cnxk_bphy/cnxk_bphy_cgx_test.c b/drivers/raw/cnxk_bphy/cnxk_bphy_cgx_test.c
new file mode 100644
index 000000000..cb4dd4b22
--- /dev/null
+++ b/drivers/raw/cnxk_bphy/cnxk_bphy_cgx_test.c
@@ -0,0 +1,206 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2021 Marvell.
+ */
+#include <stdint.h>
+
+#include <rte_cycles.h>
+#include <rte_log.h>
+#include <rte_malloc.h>
+#include <rte_rawdev.h>
+
+#include "cnxk_bphy_cgx.h"
+#include "rte_pmd_bphy.h"
+
+static int
+cnxk_bphy_cgx_enq_msg(uint16_t dev_id, unsigned int queue, void *msg)
+{
+	struct rte_rawdev_buf *bufs[1];
+	struct rte_rawdev_buf buf;
+	void *q;
+	int ret;
+
+	q = (void *)(size_t)queue;
+	buf.buf_addr = msg;
+	bufs[0] = &buf;
+
+	ret = rte_rawdev_enqueue_buffers(dev_id, bufs, 1, q);
+	if (ret < 0)
+		return ret;
+	if (ret != 1)
+		return -EIO;
+
+	return 0;
+}
+
+static int
+cnxk_bphy_cgx_deq_msg(uint16_t dev_id, unsigned int queue, void **msg)
+{
+	struct rte_rawdev_buf *bufs[1];
+	struct rte_rawdev_buf buf;
+	void *q;
+	int ret;
+
+	q = (void *)(size_t)queue;
+	bufs[0] = &buf;
+
+	ret = rte_rawdev_dequeue_buffers(dev_id, bufs, 1, q);
+	if (ret < 0)
+		return ret;
+	if (ret != 1)
+		return -EIO;
+
+	*msg = buf.buf_addr;
+
+	return 0;
+}
+
+static int
+cnxk_bphy_cgx_link_cond(uint16_t dev_id, unsigned int queue, int cond)
+{
+	int tries = 10, ret;
+
+	do {
+		struct cnxk_bphy_cgx_msg_link_info *link_info = NULL;
+		struct cnxk_bphy_cgx_msg msg;
+
+		msg.type = CNXK_BPHY_CGX_MSG_TYPE_GET_LINKINFO;
+		ret = cnxk_bphy_cgx_enq_msg(dev_id, queue, &msg);
+		if (ret)
+			return ret;
+
+		ret = cnxk_bphy_cgx_deq_msg(dev_id, queue, (void **)&link_info);
+		if (ret)
+			return ret;
+
+		if (link_info->link_up == cond) {
+			rte_free(link_info);
+			break;
+		}
+
+		rte_free(link_info);
+		rte_delay_ms(500);
+	} while (--tries);
+
+	if (tries)
+		return !!cond;
+
+	return -ETIMEDOUT;
+}
+
+int
+cnxk_bphy_cgx_dev_selftest(uint16_t dev_id)
+{
+	unsigned int queues, i;
+	int ret;
+
+	queues = rte_rawdev_queue_count(dev_id);
+	if (queues == 0)
+		return -ENODEV;
+
+	ret = rte_rawdev_start(dev_id);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < queues; i++) {
+		struct cnxk_bphy_cgx_msg_set_link_state link_state;
+		struct cnxk_bphy_cgx_msg msg;
+		unsigned int descs;
+
+		ret = rte_rawdev_queue_conf_get(dev_id, i, &descs,
+						sizeof(descs));
+		if (ret)
+			break;
+		if (descs != 1) {
+			RTE_LOG(ERR, PMD, "Wrong number of descs reported\n");
+			ret = -ENODEV;
+			break;
+		}
+
+		RTE_LOG(INFO, PMD, "Testing queue %d\n", i);
+
+		/* stop rx/tx */
+		msg.type = CNXK_BPHY_CGX_MSG_TYPE_STOP_RXTX;
+		ret = cnxk_bphy_cgx_enq_msg(dev_id, i, &msg);
+		if (ret) {
+			RTE_LOG(ERR, PMD, "Failed to stop rx/tx\n");
+			break;
+		}
+
+		/* start rx/tx */
+		msg.type = CNXK_BPHY_CGX_MSG_TYPE_START_RXTX;
+		ret = cnxk_bphy_cgx_enq_msg(dev_id, i, &msg);
+		if (ret) {
+			RTE_LOG(ERR, PMD, "Failed to start rx/tx\n");
+			break;
+		}
+
+		/* set link down */
+		link_state.state = false;
+		msg.type = CNXK_BPHY_CGX_MSG_TYPE_SET_LINK_STATE;
+		msg.data = &link_state;
+		ret = cnxk_bphy_cgx_enq_msg(dev_id, i, &msg);
+		if (ret) {
+			RTE_LOG(ERR, PMD, "Failed to set link down\n");
+			break;
+		}
+
+		ret = cnxk_bphy_cgx_link_cond(dev_id, i, 0);
+		if (ret != 0)
+			RTE_LOG(ERR, PMD,
+				"Timed out waiting for a link down\n");
+
+		/* set link up */
+		link_state.state = true;
+		msg.type = CNXK_BPHY_CGX_MSG_TYPE_SET_LINK_STATE;
+		msg.data = &link_state;
+		ret = cnxk_bphy_cgx_enq_msg(dev_id, i, &msg);
+		if (ret) {
+			RTE_LOG(ERR, PMD, "Failed to set link up\n");
+			break;
+		}
+
+		ret = cnxk_bphy_cgx_link_cond(dev_id, i, 1);
+		if (ret != 1)
+			RTE_LOG(ERR, PMD, "Timed out waiting for a link up\n");
+
+		/* enable internal loopback */
+		msg.type = CNXK_BPHY_CGX_MSG_TYPE_INTLBK_ENABLE;
+		ret = cnxk_bphy_cgx_enq_msg(dev_id, i, &msg);
+		if (ret) {
+			RTE_LOG(ERR, PMD, "Failed to enable internal lbk\n");
+			break;
+		}
+
+		/* disable internal loopback */
+		msg.type = CNXK_BPHY_CGX_MSG_TYPE_INTLBK_DISABLE;
+		ret = cnxk_bphy_cgx_enq_msg(dev_id, i, &msg);
+		if (ret) {
+			RTE_LOG(ERR, PMD, "Failed to disable internal lbk\n");
+			break;
+		}
+
+		/* enable ptp */
+		msg.type = CNXK_BPHY_CGX_MSG_TYPE_PTP_RX_ENABLE;
+		ret = cnxk_bphy_cgx_enq_msg(dev_id, i, &msg);
+		/* ptp not available on RPM */
+		if (ret < 0 && ret != -ENOTSUP) {
+			RTE_LOG(ERR, PMD, "Failed to enable ptp\n");
+			break;
+		}
+		ret = 0;
+
+		/* disable ptp */
+		msg.type = CNXK_BPHY_CGX_MSG_TYPE_PTP_RX_DISABLE;
+		ret = cnxk_bphy_cgx_enq_msg(dev_id, i, &msg);
+		/* ptp not available on RPM */
+		if (ret < 0 && ret != -ENOTSUP) {
+			RTE_LOG(ERR, PMD, "Failed to disable ptp\n");
+			break;
+		}
+		ret = 0;
+	}
+
+	rte_rawdev_stop(dev_id);
+
+	return ret;
+}
diff --git a/drivers/raw/cnxk_bphy/meson.build b/drivers/raw/cnxk_bphy/meson.build
index 0d65fc5c8..dc5558ee8 100644
--- a/drivers/raw/cnxk_bphy/meson.build
+++ b/drivers/raw/cnxk_bphy/meson.build
@@ -5,5 +5,6 @@
 deps += ['bus_pci', 'common_cnxk', 'rawdev']
 sources = files(
         'cnxk_bphy_cgx.c',
+        'cnxk_bphy_cgx_test.c',
 )
 headers = files('rte_pmd_bphy.h')
