new file mode 100644
@@ -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;
+}