diff --git a/drivers/net/wireless/celeno/cl8k/twt_cli.c b/drivers/net/wireless/celeno/cl8k/twt_cli.c
new file mode 100644
index 000000000000..258df0c83c48
--- /dev/null
+++ b/drivers/net/wireless/celeno/cl8k/twt_cli.c
@@ -0,0 +1,359 @@
+// SPDX-License-Identifier: MIT
+/* Copyright(c) 2019-2021, Celeno Communications Ltd. */
+
+#include "twt_cli.h"
+#include "twt.h"
+#include "twt_frame.h"
+#include "sta.h"
+#include "utils/utils.h"
+
+#define CL_TWT_MIN_WAKE_DURATION_MAX_VAL_US (0xFFU << 10)
+
+static int cl_twt_cli_configuration_print(struct cl_hw *cl_hw)
+{
+       struct cl_tcv_conf *conf = cl_hw->conf;
+       char *buf = NULL;
+       ssize_t buf_size;
+       int err = 0;
+       int len = 0;
+
+       cl_snprintf(&buf, &len, &buf_size,
+                   "TWT configuration:\n");
+       cl_snprintf(&buf, &len, &buf_size,
+                   "TWT enabled:                   %u\n",
+                   conf->ce_twt_en);
+       cl_snprintf(&buf, &len, &buf_size,
+                   "TWT default interval:          %u\n",
+                   conf->ce_twt_default_interval);
+       cl_snprintf(&buf, &len, &buf_size,
+                   "TWT default min wake duration: %u\n",
+                   conf->ce_twt_default_min_wake_duration);
+       cl_snprintf(&buf, &len, &buf_size,
+                   "TWT num of sessions:           %u\n",
+                   cl_hw->twt_db.num_sessions);
+       cl_snprintf(&buf, &len, &buf_size,
+                   "TWT max sessions:              %u\n",
+                   conf->ce_twt_max_sessions);
+
+       err = cl_vendor_reply(cl_hw, buf, len);
+       kfree(buf);
+
+       return err;
+}
+
+static int cl_twt_cli_sessions_print(struct cl_hw *cl_hw)
+{
+       u8 i, handled_sessions = 0, num_sessions = cl_hw->twt_db.num_sessions;
+       u8 max_sessions = cl_hw->conf->ce_twt_max_sessions;
+       struct cl_twt_session_db *session;
+       char *buf = NULL;
+       ssize_t buf_size;
+       int err = 0;
+       int len = 0;
+
+       cl_snprintf(&buf, &len, &buf_size,
+                   "-----------------------------------------------------"
+                   "---------------------------------\n"
+                   "| STA idx | Flow id | Announced | Triggered | "
+                   "Interval us | Min wake    | Start time |\n"
+                   "|         |         |           |           |        "
+                   "     | duration us |            |\n");
+       for (i = 0, session = &cl_hw->twt_db.cl_twt_sessions[0];
+            (i < max_sessions) && (handled_sessions < num_sessions);
+            i++, session++) {
+               if (!session->cl_sta)
+                       continue;
+
+               cl_snprintf(&buf, &len, &buf_size,
+                           "+---------+---------+-----------+-----------+"
+                           "-------------+-------------+------------+\n"
+                           "|   %3u   |    %1u    |     %1u     |     %1u"
+                           "     | %10llu  |   %6u    | %10llu |\n",
+                           session->cl_sta->sta_idx,
+                           session->twt_setup.req_type.fields.flow_id,
+                           !(session->twt_setup.req_type.fields.flow_type),
+                           session->twt_setup.req_type.fields.trigger,
+                           cl_twt_get_wake_interval_us(&session->twt_setup),
+                           cl_twt_get_min_wake_time_us(&session->twt_setup),
+                           session->twt_setup.target_wake_time);
+               handled_sessions++;
+       }
+
+       cl_snprintf(&buf, &len, &buf_size,
+                   "-----------------------------------------------------"
+                   "---------------------------------\n");
+
+       err = cl_vendor_reply(cl_hw, buf, len);
+       kfree(buf);
+
+       return err;
+}
+
+static int cl_twt_cli_send_individual_setup_request(struct cl_hw *cl_hw,
+                                                   struct cli_params *cli_params,
+                                                   bool simulate)
+{
+       u8 sta_idx = (u8)cli_params->params[0];
+       u8 setup_cmd = (u8)cli_params->params[1];
+       u8 flow_id = (u8)cli_params->params[2];
+       bool announced = (bool)cli_params->params[3];
+       bool triggered = (bool)cli_params->params[4];
+       u32 interval = (u32)cli_params->params[5];
+       u32 min_wake_dur = (u32)cli_params->params[6];
+       struct cl_sta *cl_sta;
+       int res = -1;
+
+       if (!cl_twt_is_enabled(cl_hw)) {
+               cl_dbg_verbose(cl_hw, "Please enable TWT first\n");
+               return -1;
+       }
+
+       cl_sta_lock_bh(cl_hw);
+       cl_sta = cl_sta_get(cl_hw, sta_idx);
+
+       if (!cl_sta) {
+               cl_dbg_err(cl_hw, "Invalid sta idx %u\n", sta_idx);
+               goto out;
+       }
+
+       if (setup_cmd > IEEE80211_TWT_SETUP_COMMAND_DEMAND) {
+               cl_dbg_verbose(cl_hw,
+                              "Invalid setup cmd %u. Must be lower than %u\n",
+                              setup_cmd, IEEE80211_TWT_SETUP_COMMAND_DEMAND);
+               goto out;
+       }
+
+       if (flow_id > CL_TWT_FLOW_ID_MAX) {
+               cl_dbg_verbose(cl_hw,
+                              "Invalid flow id %u. Must be lower than %u\n",
+                              flow_id, CL_TWT_FLOW_ID_MAX);
+               goto out;
+       }
+
+       if (min_wake_dur > CL_TWT_MIN_WAKE_DURATION_MAX_VAL_US) {
+               cl_dbg_verbose(cl_hw,
+                              "Invalid min wake duration (%u). Must be no greater than %u\n",
+                              min_wake_dur, CL_TWT_MIN_WAKE_DURATION_MAX_VAL_US);
+               goto out;
+       }
+
+       if (min_wake_dur > interval) {
+               cl_dbg_verbose(cl_hw,
+                              "Min wake duration (%u) cannot be greater than interval (%u)\n",
+                              min_wake_dur, interval);
+               goto out;
+       }
+
+       cl_dbg_info(cl_hw,
+                   "sta_idx %u, setup_cmd %u, flow_id %u, announced %u, "
+                   "triggered %u, interval %u, min wake duration %u\n",
+                   sta_idx, setup_cmd, flow_id, announced, triggered, interval, min_wake_dur);
+
+       if (simulate)
+               res = cl_twt_frame_simulate_individual_setup_request(cl_hw, cl_sta,
+                                                                    setup_cmd, flow_id,
+                                                                    announced, triggered,
+                                                                    (u64)interval, min_wake_dur);
+       else
+               res = cl_twt_frame_send_individual_setup_request(cl_hw, cl_sta, setup_cmd,
+                                                                flow_id, announced, triggered,
+                                                                (u64)interval, min_wake_dur);
+
+out:
+       cl_sta_unlock_bh(cl_hw);
+
+       return res;
+}
+
+static int cl_twt_cli_send_individual_teardown_request(struct cl_hw *cl_hw,
+                                                      struct cli_params *cli_params,
+                                                      bool simulate)
+{
+       u8 sta_idx = (u8)cli_params->params[0];
+       u8 flow_id = (u8)cli_params->params[1];
+       struct cl_sta *cl_sta;
+       int res = 0;
+
+       if (!cl_twt_is_enabled(cl_hw)) {
+               cl_dbg_verbose(cl_hw, "Please enable TWT first\n");
+               return -1;
+       }
+
+       cl_sta_lock_bh(cl_hw);
+       cl_sta = cl_sta_get(cl_hw, sta_idx);
+
+       if (!cl_sta) {
+               cl_dbg_err(cl_hw, "Invalid sta idx %u\n", sta_idx);
+               res = -1;
+               goto out;
+       }
+
+       if (flow_id > CL_TWT_FLOW_ID_MAX && flow_id != CL_TWT_FLOW_ID_ALL) {
+               cl_dbg_verbose(cl_hw,
+                              "Invalid flow id %u. Must be lower than %u or %u\n",
+                              flow_id, CL_TWT_FLOW_ID_MAX, CL_TWT_FLOW_ID_ALL);
+               res = -1;
+               goto out;
+       }
+
+       if (simulate) {
+               res = cl_twt_frame_simulate_individual_teardown_request(cl_hw, cl_sta, flow_id);
+       } else {
+               /*
+                * If the session doesn't exist or couldn't be removed -
+                * send a teardown request anyway
+                */
+               if (cl_twt_teardown_individual_sesseion(cl_hw, cl_sta, flow_id, true))
+                       cl_twt_frame_send_individual_teardown_request(cl_hw, cl_sta, flow_id);
+       }
+
+out:
+       cl_sta_unlock_bh(cl_hw);
+
+       return res;
+}
+
+static void cl_twt_cli_enable(struct cl_hw *cl_hw, bool enable)
+{
+       if (cl_hw->conf->ce_twt_en == enable) {
+               pr_debug("TWT is already %s\n", enable ? "enabled" : "disabled");
+               return;
+       }
+
+       cl_hw->conf->ce_twt_en = enable;
+
+       if (enable)
+               cl_twt_init(cl_hw);
+       else
+               cl_twt_close(cl_hw);
+
+       pr_debug("TWT has been %s\n", (enable ? "enabled" : "disabled"));
+}
+
+static int cl_twt_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,
+                "twt usage\n"
+                "-c: Print configuration\n"
+                "-e: Enable/Disable TWT [0 - disable, 1 - enable]\n"
+                "-m: Simulate a reception of a TWT individual request:\n"
+                       "\t[sta_idx].[setup_cmd (0:request, 1:suggest, "
+                       "2:demand)].[flow_id (0-7))].[announced].[triggered]."
+                       "[interval].[min_wake_duration]\n"
+                "-n: Simulate a reception of a TWT individual teardown "
+                       "request:\n\t[sta_idx].[flow_id (0-7; 255 all_twt)]\n"
+                "-p: Print sessions\n"
+                "-s: Send a TWT individual setup request:\n"
+                       "\t[sta_idx].[setup_cmd (0:request, 1:suggest, "
+                       "2:demand)].[flow_id (0-7))].[announced].[triggered]."
+                       "[interval].[min_wake_duration]\n"
+                "-t: Send a TWT individual teardown request:\n"
+                       "\t[sta_idx].[flow_id (0-7; 255 for all_twt)]\n");
+
+       err = cl_vendor_reply(cl_hw, buf, strlen(buf));
+       kfree(buf);
+
+       return err;
+}
+
+int cl_twt_cli(struct cl_hw *cl_hw, struct cli_params *cli_params)
+{
+       u32 expected_params = 0;
+       bool print_conf = false;
+       bool enable = false;
+       bool simulate_req = false;
+       bool simulate_teardown = false;
+       bool print_sessions = false;
+       bool send_req = false;
+       bool send_teardown = false;
+
+       switch (cli_params->option) {
+       case 'c':
+               print_conf = true;
+               expected_params = 0;
+               break;
+       case 'e':
+               enable = true;
+               expected_params = 1;
+               break;
+       case 'm':
+               simulate_req = true;
+               expected_params = 7;
+               break;
+       case 'n':
+               simulate_teardown = true;
+               expected_params = 2;
+               break;
+       case 'p':
+               print_sessions = true;
+               expected_params = 0;
+               break;
+       case 's':
+               send_req = true;
+               expected_params = 7;
+               break;
+       case 't':
+               send_teardown = true;
+               expected_params = 2;
+               break;
+       case '?':
+               return cl_twt_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 (print_conf)
+               return cl_twt_cli_configuration_print(cl_hw);
+
+       if (enable) {
+               cl_twt_cli_enable(cl_hw, (bool)cli_params->params[0]);
+               return 0;
+       }
+
+       if (simulate_req) {
+               int res = cl_twt_cli_send_individual_setup_request(cl_hw, cli_params, true);
+
+               if (res)
+                       cl_dbg_verbose(cl_hw, "Error %d trying to simulate TWT request\n", res);
+
+               return 0;
+       }
+
+       if (simulate_teardown) {
+               cl_twt_cli_send_individual_teardown_request(cl_hw, cli_params, true);
+               return 0;
+       }
+
+       if (print_sessions)
+               return cl_twt_cli_sessions_print(cl_hw);
+
+       if (send_req) {
+               int res = cl_twt_cli_send_individual_setup_request(cl_hw, cli_params, false);
+
+               if (res)
+                       cl_dbg_verbose(cl_hw, "Error %d trying to send TWT request\n", res);
+
+               return 0;
+       }
+
+       if (send_teardown) {
+               cl_twt_cli_send_individual_teardown_request(cl_hw, cli_params, false);
+               return 0;
+       }
+
+out_err:
+       return -EIO;
+}
