/*
 * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
 * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/**
 * DOC: wlan_hdd_btc_chain_mode.c
 *
 * The implementation of bt coex chain mode configuration
 *
 */

#include "wlan_hdd_main.h"
#include "wlan_hdd_btc_chain_mode.h"
#include "osif_sync.h"
#include "wlan_coex_ucfg_api.h"
#include "wlan_hdd_object_manager.h"
#include "wlan_cfg80211_coex.h"

static QDF_STATUS
wlan_hdd_btc_chain_mode_handler(struct wlan_objmgr_vdev *vdev)
{
	QDF_STATUS status;
	struct hdd_adapter *adapter;
	mac_handle_t mac_handle;
	uint8_t nss, band;
	enum coex_btc_chain_mode mode;
	uint8_t vdev_id;
	uint32_t freq;
	struct wlan_objmgr_psoc *psoc;
	struct wlan_hdd_link_info *link_info;

	if (!vdev) {
		hdd_err("NULL vdev");
		return QDF_STATUS_E_INVAL;
	}

	vdev_id = wlan_vdev_get_id(vdev);
	if (wlan_hdd_validate_vdev_id(vdev_id))
		return QDF_STATUS_E_INVAL;

	psoc = wlan_vdev_get_psoc(vdev);
	if (!psoc) {
		hdd_err("NULL psoc");
		return QDF_STATUS_E_INVAL;
	}

	link_info = wlan_hdd_get_link_info_from_vdev(psoc, vdev_id);
	if (!link_info) {
		hdd_err("Invalid vdev");
		return QDF_STATUS_E_INVAL;
	}

	adapter =  link_info->adapter;
	status = ucfg_coex_psoc_get_btc_chain_mode(psoc, &mode);
	if (QDF_IS_STATUS_ERROR(status)) {
		hdd_err("failed to get cur BTC chain mode, status %d", status);
		return -EFAULT;
	}

	mac_handle = adapter->hdd_ctx->mac_handle;
	if (!mac_handle) {
		hdd_err("NULL MAC handle");
		return -EINVAL;
	}

	nss = ((mode == WLAN_COEX_BTC_CHAIN_MODE_FDD ||
		mode == WLAN_COEX_BTC_CHAIN_MODE_HYBRID) ? 1 : 2);

	hdd_debug("update nss to %d for vdev %d, device mode %d",
		  nss, link_info->vdev_id, adapter->device_mode);
	band = NSS_CHAINS_BAND_2GHZ;
	sme_update_nss_in_mlme_cfg(mac_handle, nss, nss,
				   adapter->device_mode, band);
	sme_update_vdev_type_nss(mac_handle, nss, band);

	status = wlan_objmgr_vdev_try_get_ref(vdev, WLAN_OSIF_ID);
	if (QDF_IS_STATUS_SUCCESS(status)) {
		hdd_store_nss_chains_cfg_in_vdev(adapter->hdd_ctx, vdev);
		wlan_objmgr_vdev_release_ref(vdev, WLAN_OSIF_ID);
	}

	sme_update_he_cap_nss(mac_handle, link_info->vdev_id, nss);
	freq = hdd_get_adapter_home_channel(adapter);

	/*
	 * BT coex chain mode is for COEX between BT and WiFi-2.4G.
	 * Nss and related parameters have been updated upon for
	 * NSS_CHAINS_BAND_2GHZ.
	 * If the current home channel is NOT 2.4G, these parameters
	 * will take effect when switching to 2.4G, so no need to do
	 * restart here.
	 */
	if (!WLAN_REG_IS_24GHZ_CH_FREQ(freq))
		return QDF_STATUS_SUCCESS;

	switch (adapter->device_mode) {
	case QDF_STA_MODE:
	case QDF_P2P_CLIENT_MODE:
		wlan_hdd_cm_issue_disconnect(adapter,
					     REASON_PREV_AUTH_NOT_VALID, false);
		break;
	case QDF_SAP_MODE:
	case QDF_P2P_GO_MODE:
		hdd_restart_sap(adapter);
		break;
	default:
		break;
	}

	return QDF_STATUS_SUCCESS;
}

void wlan_hdd_register_btc_chain_mode_handler(struct wlan_objmgr_psoc *psoc)
{
	ucfg_coex_register_cfg_updated_handler(psoc,
					       COEX_CONFIG_BTC_CHAIN_MODE,
					       wlan_hdd_btc_chain_mode_handler
					       );
}

/**
 * __wlan_hdd_cfg80211_set_btc_chain_mode() - set btc chain mode
 * @wiphy: pointer to wireless wiphy structure.
 * @wdev: pointer to wireless_dev structure.
 * @data: pointer to btc chain mode command parameters.
 * @data_len: the length in byte of btc chain mode command parameters.
 *
 * Return: An error code or 0 on success.
 */
static int __wlan_hdd_cfg80211_set_btc_chain_mode(struct wiphy *wiphy,
						  struct wireless_dev *wdev,
						  const void *data,
						  int data_len)
{
	struct hdd_adapter *adapter = WLAN_HDD_GET_PRIV_PTR(wdev->netdev);
	struct hdd_context *hdd_ctx = wiphy_priv(wiphy);
	int errno;
	struct wlan_objmgr_vdev *vdev;

	hdd_enter_dev(wdev->netdev);

	if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
		hdd_err("Command not allowed in FTM mode");
		return -EPERM;
	}

	errno = wlan_hdd_validate_context(hdd_ctx);
	if (errno != 0)
		return errno;

	if (hdd_ctx->num_rf_chains < 2) {
		hdd_debug("Num of chains [%u] is less than 2, setting BTC separate chain mode is not allowed",
			  hdd_ctx->num_rf_chains);
		return -EINVAL;
	}

	vdev = hdd_objmgr_get_vdev_by_user(adapter->deflink, WLAN_OSIF_ID);
	if (!vdev)
		return -EINVAL;

	errno = wlan_cfg80211_coex_set_btc_chain_mode(vdev, data, data_len);
	hdd_objmgr_put_vdev_by_user(vdev, WLAN_OSIF_ID);

	return errno;
}

/**
 * wlan_hdd_cfg80211_set_btc_chain_mode() - set btc chain mode
 * @wiphy: pointer to wireless wiphy structure.
 * @wdev: pointer to wireless_dev structure.
 * @data: pointer to btc chain mode command parameters.
 * @data_len: the length in byte of btc chain mode command parameters.
 *
 * Return: An error code or 0 on success.
 */
int wlan_hdd_cfg80211_set_btc_chain_mode(struct wiphy *wiphy,
					 struct wireless_dev *wdev,
					 const void *data, int data_len)
{
	int errno;
	struct osif_vdev_sync *vdev_sync;

	errno = osif_vdev_sync_op_start(wdev->netdev, &vdev_sync);
	if (errno)
		return errno;

	errno = __wlan_hdd_cfg80211_set_btc_chain_mode(wiphy, wdev,
						       data, data_len);

	osif_vdev_sync_op_stop(vdev_sync);

	return errno;
}
