/******************************************************************************
 *
 * Copyright(c) 2019 Realtek Corporation.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 *****************************************************************************/
#define _PHL_TRX_PCIE_C_
#include "../phl_headers.h"
#include "phl_trx_pcie.h"

#define target_in_area(target, start, end) \
	((target < start || target > end) ? false : true)
void phl_recycle_payload(struct phl_info_t *phl_info, u8 dma_ch, u16 wp_seq,
			 u8 txsts);

void phl_dump_link_list(void *phl, _os_list *list_head, u8 type)
{
	struct rtw_wd_page *wd_page = NULL, *t = NULL;
	struct rtw_h2c_pkt *h2c_pkt = NULL, *h2c_t = NULL;
	struct rtw_phl_tring_list *phl_tring_list = NULL, *phl_t = NULL;
	struct phl_ring_status *ring_sts = NULL, *rsts_t = NULL;
	struct phl_info_t *phl_info = (struct phl_info_t *)phl;
	void *drv_priv = phl_to_drvpriv(phl_info);
	u8 *vir_addr = NULL;
	u32 i = 0, j = 0;
	u16 phl_idx = 0, phl_next_idx = 0;

	switch (type) {
	case TYPE_WD_PAGE:
		PHL_TRACE(COMP_PHL_DBG, _PHL_INFO_, "===Dump WD Page===\n");
		phl_list_for_loop_safe(wd_page, t, struct rtw_wd_page,
						list_head, list) {
			vir_addr = (u8 *)wd_page->vir_addr;
			PHL_TRACE(COMP_PHL_DBG, _PHL_INFO_, "vir_addr = %p, %x; phy_addr_l = %x; phy_addr_h = %x\n",
					vir_addr, *vir_addr,
					wd_page->phy_addr_l,
					wd_page->phy_addr_h);
			PHL_TRACE(COMP_PHL_DBG, _PHL_INFO_, "cache = %d; buf_len = %d, wp_seq = %d\n",
					wd_page->cache, wd_page->buf_len,
					wd_page->wp_seq);
		}
		break;
	case TYPE_PHL_RING:
		PHL_TRACE(COMP_PHL_DBG, _PHL_INFO_, "===Dump PHL Ring===\n");
		phl_list_for_loop_safe(phl_tring_list, phl_t,
					struct rtw_phl_tring_list,
					list_head, list) {

			PHL_TRACE(COMP_PHL_DBG, _PHL_INFO_,
				"-- macid = %d, band = %d, wmm = %d --\n",
					phl_tring_list->macid,
					phl_tring_list->band,
					phl_tring_list->wmm);

			for (i = 0; i < MAX_PHL_RING_CAT_NUM; i++) {
				phl_idx = (u16)_os_atomic_read(drv_priv,
						&phl_tring_list->phl_ring[i].phl_idx);
				phl_next_idx = (u16)_os_atomic_read(drv_priv,
						&phl_tring_list->phl_ring[i].phl_next_idx);

				PHL_TRACE(COMP_PHL_DBG, _PHL_INFO_,
						"tid = %d\n"
						"dma_ch = %d\n"
						"tx_thres = %d\n"
						"core_idx = %d\n"
						"phl_idx = %d\n"
						"phl_next_idx = %d\n",
						phl_tring_list->phl_ring[i].tid,
						phl_tring_list->phl_ring[i].dma_ch,
						phl_tring_list->phl_ring[i].tx_thres,
						phl_tring_list->phl_ring[i].core_idx,
						phl_idx,
						phl_next_idx);

				for (j = 0; j < 5; j++) {
					PHL_TRACE(COMP_PHL_DBG, _PHL_INFO_,
							"entry[%d] = %p\n",
							j,
					phl_tring_list->phl_ring[i].entry[j]);
				}
			}
		}
		break;
	case TYPE_RING_STS:
		PHL_TRACE(COMP_PHL_DBG, _PHL_INFO_, "===Dump PHL Ring status===\n");
		phl_list_for_loop_safe(ring_sts, rsts_t, struct phl_ring_status,
					list_head, list) {
			PHL_TRACE(COMP_PHL_DBG, _PHL_INFO_,
					"macid = %d\n"
					"req_busy = %d\n"
					"ring_ptr = %p\n",
					ring_sts->macid,
					ring_sts->req_busy,
					ring_sts->ring_ptr);
		}
		break;
	case TYPE_H2C_PKT:
		PHL_TRACE(COMP_PHL_DBG, _PHL_INFO_, "===Dump H2C PKT===\n");
		phl_list_for_loop_safe(h2c_pkt, h2c_t, struct rtw_h2c_pkt,
					list_head, list) {
			vir_addr = (u8 *)h2c_pkt->vir_head;
			PHL_TRACE(COMP_PHL_DBG, _PHL_INFO_, "vir_addr = %p, %x; phy_addr_l = %x; phy_addr_h = %x\n",
					vir_addr, *vir_addr,
					h2c_pkt->phy_addr_l,
					h2c_pkt->phy_addr_h);
			PHL_TRACE(COMP_PHL_DBG, _PHL_INFO_, "type = %d; cache = %d; buf_len = %d\n",
					h2c_pkt->type, h2c_pkt->cache, h2c_pkt->buf_len);
			PHL_TRACE(COMP_PHL_DBG, _PHL_INFO_, "vir_head = %p; vir_data = %p; vir_tail = %p; vir_end = %p\n",
					(u8 *)h2c_pkt->vir_head,
					(u8 *)h2c_pkt->vir_data,
					(u8 *)h2c_pkt->vir_tail,
					(u8 *)h2c_pkt->vir_end);
		}
		break;
	default :
		break;
	}
}

void _phl_dump_wp_stats(struct phl_info_t *phl_info)
{
	struct hci_info_t *hci_info = (struct hci_info_t *)phl_info->hci;
	struct rtw_hal_com_t *hal_com = rtw_hal_get_halcom(phl_info->hal);
	struct rtw_wp_rpt_stats *rpt_stats = NULL;
	u8 ch = 0;

	rpt_stats = (struct rtw_wp_rpt_stats *)hal_com->trx_stat.wp_rpt_stats;

	PHL_TRACE(COMP_PHL_XMIT, _PHL_DEBUG_,
		  "\n== wp report statistics == \n");
	for (ch = 0; ch < hci_info->total_txch_num; ch++) {
		PHL_TRACE(COMP_PHL_XMIT, _PHL_DEBUG_,
			  "ch			: %u\n", (int)ch);
		PHL_TRACE(COMP_PHL_XMIT, _PHL_DEBUG_,
			  "busy count		: %u\n",
			  (int)rpt_stats[ch].busy_cnt);
		PHL_TRACE(COMP_PHL_XMIT, _PHL_DEBUG_,
			  "ok count		: %u\n",
			  (int)rpt_stats[ch].tx_ok_cnt);
		PHL_TRACE(COMP_PHL_XMIT, _PHL_DEBUG_,
			  "retry fail count	: %u\n",
			  (int)rpt_stats[ch].rty_fail_cnt);
		PHL_TRACE(COMP_PHL_XMIT, _PHL_DEBUG_,
			  "lifetime drop count	: %u\n",
			  (int)rpt_stats[ch].lifetime_drop_cnt);
		PHL_TRACE(COMP_PHL_XMIT, _PHL_DEBUG_,
			  "macid drop count	: %u\n",
			  (int)rpt_stats[ch].macid_drop_cnt);
		PHL_TRACE(COMP_PHL_XMIT, _PHL_DEBUG_,
			  "sw drop count		: %u\n",
			  (int)rpt_stats[ch].sw_drop_cnt);
		PHL_TRACE(COMP_PHL_XMIT, _PHL_DEBUG_,
			  "recycle fail count	: %u\n",
			  (int)rpt_stats[ch].recycle_fail_cnt);
		PHL_TRACE(COMP_PHL_XMIT, _PHL_DEBUG_,
			  "delay ok count			: %u\n",
			  (int)rpt_stats[ch].delay_tx_ok_cnt);
		PHL_TRACE(COMP_PHL_XMIT, _PHL_DEBUG_,
			  "delay retry fail count		: %u\n",
			  (int)rpt_stats[ch].delay_rty_fail_cnt);
		PHL_TRACE(COMP_PHL_XMIT, _PHL_DEBUG_,
			  "delay lifetime drop count	: %u\n",
			  (int)rpt_stats[ch].delay_lifetime_drop_cnt);
		PHL_TRACE(COMP_PHL_XMIT, _PHL_DEBUG_,
			  "delay macid drop count		: %u\n",
			  (int)rpt_stats[ch].delay_macid_drop_cnt);

	}
}

void _phl_dump_busy_wp(struct phl_info_t *phl_info)
{
	struct hci_info_t *hci_info = (struct hci_info_t *)phl_info->hci;
	struct rtw_wd_page_ring *wd_ring = NULL;
	struct rtw_xmit_req *treq = NULL;
	void *ptr = NULL;
	u16 wp_seq = 0;
	u8 ch = 0;

	wd_ring = (struct rtw_wd_page_ring *)hci_info->wd_ring;
	PHL_TRACE(COMP_PHL_XMIT, _PHL_DEBUG_,
		  "\n== dump busy wp == \n");
	for (ch = 0; ch < hci_info->total_txch_num; ch++) {

		for (wp_seq = 0; wp_seq < WP_MAX_SEQ_NUMBER; wp_seq++) {
			if (NULL != wd_ring[ch].wp_tag[wp_seq].ptr) {
				ptr = wd_ring[ch].wp_tag[wp_seq].ptr;
				treq = (struct rtw_xmit_req *)ptr;
				PHL_TRACE(COMP_PHL_XMIT, _PHL_DEBUG_,
					  "dma_ch = %d, wp_seq = 0x%x, ptr = %p!\n",
					  ch, wp_seq, ptr);
				PHL_TRACE(COMP_PHL_XMIT, _PHL_DEBUG_,
					  "wifi seq = %d\n",
					  treq->mdata.sw_seq);
			}
		}
	}


}

u8 _phl_check_recycle(u16 target, u16 rptr, u16 wptr, u16 bndy)
{
	u8 recycle = false;
	u8 init = 0;	/* starting point */

	if (wptr > rptr) {
		if (true == target_in_area(target, wptr, (bndy-1)))
			recycle = true;
		else if (true == target_in_area(target, init, rptr))
			recycle = true;
		else
			recycle = false;

	} else if (rptr > wptr) {
		if (true == target_in_area(target, wptr, rptr))
			recycle = true;
		else
			recycle = false;
	} else {
		recycle = true;
	}

	return recycle;
}

void phl_tx_start_pcie(struct phl_info_t *phl_info)
{
	void *drv = phl_to_drvpriv(phl_info);
	_os_atomic_set(drv, &phl_info->phl_sw_tx_sts, PHL_TX_STATUS_RUNNING);
}

void phl_tx_resume_pcie(struct phl_info_t *phl_info)
{
	void *drv = phl_to_drvpriv(phl_info);
	_os_atomic_set(drv, &phl_info->phl_sw_tx_sts, PHL_TX_STATUS_RUNNING);
}

void phl_req_tx_stop_pcie(struct phl_info_t *phl_info)
{
	void *drv = phl_to_drvpriv(phl_info);
	_os_atomic_set(drv, &phl_info->phl_sw_tx_sts,
				PHL_TX_STATUS_STOP_INPROGRESS);
}

void phl_tx_stop_pcie(struct phl_info_t *phl_info)
{
	void *drv = phl_to_drvpriv(phl_info);
	_os_atomic_set(drv, &phl_info->phl_sw_tx_sts, PHL_TX_STATUS_SW_PAUSE);
}

bool phl_is_tx_sw_pause_pcie(struct phl_info_t *phl_info)
{
	void *drvpriv = phl_to_drvpriv(phl_info);

	if (PHL_TX_STATUS_SW_PAUSE == _os_atomic_read(drvpriv,
								&phl_info->phl_sw_tx_sts))
		return true;
	else
		return false;

}

void phl_rx_start_pcie(struct phl_info_t *phl_info)
{
	void *drv = phl_to_drvpriv(phl_info);
	_os_atomic_set(drv, &phl_info->phl_sw_rx_sts, PHL_RX_STATUS_RUNNING);
}

void phl_rx_resume_pcie(struct phl_info_t *phl_info)
{
	void *drv = phl_to_drvpriv(phl_info);
	_os_atomic_set(drv, &phl_info->phl_sw_rx_sts, PHL_RX_STATUS_RUNNING);
}

void phl_req_rx_stop_pcie(struct phl_info_t *phl_info)
{
	void *drv = phl_to_drvpriv(phl_info);
	_os_atomic_set(drv, &phl_info->phl_sw_rx_sts,
				PHL_RX_STATUS_STOP_INPROGRESS);
}

void phl_rx_stop_pcie(struct phl_info_t *phl_info)
{
	void *drv = phl_to_drvpriv(phl_info);
	_os_atomic_set(drv, &phl_info->phl_sw_rx_sts, PHL_RX_STATUS_SW_PAUSE);
}

bool phl_is_rx_sw_pause_pcie(struct phl_info_t *phl_info)
{
	void *drvpriv = phl_to_drvpriv(phl_info);

	if (PHL_RX_STATUS_SW_PAUSE == _os_atomic_read(drvpriv,
								&phl_info->phl_sw_rx_sts)) {
		if (true == rtw_phl_is_phl_rx_idle(phl_info))
			return true;
		else
			return false;
	} else {
		return false;
	}
}

#ifdef RTW_WKARD_DYNAMIC_LTR
static bool _phl_judge_idle_ltr_switching_conditions(
	struct phl_info_t *phl_info, u16 macid)
{
	struct rtw_phl_stainfo_t *sta_info = NULL;
	struct rtw_stats *stats = &phl_info->phl_com->phl_stats;
	u16 ltr_thre = phl_info->phl_com->bus_sw_cap.ltr_sw_ctrl_thre;
	u8 tx_thre = 0, rx_thre = 0;
	u32 last_time = phl_ltr_get_last_trigger_time(phl_info->phl_com);

	tx_thre = ltr_thre >> 8;
	rx_thre = (u8)(ltr_thre & 0xFF);

	sta_info = rtw_phl_get_stainfo_by_macid(phl_info, macid);

	if (!rtw_hal_ltr_is_sw_ctrl(phl_info->phl_com, phl_info->hal))
		return false;

	if (sta_info == NULL)
		return false;

	if (sta_info->wrole == NULL)
		return false;

	if (stats->tx_traffic.lvl > tx_thre)
		return false;

	if (stats->rx_traffic.lvl > rx_thre)
		return false;

	if (RTW_PCIE_LTR_SW_IDLE == phl_ltr_get_cur_state(phl_info->phl_com))
		return false;

	if (phl_get_passing_time_us(last_time) < 500)
		return false;

	return true;

}
static bool _phl_judge_act_ltr_switching_conditions(
	struct phl_info_t *phl_info, u8 ch)
{
	u32 last_time = phl_ltr_get_last_trigger_time(phl_info->phl_com);
	u8 fwcmd_queue_idx = 0;

	fwcmd_queue_idx = rtw_hal_get_fwcmd_queue_idx(phl_info->hal);

	if (!rtw_hal_ltr_is_sw_ctrl(phl_info->phl_com, phl_info->hal))
		return true;

	if (ch == fwcmd_queue_idx)
		return true;

	if (RTW_PCIE_LTR_SW_ACT == phl_ltr_get_cur_state(phl_info->phl_com))
		return true;

	if (phl_get_passing_time_us(last_time) < 500)
		return false;

	return true;
}

static void _phl_act_ltr_update_stats(struct phl_info_t *phl_info,
		bool success, u8 ch, u16 pending_wd_page_cnt)
{
	static bool bdly = false;
	static u32 dly_start_time = 0;

	if (!rtw_hal_ltr_is_sw_ctrl(phl_info->phl_com, phl_info->hal))
		return;

	if (success) {
		/* only those have been delayed last time*/
		if (bdly) {
			PHL_INFO("%s() ch(%u), %u packets be transmitted after defering %uus\n"
				, __func__, ch,	pending_wd_page_cnt,
				phl_get_passing_time_us(dly_start_time));
			rtw_hal_ltr_update_stats(phl_info->hal, true);
		}
		bdly = false;
	} else {

		/* the first packet that is going to defer */
		if (false == bdly)
			dly_start_time = _os_get_cur_time_us();

		PHL_DBG("%s() ch(%u), %u packets be delayed\n", __func__,
							ch,	pending_wd_page_cnt);

		rtw_hal_ltr_update_stats(phl_info->hal, false);
		bdly = true;
		dly_start_time = _os_get_cur_time_us();
	}
}

static void _phl_switch_act_ltr(struct phl_info_t *phl_info, u8 tx_dma_ch)
{
	u8 fwcmd_queue_idx = 0;

	if (!rtw_hal_ltr_is_sw_ctrl(phl_info->phl_com, phl_info->hal))
		return;

	if (RTW_PCIE_LTR_SW_ACT == phl_ltr_get_cur_state(phl_info->phl_com))
		return;

	fwcmd_queue_idx = rtw_hal_get_fwcmd_queue_idx(phl_info->hal);

	if (tx_dma_ch != fwcmd_queue_idx)
		phl_ltr_sw_trigger(phl_info->phl_com, phl_info->hal,
			RTW_PCIE_LTR_SW_ACT);

}

static void _phl_switch_idle_ltr(struct phl_info_t *phl_info,
					struct rtw_wp_rpt_stats *rpt_stats)
{
	struct hci_info_t *hci_info = (struct hci_info_t *)phl_info->hci;
	u8 ch = 0;
	bool bempty = 1;
	u8 fwcmd_queue_idx = 0;

	fwcmd_queue_idx = rtw_hal_get_fwcmd_queue_idx(phl_info->hal);

	for (ch = 0; ch < hci_info->total_txch_num; ch++) {
		if (ch == fwcmd_queue_idx)
			continue;
		if (rpt_stats[ch].busy_cnt != 0)
			bempty = 0;
	}

	if (bempty)
		phl_ltr_sw_trigger(phl_info->phl_com, phl_info->hal,
			RTW_PCIE_LTR_SW_IDLE);

}
#endif

#ifdef RTW_WKARD_TXBD_UPD_LMT
static void
_phl_free_h2c_work_ring(struct phl_info_t *phl_info,
			struct rtw_wd_page_ring *wd_page_ring)
{
	void *drv_priv = phl_to_drvpriv(phl_info);
	struct hal_spec_t *hal_spec = phl_get_ic_spec(phl_info->phl_com);
	struct rtw_h2c_work *h2c_work = &wd_page_ring->h2c_work;
	struct rtw_h2c_pkt *cmd = h2c_work->cmd;
	struct rtw_h2c_pkt *data = h2c_work->data;
	struct rtw_h2c_pkt *ldata = h2c_work->ldata;
	struct phl_hci_trx_ops *hci_trx_ops = phl_info->hci_trx_ops;
	u16 i = 0, buf_num = 0;

	buf_num = hal_spec->txbd_multi_tag;

	if (NULL != cmd) {
		for (i = 0; i < buf_num; i++) {
			if (NULL == cmd->vir_head)
				continue;
			hci_trx_ops->free_h2c_pkt_buf(phl_info, cmd);
			cmd->vir_head = NULL;
			cmd->cache = false;
			cmd++;
		}
		_os_mem_free(drv_priv, h2c_work->cmd,
			     buf_num * sizeof(*h2c_work->cmd));
	}
	if (NULL != data) {
		for (i = 0; i < buf_num; i++) {
			if (NULL == data->vir_head)
				continue;
			hci_trx_ops->free_h2c_pkt_buf(phl_info, data);
			data->vir_head = NULL;
			data->cache = false;
			data++;
		}
		_os_mem_free(drv_priv, h2c_work->data,
			     buf_num * sizeof(*h2c_work->data));
	}
	if (NULL != ldata) {
		for (i = 0; i < buf_num; i++) {
			if (NULL == ldata->vir_head)
				continue;
			hci_trx_ops->free_h2c_pkt_buf(phl_info, ldata);
			ldata->vir_head = NULL;
			ldata->cache = false;
			ldata++;
		}
		_os_mem_free(drv_priv, h2c_work->ldata,
			     buf_num * sizeof(*h2c_work->ldata));
	}

	if (NULL != h2c_work->cmd_ring) {
		_os_mem_free(drv_priv, h2c_work->cmd_ring,
			     buf_num * sizeof(struct rtw_h2c_pkt *));
        }
	if (NULL != h2c_work->data_ring) {
		_os_mem_free(drv_priv, h2c_work->data_ring,
			     buf_num * sizeof(struct rtw_h2c_pkt *));
        }
	if (NULL != h2c_work->ldata_ring) {
		_os_mem_free(drv_priv, h2c_work->ldata_ring,
			     buf_num * sizeof(struct rtw_h2c_pkt *));
        }
	h2c_work->cmd_cnt = 0;
	h2c_work->cmd_idx = 0;
	h2c_work->data_cnt = 0;
	h2c_work->data_idx = 0;
	h2c_work->ldata_cnt = 0;
	h2c_work->ldata_idx = 0;
	_os_spinlock_free(drv_priv,	&h2c_work->lock);
}


static enum rtw_phl_status
_phl_alloc_h2c_work_ring(struct phl_info_t *phl_info,
			 struct rtw_wd_page_ring *wd_page_ring)
{
	enum rtw_phl_status psts = RTW_PHL_STATUS_FAILURE;
	void *drv_priv = phl_to_drvpriv(phl_info);
	struct phl_hci_trx_ops *hci_trx_ops = phl_info->hci_trx_ops;
	struct hal_spec_t *hal_spec = phl_get_ic_spec(phl_info->phl_com);
	struct rtw_h2c_work *h2c_work = &wd_page_ring->h2c_work;
	struct rtw_h2c_pkt *cmd = NULL, *data =  NULL, *ldata = NULL;
	u16 buf_num = 0, i = 0;

	buf_num = hal_spec->txbd_multi_tag;
	_os_spinlock_init(drv_priv, &h2c_work->lock);

	h2c_work->cmd = _os_mem_alloc(drv_priv, buf_num * sizeof(*cmd));
	h2c_work->data = _os_mem_alloc(drv_priv, buf_num * sizeof(*data));
	h2c_work->ldata = _os_mem_alloc(drv_priv, buf_num * sizeof(*ldata));

	if (!h2c_work->cmd || !h2c_work->data || !h2c_work->ldata) {
		psts = RTW_PHL_STATUS_RESOURCE;
		goto out;
	}
	cmd = h2c_work->cmd;
	data = h2c_work->data;
	ldata = h2c_work->ldata;

	_os_mem_set(drv_priv, cmd, 0, buf_num * sizeof(*cmd));
	_os_mem_set(drv_priv, data, 0, buf_num * sizeof(*data));
	_os_mem_set(drv_priv, ldata, 0, buf_num * sizeof(*ldata));

	h2c_work->cmd_ring =
		_os_mem_alloc(drv_priv,
			       buf_num * sizeof(struct rtw_h2c_pkt *));
	h2c_work->data_ring =
		_os_mem_alloc(drv_priv,
			       buf_num * sizeof(struct rtw_h2c_pkt *));
	h2c_work->ldata_ring =
		_os_mem_alloc(drv_priv,
			       buf_num * sizeof(struct rtw_h2c_pkt *));

	if (!h2c_work->cmd_ring || !h2c_work->data_ring ||
	    !h2c_work->ldata_ring) {
		psts = RTW_PHL_STATUS_RESOURCE;
		goto out;
	}
	_os_mem_set(drv_priv, h2c_work->cmd_ring, 0,
		    buf_num * sizeof(struct rtw_h2c_pkt *));
	_os_mem_set(drv_priv, h2c_work->data_ring, 0,
		    buf_num * sizeof(struct rtw_h2c_pkt *));
	_os_mem_set(drv_priv, h2c_work->ldata_ring, 0,
		    buf_num * sizeof(struct rtw_h2c_pkt *));

	for (i = 0; i < buf_num; i++) {
		cmd->type = H2CB_TYPE_CMD;
		cmd->cache = false;
		cmd->buf_len = FWCMD_HDR_LEN + _WD_BODY_LEN + H2C_CMD_LEN;
		hci_trx_ops->alloc_h2c_pkt_buf(phl_info, cmd, cmd->buf_len);
		if (NULL == cmd->vir_head) {
			psts = RTW_PHL_STATUS_RESOURCE;
			goto out;
		}
		cmd->vir_data = cmd->vir_head + FWCMD_HDR_LEN + _WD_BODY_LEN;
		cmd->vir_tail = cmd->vir_data;
		cmd->vir_end = cmd->vir_data + H2C_CMD_LEN;
		INIT_LIST_HEAD(&cmd->list);
		h2c_work->cmd_ring[i] = cmd;
		h2c_work->cmd_cnt++;
		cmd++;
	}
	for (i = 0; i < buf_num; i++) {
		data->type = H2CB_TYPE_DATA;
		data->cache = false;
		data->buf_len = FWCMD_HDR_LEN + _WD_BODY_LEN + H2C_DATA_LEN;
		hci_trx_ops->alloc_h2c_pkt_buf(phl_info, data, data->buf_len);
		if (NULL == data->vir_head) {
			psts = RTW_PHL_STATUS_RESOURCE;
			goto out;
		}
		data->vir_data = data->vir_head + FWCMD_HDR_LEN + _WD_BODY_LEN;
		data->vir_tail = data->vir_data;
		data->vir_end = data->vir_data + H2C_DATA_LEN;
		INIT_LIST_HEAD(&data->list);
		h2c_work->data_ring[i] = data;
		h2c_work->data_cnt++;
		data++;
	}
	for (i = 0; i < buf_num; i++) {
		ldata->type = H2CB_TYPE_LONG_DATA;
		ldata->cache = false;
		ldata->buf_len = FWCMD_HDR_LEN + _WD_BODY_LEN +
				 H2C_LONG_DATA_LEN;
		hci_trx_ops->alloc_h2c_pkt_buf(phl_info, ldata, ldata->buf_len);
		if (NULL == ldata->vir_head) {
			psts = RTW_PHL_STATUS_RESOURCE;
			goto out;
		}
		ldata->vir_data = ldata->vir_head + FWCMD_HDR_LEN +
				 _WD_BODY_LEN;
		ldata->vir_tail = ldata->vir_data;
		ldata->vir_end = ldata->vir_data + H2C_LONG_DATA_LEN;
		INIT_LIST_HEAD(&ldata->list);
		h2c_work->ldata_ring[i] = ldata;
		h2c_work->ldata_cnt++;
		ldata++;
	}

	h2c_work->cmd_idx = 0;
	h2c_work->data_idx = 0;
	h2c_work->ldata_idx = 0;
	psts = RTW_PHL_STATUS_SUCCESS;

out:
	if (RTW_PHL_STATUS_SUCCESS != psts) {
		_phl_free_h2c_work_ring(phl_info, wd_page_ring);
		h2c_work->cmd = NULL;
		h2c_work->data = NULL;
		h2c_work->ldata = NULL;
		h2c_work->cmd_ring = NULL;
		h2c_work->data_ring = NULL;
		h2c_work->ldata_ring = NULL;
	}

	return psts;
}


static void
_phl_free_wd_work_ring(struct phl_info_t *phl_info,
		       struct rtw_wd_page_ring *wd_page_ring)
{
	void *drv_priv = phl_to_drvpriv(phl_info);
	struct hal_spec_t *hal_spec = phl_get_ic_spec(phl_info->phl_com);
	u16 i = 0, buf_num = 0;

	buf_num = hal_spec->txbd_multi_tag;

	if (NULL != wd_page_ring->wd_work) {
		for (i = 0; i < buf_num; i++) {

			if (NULL == wd_page_ring->wd_work[i].vir_addr)
				continue;

			wd_page_ring->wd_work[i].wp_seq = WP_RESERVED_SEQ;
			_os_shmem_free(drv_priv,
			       	wd_page_ring->wd_work[i].vir_addr,
			       	(_dma *)&wd_page_ring->wd_work[i].phy_addr_l,
			       	(_dma *)&wd_page_ring->wd_work[i].phy_addr_h,
			       	wd_page_ring->wd_work[i].buf_len,
				wd_page_ring->wd_work[i].cache,
				PCI_DMA_FROMDEVICE,
				wd_page_ring->wd_work[i].os_rsvd[0]);
			wd_page_ring->wd_work[i].vir_addr = NULL;
			wd_page_ring->wd_work[i].cache = 0;
		}

		_os_mem_free(drv_priv, wd_page_ring->wd_work,
			      buf_num * sizeof(*wd_page_ring->wd_work));
		wd_page_ring->wd_work = NULL;
	}

	if (NULL != wd_page_ring->wd_work_ring) {
		_os_mem_free(drv_priv, wd_page_ring->wd_work_ring,
			      buf_num * sizeof(struct rtw_wd_page *));
		wd_page_ring->wd_work_ring = NULL;
        }
	wd_page_ring->wd_work_cnt = 0;
	wd_page_ring->wd_work_idx = 0;

}

static enum rtw_phl_status
_phl_alloc_wd_work_ring(struct phl_info_t *phl_info,
			struct rtw_wd_page_ring *wd_page_ring)
{
	enum rtw_phl_status psts = RTW_PHL_STATUS_FAILURE;
	void *drv_priv = phl_to_drvpriv(phl_info);
	struct hal_spec_t *hal_spec = phl_get_ic_spec(phl_info->phl_com);
	struct rtw_wd_page *wd_work = NULL;
	u32 buf_len = 0;
	u16 buf_num = 0, i = 0;

	buf_num = hal_spec->txbd_multi_tag;

	wd_page_ring->wd_work = _os_mem_alloc(drv_priv,
					       buf_num * sizeof(*wd_work));
	if (!wd_page_ring->wd_work) {
		psts = RTW_PHL_STATUS_RESOURCE;
		goto out;
	}
	wd_work = wd_page_ring->wd_work;
	_os_mem_set(drv_priv, wd_work, 0, buf_num * sizeof(*wd_work));

	wd_page_ring->wd_work_ring =
		_os_mem_alloc(drv_priv,
			       buf_num * sizeof(struct rtw_wd_page *));
	if (!wd_page_ring->wd_work_ring) {
		psts = RTW_PHL_STATUS_RESOURCE;
		goto out;
	}
	_os_mem_set(drv_priv, wd_page_ring->wd_work_ring, 0,
		    buf_num * sizeof(struct rtw_wd_page *));

	for (i = 0; i < buf_num; i++) {
		wd_work[i].cache = true;
		buf_len = WD_PAGE_SIZE;
		wd_work[i].vir_addr = _os_shmem_alloc(drv_priv,
					(_dma *)&wd_work[i].phy_addr_l,
					(_dma *)&wd_work[i].phy_addr_h,
					buf_len,
					wd_work[i].cache,
					PCI_DMA_TODEVICE,
					&wd_work[i].os_rsvd[0]);
 		if (NULL == wd_work[i].vir_addr) {
			psts = RTW_PHL_STATUS_RESOURCE;
			goto out;
		}
		wd_work[i].buf_len = buf_len;
		wd_work[i].wp_seq = WP_RESERVED_SEQ;
		INIT_LIST_HEAD(&wd_work[i].list);

		wd_page_ring->wd_work_ring[i] = &wd_work[i];
		wd_page_ring->wd_work_cnt++;
		/* hana_todo now check 4 byte align only */
		/* if ((unsigned long)wd_page_buf & 0xF) { */
		/* 	res = _FAIL; */
		/* 	break; */
		/* } */
	}

	wd_page_ring->wd_work_idx = 0;
	psts = RTW_PHL_STATUS_SUCCESS;

out:
	if (RTW_PHL_STATUS_SUCCESS != psts) {
		_phl_free_wd_work_ring(phl_info, wd_page_ring);
		wd_page_ring->wd_work = NULL;
		wd_page_ring->wd_work_ring = NULL;
	}

	return psts;
}
#else
#define _phl_free_h2c_work_ring(_phl, _ring)
#define _phl_alloc_h2c_work_ring(_phl, _ring) RTW_PHL_STATUS_SUCCESS
#define _phl_free_wd_work_ring(_phl, _ring)
#define _phl_alloc_wd_work_ring(_phl, _ring) RTW_PHL_STATUS_SUCCESS
#endif

static enum rtw_phl_status enqueue_pending_wd_page(struct phl_info_t *phl_info,
				struct rtw_wd_page_ring *wd_page_ring,
				struct rtw_wd_page *wd_page, u8 pos)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	void *drv_priv = phl_to_drvpriv(phl_info);
	_os_list *list = &wd_page_ring->pending_wd_page_list;

	if (wd_page != NULL) {
		_os_spinlock(drv_priv, &wd_page_ring->pending_lock, _bh, NULL);

		if (_tail == pos)
			list_add_tail(&wd_page->list, list);
		else if (_first == pos)
			list_add(&wd_page->list, list);

		wd_page_ring->pending_wd_page_cnt++;

		_os_spinunlock(drv_priv, &wd_page_ring->pending_lock, _bh, NULL);

		pstatus = RTW_PHL_STATUS_SUCCESS;
	}

	return pstatus;
}

static enum rtw_phl_status enqueue_busy_wd_page(struct phl_info_t *phl_info,
				struct rtw_wd_page_ring *wd_page_ring,
				struct rtw_wd_page *wd_page, u8 pos)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	void *drv_priv = phl_to_drvpriv(phl_info);
	_os_list *list = &wd_page_ring->busy_wd_page_list;

	if (wd_page != NULL) {
		_os_spinlock(drv_priv, &wd_page_ring->busy_lock, _bh, NULL);

		if (_tail == pos)
			list_add_tail(&wd_page->list, list);
		else if (_first == pos)
			list_add(&wd_page->list, list);

		wd_page_ring->busy_wd_page_cnt++;

		_os_spinunlock(drv_priv, &wd_page_ring->busy_lock, _bh, NULL);

		pstatus = RTW_PHL_STATUS_SUCCESS;
	}

	return pstatus;
}


static enum rtw_phl_status enqueue_idle_wd_page(
				struct phl_info_t *phl_info,
				struct rtw_wd_page_ring *wd_page_ring,
				struct rtw_wd_page *wd_page)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	void *drv_priv = phl_to_drvpriv(phl_info);
	_os_list *list = &wd_page_ring->idle_wd_page_list;

	if (wd_page != NULL) {
		wd_page->buf_len = WD_PAGE_SIZE;
		wd_page->wp_seq = WP_RESERVED_SEQ;
		wd_page->host_idx = 0;
		INIT_LIST_HEAD(&wd_page->list);

		_os_spinlock(drv_priv, &wd_page_ring->idle_lock, _bh, NULL);

		list_add_tail(&wd_page->list, list);
		wd_page_ring->idle_wd_page_cnt++;

		_os_spinunlock(drv_priv, &wd_page_ring->idle_lock, _bh, NULL);

		pstatus = RTW_PHL_STATUS_SUCCESS;
	}

	return pstatus;
}

#ifdef RTW_WKARD_TXBD_UPD_LMT
static enum rtw_phl_status enqueue_h2c_work_ring(
				struct phl_info_t *phl_info,
				struct rtw_h2c_pkt *h2c)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	void *drv_priv = phl_to_drvpriv(phl_info);
	struct hci_info_t *hci_info = (struct hci_info_t *)phl_info->hci;
	struct rtw_wd_page_ring *wd_ring = NULL;
	struct rtw_h2c_work *h2c_work = NULL;
	struct rtw_h2c_pkt *work_done_h2c = NULL;
	struct rtw_h2c_pkt **ring = NULL;
	u16 *idx = 0, *cnt = 0;
	u8 fwcmd_qidx = 0;

	fwcmd_qidx = rtw_hal_get_fwcmd_queue_idx(phl_info->hal);
	wd_ring = (struct rtw_wd_page_ring *)hci_info->wd_ring;
	h2c_work = &wd_ring[fwcmd_qidx].h2c_work;

	if (h2c == NULL)
		goto out;

	_os_spinlock(drv_priv, &h2c_work->lock, _bh, NULL);

	if (H2CB_TYPE_CMD == h2c->type) {
		ring = h2c_work->cmd_ring;
		idx = &h2c_work->cmd_idx;
		cnt = &h2c_work->cmd_cnt;
	} else if (H2CB_TYPE_DATA == h2c->type) {
		ring = h2c_work->data_ring;
		idx = &h2c_work->data_idx;
		cnt = &h2c_work->data_cnt;
	} else if (H2CB_TYPE_LONG_DATA == h2c->type) {
		ring = h2c_work->ldata_ring;
		idx = &h2c_work->ldata_idx;
		cnt = &h2c_work->ldata_cnt;
	} else {
		_os_spinunlock(drv_priv, &h2c_work->lock, _bh, NULL);
		goto out;
	}

	work_done_h2c = ring[*idx];
	ring[*idx] = h2c;
	*idx = (*idx + 1) % *cnt;

	_os_spinunlock(drv_priv, &h2c_work->lock, _bh, NULL);

	pstatus = phl_enqueue_idle_h2c_pkt(phl_info, work_done_h2c);

out:
	return pstatus;
}

static enum rtw_phl_status enqueue_wd_work_ring(
				struct phl_info_t *phl_info,
				struct rtw_wd_page_ring *wd_page_ring,
				struct rtw_wd_page *wd_page)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	void *drv_priv = phl_to_drvpriv(phl_info);
	struct rtw_wd_page *work_done_wd = NULL;
	struct rtw_wd_page **ring = wd_page_ring->wd_work_ring;

	if (wd_page != NULL) {

		_os_spinlock(drv_priv, &wd_page_ring->work_lock, _bh, NULL);

		work_done_wd = ring[wd_page_ring->wd_work_idx];
		ring[wd_page_ring->wd_work_idx] = wd_page;
		wd_page_ring->wd_work_idx =
		    (wd_page_ring->wd_work_idx + 1) % wd_page_ring->wd_work_cnt;

		_os_spinunlock(drv_priv, &wd_page_ring->work_lock, _bh, NULL);

		enqueue_idle_wd_page(phl_info, wd_page_ring, work_done_wd);

		pstatus = RTW_PHL_STATUS_SUCCESS;
	}

	return pstatus;
}
#else
#define enqueue_h2c_work_ring(_phl, _h2c) RTW_PHL_STATUS_FAILURE
#define enqueue_wd_work_ring(_phl, _ring, _wd) RTW_PHL_STATUS_FAILURE
#endif


static struct rtw_wd_page *query_pending_wd_page(struct phl_info_t *phl_info,
				struct rtw_wd_page_ring *wd_page_ring)
{
	void *drv_priv = phl_to_drvpriv(phl_info);
	_os_list *pending_list = &wd_page_ring->pending_wd_page_list;
	struct rtw_wd_page *wd_page = NULL;

	_os_spinlock(drv_priv, &wd_page_ring->pending_lock, _bh, NULL);

	if (true == list_empty(pending_list)) {
		wd_page = NULL;
	} else {
		wd_page = list_first_entry(pending_list, struct rtw_wd_page,
						list);
		wd_page_ring->pending_wd_page_cnt--;
		list_del(&wd_page->list);
	}

	_os_spinunlock(drv_priv, &wd_page_ring->pending_lock, _bh, NULL);

	return wd_page;
}


static struct rtw_wd_page *query_idle_wd_page(struct phl_info_t *phl_info,
				struct rtw_wd_page_ring *wd_page_ring)
{
	void *drv_priv = phl_to_drvpriv(phl_info);
	_os_list *idle_list = &wd_page_ring->idle_wd_page_list;
	struct rtw_wd_page *wd_page = NULL;

	_os_spinlock(drv_priv, &wd_page_ring->idle_lock, _bh, NULL);

	if (true == list_empty(idle_list)) {
		wd_page = NULL;
	} else {
		wd_page = list_first_entry(idle_list, struct rtw_wd_page, list);
		wd_page_ring->idle_wd_page_cnt--;
		list_del(&wd_page->list);
	}

	_os_spinunlock(drv_priv, &wd_page_ring->idle_lock, _bh, NULL);

	return wd_page;
}

static enum rtw_phl_status rtw_release_target_wd_page(
					struct phl_info_t *phl_info,
					struct rtw_wd_page_ring *wd_page_ring,
					struct rtw_wd_page *wd_page)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;

	if (wd_page_ring != NULL && wd_page != NULL) {
		enqueue_idle_wd_page(phl_info, wd_page_ring, wd_page);
		pstatus = RTW_PHL_STATUS_SUCCESS;
	}

	return pstatus;
}

static enum rtw_phl_status rtw_release_pending_wd_page(
				struct phl_info_t *phl_info,
				struct rtw_wd_page_ring *wd_page_ring,
				u16 release_num)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	_os_list *list = &wd_page_ring->pending_wd_page_list;
	struct rtw_wd_page *wd_page = NULL;

	if (wd_page_ring != NULL) {
		while (release_num > 0 && true != list_empty(list)) {

			wd_page = query_pending_wd_page(phl_info, wd_page_ring);

			enqueue_idle_wd_page(phl_info, wd_page_ring, wd_page);

			release_num--;
		}
		pstatus = RTW_PHL_STATUS_SUCCESS;
	}
	return pstatus;
}

static enum rtw_phl_status rtw_release_busy_wd_page(
				struct phl_info_t *phl_info,
				struct rtw_wd_page_ring *wd_page_ring,
				u16 release_num)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	_os_list *list = &wd_page_ring->busy_wd_page_list;
	struct rtw_wd_page *wd_page = NULL;
	void *drv_priv = phl_to_drvpriv(phl_info);
	struct hal_spec_t *hal_spec = phl_get_ic_spec(phl_info->phl_com);

	if (wd_page_ring != NULL) {
		_os_spinlock(drv_priv, &wd_page_ring->busy_lock, _bh, NULL);

		while (release_num > 0 && true != list_empty(list)) {

			wd_page = list_first_entry(list, struct rtw_wd_page,
							list);
			wd_page_ring->busy_wd_page_cnt--;
			list_del(&wd_page->list);

			_os_spinunlock(drv_priv, &wd_page_ring->busy_lock, _bh, NULL);
			if (true == hal_spec->txbd_upd_lmt) {
				pstatus = enqueue_wd_work_ring(phl_info,
							       wd_page_ring,
							       wd_page);
			} else {
				pstatus = enqueue_idle_wd_page(phl_info,
							       wd_page_ring,
							       wd_page);
			}
			_os_spinlock(drv_priv, &wd_page_ring->busy_lock, _bh, NULL);

			if (RTW_PHL_STATUS_SUCCESS != pstatus)
				break;
			release_num--;
		}
		_os_spinunlock(drv_priv, &wd_page_ring->busy_lock, _bh, NULL);

	}
	return pstatus;
}

static void _phl_reset_txbd(struct phl_info_t *phl_info,
				struct tx_base_desc *txbd)
{
	struct rtw_hal_com_t *hal_com = rtw_hal_get_halcom(phl_info->hal);
	_os_mem_set(phl_to_drvpriv(phl_info), txbd->vir_addr, 0, txbd->buf_len);
	txbd->host_idx = 0;
	txbd->avail_num = (u16)hal_com->bus_cap.txbd_num;
}
static void _phl_reset_wp_tag(struct phl_info_t *phl_info,
			struct rtw_wd_page_ring *wd_page_ring, u8 dma_ch)
{
	u16 wp_seq = 0;

	for (wp_seq = 0; wp_seq < WP_MAX_SEQ_NUMBER; wp_seq++) {
		if (NULL != wd_page_ring->wp_tag[wp_seq].ptr)
			phl_recycle_payload(phl_info, dma_ch, wp_seq,
					    TX_STATUS_TX_FAIL_SW_DROP);
	}
}


static enum rtw_phl_status enqueue_pending_h2c_pkt(struct phl_info_t *phl_info,
				struct phl_h2c_pkt_pool *h2c_pkt_pool,
				struct rtw_h2c_pkt *h2c_pkt, u8 pos)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
#if 0
	void *drv_priv = phl_to_drvpriv(phl_info);
	_os_list *list = &wd_page_ring->pending_wd_page_list;

	if (wd_page != NULL) {
		_os_spinlock(drv_priv, &wd_page_ring->pending_lock, _bh, NULL);

		if (_tail == pos)
			list_add_tail(&wd_page->list, list);
		else if (_first == pos)
			list_add(&wd_page->list, list);

		wd_page_ring->pending_wd_page_cnt++;

		_os_spinunlock(drv_priv, &wd_page_ring->pending_lock, _bh, NULL);

		pstatus = RTW_PHL_STATUS_SUCCESS;
	}
#endif
	return pstatus;
}

static struct rtw_h2c_pkt *query_pending_h2c_pkt(struct phl_info_t *phl_info,
				struct phl_h2c_pkt_pool *h2c_pkt_pool)
{
	//void *drv_priv = phl_to_drvpriv(phl_info);
	//_os_list *pending_list = &wd_page_ring->pending_wd_page_list;
	struct rtw_h2c_pkt *h2c_pkt = NULL;
#if 0
	_os_spinlock(drv_priv, &wd_page_ring->pending_lock, _bh, NULL);

	if (true == list_empty(pending_list)) {
		wd_page = NULL;
	} else {
		wd_page = list_first_entry(pending_list, struct rtw_wd_page,
						list);
		wd_page_ring->pending_wd_page_cnt--;
		list_del(&wd_page->list);
	}

	_os_spinunlock(drv_priv, &wd_page_ring->pending_lock, _bh, NULL);
#endif
	return h2c_pkt;
}

static enum rtw_phl_status phl_release_busy_h2c_pkt(
				struct phl_info_t *phl_info,
				struct phl_h2c_pkt_pool *h2c_pkt_pool,
				u16 release_num)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	_os_list *list = &h2c_pkt_pool->busy_h2c_pkt_list.queue;
	struct rtw_h2c_pkt *h2c_pkt = NULL;
	struct hal_spec_t *hal_spec = phl_get_ic_spec(phl_info->phl_com);

	if (h2c_pkt_pool != NULL) {

		while (release_num > 0 && true != list_empty(list)) {
			h2c_pkt = phl_query_busy_h2c_pkt(phl_info);

			if (!h2c_pkt)
				break;

			if (true == hal_spec->txbd_upd_lmt) {
				pstatus = enqueue_h2c_work_ring(phl_info,
								h2c_pkt);
			} else {
				pstatus = phl_enqueue_idle_h2c_pkt(phl_info,
								   h2c_pkt);
			}

			if (RTW_PHL_STATUS_SUCCESS != pstatus)
				break;
			release_num--;
		}
	}
	return pstatus;
}

static void phl_tx_reset_pcie(struct phl_info_t *phl_info)
{
	struct hci_info_t *hci_info = (struct hci_info_t *)phl_info->hci;
	struct rtw_wd_page_ring *wd_ring = NULL;
	struct tx_base_desc *txbd = NULL;
	struct phl_h2c_pkt_pool *h2c_pool = NULL;
	u8 ch = 0;

	txbd = (struct tx_base_desc *)hci_info->txbd_buf;
	wd_ring = (struct rtw_wd_page_ring *)hci_info->wd_ring;
	h2c_pool = (struct phl_h2c_pkt_pool *)phl_info->h2c_pool;

	for (ch = 0; ch < hci_info->total_txch_num; ch++) {
		_phl_reset_txbd(phl_info, &txbd[ch]);
		rtw_release_busy_wd_page(phl_info, &wd_ring[ch],
					 wd_ring[ch].busy_wd_page_cnt);
		rtw_release_pending_wd_page(phl_info, &wd_ring[ch],
					 wd_ring[ch].pending_wd_page_cnt);
		_phl_reset_wp_tag(phl_info, &wd_ring[ch], ch);
	}

	phl_release_busy_h2c_pkt(phl_info, h2c_pool,
				 (u16)h2c_pool->busy_h2c_pkt_list.cnt);

	phl_dump_h2c_pool_stats(phl_info->h2c_pool);
}



#ifdef CONFIG_DYNAMIC_RX_BUF
void _phl_alloc_dynamic_rxbuf_pcie(struct rtw_rx_buf *rx_buf,
					struct phl_info_t *phl_info)
{
	enum rtw_phl_status sts = RTW_PHL_STATUS_FAILURE;
	void *drv_priv = phl_to_drvpriv(phl_info);
	u32 buf_len = RX_BUF_SIZE;

	if (rx_buf->reuse) {
		rx_buf->reuse = false;
		_os_pkt_buf_map_rx(drv_priv,
				(_dma *)&rx_buf->phy_addr_l,
				(_dma *)&rx_buf->phy_addr_h,
				RX_BUF_SIZE + PHL_RX_HEADROOM,
				rx_buf->os_priv);
		return;
	}

	if (rx_buf != NULL) {
		rx_buf->cache = true;
		rx_buf->vir_addr = _os_pkt_buf_alloc_rx(
				drv_priv,
				(_dma *)&rx_buf->phy_addr_l,
				(_dma *)&rx_buf->phy_addr_h,
				buf_len,
				&rx_buf->os_priv);
		if (NULL == rx_buf->vir_addr) {
			sts = RTW_PHL_STATUS_RESOURCE;
		} else {
			rx_buf->buf_len = buf_len;
			rx_buf->dynamic = 1;
			rx_buf->reuse = false;
			/* enqueue_idle_rx_buf(phl_info, rx_buf_ring, rx_buf); */
			sts = RTW_PHL_STATUS_SUCCESS;
		}
	}
}
#endif


static enum rtw_phl_status enqueue_busy_rx_buf(
				struct phl_info_t *phl_info,
				struct rtw_rx_buf_ring *rx_buf_ring,
				struct rtw_rx_buf *rx_buf, u8 pos)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	_os_list *list = &rx_buf_ring->busy_rxbuf_list;

	if (rx_buf != NULL) {
		_os_spinlock(phl_to_drvpriv(phl_info),
				&rx_buf_ring->busy_rxbuf_lock, _bh, NULL);
		if (_tail == pos)
			list_add_tail(&rx_buf->list, list);
		else if (_first == pos)
			list_add(&rx_buf->list, list);

		rx_buf_ring->busy_rxbuf_cnt++;
		_os_spinunlock(phl_to_drvpriv(phl_info),
				&rx_buf_ring->busy_rxbuf_lock, _bh, NULL);
		pstatus = RTW_PHL_STATUS_SUCCESS;
	}

	return pstatus;
}


static enum rtw_phl_status enqueue_idle_rx_buf(
				struct phl_info_t *phl_info,
				struct rtw_rx_buf_ring *rx_buf_ring,
				struct rtw_rx_buf *rx_buf)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	_os_list *list = &rx_buf_ring->idle_rxbuf_list;

	if (rx_buf != NULL) {
		_os_mem_set(phl_to_drvpriv(phl_info), rx_buf->vir_addr, 0,
					RX_BUF_SIZE);
		rx_buf->buf_len = RX_BUF_SIZE;
		INIT_LIST_HEAD(&rx_buf->list);

		_os_spinlock(phl_to_drvpriv(phl_info),
					&rx_buf_ring->idle_rxbuf_lock, _bh, NULL);
		list_add_tail(&rx_buf->list, list);
		rx_buf_ring->idle_rxbuf_cnt++;
		_os_spinunlock(phl_to_drvpriv(phl_info),
					&rx_buf_ring->idle_rxbuf_lock, _bh, NULL);

		pstatus = RTW_PHL_STATUS_SUCCESS;
	}

	return pstatus;
}

static struct rtw_rx_buf *query_busy_rx_buf(struct phl_info_t *phl_info,
					struct rtw_rx_buf_ring *rx_buf_ring)
{
	_os_list *busy_list = &rx_buf_ring->busy_rxbuf_list;
	struct rtw_rx_buf *rx_buf = NULL;

	_os_spinlock(phl_to_drvpriv(phl_info),
			&rx_buf_ring->busy_rxbuf_lock, _bh, NULL);
	if (true == list_empty(busy_list)) {
		rx_buf = NULL;
	} else {
		rx_buf = list_first_entry(busy_list, struct rtw_rx_buf, list);
		rx_buf_ring->busy_rxbuf_cnt--;
		list_del(&rx_buf->list);
	}
	_os_spinunlock(phl_to_drvpriv(phl_info),
			&rx_buf_ring->busy_rxbuf_lock, _bh, NULL);
	return rx_buf;
}

static struct rtw_rx_buf *query_idle_rx_buf(struct phl_info_t *phl_info,
					struct rtw_rx_buf_ring *rx_buf_ring)
{
	_os_list *idle_list = &rx_buf_ring->idle_rxbuf_list;
	struct rtw_rx_buf *rx_buf = NULL;

	_os_spinlock(phl_to_drvpriv(phl_info),
			&rx_buf_ring->idle_rxbuf_lock, _bh, NULL);
	if (true == list_empty(idle_list)) {
		rx_buf = NULL;
	} else {
		rx_buf = list_first_entry(idle_list, struct rtw_rx_buf, list);
		rx_buf_ring->idle_rxbuf_cnt--;
		list_del(&rx_buf->list);
	}
	_os_spinunlock(phl_to_drvpriv(phl_info),
			&rx_buf_ring->idle_rxbuf_lock, _bh, NULL);

	return rx_buf;
}

enum rtw_phl_status
phl_release_target_rx_buf(struct phl_info_t *phl_info, void *r, u8 ch,
				enum rtw_rx_type type)
{
	struct hci_info_t *hci_info = (struct hci_info_t *)phl_info->hci;
	struct rtw_rx_buf_ring *rx_buf_ring = NULL;
	struct rtw_rx_buf *rx_buf = (struct rtw_rx_buf *)r;
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;

	rx_buf_ring = (struct rtw_rx_buf_ring *)hci_info->rxbuf_pool;

#ifdef CONFIG_DYNAMIC_RX_BUF
	if(rx_buf && (type == RTW_RX_TYPE_WIFI))
		_phl_alloc_dynamic_rxbuf_pcie(rx_buf, phl_info);
#endif
	if (&rx_buf_ring[ch] != NULL && rx_buf != NULL) {
		enqueue_idle_rx_buf(phl_info, &rx_buf_ring[ch], rx_buf);
		pstatus = RTW_PHL_STATUS_SUCCESS;
	}

	return pstatus;
}

static enum rtw_phl_status phl_release_busy_rx_buf(
				struct phl_info_t *phl_info,
				struct rtw_rx_buf_ring *rx_buf_ring,
				u16 release_num)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	struct rtw_rx_buf *rx_buf = NULL;

	if (rx_buf_ring != NULL) {

		while (release_num > 0) {
			rx_buf = query_busy_rx_buf(phl_info, rx_buf_ring);
			if (NULL == rx_buf)
				break;
			enqueue_idle_rx_buf(phl_info, rx_buf_ring, rx_buf);
			release_num--;
		}
		pstatus = RTW_PHL_STATUS_SUCCESS;
	}
	return pstatus;
}



/* static void rtl8852ae_free_wd_page_buf(_adapter *adapter, void *vir_addr, */
/* 				dma_addr_t *bus_addr, size_t size) */
/* { */
/* 	struct platform_ops *ops = &adapter->platform_func; */
/* 	struct dvobj_priv *dvobj = adapter_to_dvobj(adapter); */
/* 	struct pci_dev *pdev = dvobj->ppcidev; */

/* 	FUNCIN(); */
/* 	ops->free_cache_mem(pdev,vir_addr, bus_addr, size, PCI_DMA_TODEVICE); */

/* 	/\* NONCACHE hana_todo */
/* 	 * ops->alloc_noncache_mem(pdev, vir_addr, bus_addr, size); */
/* 	 *\/ */
/* 	FUNCOUT(); */
/* } */

static void _phl_free_rxbuf_pcie(struct phl_info_t *phl_info,
				 struct rtw_rx_buf *rx_buf)
{
	struct rtw_hal_com_t *hal_com = rtw_hal_get_halcom(phl_info->hal);
	u16 rxbuf_num = (u16)hal_com->bus_cap.rxbuf_num;
	u16 i = 0;

	if (NULL != rx_buf) {
		for (i = 0; i < rxbuf_num; i++) {

			if (NULL == rx_buf[i].vir_addr)
				continue;
			_os_pkt_buf_free_rx(phl_to_drvpriv(phl_info),
								rx_buf[i].vir_addr,
								(_dma)rx_buf[i].phy_addr_l,
								(_dma)rx_buf[i].phy_addr_h,
								rx_buf[i].buf_len,
								rx_buf[i].os_priv);
			rx_buf[i].vir_addr = NULL;
			rx_buf[i].cache = 0;
		}

		_os_mem_free(phl_to_drvpriv(phl_info), rx_buf,
					sizeof(struct rtw_rx_buf) * rxbuf_num);
	}
}

static void _phl_free_rxbuf_pool_pcie(struct phl_info_t *phl_info,
						u8 *rxbuf_pool, u8 ch_num)
{
	struct rtw_rx_buf_ring *ring = NULL;
	u8 i = 0;

	FUNCIN();
		ring = (struct rtw_rx_buf_ring *)rxbuf_pool;
	if (NULL != ring) {
		for (i = 0; i < ch_num; i++) {

			ring[i].idle_rxbuf_cnt = 0;

			if (NULL == ring[i].rx_buf)
				continue;

			_phl_free_rxbuf_pcie(phl_info, ring[i].rx_buf);
			ring[i].rx_buf = NULL;
			_os_spinlock_free(phl_to_drvpriv(phl_info),
					&ring->idle_rxbuf_lock);
			_os_spinlock_free(phl_to_drvpriv(phl_info),
					&ring->busy_rxbuf_lock);
		}
		_os_mem_free(phl_to_drvpriv(phl_info), ring,
					sizeof(struct rtw_rx_buf_ring) * ch_num);
	}

	FUNCOUT();
}

/* static void *rtl8852ae_alloc_wd_page_buf(_adapter *adapter, */
/* 					 dma_addr_t *bus_addr, size_t size) */
/* { */
/* 	struct platform_ops *ops = &adapter->platform_func; */
/* 	struct dvobj_priv *dvobj = adapter_to_dvobj(adapter); */
/* 	struct pci_dev *pdev = dvobj->ppcidev; */
/* 	void *vir_addr = NULL; */

/* 	FUNCIN(); */
/* 	vir_addr = ops->alloc_cache_mem(pdev, bus_addr, size, PCI_DMA_TODEVICE); */

/* 	/\* NONCACHE hana_todo */
/* 	 * vir_addr = ops->alloc_noncache_mem(pdev, bus_addr, size); */
/* 	 *\/ */

/* 	FUNCOUT(); */
/* 	return vir_addr; */
/* } */

static 	struct rtw_rx_buf *
_phl_alloc_rxbuf_pcie(struct phl_info_t *phl_info,
				struct rtw_rx_buf_ring *rx_buf_ring)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	struct rtw_hal_com_t *hal_com = rtw_hal_get_halcom(phl_info->hal);
	struct rtw_rx_buf *rx_buf = NULL;
	u32 buf_len = 0;
	u16 rxbuf_num = (u16)hal_com->bus_cap.rxbuf_num;
	void *drv_priv = phl_to_drvpriv(phl_info);
	int i;

	buf_len = sizeof(*rx_buf) * rxbuf_num;
	rx_buf = _os_mem_alloc(drv_priv, buf_len);
	if (rx_buf != NULL) {
		for (i = 0; i < rxbuf_num; i++) {
			rx_buf[i].cache = true;
			buf_len = RX_BUF_SIZE;
			rx_buf[i].vir_addr = _os_pkt_buf_alloc_rx(
					phl_to_drvpriv(phl_info),
					(_dma *)&rx_buf[i].phy_addr_l,
					(_dma *)&rx_buf[i].phy_addr_h,
					buf_len,
					&rx_buf[i].os_priv);
			if (NULL == rx_buf[i].vir_addr) {
				pstatus = RTW_PHL_STATUS_RESOURCE;
				break;
			}
			rx_buf[i].buf_len = buf_len;
			rx_buf[i].dynamic = 0;
#ifdef CONFIG_DYNAMIC_RX_BUF
			rx_buf[i].reuse = false;
#endif

			INIT_LIST_HEAD(&rx_buf[i].list);
			enqueue_idle_rx_buf(phl_info, rx_buf_ring, &rx_buf[i]);
			pstatus = RTW_PHL_STATUS_SUCCESS;
				/* hana_todo now check 4 byte align only */
			/* if ((unsigned long)wd_page_buf & 0xF) { */
			/* 	res = _FAIL; */
			/* 	break; */
			/* } */
		}
	}

	if (RTW_PHL_STATUS_SUCCESS != pstatus) {
		_phl_free_rxbuf_pcie(phl_info, rx_buf);
		rx_buf = NULL;
	}
	return rx_buf;
}



static enum rtw_phl_status
_phl_alloc_rxbuf_pool_pcie(struct phl_info_t *phl_info, u8 ch_num)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	struct rtw_hal_com_t *hal_com = rtw_hal_get_halcom(phl_info->hal);
	struct rtw_rx_buf_ring *rx_buf_ring = NULL;
	struct rtw_rx_buf *rx_buf = NULL;
	u32 buf_len = 0;
	u16 rxbuf_num = (u16)hal_com->bus_cap.rxbuf_num;
	int i;
	FUNCIN_WSTS(pstatus);

	buf_len = sizeof(*rx_buf_ring) * ch_num;
	rx_buf_ring = _os_mem_alloc(phl_to_drvpriv(phl_info), buf_len);

	if (NULL != rx_buf_ring) {
		for (i = 0; i < ch_num; i++) {
			_os_spinlock_init(phl_to_drvpriv(phl_info),
					&rx_buf_ring[i].idle_rxbuf_lock);
			_os_spinlock_init(phl_to_drvpriv(phl_info),
					&rx_buf_ring[i].busy_rxbuf_lock);
			INIT_LIST_HEAD(&rx_buf_ring[i].idle_rxbuf_list);
			INIT_LIST_HEAD(&rx_buf_ring[i].busy_rxbuf_list);
			rx_buf = _phl_alloc_rxbuf_pcie(phl_info,
							&rx_buf_ring[i]);
			if (NULL == rx_buf) {
				pstatus = RTW_PHL_STATUS_RESOURCE;
				break;
			}
			rx_buf_ring[i].rx_buf = rx_buf;
			rx_buf_ring[i].idle_rxbuf_cnt = rxbuf_num;
			rx_buf_ring[i].busy_rxbuf_cnt = 0;
			pstatus = RTW_PHL_STATUS_SUCCESS;
		}
	}

	if (RTW_PHL_STATUS_SUCCESS == pstatus) {
		phl_info->hci->rxbuf_pool = (u8 *)rx_buf_ring;
	} else
		_phl_free_rxbuf_pool_pcie(phl_info, (u8 *)rx_buf_ring, ch_num);
	FUNCOUT_WSTS(pstatus);

	return pstatus;
}




/* static void rtl8852ae_free_wd_page_buf(_adapter *adapter, void *vir_addr, */
/* 				dma_addr_t *bus_addr, size_t size) */
/* { */
/* 	struct platform_ops *ops = &adapter->platform_func; */
/* 	struct dvobj_priv *dvobj = adapter_to_dvobj(adapter); */
/* 	struct pci_dev *pdev = dvobj->ppcidev; */

/* 	FUNCIN(); */
/* 	ops->free_cache_mem(pdev,vir_addr, bus_addr, size, PCI_DMA_TODEVICE); */

/* 	/\* NONCACHE hana_todo */
/* 	 * ops->alloc_noncache_mem(pdev, vir_addr, bus_addr, size); */
/* 	 *\/ */
/* 	FUNCOUT(); */
/* } */

static void _phl_free_wd_page_pcie(struct phl_info_t *phl_info,
					struct rtw_wd_page *wd_page)
{
	u16 i = 0;

	if (NULL != wd_page) {
		for (i = 0; i < MAX_WD_PAGE_NUM; i++) {

			if (NULL == wd_page[i].vir_addr)
				continue;

			wd_page[i].wp_seq = WP_RESERVED_SEQ;
			_os_shmem_free(phl_to_drvpriv(phl_info),
						wd_page[i].vir_addr,
						(_dma *)&wd_page[i].phy_addr_l,
						(_dma *)&wd_page[i].phy_addr_h,
						wd_page[i].buf_len,
						wd_page[i].cache,
						PCI_DMA_FROMDEVICE,
						wd_page[i].os_rsvd[0]);
			wd_page[i].vir_addr = NULL;
			wd_page[i].cache = 0;
		}

		_os_mem_free(phl_to_drvpriv(phl_info), wd_page,
					sizeof(struct rtw_wd_page) * MAX_WD_PAGE_NUM);
	}
}

static void _phl_free_wd_ring_pcie(struct phl_info_t *phl_info, u8 *wd_page_buf,
					u8 ch_num)
{
	struct rtw_wd_page_ring *wd_page_ring = NULL;
	void *drv_priv = phl_to_drvpriv(phl_info);
	u8 i = 0;
	FUNCIN();

	wd_page_ring = (struct rtw_wd_page_ring *)wd_page_buf;
	if (NULL != wd_page_ring) {
		for (i = 0; i < ch_num; i++) {

			wd_page_ring[i].idle_wd_page_cnt = 0;

			if (NULL == wd_page_ring[i].wd_page)
				continue;

			if (i == rtw_hal_get_fwcmd_queue_idx(phl_info->hal)) {
				_phl_free_h2c_work_ring(phl_info,
							&wd_page_ring[i]);
			}
			_phl_free_wd_work_ring(phl_info, &wd_page_ring[i]);
			_phl_free_wd_page_pcie(phl_info,
						wd_page_ring[i].wd_page);
			wd_page_ring[i].wd_page = NULL;
			_os_spinlock_free(drv_priv,
						&wd_page_ring[i].idle_lock);
			_os_spinlock_free(drv_priv,
						&wd_page_ring[i].busy_lock);
			_os_spinlock_free(drv_priv,
						&wd_page_ring[i].pending_lock);
			_os_spinlock_free(drv_priv,
						&wd_page_ring[i].work_lock);
			_os_spinlock_free(drv_priv,
						&wd_page_ring[i].wp_tag_lock);
		}
		_os_mem_free(phl_to_drvpriv(phl_info), wd_page_ring,
						sizeof(struct rtw_wd_page_ring) * ch_num);
	}
	FUNCOUT();
}

/* static void *rtl8852ae_alloc_wd_page_buf(_adapter *adapter, */
/* 					 dma_addr_t *bus_addr, size_t size) */
/* { */
/* 	struct platform_ops *ops = &adapter->platform_func; */
/* 	struct dvobj_priv *dvobj = adapter_to_dvobj(adapter); */
/* 	struct pci_dev *pdev = dvobj->ppcidev; */
/* 	void *vir_addr = NULL; */

/* 	FUNCIN(); */
/* 	vir_addr = ops->alloc_cache_mem(pdev, bus_addr, size, PCI_DMA_TODEVICE); */

/* 	/\* NONCACHE hana_todo */
/* 	 * vir_addr = ops->alloc_noncache_mem(pdev, bus_addr, size); */
/* 	 *\/ */

/* 	FUNCOUT(); */
/* 	return vir_addr; */
/* } */

static struct rtw_wd_page *_phl_alloc_wd_page_pcie(
			struct phl_info_t *phl_info, _os_list *list)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	struct rtw_wd_page *wd_page = NULL;
	u32 buf_len = 0;
	int i;

	buf_len = sizeof(*wd_page) * MAX_WD_PAGE_NUM;
	wd_page = _os_mem_alloc(phl_to_drvpriv(phl_info), buf_len);
	if (wd_page != NULL) {
		for (i = 0; i < MAX_WD_PAGE_NUM; i++) {
			wd_page[i].cache = true;
			buf_len = WD_PAGE_SIZE;
			wd_page[i].vir_addr = _os_shmem_alloc(
						phl_to_drvpriv(phl_info),
						(_dma *)&wd_page[i].phy_addr_l,
						(_dma *)&wd_page[i].phy_addr_h,
						buf_len,
						wd_page[i].cache,
						PCI_DMA_TODEVICE,
						&wd_page[i].os_rsvd[0]);
			if (NULL == wd_page[i].vir_addr) {
				pstatus = RTW_PHL_STATUS_RESOURCE;
				break;
			}
			wd_page[i].buf_len = buf_len;
			wd_page[i].wp_seq = WP_RESERVED_SEQ;
			INIT_LIST_HEAD(&wd_page[i].list);

			list_add_tail(&wd_page[i].list, list);

			pstatus = RTW_PHL_STATUS_SUCCESS;
				/* hana_todo now check 4 byte align only */
			/* if ((unsigned long)wd_page_buf & 0xF) { */
			/* 	res = _FAIL; */
			/* 	break; */
			/* } */
		}
	}

	if (RTW_PHL_STATUS_SUCCESS != pstatus) {
		_phl_free_wd_page_pcie(phl_info, wd_page);
		wd_page = NULL;
	}

	return wd_page;
}



static enum rtw_phl_status
_phl_alloc_wd_ring_pcie(struct phl_info_t *phl_info, u8 ch_num)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	struct rtw_wd_page_ring *wd_page_ring = NULL;
	struct rtw_wd_page *wd_page = NULL;
	void *drv_priv = NULL;
	u32 buf_len = 0;
	int i;

	FUNCIN_WSTS(pstatus);
	drv_priv = phl_to_drvpriv(phl_info);
	buf_len = sizeof(struct rtw_wd_page_ring) * ch_num;
	wd_page_ring = _os_mem_alloc(phl_to_drvpriv(phl_info), buf_len);
	if (NULL != wd_page_ring) {
		for (i = 0; i < ch_num; i++) {
			INIT_LIST_HEAD(&wd_page_ring[i].idle_wd_page_list);
			INIT_LIST_HEAD(&wd_page_ring[i].busy_wd_page_list);
			INIT_LIST_HEAD(&wd_page_ring[i].pending_wd_page_list);
			_os_spinlock_init(drv_priv,
						&wd_page_ring[i].idle_lock);
			_os_spinlock_init(drv_priv,
						&wd_page_ring[i].busy_lock);
			_os_spinlock_init(drv_priv,
						&wd_page_ring[i].pending_lock);
			_os_spinlock_init(drv_priv,
						&wd_page_ring[i].work_lock);
			_os_spinlock_init(drv_priv,
						&wd_page_ring[i].wp_tag_lock);

			wd_page = _phl_alloc_wd_page_pcie(phl_info,
					&wd_page_ring[i].idle_wd_page_list);
			if (NULL == wd_page) {
				pstatus = RTW_PHL_STATUS_RESOURCE;
				break;
			}

			pstatus = _phl_alloc_wd_work_ring(phl_info,
							  &wd_page_ring[i]);
			if (RTW_PHL_STATUS_SUCCESS != pstatus)
				break;

			if (i == rtw_hal_get_fwcmd_queue_idx(phl_info->hal)) {
				pstatus = _phl_alloc_h2c_work_ring(phl_info,
							     &wd_page_ring[i]);
				if (RTW_PHL_STATUS_SUCCESS != pstatus)
					break;
			}
			wd_page_ring[i].wd_page = wd_page;
			wd_page_ring[i].idle_wd_page_cnt = MAX_WD_PAGE_NUM;
			wd_page_ring[i].busy_wd_page_cnt = 0;
			wd_page_ring[i].pending_wd_page_cnt = 0;
			wd_page_ring[i].wp_seq = 1;
			pstatus = RTW_PHL_STATUS_SUCCESS;
		}
	}

	if (RTW_PHL_STATUS_SUCCESS == pstatus) {
		phl_info->hci->wd_ring = (u8 *)wd_page_ring;
	} else
		_phl_free_wd_ring_pcie(phl_info, (u8 *)wd_page_ring, ch_num);
	FUNCOUT_WSTS(pstatus);

	return pstatus;
}

static void _phl_free_h2c_pkt_buf_pcie(struct phl_info_t *phl_info,
				struct rtw_h2c_pkt *_h2c_pkt)
{
	struct rtw_h2c_pkt *h2c_pkt = _h2c_pkt;

	_os_shmem_free(phl_to_drvpriv(phl_info),
				h2c_pkt->vir_head,
				(_dma *)&h2c_pkt->phy_addr_l,
				(_dma *)&h2c_pkt->phy_addr_h,
				h2c_pkt->buf_len,
				h2c_pkt->cache,
				PCI_DMA_FROMDEVICE,
				h2c_pkt->os_rsvd[0]);
}

enum rtw_phl_status _phl_alloc_h2c_pkt_buf_pcie(struct phl_info_t *phl_info,
	struct rtw_h2c_pkt *_h2c_pkt, u32 buf_len)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	struct rtw_h2c_pkt *h2c_pkt = _h2c_pkt;

	h2c_pkt->vir_head = _os_shmem_alloc(
				phl_to_drvpriv(phl_info),
				(_dma *)&h2c_pkt->phy_addr_l,
				(_dma *)&h2c_pkt->phy_addr_h,
				buf_len,
				h2c_pkt->cache,
				PCI_DMA_TODEVICE,
				&h2c_pkt->os_rsvd[0]);

	if (h2c_pkt->vir_head)
		pstatus = RTW_PHL_STATUS_SUCCESS;

	return pstatus;
}

static void _phl_free_rxbd_pcie(struct phl_info_t *phl_info,
						u8 *rxbd_buf, u8 ch_num)
{
	struct rx_base_desc *rxbd = (struct rx_base_desc *)rxbd_buf;
	u8 i = 0;

	FUNCIN();

	if (NULL != rxbd) {
		for (i = 0; i < ch_num; i++) {

			if (NULL == rxbd[i].vir_addr)
				continue;
			_os_shmem_free(phl_to_drvpriv(phl_info),
						rxbd[i].vir_addr,
						(_dma *)&rxbd[i].phy_addr_l,
						(_dma *)&rxbd[i].phy_addr_h,
						rxbd[i].buf_len,
						rxbd[i].cache,
						PCI_DMA_FROMDEVICE,
						rxbd[i].os_rsvd[0]);
			rxbd[i].vir_addr = NULL;
			rxbd[i].cache = 0;
		}

		_os_mem_free(phl_to_drvpriv(phl_info), rxbd,
					sizeof(struct rx_base_desc) * ch_num);
	}
	FUNCOUT();
}


static enum rtw_phl_status
_phl_alloc_rxbd_pcie(struct phl_info_t *phl_info, u8 ch_num)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	struct rtw_hal_com_t *hal_com = rtw_hal_get_halcom(phl_info->hal);
	struct rx_base_desc *rxbd = NULL;
	u32 buf_len = 0;
	u16 rxbd_num = (u16)hal_com->bus_cap.rxbd_num;
	u8 addr_info_size = hal_com->bus_hw_cap.rxbd_len;
	u8 i = 0;
	FUNCIN_WSTS(pstatus);

	buf_len = sizeof(struct rx_base_desc) * ch_num;
	rxbd = _os_mem_alloc(phl_to_drvpriv(phl_info), buf_len);
	if (NULL != rxbd) {
		for (i = 0; i < ch_num; i++) {
			rxbd[i].cache = false;
			buf_len = addr_info_size * rxbd_num;
			rxbd[i].vir_addr = _os_shmem_alloc(
						phl_to_drvpriv(phl_info),
						(_dma *)&rxbd[i].phy_addr_l,
						(_dma *)&rxbd[i].phy_addr_h,
						buf_len,
						rxbd[i].cache,
						PCI_DMA_TODEVICE,
						&rxbd[i].os_rsvd[0]);
			if (NULL == rxbd[i].vir_addr) {
				pstatus = RTW_PHL_STATUS_RESOURCE;
				break;
			}
			rxbd[i].buf_len = buf_len;
			rxbd[i].host_idx = 0;
			rxbd[i].avail_num = rxbd_num;
			pstatus = RTW_PHL_STATUS_SUCCESS;
		}
	}

	if (RTW_PHL_STATUS_SUCCESS == pstatus)
		phl_info->hci->rxbd_buf = (u8 *)rxbd;
	else
		_phl_free_rxbd_pcie(phl_info, (u8 *)rxbd, ch_num);
	FUNCOUT_WSTS(pstatus);

	return pstatus;
}


static void _phl_free_txbd_pcie(struct phl_info_t *phl_info, u8 *txbd_buf,
				u8 ch_num)
{
	struct tx_base_desc *txbd = (struct tx_base_desc *)txbd_buf;
	u8 i = 0;
	FUNCIN();

	if (NULL != txbd) {
		for (i = 0; i < ch_num; i++) {

			if (NULL == txbd[i].vir_addr)
				continue;
			_os_shmem_free(phl_to_drvpriv(phl_info),
						txbd[i].vir_addr,
						(_dma *)&txbd[i].phy_addr_l,
						(_dma *)&txbd[i].phy_addr_h,
						txbd[i].buf_len,
						txbd[i].cache,
						PCI_DMA_FROMDEVICE,
						txbd[i].os_rsvd[0]);
			txbd[i].vir_addr = NULL;
			txbd[i].cache = 0;
			_os_spinlock_free(phl_to_drvpriv(phl_info),
						&txbd[i].txbd_lock);
		}

		_os_mem_free(phl_to_drvpriv(phl_info), txbd,
						sizeof(struct tx_base_desc) * ch_num);
	}

	FUNCOUT();
}



static enum rtw_phl_status
_phl_alloc_txbd_pcie(struct phl_info_t *phl_info, u8 ch_num)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	struct rtw_hal_com_t *hal_com = rtw_hal_get_halcom(phl_info->hal);
	struct tx_base_desc *txbd = NULL;
	u32 buf_len = 0;
	u16 txbd_num = (u16)hal_com->bus_cap.txbd_num;
	u8 addr_info_size = hal_com->bus_hw_cap.txbd_len;
	u8 i = 0;
	FUNCIN_WSTS(pstatus);

	buf_len = sizeof(struct tx_base_desc) * ch_num;
	txbd = _os_mem_alloc(phl_to_drvpriv(phl_info), buf_len);
	if (NULL != txbd) {
		for (i = 0; i < ch_num; i++) {
			txbd[i].cache = false;
			buf_len = addr_info_size * txbd_num;
			txbd[i].vir_addr = _os_shmem_alloc(
						phl_to_drvpriv(phl_info),
						(_dma *)&txbd[i].phy_addr_l,
						(_dma *)&txbd[i].phy_addr_h,
						buf_len,
						txbd[i].cache,
						PCI_DMA_TODEVICE,
						&txbd[i].os_rsvd[0]);
			if (NULL == txbd[i].vir_addr) {
				pstatus = RTW_PHL_STATUS_RESOURCE;
				break;
			}
			txbd[i].buf_len = buf_len;
			txbd[i].avail_num = txbd_num;
			_os_spinlock_init(phl_to_drvpriv(phl_info),
						&txbd[i].txbd_lock);
			pstatus = RTW_PHL_STATUS_SUCCESS;
		}
	}

	if (RTW_PHL_STATUS_SUCCESS == pstatus)
		phl_info->hci->txbd_buf = (u8 *)txbd;
	else
		_phl_free_txbd_pcie(phl_info, (u8 *)txbd, ch_num);
	FUNCOUT_WSTS(pstatus);

	return pstatus;
}

enum rtw_phl_status _phl_update_default_rx_bd(struct phl_info_t *phl_info)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	enum rtw_hal_status hstatus = RTW_HAL_STATUS_FAILURE;
	struct rtw_hal_com_t *hal_com = rtw_hal_get_halcom(phl_info->hal);
	struct hci_info_t *hci_info = (struct hci_info_t *)phl_info->hci;
	struct rx_base_desc *rxbd = NULL;
	struct rtw_rx_buf_ring *ring = NULL;
	struct rtw_rx_buf *rxbuf = NULL;
	u32 i = 0, j = 0;

	rxbd = (struct rx_base_desc *)hci_info->rxbd_buf;
	ring = (struct rtw_rx_buf_ring *)hci_info->rxbuf_pool;
	for (i = 0; i < hci_info->total_rxch_num; i++) {
		for (j = 0; j < hal_com->bus_cap.rxbd_num; j++) {
			rxbuf = query_idle_rx_buf(phl_info, &ring[i]);
			if (NULL == rxbuf) {
				PHL_TRACE(COMP_PHL_DBG, _PHL_WARNING_,
					"[WARNING] there is no resource for rx bd default setting\n");
				pstatus = RTW_PHL_STATUS_RESOURCE;
				break;
			}

			hstatus = rtw_hal_update_rxbd(phl_info->hal, &rxbd[i],
								rxbuf);
			if (RTW_HAL_STATUS_SUCCESS == hstatus) {
				enqueue_busy_rx_buf(phl_info, &ring[i], rxbuf, _tail);
				pstatus = RTW_PHL_STATUS_SUCCESS;
			} else {
				enqueue_idle_rx_buf(phl_info, &ring[i], rxbuf);
				PHL_TRACE(COMP_PHL_DBG, _PHL_WARNING_, "[WARNING] update rx bd fail\n");
				pstatus = RTW_PHL_STATUS_FAILURE;
				break;
			}
		}

	}

	return pstatus;
}

static void _phl_reset_rxbd(struct phl_info_t *phl_info,
					struct rx_base_desc *rxbd)
{
	struct rtw_hal_com_t *hal_com = rtw_hal_get_halcom(phl_info->hal);
	_os_mem_set(phl_to_drvpriv(phl_info), rxbd->vir_addr, 0, rxbd->buf_len);
	rxbd->host_idx = 0;
	rxbd->avail_num = (u16)hal_com->bus_cap.rxbd_num;
}


static void phl_rx_reset_pcie(struct phl_info_t *phl_info)
{
	struct rtw_phl_com_t *phl_com = phl_info->phl_com;
	struct hci_info_t *hci_info = (struct hci_info_t *)phl_info->hci;
	struct hal_spec_t *hal_spec = &phl_com->hal_spec;
	struct rx_base_desc *rxbd = NULL;
	struct rtw_rx_buf_ring *ring = NULL;
	u8 ch = 0;

	rxbd = (struct rx_base_desc *)hci_info->rxbd_buf;
	ring = (struct rtw_rx_buf_ring *)hci_info->rxbuf_pool;

	for (ch = 0; ch < hci_info->total_rxch_num; ch++) {
		_phl_reset_rxbd(phl_info, &rxbd[ch]);
		phl_release_busy_rx_buf(phl_info, &ring[ch],
					ring[ch].busy_rxbuf_cnt);
	}
	hal_spec->rx_tag[0] = 0;
	hal_spec->rx_tag[1] = 0;
	_phl_update_default_rx_bd(phl_info);

}


void _phl_sort_ring_by_hw_res(struct phl_info_t *phl_info)
{
	_os_list *t_fctrl_result = &phl_info->t_fctrl_result;
	struct phl_ring_status *ring_sts, *t;
	u16 hw_res = 0, host_idx = 0, hw_idx = 0;
	u32 avail = 0, no_res = 0;
	_os_list *no_res_first = NULL;

	phl_list_for_loop_safe(ring_sts, t, struct phl_ring_status,
					t_fctrl_result, list) {

		if (ring_sts->ring_ptr->dma_ch > 32)
			PHL_TRACE(COMP_PHL_DBG, _PHL_WARNING_,
			"[WARNING] dma channel number larger than record map\n");

		if (no_res & (BIT0 << ring_sts->ring_ptr->dma_ch)) {
			if (&ring_sts->list == no_res_first)
				break;
			list_del(&ring_sts->list);
			list_add_tail(&ring_sts->list, t_fctrl_result);
			continue;
		} else if (avail & (BIT0 << ring_sts->ring_ptr->dma_ch)) {
			continue;
		}

		hw_res = rtw_hal_tx_res_query(phl_info->hal,
						ring_sts->ring_ptr->dma_ch,
						&host_idx, &hw_idx);
		if (0 == hw_res) {
			if (no_res_first == NULL)
				no_res_first = &ring_sts->list;
			list_del(&ring_sts->list);
			list_add_tail(&ring_sts->list, t_fctrl_result);
			no_res = no_res | (BIT0 << ring_sts->ring_ptr->dma_ch);
		} else {
			avail = avail | (BIT0 << ring_sts->ring_ptr->dma_ch);
		}
	}
}

void _phl_tx_flow_ctrl_pcie(struct phl_info_t *phl_info, _os_list *sta_list)
{
	/* _phl_sort_ring_by_hw_res(phl_info); */
	phl_tx_flow_ctrl(phl_info, sta_list);
}

static enum rtw_phl_status _phl_handle_xmit_ring_pcie
						(struct phl_info_t *phl_info,
						struct phl_ring_status *ring_sts)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	struct phl_hci_trx_ops *hci_trx_ops = phl_info->hci_trx_ops;
	struct rtw_phl_tx_ring *tring = ring_sts->ring_ptr;
	struct rtw_xmit_req *tx_req = NULL;
	u16 rptr = 0, next_idx = 0;
	void *drv_priv = phl_to_drvpriv(phl_info);

	while (0 != ring_sts->req_busy) {
		rptr = (u16)_os_atomic_read(drv_priv, &tring->phl_next_idx);

		tx_req = (struct rtw_xmit_req *)tring->entry[rptr];
		if (NULL == tx_req)  {
			PHL_ERR("tx_req is NULL!\n");
			break;
		}
		tx_req->mdata.macid = ring_sts->macid;
		tx_req->mdata.band = ring_sts->band;
		tx_req->mdata.wmm = ring_sts->wmm;
		tx_req->mdata.hal_port = ring_sts->port;
		/*tx_req->mdata.mbssid = ring_sts->mbssid;*/
		tx_req->mdata.tid = tring->tid;
		tx_req->mdata.dma_ch = tring->dma_ch;
		pstatus = hci_trx_ops->prepare_tx(phl_info, tx_req);

		if (RTW_PHL_STATUS_SUCCESS == pstatus) {
			ring_sts->req_busy--;

			/* hana_todo, workaround here to update phl_index */
			_os_atomic_set(drv_priv, &tring->phl_idx, rptr);

			if (0 != ring_sts->req_busy) {
				next_idx = rptr + 1;

				if (next_idx >= MAX_PHL_RING_ENTRY_NUM) {
					_os_atomic_set(drv_priv,
						       &tring->phl_next_idx, 0);

				} else {
					_os_atomic_inc(drv_priv,
						       &tring->phl_next_idx);
				}
			}
		} else {
			PHL_INFO("HCI prepare tx fail\n");
			break;
		}
	}

	return pstatus;
}

static void _phl_tx_callback_pcie(void *context)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	struct rtw_phl_handler *phl_handler
		= (struct rtw_phl_handler *)phl_container_of(context,
							struct rtw_phl_handler,
							os_handler);
	struct phl_info_t *phl_info = (struct phl_info_t *)phl_handler->context;
	struct phl_hci_trx_ops *hci_trx_ops = phl_info->hci_trx_ops;
	struct phl_ring_status *ring_sts = NULL, *t;
	void *drvpriv = phl_to_drvpriv(phl_info);
	_os_list sta_list;
	bool tx_pause = false;

	FUNCIN_WSTS(pstatus);
	INIT_LIST_HEAD(&sta_list);

	/* check datapath sw state */
	tx_pause = phl_datapath_chk_trx_pause(phl_info, PHL_CTRL_TX);
	if (true == tx_pause)
		goto end;

#ifdef CONFIG_POWER_SAVE
	/* check ps state when tx is not paused */
	if (false == phl_ps_is_datapath_allowed(phl_info)) {
		PHL_WARN("%s(): datapath is not allowed now... may in low power.\n", __func__);
		goto chk_stop;
	}
#endif

	if (true == phl_check_xmit_ring_resource(phl_info, &sta_list)) {
		_phl_tx_flow_ctrl_pcie(phl_info, &sta_list);

		phl_list_for_loop_safe(ring_sts, t, struct phl_ring_status,
		                       &phl_info->t_fctrl_result, list) {
			list_del(&ring_sts->list);
			_phl_handle_xmit_ring_pcie(phl_info, ring_sts);
			phl_release_ring_sts(phl_info, ring_sts);
		}
	}

	pstatus = hci_trx_ops->tx(phl_info);
	if (RTW_PHL_STATUS_FAILURE == pstatus) {
		PHL_TRACE(COMP_PHL_DBG, _PHL_WARNING_, "[WARNING] phl_tx fail!\n");
	}

#ifdef CONFIG_POWER_SAVE
chk_stop:
#endif
	if (PHL_TX_STATUS_STOP_INPROGRESS ==
	    _os_atomic_read(drvpriv, &phl_info->phl_sw_tx_sts)) {
		PHL_WARN("PHL_TX_STATUS_STOP_INPROGRESS, going to stop sw tx.\n");
		phl_tx_stop_pcie(phl_info);
	}

end:
	phl_free_deferred_tx_ring(phl_info);

	FUNCOUT_WSTS(pstatus);
}


static u8 _phl_check_rx_hw_resource(struct phl_info_t *phl_info)
{
	struct hci_info_t *hci_info = phl_info->hci;
	u16 hw_res = 0, host_idx = 0, hw_idx = 0;
	u8 i = 0;
	u8 avail = 0;

	for (i = 0; i < hci_info->total_rxch_num; i++) {
		hw_res = rtw_hal_rx_res_query(phl_info->hal,
							i,
							&host_idx, &hw_idx);

		if (0 != hw_res) {
			avail = true;
			break;
		} else {
			avail = false;
		}
	}

	return avail;
}

static void _phl_rx_callback_pcie(void *context)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	struct rtw_phl_handler *phl_handler
		= (struct rtw_phl_handler *)phl_container_of(context,
							struct rtw_phl_handler,
							os_handler);
	struct phl_info_t *phl_info = (struct phl_info_t *)phl_handler->context;
	struct phl_hci_trx_ops *hci_trx_ops = phl_info->hci_trx_ops;
	void *drvpriv = phl_to_drvpriv(phl_info);
	bool rx_pause = false;
#ifdef CONFIG_SYNC_INTERRUPT
	struct rtw_phl_evt_ops *ops = &phl_info->phl_com->evt_ops;
#endif /* CONFIG_SYNC_INTERRUPT */

	FUNCIN_WSTS(pstatus);

	/* check datapath sw state */
	rx_pause = phl_datapath_chk_trx_pause(phl_info, PHL_CTRL_RX);
	if (true == rx_pause)
		goto end;

	do {
		if (false == phl_check_recv_ring_resource(phl_info))
			break;

		pstatus = hci_trx_ops->rx(phl_info);

		if (RTW_PHL_STATUS_FAILURE == pstatus) {
			PHL_TRACE(COMP_PHL_DBG, _PHL_WARNING_, "[WARNING] phl_rx fail!\n");
		}
	} while (false);

	if (PHL_RX_STATUS_STOP_INPROGRESS ==
	    _os_atomic_read(drvpriv, &phl_info->phl_sw_rx_sts)) {
		phl_rx_stop_pcie(phl_info);
	}

end:
	/* restore int mask of rx */
	rtw_hal_restore_rx_interrupt(phl_info->hal);
#ifdef CONFIG_SYNC_INTERRUPT
	ops->interrupt_restore(phl_to_drvpriv(phl_info), true);
#endif /* CONFIG_SYNC_INTERRUPT */

	FUNCOUT_WSTS(pstatus);

}
void _phl_fill_tx_meta_data(struct rtw_xmit_req *tx_req,
                            u16 packet_len)
{
	tx_req->mdata.wp_offset = 56;
	tx_req->mdata.wd_page_size = 1;
	tx_req->mdata.addr_info_num = tx_req->pkt_cnt;
	tx_req->mdata.pktlen = packet_len;
}



void phl_trx_resume_pcie(struct phl_info_t *phl_info, u8 type)
{
	if (PHL_CTRL_TX & type)
		phl_tx_resume_pcie(phl_info);

	if (PHL_CTRL_RX & type)
		phl_rx_resume_pcie(phl_info);
}

void phl_trx_reset_pcie(struct phl_info_t *phl_info, u8 type)
{
	struct rtw_phl_com_t *phl_com = phl_info->phl_com;
	struct rtw_stats *phl_stats = &phl_com->phl_stats;

	PHL_INFO("%s\n", __func__);

	if (PHL_CTRL_TX & type) {
		phl_tx_reset_pcie(phl_info);
		phl_reset_tx_stats(phl_stats);
	}

	if (PHL_CTRL_RX & type) {
		phl_rx_reset_pcie(phl_info);
		phl_reset_rx_stats(phl_stats);
	}
}

void phl_trx_stop_pcie(struct phl_info_t *phl_info)
{
	phl_trx_reset_pcie(phl_info, PHL_CTRL_TX|PHL_CTRL_RX);
}

void phl_trx_deinit_pcie(struct phl_info_t *phl_info)
{
	struct hci_info_t *hci_info = phl_info->hci;
	FUNCIN();
	_phl_free_rxbuf_pool_pcie(phl_info, hci_info->rxbuf_pool,
					hci_info->total_rxch_num);
	hci_info->rxbuf_pool = NULL;

	_phl_free_rxbd_pcie(phl_info, hci_info->rxbd_buf,
					hci_info->total_rxch_num);
	hci_info->rxbd_buf = NULL;

	_phl_free_wd_ring_pcie(phl_info, hci_info->wd_ring,
					hci_info->total_txch_num);
	hci_info->wd_ring = NULL;

	_phl_free_txbd_pcie(phl_info, hci_info->txbd_buf,
					hci_info->total_txch_num);
	hci_info->txbd_buf = NULL;
	FUNCOUT();
}

enum rtw_phl_status phl_trx_init_pcie(struct phl_info_t *phl_info)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	struct hci_info_t *hci_info = phl_info->hci;
	struct rtw_phl_handler *tx_handler = &phl_info->phl_tx_handler;
	struct rtw_phl_handler *rx_handler = &phl_info->phl_rx_handler;
	void *drv_priv = phl_to_drvpriv(phl_info);

	u8 txch_num = 0, rxch_num = 0;
	u16 i = 0;

	FUNCIN_WSTS(pstatus);

	do {
#ifdef CONFIG_PHL_CPU_BALANCE_TX
		_os_workitem *workitem = &tx_handler->os_handler.u.workitem;
		_os_workitem_config_cpu(drv_priv, workitem, "TX_PHL_0", CPU_ID_TX_PHL_0);
		tx_handler->type = RTW_PHL_HANDLER_PRIO_LOW;
#else
		tx_handler->type = RTW_PHL_HANDLER_PRIO_HIGH; /* tasklet */
#endif
		tx_handler->callback = _phl_tx_callback_pcie;
		tx_handler->context = phl_info;
		tx_handler->drv_priv = drv_priv;
		pstatus = phl_register_handler(phl_info->phl_com, tx_handler);
		if (RTW_PHL_STATUS_SUCCESS != pstatus)
			break;

		rx_handler->type = RTW_PHL_HANDLER_PRIO_HIGH;
		rx_handler->callback = _phl_rx_callback_pcie;
		rx_handler->context = phl_info;
		rx_handler->drv_priv = drv_priv;
		pstatus = phl_register_handler(phl_info->phl_com, rx_handler);
		if (RTW_PHL_STATUS_SUCCESS != pstatus)
			break;
		/* pcie tx sw resource */
		txch_num = rtw_hal_query_txch_num(phl_info->hal);
		hci_info->total_txch_num = txch_num;
		/* allocate tx bd */
		pstatus = _phl_alloc_txbd_pcie(phl_info, txch_num);
		if (RTW_PHL_STATUS_SUCCESS != pstatus)
			break;
		/* allocate wd page */
		pstatus = _phl_alloc_wd_ring_pcie(phl_info, txch_num);
		if (RTW_PHL_STATUS_SUCCESS != pstatus)
			break;

		for (i = 0; i < PHL_MACID_MAX_NUM; i++)
			hci_info->wp_seq[i] = WP_RESERVED_SEQ;

		/* pcie rx sw resource */
		rxch_num = rtw_hal_query_rxch_num(phl_info->hal);
		hci_info->total_rxch_num = rxch_num;
		/* allocate rx bd */
		pstatus = _phl_alloc_rxbd_pcie(phl_info, rxch_num);
		if (RTW_PHL_STATUS_SUCCESS != pstatus)
			break;
		/* allocate wd page */
		pstatus = _phl_alloc_rxbuf_pool_pcie(phl_info, rxch_num);
		if (RTW_PHL_STATUS_SUCCESS != pstatus)
			break;

	} while (false);

	if (RTW_PHL_STATUS_SUCCESS != pstatus)
		phl_trx_deinit_pcie(phl_info);
	else
		pstatus = _phl_update_default_rx_bd(phl_info);

	FUNCOUT_WSTS(pstatus);
	return pstatus;
}


enum rtw_phl_status phl_trx_config_pcie(struct phl_info_t *phl_info)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	enum rtw_hal_status hstatus = RTW_HAL_STATUS_FAILURE;
	struct hci_info_t *hci_info = phl_info->hci;

	do {
		hstatus = rtw_hal_trx_init(phl_info->hal, hci_info->txbd_buf,
						hci_info->rxbd_buf);
		if (RTW_HAL_STATUS_SUCCESS != hstatus) {
			PHL_ERR("rtw_hal_trx_init fail with status 0x%08X\n",
				hstatus);
			pstatus = RTW_PHL_STATUS_FAILURE;
			break;
		}
		else {
			pstatus = RTW_PHL_STATUS_SUCCESS;
		}

		phl_tx_start_pcie(phl_info);
		phl_rx_start_pcie(phl_info);

	} while (false);

	return pstatus;
}

#ifdef CONFIG_PHL_TXSC
u8 *_phl_txsc_apply_shortcut(struct phl_info_t *phl_info, struct rtw_xmit_req *tx_req,
							struct rtw_phl_stainfo_t *phl_sta, struct rtw_phl_pkt_req *phl_pkt_req)
{
	struct phl_txsc_entry *ptxsc = NULL;

	if (phl_sta == NULL)
		return (u8 *)ptxsc;

	if (tx_req->shortcut_id >= PHL_TXSC_ENTRY_NUM) {
		PHL_ERR("[PHL][TXSC] wrong shortcut_id:%d, plz check !!!\n", tx_req->shortcut_id);
		return (u8 *)ptxsc;
	}

	ptxsc = &phl_sta->phl_txsc[tx_req->shortcut_id];

	if ((tx_req->treq_type == RTW_PHL_TREQ_TYPE_CORE_TXSC)) {

		if (ptxsc == NULL) {
			PHL_ERR("[txsc][phl] fetal err: ptxsc = NULL, plz check.\n");
			return (u8 *)ptxsc;
		}

		if (!ptxsc->txsc_wd_cached) {
			PHL_ERR("[txsc][phl] fetal err: txsc_wd_cached = 0, plz check.\n");
			return (u8 *)ptxsc;
		}

		_os_mem_cpy(phl_info, phl_pkt_req->wd_page, ptxsc->txsc_wd_cache, ptxsc->txsc_wd_len);
		phl_pkt_req->wd_len = ptxsc->txsc_wd_len;

		/* update pktlen in wd_page, wd_body[8:15] = pktsize */
		#if 0
		packet_len = cpu_to_le16(tx_req->mdata.pktlen);
		_os_mem_cpy(phl_info, phl_pkt_req.wd_page+8, &packet_len, sizeof(u16));
		#endif

		ptxsc->txsc_cache_hit++;
	}

	return (u8 *)ptxsc;
}

enum rtw_phl_status
_phl_txsc_add_shortcut(struct phl_info_t *phl_info, struct rtw_xmit_req *tx_req,
								struct rtw_phl_pkt_req *phl_pkt_req, struct phl_txsc_entry *ptxsc)
{

	if (tx_req->shortcut_id >= PHL_TXSC_ENTRY_NUM) {
		PHL_ERR("[PHL][TXSC] wrong shortcut_id:%d, plz check.\n", tx_req->shortcut_id);
		return RTW_PHL_STATUS_FAILURE;
	}

	if (ptxsc == NULL) {
		PHL_ERR("[txsc][phl] fetal err: ptxsc = NULL, shortcut_id = %d, plz check.\n", tx_req->shortcut_id);
		return RTW_PHL_STATUS_FAILURE;
	}

	if (tx_req->treq_type & RTW_PHL_TREQ_TYPE_PHL_UPDATE_TXSC) {

		_os_mem_set(phl_info, ptxsc, 0x0, sizeof(struct phl_txsc_entry));
		_os_mem_cpy(phl_info, ptxsc->txsc_wd_cache, phl_pkt_req->wd_page, phl_pkt_req->wd_len);

		ptxsc->txsc_wd_len = phl_pkt_req->wd_len;
		ptxsc->txsc_wd_cached = true;

		#if 0
		PHL_PRINT("\n[txsc][phl] shortcut_id:%d, wd_page cached, len:%d. SMH: %u (%u)\n\n",
			tx_req->shortcut_id, ptxsc->txsc_wd_len, tx_req->mdata.smh_en,
			tx_req->treq_type);
		#endif

		tx_req->treq_type &= ~RTW_PHL_TREQ_TYPE_PHL_UPDATE_TXSC;
		if (tx_req->treq_type != RTW_PHL_TREQ_TYPE_PHL_ADD_TXSC)
			PHL_PRINT("Updated WD for request type %u\n", tx_req->treq_type);
	}

	return RTW_PHL_STATUS_SUCCESS;
}
#endif

enum rtw_phl_status
phl_prepare_tx_pcie(struct phl_info_t *phl_info, struct rtw_xmit_req *tx_req)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	enum rtw_hal_status hstatus = RTW_HAL_STATUS_FAILURE;
	struct rtw_hal_com_t *hal_com = rtw_hal_get_halcom(phl_info->hal);
 	struct rtw_wp_rpt_stats *rpt_stats =
		(struct rtw_wp_rpt_stats *)hal_com->trx_stat.wp_rpt_stats;
	struct hci_info_t *hci_info = NULL;
	struct rtw_pkt_buf_list *pkt_buf = NULL;
	struct rtw_wd_page_ring *wd_ring = NULL;
	struct rtw_wd_page *wd_page = NULL;
	struct rtw_phl_pkt_req phl_pkt_req;
	void *ptr = NULL;
	u16 packet_len = 0, wp_seq = 0;
	u8 dma_ch = 0, i = 0;
	u16 mid = 0;
#ifdef CONFIG_PHL_TXSC
	struct phl_txsc_entry *ptxsc = NULL;
	struct rtw_phl_stainfo_t *phl_sta = rtw_phl_get_stainfo_by_macid(phl_info, tx_req->mdata.macid);
#endif
	FUNCIN_WSTS(pstatus);
	do {
		if (NULL == phl_info->hci) {
			PHL_ERR("phl_info->hci is NULL!\n");
			break;
		}
		hci_info = (struct hci_info_t *)phl_info->hci;
		wd_ring = (struct rtw_wd_page_ring *)hci_info->wd_ring;

		if (NULL == tx_req)  {
			PHL_ERR("tx_req is NULL!\n");
			break;
		}
		mid = tx_req->mdata.macid;
		dma_ch = tx_req->mdata.dma_ch;
		wp_seq = wd_ring[dma_ch].wp_seq;

		if (NULL != wd_ring[dma_ch].wp_tag[wp_seq].ptr) {
			ptr = wd_ring[dma_ch].wp_tag[wp_seq].ptr;
			PHL_ERR("wp_tag out of resource!\n");
			PHL_ERR("stuck wp info:\n");
			PHL_ERR("dma_ch = %d, wp_seq = 0x%x, ptr = %p!\n",
				dma_ch, wp_seq, ptr);
			PHL_ERR("wifi seq = %d\n",
				((struct rtw_xmit_req *)ptr)->mdata.sw_seq);
			_phl_dump_busy_wp(phl_info);
			break;
		}

		pkt_buf = (struct rtw_pkt_buf_list *)&tx_req->pkt_list[0];
		for (i = 0; i < tx_req->pkt_cnt; i++) {
			packet_len += pkt_buf->length;
			pkt_buf++;
		}

		tx_req->total_len = packet_len;

		wd_page = query_idle_wd_page(phl_info, &wd_ring[dma_ch]);
		if (NULL == wd_page) {
			PHL_ERR("query idle wd page fail!\n");
			PHL_ERR("dma_ch = %d, idle wd num = %d, "
				"busy wd num = %d, pending wd num = %d\n",
				dma_ch,
				wd_ring[dma_ch].idle_wd_page_cnt,
				wd_ring[dma_ch].busy_wd_page_cnt,
				wd_ring[dma_ch].pending_wd_page_cnt);
			if (wd_ring[dma_ch].busy_wd_page_cnt > MAX_WD_PAGE_NUM * 4 / 5)
				rtw_hal_tx_dbg_status_dump(phl_info->hal);
			break;
		}
		/* hana_todo */
		_phl_fill_tx_meta_data(tx_req, packet_len);

		phl_pkt_req.wd_page = wd_page->vir_addr;

		phl_pkt_req.wp_seq = wp_seq;
		phl_pkt_req.tx_req = tx_req;

#ifdef CONFIG_PHL_TXSC
		phl_pkt_req.wd_len = 0;
		ptxsc = (struct phl_txsc_entry *)_phl_txsc_apply_shortcut(phl_info, tx_req, phl_sta, &phl_pkt_req);
#endif

		hstatus = rtw_hal_update_wd_page(phl_info->hal, &phl_pkt_req);
		wd_page->buf_len = phl_pkt_req.wd_len;

		if (RTW_HAL_STATUS_SUCCESS == hstatus) {
			hci_info->wp_seq[mid] = phl_pkt_req.wp_seq;
			enqueue_pending_wd_page(phl_info, &wd_ring[dma_ch],
						wd_page, _tail);
			tx_req->tx_time = _os_get_cur_time_ms();
#ifdef CONFIG_PHL_TX_DBG
			if (tx_req->tx_dbg.en_dbg) {
				tx_req->tx_dbg.enq_pending_wd_t =
						_os_get_cur_time_us();
			}
#endif /* CONFIG_PHL_TX_DBG */
			_os_spinlock(phl_to_drvpriv(phl_info),
				     &wd_ring[dma_ch].wp_tag_lock,
				     _bh, NULL);
			PHL_TRACE(COMP_PHL_XMIT, _PHL_DEBUG_,
				  "update tx req(%p) in ch(%d) with wp_seq(0x%x) and wifi seq(%d)!\n",
				  tx_req, dma_ch, wp_seq, tx_req->mdata.sw_seq);
			wd_ring[dma_ch].wp_tag[wp_seq].ptr = (u8 *)tx_req;
			rpt_stats[dma_ch].busy_cnt++;
			_os_spinunlock(phl_to_drvpriv(phl_info),
				       &wd_ring[dma_ch].wp_tag_lock,
				       _bh, NULL);

			wp_seq = (wp_seq + 1) % WP_MAX_SEQ_NUMBER;
			if (0 == wp_seq)
				wp_seq = 1;

			wd_ring[dma_ch].wp_seq = wp_seq;

			pstatus = RTW_PHL_STATUS_SUCCESS;

			//wb wd page
			if(wd_page->cache == true) {
				PHL_TRACE(COMP_PHL_DBG, _PHL_DEBUG_, "[%s] wd page cache wback \n",
					__FUNCTION__);
				_os_cache_wback(phl_to_drvpriv(phl_info),
					(_dma *)&wd_page->phy_addr_l,
					(_dma *)&wd_page->phy_addr_h,
					wd_page->buf_len, PCI_DMA_TODEVICE);
			}

#ifdef CONFIG_PHL_TXSC
			_phl_txsc_add_shortcut(phl_info, tx_req, &phl_pkt_req, ptxsc);
#endif

			break;
		} else {
			rtw_release_target_wd_page(phl_info, &wd_ring[dma_ch],
						wd_page);
			pstatus = RTW_PHL_STATUS_FAILURE;
			break;
		}
	} while(false);
	FUNCOUT_WSTS(pstatus);
	return pstatus;
}

static enum rtw_phl_status
phl_handle_pending_wd(struct phl_info_t *phl_info,
				struct rtw_wd_page_ring *wd_ring,
				u16 txcnt, u8 ch)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	enum rtw_hal_status hstatus = RTW_HAL_STATUS_FAILURE;
	struct hci_info_t *hci_info = (struct hci_info_t *)phl_info->hci;
	struct tx_base_desc *txbd = NULL;
	struct rtw_wd_page *wd = NULL;
	u16 cnt = 0;

#ifdef RTW_WKARD_DYNAMIC_LTR
	if (true != _phl_judge_act_ltr_switching_conditions(phl_info, ch)) {
		_phl_act_ltr_update_stats(phl_info, false, ch,
									wd_ring->pending_wd_page_cnt);
		return RTW_PHL_STATUS_FAILURE;
	} else {
		_phl_act_ltr_update_stats(phl_info, true, ch,
									wd_ring->pending_wd_page_cnt);
	}
#endif

	txbd = (struct tx_base_desc *)hci_info->txbd_buf;
	while (txcnt > cnt) {
		wd = query_pending_wd_page(phl_info, wd_ring);

		if (NULL == wd) {
			pstatus = RTW_PHL_STATUS_RESOURCE;
			PHL_TRACE(COMP_PHL_DBG, _PHL_INFO_, "query Tx pending WD fail!\n");
			break;
		}

		wd->ls = 1;//tmp set LS=1
		hstatus = rtw_hal_update_txbd(phl_info->hal, txbd, wd, ch, 1);
		if (RTW_HAL_STATUS_SUCCESS == hstatus) {
			enqueue_busy_wd_page(phl_info, wd_ring, wd, _tail);
			pstatus = RTW_PHL_STATUS_SUCCESS;
		} else {
			enqueue_pending_wd_page(phl_info, wd_ring, wd, _first);
			pstatus = RTW_PHL_STATUS_RESOURCE;
			PHL_TRACE(COMP_PHL_DBG, _PHL_INFO_, "update Tx BD fail!\n");
			break;
		}

		cnt++;
	}

	if (RTW_PHL_STATUS_SUCCESS == pstatus) {
#ifdef RTW_WKARD_DYNAMIC_LTR
		_phl_switch_act_ltr(phl_info, ch);
#endif
		hstatus = rtw_hal_trigger_txstart(phl_info->hal, txbd, ch);
		if (RTW_HAL_STATUS_SUCCESS != hstatus) {
			PHL_TRACE(COMP_PHL_DBG, _PHL_WARNING_, "[WARNING]update Tx RW ptr fail!\n");
			pstatus = RTW_PHL_STATUS_FAILURE;
		}
	}

	return pstatus;
}


static enum rtw_phl_status
phl_handle_busy_wd(struct phl_info_t *phl_info,
			struct rtw_wd_page_ring *wd_ring, u16 hw_idx)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	struct rtw_hal_com_t *hal_com = rtw_hal_get_halcom(phl_info->hal);
	void *drv_priv = phl_to_drvpriv(phl_info);
	_os_list *list = &wd_ring->busy_wd_page_list;
	struct rtw_wd_page *wd = NULL;
	u16 bndy = (u16)hal_com->bus_cap.txbd_num;
	u16 target = 0;
	u16 release_num = 0;

	do {
		_os_spinlock(drv_priv, &wd_ring->busy_lock, _bh, NULL);

		if (list_empty(list)) {
			pstatus = RTW_PHL_STATUS_SUCCESS;
			_os_spinunlock(drv_priv, &wd_ring->busy_lock, _bh, NULL);
			break;
		}

		if (wd_ring->busy_wd_page_cnt > (bndy - 1)) {
			release_num = wd_ring->busy_wd_page_cnt - (bndy - 1);
			_os_spinunlock(drv_priv, &wd_ring->busy_lock, _bh, NULL);
			pstatus = rtw_release_busy_wd_page(phl_info, wd_ring,
								release_num);

			if (RTW_PHL_STATUS_SUCCESS != pstatus)
				break;
			else
				_os_spinlock(drv_priv, &wd_ring->busy_lock, _bh, NULL);
		}

		wd = list_first_entry(list, struct rtw_wd_page, list);
		target = wd->host_idx;

		if (hw_idx >= target)
			release_num = ((hw_idx - target) + 1) % bndy;
		else
			release_num = ((bndy - target) + (hw_idx + 1)) % bndy;

		_os_spinunlock(drv_priv, &wd_ring->busy_lock, _bh, NULL);

		pstatus = rtw_release_busy_wd_page(phl_info, wd_ring,
							release_num);

		if (RTW_PHL_STATUS_SUCCESS != pstatus)
			break;
	} while (false);

	return pstatus;
}

enum rtw_phl_status phl_recycle_busy_wd(struct phl_info_t *phl_info)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	struct hci_info_t *hci_info = (struct hci_info_t *)phl_info->hci;
	struct rtw_wd_page_ring *wd_ring = NULL;
	u16 hw_res = 0, host_idx = 0, hw_idx = 0;
	u8 ch = 0;
	FUNCIN_WSTS(pstatus);
	wd_ring = (struct rtw_wd_page_ring *)hci_info->wd_ring;

	for (ch = 0; ch < hci_info->total_txch_num; ch++) {
		hw_res = rtw_hal_tx_res_query(phl_info->hal, ch, &host_idx,
							&hw_idx);
		pstatus = phl_handle_busy_wd(phl_info, &wd_ring[ch], hw_idx);
	}

	FUNCOUT_WSTS(pstatus);
	return pstatus;
}

static enum rtw_phl_status
phl_handle_busy_h2c(struct phl_info_t *phl_info,
			struct phl_h2c_pkt_pool *h2c_pool, u16 hw_idx)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	struct rtw_hal_com_t *hal_com = rtw_hal_get_halcom(phl_info->hal);
	void *drv_priv = phl_to_drvpriv(phl_info);
	struct phl_queue *queue = &h2c_pool->busy_h2c_pkt_list;
	_os_list *list = &h2c_pool->busy_h2c_pkt_list.queue;
	struct rtw_h2c_pkt *h2c = NULL;
	u16 bndy = (u16)hal_com->bus_cap.txbd_num;
	u16 target = 0;
	u16 release_num = 0;
	u16 tmp_cnt = 0;

	do {
		_os_spinlock(drv_priv, &queue->lock, _bh, NULL);

		if (list_empty(list)) {
			pstatus = RTW_PHL_STATUS_SUCCESS;

			_os_spinunlock(drv_priv, &queue->lock, _bh, NULL);
			break;
		}

		tmp_cnt = (u16)queue->cnt;
		if (tmp_cnt > (bndy - 1)) {
			release_num = tmp_cnt - (bndy - 1);
			_os_spinunlock(drv_priv, &queue->lock, _bh, NULL);
			pstatus = phl_release_busy_h2c_pkt(phl_info, h2c_pool,
							release_num);

			if (RTW_PHL_STATUS_SUCCESS != pstatus)
				break;
			else
				_os_spinlock(drv_priv, &queue->lock, _bh, NULL);
		}

		h2c = list_first_entry(list, struct rtw_h2c_pkt, list);
		target = h2c->host_idx;

		if (hw_idx >= target)
			release_num = ((hw_idx - target) + 1) % bndy;
		else
			release_num = ((bndy - target) + (hw_idx + 1)) % bndy;

		_os_spinunlock(drv_priv, &queue->lock, _bh, NULL);

		PHL_TRACE(COMP_PHL_DBG, _PHL_DEBUG_, "%s : release_num %d.\n", __func__, release_num);

		pstatus = phl_release_busy_h2c_pkt(phl_info, h2c_pool,
							release_num);

		if (RTW_PHL_STATUS_SUCCESS != pstatus)
			break;
	} while (false);

	return pstatus;
}

enum rtw_phl_status phl_recycle_busy_h2c(struct phl_info_t *phl_info)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	struct phl_h2c_pkt_pool *h2c_pool = NULL;
	u16 hw_res = 0, host_idx = 0, hw_idx = 0;
	u8 fwcmd_queue_idx = 0;

	FUNCIN_WSTS(pstatus);
	h2c_pool = (struct phl_h2c_pkt_pool *)phl_info->h2c_pool;
	_os_spinlock(phl_to_drvpriv(phl_info), &h2c_pool->recycle_lock, _bh, NULL);
	fwcmd_queue_idx = rtw_hal_get_fwcmd_queue_idx(phl_info->hal);

	hw_res = rtw_hal_tx_res_query(phl_info->hal, fwcmd_queue_idx, &host_idx,
						&hw_idx);
	PHL_TRACE(COMP_PHL_DBG, _PHL_DEBUG_, "%s : host idx %d, hw_idx %d.\n",
			  __func__, host_idx, hw_idx);
	pstatus = phl_handle_busy_h2c(phl_info, h2c_pool, hw_idx);
	_os_spinunlock(phl_to_drvpriv(phl_info), &h2c_pool->recycle_lock, _bh, NULL);
	FUNCOUT_WSTS(pstatus);
	return pstatus;
}

static enum rtw_phl_status phl_tx_pcie(struct phl_info_t *phl_info)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	struct hci_info_t *hci_info = (struct hci_info_t *)phl_info->hci;
	struct rtw_wd_page_ring *wd_ring = NULL;
	u16 hw_res = 0, host_idx = 0, hw_idx = 0, txcnt = 0;
	u8 ch = 0;
	FUNCIN_WSTS(pstatus);
	wd_ring = (struct rtw_wd_page_ring *)hci_info->wd_ring;

	for (ch = 0; ch < hci_info->total_txch_num; ch++) {
#ifndef RTW_WKARD_WIN_TRX_BALANCE
		/* if wd_ring is empty, do not read hw_idx for saving cpu cycle */
		if (wd_ring[ch].pending_wd_page_cnt == 0 && wd_ring[ch].busy_wd_page_cnt == 0){
			pstatus = RTW_PHL_STATUS_SUCCESS;
			continue;
		}
#endif
		/* hana_todo skip fwcmd queue */
		hw_res = rtw_hal_tx_res_query(phl_info->hal, ch, &host_idx,
							&hw_idx);

		pstatus = phl_handle_busy_wd(phl_info, &wd_ring[ch], hw_idx);

		if (list_empty(&wd_ring[ch].pending_wd_page_list))
			continue;

		if (RTW_PHL_STATUS_FAILURE == pstatus)
			continue;

		if (0 == hw_res) {
			continue;

		} else {
			txcnt = (hw_res < wd_ring[ch].pending_wd_page_cnt) ?
				hw_res : wd_ring[ch].pending_wd_page_cnt;

			pstatus = phl_handle_pending_wd(phl_info, &wd_ring[ch],
							txcnt, ch);

			if (RTW_PHL_STATUS_SUCCESS != pstatus)
				continue;
		}
	}
	FUNCOUT_WSTS(pstatus);
	return pstatus;
}


enum rtw_phl_status _phl_refill_rxbd(struct phl_info_t *phl_info,
					void* rx_buf_ring,
					struct rx_base_desc *rxbd,
					u8 ch, u16 refill_cnt)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	enum rtw_hal_status hstatus = RTW_HAL_STATUS_FAILURE;
	struct rtw_rx_buf *rxbuf = NULL;
	u16 cnt = 0;

	for (cnt = 0; cnt < refill_cnt; cnt++) {
		rxbuf = query_idle_rx_buf(phl_info, rx_buf_ring);
		if (NULL == rxbuf) {
			PHL_TRACE(COMP_PHL_DBG, _PHL_WARNING_,
				"[WARNING] there is no resource for rx bd refill setting\n");
			pstatus = RTW_PHL_STATUS_RESOURCE;
			break;
		}
		hstatus = rtw_hal_update_rxbd(phl_info->hal, rxbd, rxbuf);
		if (RTW_HAL_STATUS_SUCCESS != hstatus) {
			PHL_TRACE(COMP_PHL_DBG, _PHL_WARNING_,
				"[WARNING] update rxbd fail\n");
			pstatus = RTW_PHL_STATUS_FAILURE;
			break;
		}
		enqueue_busy_rx_buf(phl_info, rx_buf_ring, rxbuf, _tail);
		pstatus = RTW_PHL_STATUS_SUCCESS;
	}

	/* hana_todo */
	/* wmb(); */

	if (cnt) {
		hstatus = rtw_hal_notify_rxdone(phl_info->hal, rxbd, ch, cnt);
		if (RTW_HAL_STATUS_SUCCESS != hstatus) {
			PHL_TRACE(COMP_PHL_DBG, _PHL_WARNING_,
				"[WARNING] notify rxdone fail\n");
			pstatus = RTW_PHL_STATUS_FAILURE;
		}
	}
	return pstatus;
}

enum rtw_phl_status phl_get_single_rx(struct phl_info_t *phl_info,
					 struct rtw_rx_buf_ring *rx_buf_ring,
					 u8 ch,
					 struct rtw_phl_rx_pkt **pphl_rx)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	enum rtw_hal_status hstatus = RTW_HAL_STATUS_FAILURE;
	struct rtw_phl_rx_pkt *phl_rx = NULL;
	struct rtw_rx_buf *rxbuf = NULL;
	struct hal_spec_t *hal_spec = phl_get_ic_spec(phl_info->phl_com);
	u16 buf_size = 0;

	phl_rx = rtw_phl_query_phl_rx(phl_info);
	rxbuf = query_busy_rx_buf(phl_info, rx_buf_ring);

	do {
		if (NULL == phl_rx) {
			PHL_TRACE(COMP_PHL_DBG, _PHL_INFO_, "%s(%d) phl_rx out of resource\n",
				__func__, __LINE__);
				pstatus = RTW_PHL_STATUS_FAILURE;
			break;
		}
		if (NULL == rxbuf) {
			PHL_TRACE(COMP_PHL_DBG, _PHL_WARNING_, "%s(%d) [WARNING] queried NULL rxbuf\n",
				__func__, __LINE__);
				pstatus = RTW_PHL_STATUS_FAILURE;
			break;
		}

		phl_rx->rxbuf_ptr = (u8 *)rxbuf;

		if(rxbuf->cache == true){
		_os_cache_inv(phl_to_drvpriv(phl_info),
			(_dma *)&rxbuf->phy_addr_l,
			(_dma *)&rxbuf->phy_addr_h,
			hal_spec->rx_bd_info_sz,
			PCI_DMA_FROMDEVICE);
		}
		if (true != rtw_hal_check_rxrdy(phl_info->phl_com,
						phl_info->hal,
						rxbuf->vir_addr,
						ch)) {
			PHL_TRACE(COMP_PHL_DBG, _PHL_INFO_, "RX:%s(%d) packet not ready\n",
					__func__, __LINE__);
				pstatus = RTW_PHL_STATUS_FAILURE;
#ifdef RTW_WKARD_98D_RXTAG
			phl_rx->rxbuf_ptr = (u8 *)rxbuf;
			goto drop;
#endif
			break;
		}

		if (true != rtw_hal_handle_rxbd_info(phl_info->hal,
								rxbuf->vir_addr,
								&buf_size))
			goto drop;

#ifdef CONFIG_DYNAMIC_RX_BUF
		phl_rx->r.os_priv = rxbuf->os_priv;
#endif

		/* hana_todo handle rx amsdu */
		if(rxbuf->cache == true){
		_os_cache_inv(phl_to_drvpriv(phl_info),
			(_dma *)&rxbuf->phy_addr_l,
			(_dma *)&rxbuf->phy_addr_h,
				buf_size,
			PCI_DMA_FROMDEVICE);
		}

		hstatus = rtw_hal_handle_rx_buffer(phl_info->phl_com,
							phl_info->hal,
							rxbuf->vir_addr,
							buf_size, phl_rx);

		if (RTW_HAL_STATUS_SUCCESS != hstatus)
			goto drop;

		pstatus = RTW_PHL_STATUS_SUCCESS;

	} while (false);

	if (RTW_PHL_STATUS_SUCCESS != pstatus) {
		/* hana_todo cache validate api */
		if (NULL != rxbuf) {
			enqueue_busy_rx_buf(phl_info, rx_buf_ring, rxbuf, _first);
		}

		if (NULL != phl_rx) {
			phl_release_phl_rx(phl_info, phl_rx);
			phl_rx = NULL;
		}
	}
	*pphl_rx = phl_rx;

	return pstatus;

drop:
#ifdef CONFIG_DYNAMIC_RX_BUF
	/* avoid re-allocating buffer carried on rxbuf */
	phl_rx->type = RTW_RX_TYPE_MAX;
#endif
	phl_rx->r.mdata.dma_ch = ch;
	phl_recycle_rx_buf(phl_info, phl_rx);

	return RTW_PHL_STATUS_FRAME_DROP;
}

#define PHL_RX_HEADROOM 50
void phl_rx_handle_normal(struct phl_info_t *phl_info,
						 struct rtw_phl_rx_pkt *phl_rx)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	_os_list frames;
#ifdef CONFIG_DYNAMIC_RX_BUF
	struct rtw_rx_buf *rxbuf = (struct rtw_rx_buf *)phl_rx->rxbuf_ptr;
#endif
	FUNCIN_WSTS(pstatus);
	INIT_LIST_HEAD(&frames);

	/* unmap rx buffer */
#ifdef CONFIG_DYNAMIC_RX_BUF
	_os_pkt_buf_unmap_rx(phl_to_drvpriv(phl_info), rxbuf->phy_addr_l,
		rxbuf->phy_addr_h, RX_BUF_SIZE+PHL_RX_HEADROOM);
#endif

	/* stat : rx rate counter */
	if (phl_rx->r.mdata.rx_rate <= RTW_DATA_RATE_HE_NSS4_MCS11)
		phl_info->phl_com->phl_stats.rx_rate_nmr[phl_rx->r.mdata.rx_rate]++;

	pstatus = phl_rx_reorder(phl_info, phl_rx, &frames);
	if (pstatus == RTW_PHL_STATUS_SUCCESS)
		phl_handle_rx_frame_list(phl_info, &frames);
	else
		PHL_TRACE(COMP_PHL_RECV, _PHL_WARNING_, "[WARNING]handle normal rx error (0x%08X)!\n", pstatus);

	FUNCOUT_WSTS(pstatus);
}

void _phl_wp_rpt_statistics(struct phl_info_t *phl_info, u8 ch, u16 wp_seq,
			    u8 txsts, struct rtw_xmit_req *treq)
{
	struct rtw_hal_com_t *hal_com = rtw_hal_get_halcom(phl_info->hal);
	struct rtw_wp_rpt_stats *rpt_stats = NULL;
	u32 diff_t = 0, cur_time = _os_get_cur_time_ms();

	rpt_stats = (struct rtw_wp_rpt_stats *)hal_com->trx_stat.wp_rpt_stats;

	PHL_TRACE(COMP_PHL_XMIT, _PHL_DEBUG_,
		  "recycle tx req(%p) in ch(%d) with wp_seq(0x%x) and wifi seq(%d)!\n",
		  treq, ch, wp_seq, treq->mdata.sw_seq);

#ifdef CONFIG_PHL_TX_DBG
	if (treq->tx_dbg.en_dbg) {
		treq->tx_dbg.recycle_wd_t = _os_get_cur_time_us();
		PHL_TRACE(COMP_PHL_DBG, _PHL_INFO_, "tx dbg rpt: macid(%02d), tx_dbg_pkt_type(%02d), txsts(%d), sw_seq(%04d), total tx time(%08d) us\n",
			treq->mdata.macid, treq->tx_dbg.tx_dbg_pkt_type, txsts,
			treq->mdata.sw_seq, phl_get_passing_time_us(
			treq->tx_dbg.core_add_tx_t));
		PHL_TRACE(COMP_PHL_DBG, _PHL_INFO_, "tx dbg rpt: core_add_tx_t(0x%08x), enq_pending_wd_t(0x%08x), recycle_wd_t(0x%08x)\n",
			treq->tx_dbg.core_add_tx_t,
			treq->tx_dbg.enq_pending_wd_t,
			treq->tx_dbg.recycle_wd_t);

		if(TX_STATUS_TX_DONE != txsts) {
			PHL_TRACE(COMP_PHL_DBG, _PHL_INFO_, "tx dbg rpt: tx fail(%d)\n", txsts);
			if(NULL != treq->tx_dbg.statecb) {
				treq->tx_dbg.statecb(phl_to_drvpriv(phl_info), treq->tx_dbg.pctx, false);
			}
		} else {
			PHL_TRACE(COMP_PHL_DBG, _PHL_INFO_, "tx dbg rpt: tx done(%d)\n", txsts);
			if(NULL != treq->tx_dbg.statecb) {
				treq->tx_dbg.statecb(phl_to_drvpriv(phl_info), treq->tx_dbg.pctx, true);
			}
		}
	}
#endif /* CONFIG_PHL_TX_DBG */

	if (cur_time >= treq->tx_time)
		diff_t = cur_time - treq->tx_time;
	else
		diff_t = RTW_U32_MAX - treq->tx_time + cur_time;

	if (diff_t > WP_DELAY_THRES_MS) {
		if (TX_STATUS_TX_DONE == txsts)
			rpt_stats[ch].delay_tx_ok_cnt++;
		else if (TX_STATUS_TX_FAIL_REACH_RTY_LMT == txsts)
			rpt_stats[ch].delay_rty_fail_cnt++;
		else if (TX_STATUS_TX_FAIL_LIFETIME_DROP == txsts)
			rpt_stats[ch].delay_lifetime_drop_cnt++;
		else if (TX_STATUS_TX_FAIL_MACID_DROP == txsts)
			rpt_stats[ch].delay_macid_drop_cnt++;
	} else {
		if (TX_STATUS_TX_DONE == txsts)
			rpt_stats[ch].tx_ok_cnt++;
		else if (TX_STATUS_TX_FAIL_REACH_RTY_LMT == txsts)
			rpt_stats[ch].rty_fail_cnt++;
		else if (TX_STATUS_TX_FAIL_LIFETIME_DROP == txsts)
			rpt_stats[ch].lifetime_drop_cnt++;
		else if (TX_STATUS_TX_FAIL_MACID_DROP == txsts)
			rpt_stats[ch].macid_drop_cnt++;
	}
}


void _phl_wp_rpt_chk_txsts(struct phl_info_t *phl_info, u8 ch, u16 wp_seq,
			    u8 txsts, struct rtw_xmit_req *treq)
{
	struct rtw_hal_com_t *hal_com = rtw_hal_get_halcom(phl_info->hal);
	struct rtw_wp_rpt_stats *rpt_stats = NULL;
	struct rtw_pkt_buf_list *pkt_buf = (struct rtw_pkt_buf_list *)treq->pkt_list;
	int i;

	rpt_stats = (struct rtw_wp_rpt_stats *)hal_com->trx_stat.wp_rpt_stats;

	if(TX_STATUS_TX_DONE != txsts) {
		if (TX_STATUS_TX_FAIL_REACH_RTY_LMT == txsts) {
			PHL_TRACE(COMP_PHL_DBG, _PHL_WARNING_,
				"this wp is tx fail (REACH_RTY_LMT): wp(%p), ch(%d), wp_seq(0x%x), macid(%d), Sw SN(%d), tid(%d), Rty_lmt_en/cnt(%d/%d)!\n",
				treq, ch, wp_seq, treq->mdata.macid,
				treq->mdata.sw_seq, treq->mdata.tid,
				treq->mdata.data_tx_cnt_lmt_en,
				treq->mdata.data_tx_cnt_lmt);
		} else if (TX_STATUS_TX_FAIL_LIFETIME_DROP == txsts) {
			PHL_TRACE(COMP_PHL_DBG, _PHL_WARNING_,
				"this wp is tx fail (LIFETIME_DROP): wp(%p), ch(%d), wp_seq(0x%x), macid(%d), Sw SN(%d), tid(%d), Rty_lmt_en/cnt(%d/%d)!\n",
				treq, ch, wp_seq, treq->mdata.macid,
				treq->mdata.sw_seq, treq->mdata.tid,
				treq->mdata.data_tx_cnt_lmt_en,
				treq->mdata.data_tx_cnt_lmt);
		} else if (TX_STATUS_TX_FAIL_MACID_DROP == txsts) {
			PHL_TRACE(COMP_PHL_DBG, _PHL_WARNING_,
				"this wp is tx fail (MACID_DROP): wp(%p), ch(%d), wp_seq(0x%x), macid(%d), Sw SN(%d), tid(%d), Rty_lmt_en/cnt(%d/%d)!\n",
				treq, ch, wp_seq, treq->mdata.macid,
				treq->mdata.sw_seq, treq->mdata.tid,
				treq->mdata.data_tx_cnt_lmt_en,
				treq->mdata.data_tx_cnt_lmt);
		} else if(TX_STATUS_TX_FAIL_SW_DROP == txsts) {
			PHL_TRACE(COMP_PHL_DBG, _PHL_WARNING_,
				"this wp is tx fail (SW_DROP): wp(%p), ch(%d), wp_seq(0x%x), macid(%d), Sw SN(%d), tid(%d), Rty_lmt_en/cnt(%d/%d)!\n",
				treq, ch, wp_seq, treq->mdata.macid,
				treq->mdata.sw_seq, treq->mdata.tid,
				treq->mdata.data_tx_cnt_lmt_en,
				treq->mdata.data_tx_cnt_lmt);
		} else {
			PHL_TRACE(COMP_PHL_DBG, _PHL_WARNING_,
				"this wp is tx fail (UNKNOWN(%d)): wp(%p), ch(%d), wp_seq(0x%x), macid(%d), Sw SN(%d), tid(%d), Rty_lmt_en/cnt(%d/%d)!\n",
				txsts, treq, ch, wp_seq, treq->mdata.macid,
				treq->mdata.sw_seq, treq->mdata.tid,
				treq->mdata.data_tx_cnt_lmt_en,
				treq->mdata.data_tx_cnt_lmt);
		}

		/* dump tx fail mac hdr */
		if(MAC_HDR_LEN <= pkt_buf[0].length) {
			PHL_DATA(COMP_PHL_XMIT, _PHL_INFO_, "=== Dump Tx MAC HDR ===");
			for (i = 0; i < MAC_HDR_LEN; i++) {
				if (!(i % 8))
					PHL_DATA(COMP_PHL_XMIT, _PHL_INFO_, "\n");
				PHL_DATA(COMP_PHL_XMIT, _PHL_INFO_, "%02X ", pkt_buf[0].vir_addr[i]);
			}
			PHL_DATA(COMP_PHL_XMIT, _PHL_INFO_, "\n");
		}
	}

	if (treq->txfb) {
		treq->txfb->txsts = txsts;
		if (treq->txfb->txfb_cb)
			treq->txfb->txfb_cb(treq->txfb);
	}
}

void phl_recycle_payload(struct phl_info_t *phl_info, u8 dma_ch, u16 wp_seq,
			 u8 txsts)
{
	enum rtw_phl_status sts = RTW_PHL_STATUS_FAILURE;
	struct rtw_hal_com_t *hal_com = rtw_hal_get_halcom(phl_info->hal);
	struct rtw_wp_rpt_stats *rpt_stats =
		(struct rtw_wp_rpt_stats *)hal_com->trx_stat.wp_rpt_stats;
	struct hci_info_t *hci_info = (struct hci_info_t *)phl_info->hci;
	struct rtw_phl_evt_ops *ops = &phl_info->phl_com->evt_ops;
	struct rtw_wd_page_ring *wd_ring = NULL;
	struct rtw_xmit_req *treq = NULL;
	u16 macid = 0;

	wd_ring = (struct rtw_wd_page_ring *)hci_info->wd_ring;
	treq = (struct rtw_xmit_req *)wd_ring[dma_ch].wp_tag[wp_seq].ptr;

	if (NULL == treq)
		goto end;

	macid = treq->mdata.macid;

	_phl_wp_rpt_statistics(phl_info, dma_ch, wp_seq, txsts, treq);
	_phl_wp_rpt_chk_txsts(phl_info, dma_ch, wp_seq, txsts, treq);

	if (RTW_PHL_TREQ_TYPE_TEST_PATTERN == treq->treq_type) {
		if (NULL == ops->tx_test_recycle)
			goto end;
		PHL_INFO("call tx_test_recycle\n");
		sts = ops->tx_test_recycle(phl_info, treq);
	} else if (RTW_PHL_TREQ_TYPE_NORMAL == treq->treq_type
#if defined(CONFIG_CORE_TXSC) || defined(CONFIG_PHL_TXSC)
		   || RTW_PHL_TREQ_TYPE_CORE_TXSC == treq->treq_type
		   || RTW_PHL_TREQ_TYPE_PHL_ADD_TXSC == treq->treq_type
#endif
	) {
		if (NULL == ops->tx_recycle)
			goto end;
		PHL_TRACE(COMP_PHL_DBG, _PHL_DEBUG_, "call tx_recycle\n");
		sts = ops->tx_recycle(phl_to_drvpriv(phl_info), treq);
	}

end:
	if (RTW_PHL_STATUS_SUCCESS != sts) {
		PHL_WARN("tx recycle fail\n");
		rpt_stats[dma_ch].recycle_fail_cnt++;
	} else {
		_os_spinlock(phl_to_drvpriv(phl_info),
			     &wd_ring[dma_ch].wp_tag_lock,
			     _bh, NULL);
		wd_ring[dma_ch].wp_tag[wp_seq].ptr = NULL;
		rpt_stats[dma_ch].busy_cnt--;

#ifdef RTW_WKARD_DYNAMIC_LTR
		if (true ==
		    _phl_judge_idle_ltr_switching_conditions(phl_info, macid))
			_phl_switch_idle_ltr(phl_info, rpt_stats);
#endif
		_os_spinunlock(phl_to_drvpriv(phl_info),
			       &wd_ring[dma_ch].wp_tag_lock,
			       _bh, NULL);
	}
	/* phl_indic_pkt_complete(phl_info); */
}

void _phl_rx_handle_wp_report(struct phl_info_t *phl_info,
							struct rtw_phl_rx_pkt *phl_rx)
{
	struct rtw_recv_pkt *r = &phl_rx->r;
	u8 *pkt = NULL;
	u16 pkt_len = 0;
	u16 wp_seq = 0, rsize = 0;
	u8 sw_retry = 0, dma_ch = 0, txsts = 0;

	pkt = r->pkt_list[0].vir_addr;
	pkt_len = r->pkt_list[0].length;

	while (pkt_len > 0) {
		rsize = rtw_hal_handle_wp_rpt(phl_info->hal, pkt, pkt_len,
					      &sw_retry, &dma_ch, &wp_seq,
					      &txsts);

		if (0 == rsize)
			break;

		if (false == sw_retry) {
			phl_recycle_payload(phl_info, dma_ch, wp_seq, txsts);
		} else {
			/* hana_todo handle sw retry */
			phl_recycle_payload(phl_info, dma_ch, wp_seq, txsts);
		}
		pkt += rsize;
		pkt_len -= rsize;
	}
}


static void phl_rx_process_pcie(struct phl_info_t *phl_info,
							struct rtw_phl_rx_pkt *phl_rx)
{
	switch (phl_rx->type) {
	case RTW_RX_TYPE_WIFI:
#ifdef CONFIG_PHL_RX_PSTS_PER_PKT
		if (false == phl_rx_proc_wait_phy_sts(phl_info, phl_rx)) {
			PHL_TRACE(COMP_PHL_PSTS, _PHL_DEBUG_,
				  "phl_rx_proc_wait_phy_sts() return false \n");
			phl_rx_handle_normal(phl_info, phl_rx);
		}
#else
		phl_rx_handle_normal(phl_info, phl_rx);
#endif
		break;
	case RTW_RX_TYPE_TX_WP_RELEASE_HOST:
		_phl_rx_handle_wp_report(phl_info, phl_rx);
		phl_recycle_rx_buf(phl_info, phl_rx);
		break;
	case RTW_RX_TYPE_PPDU_STATUS:
		phl_rx_proc_ppdu_sts(phl_info, phl_rx);
#ifdef CONFIG_PHL_RX_PSTS_PER_PKT
		phl_rx_proc_phy_sts(phl_info, phl_rx);
#endif
		phl_recycle_rx_buf(phl_info, phl_rx);
		break;
	case RTW_RX_TYPE_C2H:
		phl_recycle_rx_buf(phl_info, phl_rx);
		break;
	case RTW_RX_TYPE_CHANNEL_INFO:
	case RTW_RX_TYPE_TX_RPT:
	case RTW_RX_TYPE_DFS_RPT:
	case RTW_RX_TYPE_MAX:
		PHL_TRACE(COMP_PHL_RECV, _PHL_WARNING_, "phl_rx_process_pcie(): Unsupported case:%d, please check it\n",
				phl_rx->type);
		phl_recycle_rx_buf(phl_info, phl_rx);
		break;
	default :
		PHL_TRACE(COMP_PHL_RECV, _PHL_WARNING_, "[WARNING] unrecognize rx type\n");
		phl_recycle_rx_buf(phl_info, phl_rx);
		break;
	}
}

static u16 _phl_get_idle_rxbuf_cnt(struct phl_info_t *phl_info,
					struct rtw_rx_buf_ring *rx_buf_ring)
{
	return rx_buf_ring->idle_rxbuf_cnt;
}

static enum rtw_phl_status phl_rx_pcie(struct phl_info_t *phl_info)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	struct hci_info_t *hci_info = (struct hci_info_t *)phl_info->hci;
	struct rtw_rx_buf_ring *rx_buf_ring = NULL;
	struct rx_base_desc *rxbd = NULL;
	struct rtw_phl_rx_pkt *phl_rx = NULL;
	u16 i = 0, rxcnt = 0, host_idx = 0, hw_idx = 0, idle_rxbuf_cnt = 0;
	u8 ch = 0;

	FUNCIN_WSTS(pstatus);

	rx_buf_ring = (struct rtw_rx_buf_ring *)hci_info->rxbuf_pool;
	rxbd = (struct rx_base_desc *)hci_info->rxbd_buf;

	for (ch = 0; ch < hci_info->total_rxch_num; ch++) {

		idle_rxbuf_cnt = _phl_get_idle_rxbuf_cnt(phl_info,
							 &rx_buf_ring[ch]);

		if (idle_rxbuf_cnt == 0) {
			PHL_WARN("%s, idle rxbuf is empty. (ch = %d)\n",
				 __func__, ch);
			phl_dump_all_sta_rx_info(phl_info);
			PHL_INFO("phl_rx_ring stored rx number = %d\n",
				 rtw_phl_query_new_rx_num(phl_info));
#ifdef PHL_RX_BATCH_IND
			if (ch == 0)
				_phl_indic_new_rxpkt(phl_info);
#endif
			pstatus = RTW_PHL_STATUS_SUCCESS;
			continue;
		}

		rxcnt = rtw_hal_rx_res_query(phl_info->hal, ch, &host_idx, &hw_idx);

		if (rxcnt == 0) {
			PHL_TRACE(COMP_PHL_DBG, _PHL_DEBUG_,
				"no avail hw rx\n");
			pstatus = RTW_PHL_STATUS_SUCCESS;
			continue;
		}

		/* only handle affordable amount of rxpkt */
		if (rxcnt > idle_rxbuf_cnt) {
			PHL_WARN("rxcnt %d is lager than idle rxbuf cnt %d.\n", rxcnt, idle_rxbuf_cnt);
			rxcnt = idle_rxbuf_cnt;
		}

		for (i = 0; i < rxcnt; i++) {
			pstatus = phl_get_single_rx(phl_info, &rx_buf_ring[ch],
							ch, &phl_rx);
			if (RTW_PHL_STATUS_FRAME_DROP == pstatus)
				continue;
			if (NULL == phl_rx) {
				rxcnt = i;
				break;
			}

			/* hana_todo */
			phl_rx->r.mdata.dma_ch = ch;
			phl_rx_process_pcie(phl_info, phl_rx);
		}

#ifdef PHL_RX_BATCH_IND
		if (ch == 0 && phl_info->rx_new_pending)
			_phl_indic_new_rxpkt(phl_info);
#endif

		pstatus = _phl_refill_rxbd(phl_info, &rx_buf_ring[ch],
							&rxbd[ch], ch, rxcnt);

		if (RTW_PHL_STATUS_RESOURCE == pstatus)
			PHL_WARN("%s, rxcnt is not refilled %d.\n", __func__ , rxcnt);

		if (RTW_PHL_STATUS_SUCCESS != pstatus)
			continue;
	}

	FUNCOUT_WSTS(pstatus);

	return pstatus;
}

enum rtw_phl_status phl_pltfm_tx_pcie(struct phl_info_t *phl_info, void *pkt)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	enum rtw_hal_status hstatus = RTW_HAL_STATUS_FAILURE;
	struct rtw_h2c_pkt *h2c_pkt = (struct rtw_h2c_pkt *)pkt;
	struct tx_base_desc *txbd = NULL;
	struct phl_h2c_pkt_pool *h2c_pool = NULL;
	struct rtw_wd_page wd;
	u8 fwcmd_queue_idx = 0;

	txbd = (struct tx_base_desc *)phl_info->hci->txbd_buf;
	h2c_pool = (struct phl_h2c_pkt_pool *)phl_info->h2c_pool;

	_os_mem_set(phl_to_drvpriv(phl_info), &wd, 0, sizeof(wd));
	/* fowart h2c pkt information into the format of wd page */
	wd.phy_addr_l = h2c_pkt->phy_addr_l;
	wd.phy_addr_h= h2c_pkt->phy_addr_h;
	wd.buf_len = h2c_pkt->data_len;
	wd.ls = 1;

	PHL_TRACE(COMP_PHL_DBG, _PHL_DEBUG_, "%s : wd.phy_addr_l %x, wd.phy_addr_h %x\n", __func__ , wd.phy_addr_l, wd.phy_addr_h);
	PHL_TRACE(COMP_PHL_DBG, _PHL_DEBUG_, "%s : buf_len %x.\n", __func__ , wd.buf_len);

	fwcmd_queue_idx = rtw_hal_get_fwcmd_queue_idx(phl_info->hal);

	_os_spinlock(phl_to_drvpriv(phl_info), &txbd[fwcmd_queue_idx].txbd_lock, _bh, NULL);
	hstatus = rtw_hal_update_txbd(phl_info->hal, txbd, &wd, fwcmd_queue_idx, 1);
	_os_spinunlock(phl_to_drvpriv(phl_info), &txbd[fwcmd_queue_idx].txbd_lock, _bh, NULL);

	h2c_pkt->host_idx = wd.host_idx;

	PHL_TRACE(COMP_PHL_DBG, _PHL_DEBUG_, "%s : h2c_pkt->host_idx %d.\n", __func__, h2c_pkt->host_idx);

	if (RTW_HAL_STATUS_SUCCESS == hstatus)
			pstatus = phl_enqueue_busy_h2c_pkt(phl_info, h2c_pkt, _tail);

	if (RTW_PHL_STATUS_SUCCESS == pstatus) {
		_os_spinlock(phl_to_drvpriv(phl_info), &txbd[fwcmd_queue_idx].txbd_lock, _bh, NULL);
		hstatus = rtw_hal_trigger_txstart(phl_info->hal, txbd, fwcmd_queue_idx);
		_os_spinunlock(phl_to_drvpriv(phl_info), &txbd[fwcmd_queue_idx].txbd_lock, _bh, NULL);
		if (RTW_HAL_STATUS_SUCCESS != hstatus) {
			PHL_TRACE(COMP_PHL_DBG, _PHL_WARNING_, "[WARNING]update Tx RW ptr fail!\n");
			pstatus = RTW_PHL_STATUS_FAILURE;
		}
	}

	return pstatus;
}

void *phl_get_txbd_buf_pcie(struct phl_info_t *phl_info)
{
	struct hci_info_t *hci_info = phl_info->hci;

	return hci_info->txbd_buf;
}

void *phl_get_rxbd_buf_pcie(struct phl_info_t *phl_info)
{
	struct hci_info_t *hci_info = phl_info->hci;

	return hci_info->rxbd_buf;
}

void phl_recycle_rx_pkt_pcie(struct phl_info_t *phl_info,
				struct rtw_phl_rx_pkt *phl_rx)
{
#ifdef CONFIG_DYNAMIC_RX_BUF
	struct rtw_rx_buf *rx_buf = (struct rtw_rx_buf *)phl_rx->rxbuf_ptr;

	rx_buf->reuse = true;
#endif

	phl_recycle_rx_buf(phl_info, phl_rx);
}

enum rtw_phl_status phl_register_trx_hdlr_pcie(struct phl_info_t *phl_info)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;
	struct rtw_phl_handler *tx_handler = &phl_info->phl_tx_handler;
	struct rtw_phl_handler *rx_handler = &phl_info->phl_rx_handler;
	void *drv_priv = phl_to_drvpriv(phl_info);

	tx_handler->type = RTW_PHL_HANDLER_PRIO_HIGH; /* tasklet */
	tx_handler->callback = _phl_tx_callback_pcie;
	tx_handler->context = phl_info;
	tx_handler->drv_priv = drv_priv;
	pstatus = phl_register_handler(phl_info->phl_com, tx_handler);
	if (RTW_PHL_STATUS_SUCCESS != pstatus)
		PHL_ERR("%s : register tx_handler fail.\n", __FUNCTION__);

	rx_handler->type = RTW_PHL_HANDLER_PRIO_HIGH;
	rx_handler->callback = _phl_rx_callback_pcie;
	rx_handler->context = phl_info;
	rx_handler->drv_priv = drv_priv;
	pstatus = phl_register_handler(phl_info->phl_com, rx_handler);
	if (RTW_PHL_STATUS_SUCCESS != pstatus)
		PHL_ERR("%s : register rx_handler fail.\n", __FUNCTION__);

	return pstatus;
}

void
phl_tx_watchdog_pcie(struct phl_info_t *phl_info)
{
	struct rtw_stats *phl_stats = NULL;

	phl_stats = &phl_info->phl_com->phl_stats;

	PHL_TRACE(COMP_PHL_XMIT, _PHL_DEBUG_,
		  "\n=== Tx statistics === \n");
	PHL_TRACE(COMP_PHL_XMIT, _PHL_DEBUG_,
		"\nunicast tx bytes	: %llu\n", phl_stats->tx_byte_uni);
	PHL_TRACE(COMP_PHL_XMIT, _PHL_DEBUG_,
		"total tx bytes		: %llu\n", phl_stats->tx_byte_total);
	PHL_TRACE(COMP_PHL_XMIT, _PHL_DEBUG_,
		 "tx throughput		: %d(kbps)\n",
			 (int)phl_stats->tx_tp_kbits);
	PHL_TRACE(COMP_PHL_XMIT, _PHL_DEBUG_,
		"last tx time		: %d(ms)\n",
			 (int)phl_stats->last_tx_time_ms);
	PHL_TRACE(COMP_PHL_XMIT, _PHL_DEBUG_,
		"tx request num to phl	: %d\n",
			 (int)phl_stats->txreq_num);

	#ifdef RTW_WKARD_DYNAMIC_LTR
	if (rtw_hal_ltr_is_sw_ctrl(phl_info->phl_com, phl_info->hal)) {
		PHL_INFO(
			"ltr sw ctrl 			: %u\n",
			rtw_hal_ltr_is_sw_ctrl(phl_info->phl_com, phl_info->hal) ? 1 : 0);
		PHL_INFO(
			"ltr current state 		: %u\n",
			phl_ltr_get_cur_state(phl_info->phl_com));
		PHL_INFO(
			"ltr active trigger cnt : %lu\n",
			phl_ltr_get_tri_cnt(phl_info->phl_com, RTW_PCIE_LTR_SW_ACT));
		PHL_INFO(
			"ltr idle trigger cnt   : %lu\n",
			phl_ltr_get_tri_cnt(phl_info->phl_com, RTW_PCIE_LTR_SW_IDLE));
		PHL_INFO(
			"ltr last trigger time  : %lu\n",
			phl_ltr_get_last_trigger_time(phl_info->phl_com));
	}
	#endif

	_phl_dump_wp_stats(phl_info);

	_phl_dump_busy_wp(phl_info);

	PHL_TRACE(COMP_PHL_XMIT, _PHL_DEBUG_,
		  "\n===================== \n");

}

static struct phl_hci_trx_ops ops= {0};
void phl_hci_trx_ops_init(void)
{
	ops.hci_trx_init = phl_trx_init_pcie;
	ops.hci_trx_deinit = phl_trx_deinit_pcie;
	ops.prepare_tx = phl_prepare_tx_pcie;
	ops.recycle_rx_buf = phl_release_target_rx_buf;
	ops.tx = phl_tx_pcie;
	ops.rx = phl_rx_pcie;
	ops.trx_cfg = phl_trx_config_pcie;
	ops.trx_stop = phl_trx_stop_pcie;
	ops.recycle_busy_wd = phl_recycle_busy_wd;
	ops.recycle_busy_h2c = phl_recycle_busy_h2c;
	ops.pltfm_tx = phl_pltfm_tx_pcie;
	ops.alloc_h2c_pkt_buf = _phl_alloc_h2c_pkt_buf_pcie;
	ops.free_h2c_pkt_buf = _phl_free_h2c_pkt_buf_pcie;
	ops.trx_reset = phl_trx_reset_pcie;
	ops.trx_resume = phl_trx_resume_pcie;
	ops.req_tx_stop = phl_req_tx_stop_pcie;
	ops.req_rx_stop = phl_req_rx_stop_pcie;
	ops.is_tx_pause = phl_is_tx_sw_pause_pcie;
	ops.is_rx_pause = phl_is_rx_sw_pause_pcie;
	ops.get_txbd_buf = phl_get_txbd_buf_pcie;
	ops.get_rxbd_buf = phl_get_rxbd_buf_pcie;
	ops.recycle_rx_pkt = phl_recycle_rx_pkt_pcie;
	ops.register_trx_hdlr = phl_register_trx_hdlr_pcie;
	ops.rx_handle_normal = phl_rx_handle_normal;
	ops.tx_watchdog = phl_tx_watchdog_pcie;
}


enum rtw_phl_status phl_hook_trx_ops_pci(struct phl_info_t *phl_info)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;

	if (NULL != phl_info) {
		phl_hci_trx_ops_init();
		phl_info->hci_trx_ops = &ops;
		pstatus = RTW_PHL_STATUS_SUCCESS;
	}

	return pstatus;
}

enum rtw_phl_status phl_cmd_set_l2_leave(struct phl_info_t *phl_info)
{
	enum rtw_phl_status pstatus = RTW_PHL_STATUS_FAILURE;

#ifdef CONFIG_CMD_DISP
	pstatus = phl_cmd_enqueue(phl_info, HW_BAND_0, MSG_EVT_HAL_SET_L2_LEAVE, NULL, 0, NULL, PHL_CMD_WAIT, 0);

	if (is_cmd_failure(pstatus)) {
		/* Send cmd success, but wait cmd fail*/
		pstatus = RTW_PHL_STATUS_FAILURE;
	} else if (pstatus != RTW_PHL_STATUS_SUCCESS) {
		/* Send cmd fail */
		pstatus = RTW_PHL_STATUS_FAILURE;
	}
#else
	if (rtw_hal_set_l2_leave(phl_info->hal) == RTW_HAL_STATUS_SUCCESS)
		pstatus = RTW_PHL_STATUS_SUCCESS;
#endif
	return pstatus;
}
