/*
 * Copyright (c) 2011-2020 The Linux Foundation. All rights reserved.
 * Copyright (c) 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.
 */

/* OS abstraction libraries */
#include <qdf_nbuf.h>           /* qdf_nbuf_t, etc. */
#include <qdf_atomic.h>         /* qdf_atomic_read, etc. */
#include <qdf_util.h>           /* qdf_unlikely */

/* APIs for other modules */
#include <htt.h>                /* HTT_TX_EXT_TID_MGMT */
#include <ol_htt_tx_api.h>      /* htt_tx_desc_tid */

/* internal header files relevant for all systems */
#include <ol_txrx_internal.h>   /* TXRX_ASSERT1 */
#include <ol_tx_desc.h>         /* ol_tx_desc */
#include <ol_tx_send.h>         /* ol_tx_send */
#include <ol_txrx.h>            /* ol_txrx_get_vdev_from_vdev_id */

/* internal header files relevant only for HL systems */
#include <ol_tx_queue.h>        /* ol_tx_enqueue */

/* internal header files relevant only for specific systems (Pronto) */
#include <ol_txrx_encap.h>      /* OL_TX_ENCAP, etc */
#include <ol_tx.h>
#include <ol_cfg.h>
#include <cdp_txrx_handle.h>

void ol_txrx_vdev_pause(struct cdp_soc_t *soc_hdl, uint8_t vdev_id,
			uint32_t reason, uint32_t pause_type)
{
	struct ol_txrx_vdev_t *vdev =
		(struct ol_txrx_vdev_t *)ol_txrx_get_vdev_from_vdev_id(vdev_id);

	if (qdf_unlikely(!vdev)) {
		ol_txrx_err("vdev is NULL");
		return;
	}

	/* TO DO: log the queue pause */
	/* acquire the mutex lock, since we'll be modifying the queues */
	TX_SCHED_DEBUG_PRINT("Enter");

	qdf_spin_lock_bh(&vdev->ll_pause.mutex);
	vdev->ll_pause.paused_reason |= reason;
	vdev->ll_pause.q_pause_cnt++;
	vdev->ll_pause.is_q_paused = true;
	qdf_spin_unlock_bh(&vdev->ll_pause.mutex);

	TX_SCHED_DEBUG_PRINT("Leave");
}

void ol_txrx_vdev_unpause(struct cdp_soc_t *soc_hdl, uint8_t vdev_id,
			  uint32_t reason, uint32_t pause_type)
{
	struct ol_txrx_vdev_t *vdev =
		(struct ol_txrx_vdev_t *)ol_txrx_get_vdev_from_vdev_id(vdev_id);

	if (qdf_unlikely(!vdev)) {
		ol_txrx_err("vdev is NULL");
		return;
	}

	/* TO DO: log the queue unpause */
	/* acquire the mutex lock, since we'll be modifying the queues */
	TX_SCHED_DEBUG_PRINT("Enter");

	qdf_spin_lock_bh(&vdev->ll_pause.mutex);
	if (vdev->ll_pause.paused_reason & reason) {
		vdev->ll_pause.paused_reason &= ~reason;
		if (!vdev->ll_pause.paused_reason) {
			vdev->ll_pause.is_q_paused = false;
			vdev->ll_pause.q_unpause_cnt++;
			qdf_spin_unlock_bh(&vdev->ll_pause.mutex);
			ol_tx_vdev_ll_pause_queue_send((void *)vdev);
		} else {
			qdf_spin_unlock_bh(&vdev->ll_pause.mutex);
		}
	} else {
		qdf_spin_unlock_bh(&vdev->ll_pause.mutex);
	}
	TX_SCHED_DEBUG_PRINT("Leave");
}

void ol_txrx_vdev_flush(struct cdp_soc_t *soc_hdl, uint8_t vdev_id)
{
	struct ol_txrx_vdev_t *vdev =
		(struct ol_txrx_vdev_t *)ol_txrx_get_vdev_from_vdev_id(vdev_id);

	if (qdf_unlikely(!vdev)) {
		ol_txrx_err("vdev is NULL");
		return;
	}

	qdf_spin_lock_bh(&vdev->ll_pause.mutex);
	qdf_timer_stop(&vdev->ll_pause.timer);
	vdev->ll_pause.is_q_timer_on = false;
	while (vdev->ll_pause.txq.head) {
		qdf_nbuf_t next =
			qdf_nbuf_next(vdev->ll_pause.txq.head);
		qdf_nbuf_set_next(vdev->ll_pause.txq.head, NULL);
		if (QDF_NBUF_CB_PADDR(vdev->ll_pause.txq.head)) {
			if (!qdf_nbuf_ipa_owned_get(vdev->ll_pause.txq.head))
				qdf_nbuf_unmap(vdev->pdev->osdev,
					       vdev->ll_pause.txq.head,
					       QDF_DMA_TO_DEVICE);
		}
		qdf_nbuf_tx_free(vdev->ll_pause.txq.head,
				 QDF_NBUF_PKT_ERROR);
		vdev->ll_pause.txq.head = next;
	}
	vdev->ll_pause.txq.tail = NULL;
	vdev->ll_pause.txq.depth = 0;
	qdf_spin_unlock_bh(&vdev->ll_pause.mutex);
}

#define OL_TX_VDEV_PAUSE_QUEUE_SEND_MARGIN 400
#define OL_TX_VDEV_PAUSE_QUEUE_SEND_PERIOD_MS 5

#define OL_TX_THROTTLE_MAX_SEND_LEVEL1 80
#define OL_TX_THROTTLE_MAX_SEND_LEVEL2 65
#define OL_TX_THROTTLE_MAX_SEND_LEVEL3 55
#define OL_TX_THROTTLE_MAX_SEND_LEVEL4 45
#define OL_TX_THROTTLE_MAX_SEND_LEVEL5 35
#define OL_TX_THROTTLE_MAX_SEND_LEVEL6 20
#define OL_TX_THROTTLE_MAX_SEND_LEVEL7 10
#define OL_TX_THROTTLE_MAX_SEND_LEVEL8 5
#define OL_TX_THROTTLE_MAX_SEND_LEVEL9 1

/**
 * ol_tx_get_max_to_send() - Get the maximum number of packets
 * that can be sent for different phy rate
 * @pdev: datapath pdev handle
 *
 * Return: int type value
 */
static int ol_tx_get_max_to_send(struct ol_txrx_pdev_t *pdev)
{
	uint16_t consume_num_last_timer;
	int max_to_send;

	qdf_spin_lock_bh(&pdev->tx_mutex);
	if (!pdev->tx_throttle.prev_outstanding_num) {
		max_to_send = OL_TX_THROTTLE_MAX_SEND_LEVEL5;
	} else {
		consume_num_last_timer =
			(pdev->tx_throttle.prev_outstanding_num -
			 pdev->tx_desc.pool_size +
			 pdev->tx_desc.num_free);
		if (consume_num_last_timer >=
			OL_TX_THROTTLE_MAX_SEND_LEVEL1) {
			max_to_send = pdev->tx_throttle.tx_threshold;
		} else if (consume_num_last_timer >=
				OL_TX_THROTTLE_MAX_SEND_LEVEL2) {
			max_to_send =
				OL_TX_THROTTLE_MAX_SEND_LEVEL1;
		} else if (consume_num_last_timer >=
				OL_TX_THROTTLE_MAX_SEND_LEVEL3) {
			max_to_send =
				OL_TX_THROTTLE_MAX_SEND_LEVEL2;
		} else if (consume_num_last_timer >=
				OL_TX_THROTTLE_MAX_SEND_LEVEL4) {
			max_to_send =
				OL_TX_THROTTLE_MAX_SEND_LEVEL3;
		} else if (consume_num_last_timer >=
				OL_TX_THROTTLE_MAX_SEND_LEVEL5) {
			max_to_send =
				OL_TX_THROTTLE_MAX_SEND_LEVEL4;
		} else if (pdev->tx_throttle.prev_outstanding_num >
				consume_num_last_timer) {
			/*
			 * when TX packet number is smaller than 35,
			 * most likely low phy rate is being used.
			 * As long as pdev->tx_throttle.prev_outstanding_num
			 * is greater than consume_num_last_timer, it
			 * means small TX packet number isn't limited
			 * by packets injected from host.
			 */
			if (consume_num_last_timer >=
				OL_TX_THROTTLE_MAX_SEND_LEVEL6)
				max_to_send =
					OL_TX_THROTTLE_MAX_SEND_LEVEL6;
			else if (consume_num_last_timer >=
					OL_TX_THROTTLE_MAX_SEND_LEVEL7)
				max_to_send =
					OL_TX_THROTTLE_MAX_SEND_LEVEL7;
			else if (consume_num_last_timer >=
					OL_TX_THROTTLE_MAX_SEND_LEVEL8)
				max_to_send =
					OL_TX_THROTTLE_MAX_SEND_LEVEL8;
			else
				max_to_send =
					OL_TX_THROTTLE_MAX_SEND_LEVEL9;
		} else {
			/*
			 * when come here, it means it's hard to evaluate
			 * current phy rate, for safety, max_to_send set
			 * to OL_TX_THROTTLE_MAX_SEND_LEVEL5.
			 */
			max_to_send = OL_TX_THROTTLE_MAX_SEND_LEVEL5;
		}
	}
	qdf_spin_unlock_bh(&pdev->tx_mutex);

	return max_to_send;
}

static void ol_tx_vdev_ll_pause_queue_send_base(struct ol_txrx_vdev_t *vdev)
{
	int max_to_accept;

	if (!vdev)
		return;

	qdf_spin_lock_bh(&vdev->ll_pause.mutex);
	if (vdev->ll_pause.paused_reason) {
		qdf_spin_unlock_bh(&vdev->ll_pause.mutex);
		return;
	}

	/*
	 * Send as much of the backlog as possible, but leave some margin
	 * of unallocated tx descriptors that can be used for new frames
	 * being transmitted by other vdevs.
	 * Ideally there would be a scheduler, which would not only leave
	 * some margin for new frames for other vdevs, but also would
	 * fairly apportion the tx descriptors between multiple vdevs that
	 * have backlogs in their pause queues.
	 * However, the fairness benefit of having a scheduler for frames
	 * from multiple vdev's pause queues is not sufficient to outweigh
	 * the extra complexity.
	 */
	max_to_accept = vdev->pdev->tx_desc.num_free -
		OL_TX_VDEV_PAUSE_QUEUE_SEND_MARGIN;
	while (max_to_accept > 0 && vdev->ll_pause.txq.depth) {
		qdf_nbuf_t tx_msdu;

		max_to_accept--;
		vdev->ll_pause.txq.depth--;
		tx_msdu = vdev->ll_pause.txq.head;
		if (tx_msdu) {
			vdev->ll_pause.txq.head = qdf_nbuf_next(tx_msdu);
			if (!vdev->ll_pause.txq.head)
				vdev->ll_pause.txq.tail = NULL;
			qdf_nbuf_set_next(tx_msdu, NULL);
			QDF_NBUF_UPDATE_TX_PKT_COUNT(tx_msdu,
						QDF_NBUF_TX_PKT_TXRX_DEQUEUE);
			tx_msdu = ol_tx_ll_wrapper(vdev, tx_msdu);
			/*
			 * It is unexpected that ol_tx_ll would reject the frame
			 * since we checked that there's room for it, though
			 * there's an infinitesimal possibility that between the
			 * time we checked the room available and now, a
			 * concurrent batch of tx frames used up all the room.
			 * For simplicity, just drop the frame.
			 */
			if (tx_msdu) {
				qdf_nbuf_unmap(vdev->pdev->osdev, tx_msdu,
					       QDF_DMA_TO_DEVICE);
				qdf_nbuf_tx_free(tx_msdu, QDF_NBUF_PKT_ERROR);
			}
		}
	}
	if (vdev->ll_pause.txq.depth) {
		qdf_timer_stop(&vdev->ll_pause.timer);
		if (!qdf_atomic_read(&vdev->delete.detaching)) {
			qdf_timer_start(&vdev->ll_pause.timer,
					OL_TX_VDEV_PAUSE_QUEUE_SEND_PERIOD_MS);
			vdev->ll_pause.is_q_timer_on = true;
		}
		if (vdev->ll_pause.txq.depth >= vdev->ll_pause.max_q_depth)
			vdev->ll_pause.q_overflow_cnt++;
	}

	qdf_spin_unlock_bh(&vdev->ll_pause.mutex);
}

static qdf_nbuf_t
ol_tx_vdev_pause_queue_append(struct ol_txrx_vdev_t *vdev,
			      qdf_nbuf_t msdu_list, uint8_t start_timer)
{
	qdf_spin_lock_bh(&vdev->ll_pause.mutex);
	while (msdu_list &&
	       vdev->ll_pause.txq.depth < vdev->ll_pause.max_q_depth) {
		qdf_nbuf_t next = qdf_nbuf_next(msdu_list);

		QDF_NBUF_UPDATE_TX_PKT_COUNT(msdu_list,
					     QDF_NBUF_TX_PKT_TXRX_ENQUEUE);
		DPTRACE(qdf_dp_trace(msdu_list,
			QDF_DP_TRACE_TXRX_QUEUE_PACKET_PTR_RECORD,
			QDF_TRACE_DEFAULT_PDEV_ID,
			qdf_nbuf_data_addr(msdu_list),
			sizeof(qdf_nbuf_data(msdu_list)), QDF_TX));

		vdev->ll_pause.txq.depth++;
		if (!vdev->ll_pause.txq.head) {
			vdev->ll_pause.txq.head = msdu_list;
			vdev->ll_pause.txq.tail = msdu_list;
		} else {
			qdf_nbuf_set_next(vdev->ll_pause.txq.tail, msdu_list);
		}
		vdev->ll_pause.txq.tail = msdu_list;

		msdu_list = next;
	}
	if (vdev->ll_pause.txq.tail)
		qdf_nbuf_set_next(vdev->ll_pause.txq.tail, NULL);

	if (start_timer) {
		qdf_timer_stop(&vdev->ll_pause.timer);
		if (!qdf_atomic_read(&vdev->delete.detaching)) {
			qdf_timer_start(&vdev->ll_pause.timer,
					OL_TX_VDEV_PAUSE_QUEUE_SEND_PERIOD_MS);
			vdev->ll_pause.is_q_timer_on = true;
		}
	}
	qdf_spin_unlock_bh(&vdev->ll_pause.mutex);

	return msdu_list;
}

/*
 * Store up the tx frame in the vdev's tx queue if the vdev is paused.
 * If there are too many frames in the tx queue, reject it.
 */
qdf_nbuf_t ol_tx_ll_queue(ol_txrx_vdev_handle vdev, qdf_nbuf_t msdu_list)
{
	uint16_t eth_type;
	uint32_t paused_reason;

	if (!msdu_list)
		return NULL;

	paused_reason = vdev->ll_pause.paused_reason;
	if (paused_reason) {
		if (qdf_unlikely((paused_reason &
				  OL_TXQ_PAUSE_REASON_PEER_UNAUTHORIZED) ==
				 paused_reason)) {
			eth_type = (((struct ethernet_hdr_t *)
				     qdf_nbuf_data(msdu_list))->
				    ethertype[0] << 8) |
				   (((struct ethernet_hdr_t *)
				     qdf_nbuf_data(msdu_list))->ethertype[1]);
			if (ETHERTYPE_IS_EAPOL_WAPI(eth_type)) {
				msdu_list = ol_tx_ll_wrapper(vdev, msdu_list);
				return msdu_list;
			}
		}
		msdu_list = ol_tx_vdev_pause_queue_append(vdev, msdu_list, 1);
	} else {
		if (vdev->ll_pause.txq.depth > 0 ||
		    vdev->pdev->tx_throttle.current_throttle_level !=
		    THROTTLE_LEVEL_0) {
			/*
			 * not paused, but there is a backlog of frms
			 * from a prior pause or throttle off phase
			 */
			msdu_list = ol_tx_vdev_pause_queue_append(
				vdev, msdu_list, 0);
			/*
			 * if throttle is disabled or phase is "on",
			 * send the frame
			 */
			if (vdev->pdev->tx_throttle.current_throttle_level ==
			    THROTTLE_LEVEL_0) {
				/*
				 * send as many frames as possible
				 * from the vdevs backlog
				 */
				ol_tx_vdev_ll_pause_queue_send_base(vdev);
			}
		} else {
			/*
			 * not paused, no throttle and no backlog -
			 * send the new frames
			 */
			msdu_list = ol_tx_ll_wrapper(vdev, msdu_list);
		}
	}
	return msdu_list;
}

/*
 * Run through the transmit queues for all the vdevs and
 * send the pending frames
 */
void ol_tx_pdev_ll_pause_queue_send_all(struct ol_txrx_pdev_t *pdev)
{
	int max_to_send;        /* tracks how many frames have been sent */
	qdf_nbuf_t tx_msdu;
	struct ol_txrx_vdev_t *vdev = NULL;
	uint8_t more;

	if (!pdev)
		return;

	if (pdev->tx_throttle.current_throttle_phase == THROTTLE_PHASE_OFF)
		return;

	/*
	 * For host implementation thermal mitigation, there has limitation
	 * in low phy rate case, like 11A 6M, 11B 11M. Host may have entered
	 * throttle off state, if a big number packets are queued to ring
	 * buffer in low phy rate, FW will have to keep active state during
	 * the whole throttle cycle. So you need to be careful when
	 * configuring the max_to_send value to avoid the chip temperature
	 * suddenly rises to very high in high temperature test.
	 * So add variable prev_outstanding_num to save last time outstanding
	 * number, when pdev->tx_throttle.tx_timer come again, we can check
	 * the gap to know high or low phy rate is being used, then choose
	 * right max_to_send.
	 * When it's the first time to enter the function, there doesn't have
	 * info for prev_outstanding_num, to satisfy all rate, the maximum
	 * safe number is OL_TX_THROTTLE_MAX_SEND_LEVEL5(35).
	 */
	max_to_send = ol_tx_get_max_to_send(pdev);

	/* round robin through the vdev queues for the given pdev */

	/*
	 * Potential improvement: download several frames from the same vdev
	 * at a time, since it is more likely that those frames could be
	 * aggregated together, remember which vdev was serviced last,
	 * so the next call this function can resume the round-robin
	 * traversing where the current invocation left off
	 */
	do {
		more = 0;
		TAILQ_FOREACH(vdev, &pdev->vdev_list, vdev_list_elem) {
			qdf_spin_lock_bh(&vdev->ll_pause.mutex);
			if (vdev->ll_pause.txq.depth) {
				if (vdev->ll_pause.paused_reason) {
					qdf_spin_unlock_bh(&vdev->ll_pause.
							   mutex);
					continue;
				}

				tx_msdu = vdev->ll_pause.txq.head;
				if (!tx_msdu) {
					qdf_spin_unlock_bh(&vdev->ll_pause.
							   mutex);
					continue;
				}

				max_to_send--;
				vdev->ll_pause.txq.depth--;

				vdev->ll_pause.txq.head =
					qdf_nbuf_next(tx_msdu);

				if (!vdev->ll_pause.txq.head)
					vdev->ll_pause.txq.tail = NULL;

				qdf_nbuf_set_next(tx_msdu, NULL);
				tx_msdu = ol_tx_ll_wrapper(vdev, tx_msdu);
				/*
				 * It is unexpected that ol_tx_ll would reject
				 * the frame, since we checked that there's
				 * room for it, though there's an infinitesimal
				 * possibility that between the time we checked
				 * the room available and now, a concurrent
				 * batch of tx frames used up all the room.
				 * For simplicity, just drop the frame.
				 */
				if (tx_msdu) {
					qdf_nbuf_unmap(pdev->osdev, tx_msdu,
						       QDF_DMA_TO_DEVICE);
					qdf_nbuf_tx_free(tx_msdu,
							 QDF_NBUF_PKT_ERROR);
				}
			}
			/*check if there are more msdus to transmit */
			if (vdev->ll_pause.txq.depth)
				more = 1;
			qdf_spin_unlock_bh(&vdev->ll_pause.mutex);
		}
	} while (more && max_to_send);

	qdf_spin_lock_bh(&pdev->tx_mutex);
	pdev->tx_throttle.prev_outstanding_num =
		(pdev->tx_desc.pool_size - pdev->tx_desc.num_free);
	qdf_spin_unlock_bh(&pdev->tx_mutex);

	/*
	 * currently as long as pdev->tx_throttle.current_throttle_level
	 * isn't THROTTLE_LEVEL_0, all TX data is scheduled by Tx
	 * throttle. It's needed to always start pdev->tx_throttle.tx_timer
	 * at the end of each TX throttle processing to avoid TX cannot be
	 * scheduled in the remaining throttle_on time.
	 */
	qdf_timer_stop(&pdev->tx_throttle.tx_timer);
	qdf_timer_start(&pdev->tx_throttle.tx_timer,
			OL_TX_VDEV_PAUSE_QUEUE_SEND_PERIOD_MS);
}

void ol_tx_vdev_ll_pause_queue_send(void *context)
{
	struct ol_txrx_vdev_t *vdev = (struct ol_txrx_vdev_t *)context;
	struct ol_txrx_pdev_t *pdev = vdev->pdev;

	if (pdev &&
	    pdev->tx_throttle.current_throttle_level != THROTTLE_LEVEL_0)
		return;
	ol_tx_vdev_ll_pause_queue_send_base(vdev);
}

int ol_txrx_register_tx_flow_control(struct cdp_soc_t *soc_hdl,
				     uint8_t vdev_id,
				     ol_txrx_tx_flow_control_fp flowControl,
				     void *osif_fc_ctx,
				     ol_txrx_tx_flow_control_is_pause_fp
				     flow_control_is_pause)
{
	struct ol_txrx_vdev_t *vdev =
		(struct ol_txrx_vdev_t *)ol_txrx_get_vdev_from_vdev_id(vdev_id);

	if (!vdev) {
		QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
			  "%s: Invalid vdev_id %d", __func__, vdev_id);
		return -EINVAL;
	}

	qdf_spin_lock_bh(&vdev->flow_control_lock);
	vdev->osif_flow_control_cb = flowControl;
	vdev->osif_flow_control_is_pause = flow_control_is_pause;
	vdev->osif_fc_ctx = osif_fc_ctx;
	qdf_spin_unlock_bh(&vdev->flow_control_lock);
	return 0;
}

int ol_txrx_deregister_tx_flow_control_cb(struct cdp_soc_t *soc_hdl,
					  uint8_t vdev_id)
{
	struct ol_txrx_vdev_t *vdev =
		(struct ol_txrx_vdev_t *)ol_txrx_get_vdev_from_vdev_id(vdev_id);

	if (!vdev) {
		QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
			  "%s: Invalid vdev_id", __func__);
		return -EINVAL;
	}

	qdf_spin_lock_bh(&vdev->flow_control_lock);
	vdev->osif_flow_control_cb = NULL;
	vdev->osif_flow_control_is_pause = NULL;
	vdev->osif_fc_ctx = NULL;
	qdf_spin_unlock_bh(&vdev->flow_control_lock);
	return 0;
}

/**
 * ol_txrx_get_tx_resource() - if tx resource less than low_watermark
 * soc_hdl: soc handle
 * @pdev_id: datapath pdev identifier
 * @peer_addr: peer mac address
 * @low_watermark: low watermark
 * @high_watermark_offset: high watermark offset value
 *
 * Return: true/false
 */
bool
ol_txrx_get_tx_resource(struct cdp_soc_t *soc_hdl, uint8_t pdev_id,
			struct qdf_mac_addr peer_addr,
			unsigned int low_watermark,
			unsigned int high_watermark_offset)
{
	struct ol_txrx_soc_t *soc = cdp_soc_t_to_ol_txrx_soc_t(soc_hdl);
	ol_txrx_pdev_handle pdev =
				ol_txrx_get_pdev_from_pdev_id(soc, pdev_id);
	ol_txrx_vdev_handle vdev;

	if (qdf_unlikely(!pdev)) {
		ol_txrx_err("pdev is NULL");
		return true;
	}

	vdev = ol_txrx_get_vdev_by_peer_addr(ol_txrx_pdev_t_to_cdp_pdev(pdev),
					     peer_addr);
	if (!vdev) {
		QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_INFO_HIGH,
			  "%s: Invalid peer address: " QDF_MAC_ADDR_FMT,
			  __func__, QDF_MAC_ADDR_REF(peer_addr.bytes));
		/* Return true so caller do not understand that resource
		 * is less than low_watermark.
		 * sta_id validation will be done in ol_tx_send_data_frame
		 * and if sta_id is not registered then host will drop
		 * packet.
		 */
		return true;
	}

	qdf_spin_lock_bh(&vdev->pdev->tx_mutex);

	if (vdev->pdev->tx_desc.num_free < (uint16_t)low_watermark) {
		vdev->tx_fl_lwm = (uint16_t)low_watermark;
		vdev->tx_fl_hwm =
			(uint16_t)(low_watermark + high_watermark_offset);
		/* Not enough free resource, stop TX OS Q */
		qdf_atomic_set(&vdev->os_q_paused, 1);
		qdf_spin_unlock_bh(&vdev->pdev->tx_mutex);
		return false;
	}
	qdf_spin_unlock_bh(&vdev->pdev->tx_mutex);
	return true;
}

int ol_txrx_ll_set_tx_pause_q_depth(struct cdp_soc_t *soc_hdl, uint8_t vdev_id,
				    int pause_q_depth)
{
	struct ol_txrx_vdev_t *vdev =
		(struct ol_txrx_vdev_t *)ol_txrx_get_vdev_from_vdev_id(vdev_id);

	if (!vdev) {
		QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
			  "%s: Invalid vdev_id %d", __func__, vdev_id);
		return -EINVAL;
	}

	qdf_spin_lock_bh(&vdev->ll_pause.mutex);
	vdev->ll_pause.max_q_depth = pause_q_depth;
	qdf_spin_unlock_bh(&vdev->ll_pause.mutex);

	return 0;
}

void ol_txrx_flow_control_cb(struct cdp_soc_t *soc_hdl, uint8_t vdev_id,
			     bool tx_resume)
{
	struct ol_txrx_vdev_t *vdev =
		(struct ol_txrx_vdev_t *)ol_txrx_get_vdev_from_vdev_id(vdev_id);

	if (qdf_unlikely(!vdev)) {
		ol_txrx_err("vdev is NULL");
		return;
	}

	qdf_spin_lock_bh(&vdev->flow_control_lock);
	if ((vdev->osif_flow_control_cb) && (vdev->osif_fc_ctx))
		vdev->osif_flow_control_cb(vdev->osif_fc_ctx, tx_resume);
	qdf_spin_unlock_bh(&vdev->flow_control_lock);
}

/**
 * ol_txrx_flow_control_is_pause() - is osif paused by flow control
 * @vdev: vdev handle
 *
 * Return: true if osif is paused by flow control
 */
static bool ol_txrx_flow_control_is_pause(ol_txrx_vdev_handle vdev)
{
	bool is_pause = false;

	if ((vdev->osif_flow_control_is_pause) && (vdev->osif_fc_ctx))
		is_pause = vdev->osif_flow_control_is_pause(vdev->osif_fc_ctx);

	return is_pause;
}

/**
 * ol_tx_flow_ct_unpause_os_q() - Unpause OS Q
 * @pdev: physical device object
 *
 *
 * Return: None
 */
void ol_tx_flow_ct_unpause_os_q(ol_txrx_pdev_handle pdev)
{
	struct ol_txrx_vdev_t *vdev;
	struct cdp_soc_t *soc_hdl = ol_txrx_soc_t_to_cdp_soc_t(pdev->soc);

	TAILQ_FOREACH(vdev, &pdev->vdev_list, vdev_list_elem) {
		if ((qdf_atomic_read(&vdev->os_q_paused) &&
		     (vdev->tx_fl_hwm != 0)) ||
		     ol_txrx_flow_control_is_pause(vdev)) {
			qdf_spin_lock(&pdev->tx_mutex);
			if (pdev->tx_desc.num_free > vdev->tx_fl_hwm) {
				qdf_atomic_set(&vdev->os_q_paused, 0);
				qdf_spin_unlock(&pdev->tx_mutex);
				ol_txrx_flow_control_cb(soc_hdl,
							vdev->vdev_id, true);
			} else {
				qdf_spin_unlock(&pdev->tx_mutex);
			}
		}
	}
}

