diff --git a/drivers/net/wireless/celeno/cl8k/reg/reg_cli.c b/drivers/net/wireless/celeno/cl8k/reg/reg_cli.c
new file mode 100644
index 000000000000..2293b09c0da9
--- /dev/null
+++ b/drivers/net/wireless/celeno/cl8k/reg/reg_cli.c
@@ -0,0 +1,277 @@
+// SPDX-License-Identifier: MIT
+/* Copyright(c) 2019-2021, Celeno Communications Ltd. */
+
+#include "reg/reg_cli.h"
+#include "reg/reg_access.h"
+#include "reg/reg_modem_gcu.h"
+
+/*
+ * iwcl command to read registers and write to registers:
+ * iwcl <iface> cecli reg.-r.<reg_addr>
+ * iwcl <iface> cecli reg.-w.<reg_addr>.<reg_value>
+ * iwcl <iface> cecli reg.-m.<reg_addr>.<reg_value>.<reg_mask>
+ * iwcl <iface> cecli reg.-x.<type>.<offset>.<value>
+ * iwcl <iface> cecli reg.-y.<type>.<offset>
+ * iwcl <iface> cecli reg.-z.<type>.<offset>.<value>.<mask>
+ * reg_value should not include PCI base address
+ * The advantage of using iwcl command instead of mem command is
+ * that with using the iwcl command the relevant registers will
+ * be added to the recovery procedure.
+ */
+
+static int cl_reg_cli_write_mask(struct cl_hw *cl_hw, u32 address, u32 value, u32 mask)
+{
+       char reply_str[7] = {0};
+       u16 reply_strlen = 0;
+       int ret = cl_reg_write_mask(cl_hw, address, value, mask);
+
+       cl_dbg_verbose(cl_hw, "WRITE: Address = 0x%x, Value = 0x%x, Mask = 0x%x\n",
+                      address, value, mask);
+       reply_strlen = snprintf(&reply_str[0], sizeof(reply_str), "ret=%d", ret);
+
+       return cl_vendor_reply(cl_hw, reply_str, reply_strlen);
+}
+
+static int cl_reg_cli_read(struct cl_hw *cl_hw, u32 address)
+{
+       u32 value = cl_reg_read(cl_hw, address);
+       char reply_str[11] = {0};
+       u16 reply_strlen = 0;
+
+       reply_strlen = snprintf(&reply_str[0], sizeof(reply_str), "0x%08x", value);
+
+       return cl_vendor_reply(cl_hw, reply_str, reply_strlen);
+}
+
+static void cl_reg_cli_read_block(struct cl_hw *cl_hw, u32 first_address, u32 last_address)
+{
+       u32 address = first_address;
+       u32 value;
+
+       if ((first_address & 0x3) != 0) {
+               pr_err("Invalid first address - 0x%x\n", first_address);
+               return;
+       }
+
+       if ((last_address & 0x3) != 0) {
+               pr_err("Invalid last address - 0x%x\n", last_address);
+               return;
+       }
+
+       if (first_address > last_address) {
+               pr_err("Invalid addresses - first [0x%x] > last [0x%x]\n",
+                      first_address, last_address);
+               return;
+       }
+
+       pr_debug("-------------------------\n");
+       pr_debug("| Address  | Value      |\n");
+       pr_debug("|----------+------------|\n");
+
+       while (address <= last_address) {
+               value = cl_reg_read(cl_hw, address);
+               pr_debug("| 0x%06x | 0x%08x |\n", address, value);
+               address += 4;
+       }
+
+       pr_debug("-------------------------\n");
+}
+
+static int cl_reg_cli_write(struct cl_hw *cl_hw, u32 address, u32 value)
+{
+       char reply_str[7] = {0};
+       u16 reply_strlen = 0;
+       int ret = cl_reg_write(cl_hw, address, value);
+
+       cl_dbg_verbose(cl_hw, "WRITE: Address = 0x%x, Value = 0x%x\n", address, value);
+       reply_strlen = snprintf(&reply_str[0], sizeof(reply_str), "ret=%d", ret);
+
+       return cl_vendor_reply(cl_hw, reply_str, reply_strlen);
+}
+
+static int cl_reg_cli_write_type(struct cl_hw *cl_hw, u32 type, u32 offset, u32 value)
+{
+       if (type == 0) /* GCU */
+               cl_reg_write_direct(cl_hw, REG_MODEM_GCU_BASE_ADDR + offset, value);
+       else /* RIU */
+               cl_reg_write_direct(cl_hw, REG_RIU_BASE_ADDR + offset, value);
+
+       return 0;
+}
+
+static int cl_reg_cli_read_type(struct cl_hw *cl_hw, u32 type, u32 offset)
+{
+       u32 base = (type == 0) ? REG_MODEM_GCU_BASE_ADDR : REG_RIU_BASE_ADDR;
+
+       return cl_reg_cli_read(cl_hw, base + offset);
+}
+
+static int cl_reg_cli_write_type_mask(struct cl_hw *cl_hw, u32 type, u32 offset,
+                                     u32 value, u32 mask)
+{
+       if (type == 0) /* GCU */
+               cl_reg_write_mask(cl_hw, REG_MODEM_GCU_BASE_ADDR + offset, value, mask);
+       else /* RIU */
+               cl_reg_write_mask(cl_hw, REG_RIU_BASE_ADDR + offset, value, mask);
+
+       return 0;
+}
+
+static int cl_reg_cli_set_debug(struct cl_hw *cl_hw, bool enable)
+{
+       if (enable) {
+               cl_hw->reg_dbg = true;
+               cl_hw->chip->reg_dbg |= (1 << cl_hw->tcv_idx);
+       } else {
+               cl_hw->reg_dbg = false;
+               cl_hw->chip->reg_dbg &= ~(1 << cl_hw->tcv_idx);
+       }
+
+       return 0;
+}
+
+static int cl_reg_cli_help(struct cl_hw *cl_hw)
+{
+       char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+       int err = 0;
+
+       if (!buf)
+               return -ENOMEM;
+
+       snprintf(buf, PAGE_SIZE,
+                "reg usage:\n"
+                "-d : Set debug [0:Disable|1:Enable]\n"
+                "-m : Write masked value to address [address].[value].[mask]\n"
+                "-r : Read address [address]\n"
+                "-s : Read block [first address].[last address]\n"
+                "-w : Write value to address [address].[value]\n"
+                "-x : Write type [0:GCU|1:RIU][offset].[value]\n"
+                "-y : Read type [0:GCU|1:RIU][offset]\n"
+                "-z : Write type mask [0:GCU|1:RIU][offset].[value].[mask]\n");
+
+       err = cl_vendor_reply(cl_hw, buf, strlen(buf));
+       kfree(buf);
+
+       return err;
+}
+
+int cl_reg_cli(struct cl_hw *cl_hw, struct cli_params *cli_params)
+{
+       u32 expected_params = 0;
+       bool set_debug = false;
+       bool reg_write_mask = false;
+       bool reg_read = false;
+       bool reg_read_block = false;
+       bool reg_write = false;
+       bool reg_write_type = false;
+       bool reg_read_type = false;
+       bool reg_write_type_mask = false;
+
+       switch (cli_params->option) {
+       case 'd':
+               set_debug = true;
+               expected_params = 1;
+               break;
+       case 'm':
+               reg_write_mask = true;
+               expected_params = 3;
+               break;
+       case 'r':
+               reg_read = true;
+               expected_params = 1;
+               break;
+       case 's':
+               reg_read_block = true;
+               expected_params = 2;
+               break;
+       case 'w':
+               reg_write = true;
+               expected_params = 2;
+               break;
+       case 'x':
+               reg_write_type = true;
+               expected_params = 3;
+               break;
+       case 'y':
+               reg_read_type = true;
+               expected_params = 2;
+               break;
+       case 'z':
+               reg_write_type_mask = true;
+               expected_params = 4;
+               break;
+       case '?':
+               return cl_reg_cli_help(cl_hw);
+       default:
+               cl_dbg_err(cl_hw, "Illegal option (%c) - try '?' for help\n", cli_params->option);
+               goto out_err;
+       }
+
+       if (expected_params != cli_params->num_params) {
+               cl_dbg_err(cl_hw, "Wrong number of arguments (expected %u) (actual %u)\n",
+                          expected_params, cli_params->num_params);
+               goto out_err;
+       }
+
+       if (set_debug) {
+               u32 enable = cli_params->params[0];
+
+               return cl_reg_cli_set_debug(cl_hw, enable);
+       }
+
+       if (reg_write_mask) {
+               u32 address = cli_params->params[0];
+               u32 value = cli_params->params[1];
+               u32 mask = cli_params->params[2];
+
+               return cl_reg_cli_write_mask(cl_hw, address, value, mask);
+       }
+
+       if (reg_read) {
+               u32 address = cli_params->params[0];
+
+               return cl_reg_cli_read(cl_hw, address);
+       }
+
+       if (reg_read_block) {
+               u32 first_address = cli_params->params[0];
+               u32 last_address = cli_params->params[1];
+
+               cl_reg_cli_read_block(cl_hw, first_address, last_address);
+               return 0;
+       }
+
+       if (reg_write) {
+               u32 address = cli_params->params[0];
+               u32 value = cli_params->params[1];
+
+               return cl_reg_cli_write(cl_hw, address, value);
+       }
+
+       if (reg_write_type) {
+               u32 type = cli_params->params[0];
+               u32 offset = cli_params->params[1];
+               u32 value = cli_params->params[2];
+
+               return cl_reg_cli_write_type(cl_hw, type, offset, value);
+       }
+
+       if (reg_read_type) {
+               u32 type = cli_params->params[0];
+               u32 offset = cli_params->params[1];
+
+               return cl_reg_cli_read_type(cl_hw, type, offset);
+       }
+
+       if (reg_write_type_mask) {
+               u32 type = cli_params->params[0];
+               u32 offset = cli_params->params[1];
+               u32 value = cli_params->params[2];
+               u32 mask = cli_params->params[3];
+
+               return cl_reg_cli_write_type_mask(cl_hw, type, offset, value, mask);
+       }
+
+out_err:
+       return -EIO;
+}
