qcacmn: Add TDLS off channel changes for TDLS UMAC

Add TDLS offchannel and related osif changes in TDLS
UMAC component.

Change-Id: Ic562709eb8a6361322860c4f7333633a7894af99
CRs-Fixed: 2034220
diff --git a/os_if/linux/tdls/inc/wlan_cfg80211_tdls.h b/os_if/linux/tdls/inc/wlan_cfg80211_tdls.h
index c4efaad..812c8bc 100644
--- a/os_if/linux/tdls/inc/wlan_cfg80211_tdls.h
+++ b/os_if/linux/tdls/inc/wlan_cfg80211_tdls.h
@@ -33,6 +33,7 @@
 #include <qdf_types.h>
 #include <wlan_tdls_ucfg_api.h>
 
+#ifdef CONVERGED_TDLS_ENABLE
 
 #define TDLS_VDEV_MAGIC 0x54444c53       /* "TDLS" */
 
@@ -51,6 +52,7 @@
 	struct completion tdls_del_peer_comp;
 	struct completion tdls_mgmt_comp;
 	struct completion tdls_link_establish_req_comp;
+	struct completion tdls_teardown_comp;
 	QDF_STATUS tdls_add_peer_status;
 	uint32_t mgmt_tx_completion_status;
 };
@@ -191,4 +193,76 @@
 void wlan_cfg80211_tdls_rx_callback(void *user_data,
 	struct tdls_rx_mgmt_frame *rx_frame);
 
+/**
+ * hdd_notify_sta_connect() - notify sta connect to TDLS
+ * @session_id: pointer to soc object
+ * @tdls_chan_swit_prohibited: indicates channel switch capability
+ * @tdls_prohibited: indicates tdls allowed or not
+ * @vdev: vdev object manager
+ *
+ * Notify sta connect event to TDLS component
+ *
+ * Return: None
+ */
+void
+hdd_notify_sta_connect(uint8_t session_id,
+		       bool tdls_chan_swit_prohibited,
+		       bool tdls_prohibited,
+		       struct wlan_objmgr_vdev *vdev);
+
+/**
+ * hdd_notify_sta_disconnect() - notify sta disconnect to TDLS
+ * @session_id: pointer to soc object
+ * @lfr_roam: indicate, whether disconnect due to lfr roam
+ * @vdev: vdev object manager
+ *
+ * Notify sta disconnect event to TDLS component
+ *
+ * Return: None
+ */
+void hdd_notify_sta_disconnect(uint8_t session_id,
+			       bool lfr_roam,
+			       struct wlan_objmgr_vdev *vdev);
+
+/**
+ * hdd_notify_teardown_tdls_links() - notify TDLS to teardown links
+ * @vdev: vdev object manager
+ *
+ * Notify tdls to teardown all the links, due to certain events
+ * in the system
+ *
+ * Return: None
+ */
+void hdd_notify_teardown_tdls_links(struct wlan_objmgr_vdev *vdev);
+
+#else
+static inline void
+hdd_notify_sta_connect(uint8_t session_id,
+		       bool tdls_chan_swit_prohibited,
+		       bool tdls_prohibited,
+		       struct wlan_objmgr_vdev *vdev)
+{
+}
+
+static inline
+void hdd_notify_sta_disconnect(uint8_t session_id,
+			       bool lfr_roam,
+			       struct wlan_objmgr_vdev *vdev)
+{
+
+}
+
+static inline
+int wlan_cfg80211_tdls_configure_mode(struct wlan_objmgr_vdev *vdev,
+						uint32_t trigger_mode)
+{
+	return 0;
+}
+
+static inline
+void hdd_notify_teardown_tdls_links(struct wlan_objmgr_vdev *vdev)
+{
+
+}
+#endif
 #endif
diff --git a/os_if/linux/tdls/src/wlan_cfg80211_tdls.c b/os_if/linux/tdls/src/wlan_cfg80211_tdls.c
index 714b5f5..238d27b 100644
--- a/os_if/linux/tdls/src/wlan_cfg80211_tdls.c
+++ b/os_if/linux/tdls/src/wlan_cfg80211_tdls.c
@@ -53,6 +53,7 @@
 	init_completion(&tdls_priv->tdls_del_peer_comp);
 	init_completion(&tdls_priv->tdls_mgmt_comp);
 	init_completion(&tdls_priv->tdls_link_establish_req_comp);
+	init_completion(&tdls_priv->tdls_teardown_comp);
 
 	osif_priv->osif_tdls = tdls_priv;
 
@@ -67,6 +68,72 @@
 	osif_priv->osif_tdls = NULL;
 }
 
+void hdd_notify_teardown_tdls_links(struct wlan_objmgr_vdev *vdev)
+{
+	struct vdev_osif_priv *osif_priv;
+	struct osif_tdls_vdev *tdls_priv;
+	QDF_STATUS status;
+	unsigned long rc;
+
+	if (!vdev)
+		return;
+
+	wlan_vdev_obj_lock(vdev);
+	osif_priv = wlan_vdev_get_ospriv(vdev);
+	wlan_vdev_obj_unlock(vdev);
+
+	tdls_priv = osif_priv->osif_tdls;
+
+	reinit_completion(&tdls_priv->tdls_teardown_comp);
+	status = ucfg_tdls_teardown_links(vdev);
+	if (QDF_IS_STATUS_ERROR(status)) {
+		cfg80211_err("ucfg_tdls_teardown_links failed err %d", status);
+		return;
+	}
+
+	cfg80211_info("Wait for tdls teardown completion. Timeout %u ms",
+		WAIT_TIME_FOR_TDLS_TEARDOWN_LINKS);
+
+	rc = wait_for_completion_timeout(
+		&tdls_priv->tdls_teardown_comp,
+		msecs_to_jiffies(WAIT_TIME_FOR_TDLS_TEARDOWN_LINKS));
+
+	if (0 == rc) {
+		cfg80211_err(" Teardown Completion timed out rc: %ld", rc);
+		return;
+	}
+
+	cfg80211_info("TDLS teardown completion status %ld ", rc);
+}
+
+void
+hdd_notify_sta_connect(uint8_t session_id,
+		       bool tdls_chan_swit_prohibited,
+		       bool tdls_prohibited,
+		       struct wlan_objmgr_vdev *vdev)
+{
+	struct tdls_sta_notify_params notify_info;
+
+	notify_info.session_id = session_id;
+	notify_info.vdev = vdev;
+	notify_info.tdls_chan_swit_prohibited = tdls_chan_swit_prohibited;
+	notify_info.tdls_prohibited = tdls_prohibited;
+	ucfg_tdls_notify_sta_connect(&notify_info);
+
+}
+
+void hdd_notify_sta_disconnect(uint8_t session_id,
+			       bool lfr_roam,
+			       struct wlan_objmgr_vdev *vdev)
+{
+	struct tdls_sta_notify_params notify_info;
+
+	notify_info.session_id = session_id;
+	notify_info.lfr_roam = lfr_roam;
+	notify_info.vdev = vdev;
+	ucfg_tdls_notify_sta_disconnect(&notify_info);
+
+}
 int wlan_cfg80211_tdls_add_peer(struct wlan_objmgr_pdev *pdev,
 				struct net_device *dev, const uint8_t *mac)
 {
@@ -385,14 +452,14 @@
 
 	switch (trigger_mode) {
 	case WLAN_VENDOR_TDLS_TRIGGER_MODE_EXPLICIT:
-		tdls_mode = TDLS_SUPPORT_IMP_MODE;
-		break;
+		tdls_mode = TDLS_SUPPORT_EXP_TRIG_ONLY;
+		return 0;
 	case WLAN_VENDOR_TDLS_TRIGGER_MODE_EXTERNAL:
 		tdls_mode = TDLS_SUPPORT_EXT_CONTROL;
 		break;
 	case WLAN_VENDOR_TDLS_TRIGGER_MODE_IMPLICIT:
 		tdls_mode = TDLS_SUPPORT_IMP_MODE;
-		break;
+		return 0;
 	default:
 		cfg80211_err("Invalid TDLS trigger mode");
 		return -EINVAL;
@@ -752,6 +819,9 @@
 	case TDLS_EVENT_SETUP_REQ:
 		wlan_cfg80211_tdls_indicate_setup(ind);
 		break;
+	case TDLS_EVENT_TEARDOWN_LINKS_DONE:
+		complete(&tdls_priv->tdls_teardown_comp);
+		break;
 	default:
 		break;
 	}
diff --git a/umac/tdls/core/src/wlan_tdls_cmds_process.c b/umac/tdls/core/src/wlan_tdls_cmds_process.c
index 26594e3..35cd7f3 100644
--- a/umac/tdls/core/src/wlan_tdls_cmds_process.c
+++ b/umac/tdls/core/src/wlan_tdls_cmds_process.c
@@ -43,7 +43,7 @@
  *
  * Return: None.
  */
-static void tdls_decrement_peer_count(struct tdls_soc_priv_obj *soc_obj)
+void tdls_decrement_peer_count(struct tdls_soc_priv_obj *soc_obj)
 {
 	if (soc_obj->connected_peer_count)
 		soc_obj->connected_peer_count--;
diff --git a/umac/tdls/core/src/wlan_tdls_cmds_process.h b/umac/tdls/core/src/wlan_tdls_cmds_process.h
index ec2f40e..5e2c035 100644
--- a/umac/tdls/core/src/wlan_tdls_cmds_process.h
+++ b/umac/tdls/core/src/wlan_tdls_cmds_process.h
@@ -365,4 +365,14 @@
  */
 int tdls_set_responder(struct tdls_set_responder_req *set_req);
 
+/**
+ * tdls_decrement_peer_count() - decrement connected TDLS peer counter
+ * @soc_obj: TDLS soc object
+ *
+ * Used in scheduler thread context, no lock needed.
+ *
+ * Return: None.
+ */
+void tdls_decrement_peer_count(struct tdls_soc_priv_obj *soc_obj);
+
 #endif
diff --git a/umac/tdls/core/src/wlan_tdls_ct.c b/umac/tdls/core/src/wlan_tdls_ct.c
index cb48dd2..3b5b554 100644
--- a/umac/tdls/core/src/wlan_tdls_ct.c
+++ b/umac/tdls/core/src/wlan_tdls_ct.c
@@ -25,6 +25,7 @@
 #include "wlan_tdls_main.h"
 #include "wlan_tdls_peer.h"
 #include "wlan_tdls_ct.h"
+#include "wlan_tdls_cmds_process.h"
 
 bool tdls_is_vdev_connected(struct wlan_objmgr_vdev *vdev)
 {
@@ -936,3 +937,386 @@
 				     WLAN_TDLS_NB_ID);
 }
 
+/**
+ * tdls_set_tdls_offchannel() - set tdls off-channel number
+ * @tdls_soc: tdls soc object
+ * @offchanmode: tdls off-channel number
+ *
+ * This function sets tdls off-channel number
+ *
+ * Return: 0 on success; negative errno otherwise
+ */
+static
+int tdls_set_tdls_offchannel(struct tdls_soc_priv_obj *tdls_soc,
+			     int offchannel)
+{
+	uint32_t tdls_feature_flags;
+
+	tdls_feature_flags = tdls_soc->tdls_configs.tdls_feature_flags;
+
+	if (TDLS_IS_OFF_CHANNEL_ENABLED(tdls_feature_flags) &&
+	   (TDLS_SUPPORT_EXP_TRIG_ONLY == tdls_soc->tdls_current_mode ||
+	    TDLS_SUPPORT_IMP_MODE == tdls_soc->tdls_current_mode ||
+	    TDLS_SUPPORT_EXT_CONTROL == tdls_soc->tdls_current_mode)) {
+		if (offchannel < TDLS_PREFERRED_OFF_CHANNEL_NUM_MIN ||
+			offchannel > TDLS_PREFERRED_OFF_CHANNEL_NUM_MAX) {
+			tdls_err("Invalid tdls off channel %u", offchannel);
+			return -EINVAL;
+			}
+	} else {
+		tdls_err("Either TDLS or TDLS Off-channel is not enabled");
+		return  -ENOTSUPP;
+	}
+	tdls_notice("change tdls off channel from %d to %d",
+		   tdls_soc->tdls_off_channel, offchannel);
+	tdls_soc->tdls_off_channel = offchannel;
+	return 0;
+}
+
+/**
+ * tdls_set_tdls_secoffchanneloffset() - set secondary tdls off-channel offset
+ * @tdls_soc: tdls soc object
+ * @offchanmode: tdls off-channel offset
+ *
+ * This function sets 2nd tdls off-channel offset
+ *
+ * Return: 0 on success; negative errno otherwise
+ */
+static
+int tdls_set_tdls_secoffchanneloffset(struct tdls_soc_priv_obj *tdls_soc,
+				int offchanoffset)
+{
+	uint32_t tdls_feature_flags;
+
+	tdls_feature_flags = tdls_soc->tdls_configs.tdls_feature_flags;
+
+	if (!TDLS_IS_OFF_CHANNEL_ENABLED(tdls_feature_flags) ||
+	    TDLS_SUPPORT_SUSPENDED >= tdls_soc->tdls_current_mode) {
+		tdls_err("Either TDLS or TDLS Off-channel is not enabled");
+		return  -ENOTSUPP;
+	}
+
+	tdls_soc->tdls_channel_offset = 0;
+
+	switch (offchanoffset) {
+	case TDLS_SEC_OFFCHAN_OFFSET_0:
+		tdls_soc->tdls_channel_offset = (1 << BW_20_OFFSET_BIT);
+		break;
+	case TDLS_SEC_OFFCHAN_OFFSET_40PLUS:
+	case TDLS_SEC_OFFCHAN_OFFSET_40MINUS:
+		tdls_soc->tdls_channel_offset = (1 << BW_40_OFFSET_BIT);
+		break;
+	case TDLS_SEC_OFFCHAN_OFFSET_80:
+		tdls_soc->tdls_channel_offset = (1 << BW_80_OFFSET_BIT);
+		break;
+	case TDLS_SEC_OFFCHAN_OFFSET_160:
+		tdls_soc->tdls_channel_offset = (1 << BW_160_OFFSET_BIT);
+		break;
+	default:
+		tdls_err("Invalid tdls secondary off channel offset %d",
+			offchanoffset);
+		return -EINVAL;
+	} /* end switch */
+
+	tdls_notice("change tdls secondary off channel offset to 0x%x",
+		    tdls_soc->tdls_channel_offset);
+	return 0;
+}
+
+/**
+ * tdls_set_tdls_offchannelmode() - set tdls off-channel mode
+ * @adapter: Pointer to the HDD adapter
+ * @offchanmode: tdls off-channel mode
+ *
+ * This function sets tdls off-channel mode
+ *
+ * Return: 0 on success; negative errno otherwise
+ */
+static
+int tdls_set_tdls_offchannelmode(struct wlan_objmgr_vdev *vdev,
+				 int offchanmode)
+{
+	struct tdls_peer *conn_peer = NULL;
+	struct tdls_channel_switch_params chan_switch_params;
+	QDF_STATUS status = QDF_STATUS_E_FAILURE;
+	int ret_value = 0;
+	struct tdls_vdev_priv_obj *tdls_vdev;
+	struct tdls_soc_priv_obj *tdls_soc;
+	uint32_t tdls_feature_flags;
+
+
+	status = tdls_get_vdev_objects(vdev, &tdls_vdev, &tdls_soc);
+
+	if (status != QDF_STATUS_SUCCESS)
+		return -EINVAL;
+
+
+	if (offchanmode < ENABLE_CHANSWITCH ||
+			offchanmode > DISABLE_CHANSWITCH) {
+		tdls_err("Invalid tdls off channel mode %d", offchanmode);
+		return -EINVAL;
+	}
+
+	if (tdls_is_vdev_connected(vdev)) {
+		tdls_err("tdls off channel req in not associated state %d",
+			offchanmode);
+		return -EPERM;
+	}
+
+	tdls_feature_flags = tdls_soc->tdls_configs.tdls_feature_flags;
+	if (!TDLS_IS_OFF_CHANNEL_ENABLED(tdls_feature_flags) ||
+	    TDLS_SUPPORT_SUSPENDED >= tdls_soc->tdls_current_mode) {
+		tdls_err("Either TDLS or TDLS Off-channel is not enabled");
+		return  -ENOTSUPP;
+	}
+
+	conn_peer = tdls_find_first_connected_peer(tdls_vdev);
+	if (NULL == conn_peer) {
+		tdls_err("No TDLS Connected Peer");
+		return -EPERM;
+	}
+
+	tdls_notice("TDLS Channel Switch in swmode=%d tdls_off_channel %d offchanoffset %d",
+		   offchanmode, tdls_soc->tdls_off_channel,
+		   tdls_soc->tdls_channel_offset);
+
+	switch (offchanmode) {
+	case ENABLE_CHANSWITCH:
+		if (tdls_soc->tdls_off_channel &&
+			tdls_soc->tdls_channel_offset) {
+			chan_switch_params.tdls_off_ch =
+				tdls_soc->tdls_off_channel;
+			chan_switch_params.tdls_off_ch_bw_offset =
+				tdls_soc->tdls_channel_offset;
+			chan_switch_params.oper_class =
+			   tdls_find_opclass(tdls_soc->soc,
+				chan_switch_params.tdls_off_ch,
+				chan_switch_params.tdls_off_ch_bw_offset);
+		} else {
+			tdls_err("TDLS off-channel parameters are not set yet!!!");
+			return -EINVAL;
+
+		}
+		break;
+	case DISABLE_CHANSWITCH:
+		chan_switch_params.tdls_off_ch = 0;
+		chan_switch_params.tdls_off_ch_bw_offset = 0;
+		chan_switch_params.oper_class = 0;
+		break;
+	default:
+		tdls_err("Incorrect Parameters mode: %d tdls_off_channel: %d offchanoffset: %d",
+			offchanmode, tdls_soc->tdls_off_channel,
+			tdls_soc->tdls_channel_offset);
+		return -EINVAL;
+	} /* end switch */
+
+	chan_switch_params.vdev_id = tdls_vdev->session_id;
+	chan_switch_params.tdls_sw_mode = offchanmode;
+	chan_switch_params.is_responder =
+		conn_peer->is_responder;
+	qdf_mem_copy(&chan_switch_params.peer_mac_addr,
+		     &conn_peer->peer_mac.bytes,
+		     QDF_MAC_ADDR_SIZE);
+	tdls_notice("Peer " QDF_MAC_ADDRESS_STR " vdevId: %d, off channel: %d, offset: %d, mode: %d, is_responder: %d",
+		 QDF_MAC_ADDR_ARRAY(chan_switch_params.peer_mac_addr),
+		 chan_switch_params.vdev_id,
+		 chan_switch_params.tdls_off_ch,
+		 chan_switch_params.tdls_off_ch_bw_offset,
+		 chan_switch_params.tdls_sw_mode,
+		 chan_switch_params.is_responder);
+
+	status = tdls_set_offchan_mode(tdls_soc->soc,
+				       &chan_switch_params);
+
+	if (status != QDF_STATUS_SUCCESS) {
+		tdls_err("Failed to send channel switch request to wmi");
+		return -EINVAL;
+	}
+
+	tdls_soc->tdls_fw_off_chan_mode = offchanmode;
+
+	if (ENABLE_CHANSWITCH == offchanmode) {
+		conn_peer = tdls_find_first_connected_peer(tdls_vdev);
+		if (NULL == conn_peer) {
+			tdls_err("No TDLS Connected Peer");
+			return -EPERM;
+		}
+		conn_peer->pref_off_chan_num =
+			chan_switch_params.tdls_off_ch;
+		conn_peer->op_class_for_pref_off_chan =
+			chan_switch_params.oper_class;
+	}
+
+	return ret_value;
+}
+
+/**
+ * tdls_delete_all_tdls_peers(): send request to delete tdls peers
+ * @vdev: vdev object
+ * @tdls_soc: tdls soc object
+ *
+ * This function sends request to lim to delete tdls peers
+ *
+ * Return: QDF_STATUS
+ */
+static
+QDF_STATUS tdls_delete_all_tdls_peers(struct wlan_objmgr_vdev *vdev,
+					  struct tdls_soc_priv_obj *tdls_soc)
+{
+	struct wlan_objmgr_peer *peer;
+	struct tdls_del_all_tdls_peers *del_msg;
+	struct scheduler_msg msg;
+	QDF_STATUS status;
+
+
+	del_msg = qdf_mem_malloc(sizeof(*del_msg));
+	if (NULL == del_msg) {
+		tdls_err("memory alloc failed");
+		return QDF_STATUS_E_FAILURE;
+	}
+	qdf_mem_zero(del_msg, sizeof(*del_msg));
+
+	peer = wlan_vdev_get_bsspeer(vdev);
+	if (QDF_STATUS_SUCCESS != wlan_objmgr_peer_try_get_ref(peer,
+							WLAN_TDLS_SB_ID)) {
+		return QDF_STATUS_E_FAILURE;
+	}
+
+	qdf_mem_copy(del_msg->bssid.bytes,
+		     wlan_peer_get_macaddr(peer), QDF_MAC_ADDR_SIZE);
+
+	del_msg->msg_type = tdls_soc->tdls_del_all_peers;
+	del_msg->msg_len = (uint16_t) sizeof(*del_msg);
+
+	/* Send the request to PE. */
+	qdf_mem_zero(&msg, sizeof(msg));
+
+	tdls_debug("sending delete all peers req to PE ");
+
+	msg.type = del_msg->msg_type;
+	msg.bodyptr = del_msg;
+
+	status = scheduler_post_msg(QDF_MODULE_ID_PE, &msg);
+
+	wlan_objmgr_peer_release_ref(peer, WLAN_TDLS_SB_ID);
+	return status;
+}
+
+void tdls_disable_offchan_and_teardown_links(
+				struct wlan_objmgr_vdev *vdev)
+{
+	uint16_t connected_tdls_peers = 0;
+	uint8_t staidx;
+	struct tdls_peer *curr_peer = NULL;
+	struct tdls_vdev_priv_obj *tdls_vdev;
+	struct tdls_soc_priv_obj *tdls_soc;
+	QDF_STATUS status;
+
+	status = tdls_get_vdev_objects(vdev, &tdls_vdev, &tdls_soc);
+	if (QDF_STATUS_SUCCESS != status) {
+		tdls_err("tdls objects are NULL ");
+		return;
+	}
+
+	if (TDLS_SUPPORT_SUSPENDED >= tdls_soc->tdls_current_mode) {
+		tdls_notice("TDLS mode %d is disabled OR not suspended now",
+			   tdls_soc->tdls_current_mode);
+		return;
+	}
+
+	connected_tdls_peers = tdls_soc->connected_peer_count;
+
+	if (!connected_tdls_peers) {
+		tdls_notice("No TDLS connected peers to delete");
+		return;
+	}
+
+	/* TDLS is not supported in case of concurrency.
+	 * Disable TDLS Offchannel in FW to avoid more
+	 * than two concurrent channels and generate TDLS
+	 * teardown indication to supplicant.
+	 * Below function Finds the first connected peer and
+	 * disables TDLS offchannel for that peer.
+	 * FW enables TDLS offchannel only when there is
+	 * one TDLS peer. When there are more than one TDLS peer,
+	 * there will not be TDLS offchannel in FW.
+	 * So to avoid sending multiple request to FW, for now,
+	 * just invoke offchannel mode functions only once
+	 */
+	tdls_set_tdls_offchannel(tdls_soc,
+				tdls_soc->tdls_configs.tdls_pre_off_chan_num);
+	tdls_set_tdls_secoffchanneloffset(tdls_soc,
+			TDLS_SEC_OFFCHAN_OFFSET_40PLUS);
+	tdls_set_tdls_offchannelmode(vdev, DISABLE_CHANSWITCH);
+
+	/* Send Msg to PE for deleting all the TDLS peers */
+	tdls_delete_all_tdls_peers(vdev, tdls_soc);
+
+	for (staidx = 0; staidx < tdls_soc->max_num_tdls_sta;
+							staidx++) {
+		if (!tdls_soc->tdls_conn_info[staidx].sta_id)
+			continue;
+
+		curr_peer = tdls_find_all_peer(tdls_soc,
+			tdls_soc->tdls_conn_info[staidx].peer_mac.bytes);
+		if (!curr_peer)
+			continue;
+
+		tdls_notice("indicate TDLS teardown (staId %d)",
+			   curr_peer->sta_id);
+
+		/* Indicate teardown to supplicant */
+		tdls_indicate_teardown(tdls_vdev,
+				       curr_peer,
+				       TDLS_TEARDOWN_PEER_UNSPEC_REASON);
+
+		/*
+		 * Del Sta happened already as part of tdls_delete_all_tdls_peers
+		 * Hence clear tdls vdev data structure.
+		 */
+		tdls_reset_peer(tdls_vdev, curr_peer->peer_mac.bytes);
+
+		if (tdls_soc->tdls_dereg_tl_peer)
+			tdls_soc->tdls_dereg_tl_peer(
+					tdls_soc->tdls_tl_peer_data,
+					wlan_vdev_get_id(vdev),
+					curr_peer->sta_id);
+
+		tdls_decrement_peer_count(tdls_soc);
+		tdls_soc->tdls_conn_info[staidx].sta_id = 0;
+		tdls_soc->tdls_conn_info[staidx].session_id = 255;
+
+		qdf_mem_zero(&tdls_soc->tdls_conn_info[staidx].peer_mac,
+			     sizeof(struct qdf_mac_addr));
+	}
+}
+
+void tdls_teardown_connections(struct wlan_objmgr_vdev *vdev)
+{
+	struct tdls_osif_indication indication;
+	struct tdls_soc_priv_obj *tdls_soc;
+	struct wlan_objmgr_vdev *tdls_vdev_obj;
+
+	if (!vdev) {
+		QDF_ASSERT(0);
+		return;
+	}
+
+	tdls_soc = wlan_vdev_get_tdls_soc_obj(vdev);
+	if (!tdls_soc)
+		return;
+
+	/* Get the tdls specific vdev and clear the links */
+	tdls_vdev_obj = tdls_get_vdev(tdls_soc->soc, WLAN_TDLS_SB_ID);
+	if (tdls_vdev_obj) {
+		tdls_disable_offchan_and_teardown_links(tdls_vdev_obj);
+		wlan_objmgr_vdev_release_ref(tdls_vdev_obj, WLAN_TDLS_SB_ID);
+	}
+
+	indication.vdev = vdev;
+
+	if (tdls_soc->tdls_event_cb)
+		tdls_soc->tdls_event_cb(tdls_soc->tdls_evt_cb_data,
+				     TDLS_EVENT_TEARDOWN_LINKS_DONE,
+				     &indication);
+}
diff --git a/umac/tdls/core/src/wlan_tdls_ct.h b/umac/tdls/core/src/wlan_tdls_ct.h
index e497dae..63e3063 100644
--- a/umac/tdls/core/src/wlan_tdls_ct.h
+++ b/umac/tdls/core/src/wlan_tdls_ct.h
@@ -31,6 +31,10 @@
   */
 #define TDLS_DISCOVERY_TIMEOUT_ERE_UPDATE     1000
 
+#define TDLS_PREFERRED_OFF_CHANNEL_NUM_MIN      1
+#define TDLS_PREFERRED_OFF_CHANNEL_NUM_MAX      165
+#define TDLS_PREFERRED_OFF_CHANNEL_NUM_DEFAULT  36
+
 /**
  * tdls_is_vdev_connected() - check the vdev is connected to ap
  * @vdev: vdev object manager
@@ -172,4 +176,22 @@
  */
 bool tdls_is_vdev_authenticated(struct wlan_objmgr_vdev *vdev);
 
+/**
+ * tdls_teardown_connections() -teardown and delete all the tdls peers
+ * @vdev: vdev oobject
+ *
+ * Return: true or false
+ */
+void tdls_teardown_connections(struct wlan_objmgr_vdev *vdev);
+
+/**
+ * tdls_disable_offchan_and_teardown_links - Disable offchannel
+ * and teardown TDLS links
+ * @tdls_soc : tdls soc object
+ *
+ * Return: None
+ */
+void tdls_disable_offchan_and_teardown_links(
+				struct wlan_objmgr_vdev *vdev);
+
 #endif
diff --git a/umac/tdls/core/src/wlan_tdls_main.c b/umac/tdls/core/src/wlan_tdls_main.c
index c58d868..f98d993 100644
--- a/umac/tdls/core/src/wlan_tdls_main.c
+++ b/umac/tdls/core/src/wlan_tdls_main.c
@@ -287,6 +287,9 @@
 	case TDLS_CMD_SESSION_DECREMENT:
 		tdls_process_policy_mgr_notification(msg->bodyptr);
 		break;
+	case TDLS_CMD_TEARDOWN_LINKS:
+		tdls_teardown_connections(msg->bodyptr);
+		break;
 	default:
 		break;
 	}
@@ -468,6 +471,20 @@
 
 }
 
+QDF_STATUS tdls_set_offchan_mode(struct wlan_objmgr_psoc *psoc,
+				     struct tdls_channel_switch_params *param)
+{
+	QDF_STATUS status;
+
+	/*  wmi_unified_set_tdls_offchan_mode_cmd() will be called directly */
+	status = tgt_tdls_set_offchan_mode(psoc, param);
+
+	if (!QDF_IS_STATUS_SUCCESS(status))
+		status = QDF_STATUS_E_FAILURE;
+
+	return status;
+}
+
 /**
  * tdls_update_fw_tdls_state() - update tdls status info
  * @tdls_soc_obj: TDLS soc object
@@ -745,12 +762,15 @@
 			tdls_soc_obj->set_state_info.set_state_cnt);
 		/* disable off channel and teardown links */
 		/* Go through the peer list and delete them */
+		tdls_disable_offchan_and_teardown_links(tdls_vdev_obj->vdev);
 		tdls_soc_obj->tdls_current_mode = TDLS_SUPPORT_DISABLED;
 		tdls_info_to_fw->vdev_id = tdls_soc_obj->set_state_info.vdev_id;
 	} else {
 		tdls_info_to_fw->vdev_id = session_id;
 	}
 
+	/* record the session id in vdev context */
+	tdls_vdev_obj->session_id = session_id;
 	tdls_info_to_fw->tdls_state = tdls_soc_obj->tdls_current_mode;
 	tdls_info_to_fw->tdls_options = 0;
 
diff --git a/umac/tdls/core/src/wlan_tdls_main.h b/umac/tdls/core/src/wlan_tdls_main.h
index bfbddf0..f6096f2 100644
--- a/umac/tdls/core/src/wlan_tdls_main.h
+++ b/umac/tdls/core/src/wlan_tdls_main.h
@@ -180,6 +180,7 @@
  * @tdls_add_sta_req: store eWNI_SME_TDLS_ADD_STA_REQ value
  * @tdls_del_sta_req: store eWNI_SME_TDLS_DEL_STA_REQ value
  * @tdls_update_peer_state: store WMA_UPDATE_TDLS_PEER_STATE value
+ * @tdls_del_all_peers:store eWNI_SME_DEL_ALL_TDLS_PEERS
  * @tdls_idle_peer_data: provide information about idle peer
  * @tdls_ct_spinlock: connection tracker spin lock
  */
@@ -221,6 +222,7 @@
 	uint16_t tdls_add_sta_req;
 	uint16_t tdls_del_sta_req;
 	uint16_t tdls_update_peer_state;
+	uint16_t tdls_del_all_peers;
 	struct tdls_ct_idle_peer_data tdls_idle_peer_data;
 	qdf_spinlock_t tdls_ct_spinlock;
 };
@@ -255,6 +257,7 @@
 			ct_peer_table[WLAN_TDLS_CT_TABLE_SIZE];
 	uint8_t valid_mac_entries;
 	uint32_t magic;
+	uint8_t session_id;
 	qdf_list_t tx_queue;
 };
 
@@ -667,4 +670,16 @@
 void tdls_scan_serialization_comp_info_cb(
 		union wlan_serialization_rules_info *comp_info);
 
+/**
+ * tdls_set_offchan_mode() - update tdls status info
+ * @psoc: soc object
+ * @param: channel switch params
+ *
+ * send message to WMI to set TDLS off channel in f/w
+ *
+ * Return: QDF_STATUS.
+ */
+QDF_STATUS tdls_set_offchan_mode(struct wlan_objmgr_psoc *psoc,
+				     struct tdls_channel_switch_params *param);
+
 #endif
diff --git a/umac/tdls/core/src/wlan_tdls_peer.c b/umac/tdls/core/src/wlan_tdls_peer.c
index a89736f..56e5ec7 100644
--- a/umac/tdls/core/src/wlan_tdls_peer.c
+++ b/umac/tdls/core/src/wlan_tdls_peer.c
@@ -125,7 +125,7 @@
 	return tdls_search_param.peer;
 }
 
-static uint8_t tdls_find_opclass(struct wlan_objmgr_psoc *psoc, uint8_t channel,
+uint8_t tdls_find_opclass(struct wlan_objmgr_psoc *psoc, uint8_t channel,
 				 uint8_t bw_offset)
 {
 	char country[REG_ALPHA2_LEN + 1];
diff --git a/umac/tdls/core/src/wlan_tdls_peer.h b/umac/tdls/core/src/wlan_tdls_peer.h
index 511b4d4..7a510c1 100644
--- a/umac/tdls/core/src/wlan_tdls_peer.h
+++ b/umac/tdls/core/src/wlan_tdls_peer.h
@@ -85,6 +85,20 @@
 tdls_find_all_peer(struct tdls_soc_priv_obj *soc_obj, const uint8_t *macaddr);
 
 /**
+ * tdls_find_all_peer() - find peer matching the input MACaddr in soc range
+ * @soc_obj: TDLS soc object
+ * @channel:channel number
+ * @bw_offset: offset to bandwidth
+ *
+ * This is in scheduler thread context, no lock required.
+ *
+ * Return: Operating class
+ */
+uint8_t tdls_find_opclass(struct wlan_objmgr_psoc *psoc,
+				 uint8_t channel,
+				 uint8_t bw_offset);
+
+/**
  * tdls_find_first_connected_peer() - find the 1st connected tdls peer from vdev
  * @vdev_obj: tdls vdev object
  *
diff --git a/umac/tdls/dispatcher/inc/wlan_tdls_public_structs.h b/umac/tdls/dispatcher/inc/wlan_tdls_public_structs.h
index 36e3a46..3632af5 100644
--- a/umac/tdls/dispatcher/inc/wlan_tdls_public_structs.h
+++ b/umac/tdls/dispatcher/inc/wlan_tdls_public_structs.h
@@ -29,7 +29,6 @@
 #include <qdf_mc_timer.h>
 #include <wlan_cmn.h>
 #include <wlan_cmn_ieee80211.h>
-#include <wlan_objmgr_psoc_obj.h>
 
 
 #define WLAN_TDLS_STA_MAX_NUM                        8
@@ -63,6 +62,9 @@
 /** Maximum time(ms) to wait for tdls mgmt to complete **/
 #define WAIT_TIME_FOR_TDLS_MGMT         11000
 
+/** Maximum waittime for TDLS teardown links **/
+#define WAIT_TIME_FOR_TDLS_TEARDOWN_LINKS 10000
+
 #define TDLS_TEARDOWN_PEER_UNREACHABLE   25
 #define TDLS_TEARDOWN_PEER_UNSPEC_REASON 26
 
@@ -188,6 +190,7 @@
 	TDLS_CMD_SET_TDLS_MODE,
 	TDLS_CMD_SESSION_INCREMENT,
 	TDLS_CMD_SESSION_DECREMENT,
+	TDLS_CMD_TEARDOWN_LINKS,
 };
 
 /**
@@ -210,6 +213,7 @@
 	TDLS_EVENT_DISCOVERY_REQ,
 	TDLS_EVENT_TEARDOWN_REQ,
 	TDLS_EVENT_SETUP_REQ,
+	TDLS_EVENT_TEARDOWN_LINKS_DONE,
 };
 
 /**
@@ -547,6 +551,7 @@
 	uint16_t tdls_add_sta_req;
 	uint16_t tdls_del_sta_req;
 	uint16_t tdls_update_peer_state;
+	uint16_t tdls_del_all_peers;
 	tdls_rx_callback tdls_rx_cb;
 	void *tdls_rx_cb_data;
 	tdls_wmm_check tdls_wmm_cb;
@@ -1001,4 +1006,16 @@
 	enum tdls_disable_sources source;
 };
 
+/**
+ * struct tdls_del_all_tdls_peers - delete all tdls peers
+ * @msg_type: type of message
+ * @msg_len: length of message
+ * @bssid: bssid of peer device
+ */
+struct tdls_del_all_tdls_peers {
+	uint16_t msg_type;
+	uint16_t msg_len;
+	struct qdf_mac_addr bssid;
+};
+
 #endif
diff --git a/umac/tdls/dispatcher/inc/wlan_tdls_ucfg_api.h b/umac/tdls/dispatcher/inc/wlan_tdls_ucfg_api.h
index 6c7d117..5d99669 100644
--- a/umac/tdls/dispatcher/inc/wlan_tdls_ucfg_api.h
+++ b/umac/tdls/dispatcher/inc/wlan_tdls_ucfg_api.h
@@ -139,6 +139,14 @@
 QDF_STATUS ucfg_tdls_responder(struct tdls_set_responder_req *msg_req);
 
 /**
+ * ucfg_tdls_teardown_links() - teardown all TDLS links
+ * @vdev: vdev object manager
+ *
+ * Return: None
+ */
+QDF_STATUS ucfg_tdls_teardown_links(struct wlan_objmgr_vdev *vdev);
+
+/**
  * ucfg_tdls_notify_sta_connect() - notify sta connect
  * @notify_info: sta notification info
  *
diff --git a/umac/tdls/dispatcher/src/wlan_tdls_ucfg_api.c b/umac/tdls/dispatcher/src/wlan_tdls_ucfg_api.c
index ecef61f..4412c58 100644
--- a/umac/tdls/dispatcher/src/wlan_tdls_ucfg_api.c
+++ b/umac/tdls/dispatcher/src/wlan_tdls_ucfg_api.c
@@ -216,6 +216,7 @@
 	soc_obj->tdls_add_sta_req = req->tdls_add_sta_req;
 	soc_obj->tdls_del_sta_req = req->tdls_del_sta_req;
 	soc_obj->tdls_update_peer_state = req->tdls_update_peer_state;
+	soc_obj->tdls_del_all_peers = req->tdls_del_all_peers;
 	tdls_pm_call_backs.tdls_notify_increment_session =
 			tdls_notify_increment_session;
 
@@ -238,6 +239,7 @@
 	else
 		soc_obj->tdls_current_mode = TDLS_SUPPORT_IMP_MODE;
 
+	soc_obj->tdls_last_mode = soc_obj->tdls_current_mode;
 	return QDF_STATUS_SUCCESS;
 }
 
@@ -538,6 +540,26 @@
 	return status;
 }
 
+QDF_STATUS ucfg_tdls_teardown_links(struct wlan_objmgr_vdev *vdev)
+{
+	QDF_STATUS status;
+	struct scheduler_msg msg = {0, };
+
+	if (!vdev) {
+		tdls_err("vdev is NULL ");
+		return QDF_STATUS_E_NULL_VALUE;
+	}
+	tdls_notice("Enter ");
+
+	msg.bodyptr = vdev;
+	msg.callback = tdls_process_cmd;
+	msg.type = TDLS_CMD_TEARDOWN_LINKS;
+	status = scheduler_post_msg(QDF_MODULE_ID_OS_IF, &msg);
+
+	tdls_notice("Exit ");
+	return status;
+}
+
 QDF_STATUS ucfg_tdls_notify_sta_connect(
 			struct tdls_sta_notify_params *notify_info)
 {