/******************************************************************************
 *
 * Copyright(c) 2007 - 2017 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 _HAL_SDIO_C_

#include <drv_types.h>
#include <hal_data.h>

#ifndef RTW_HALMAC
const char *_sdio_tx_queue_str[] = {
	"H",
	"M",
	"L",
};

static void dump_mac_page0(PADAPTER padapter)
{
	char str_out[128];
	char str_val[8];
	char *p = NULL;
	int index = 0, i = 0;
	u8 val8 = 0, len = 0;

	RTW_ERR("Dump MAC Page0 register:\n");
	for (index = 0 ; index < 0x100 ; index += 16) {
		p = &str_out[0];
		len = snprintf(str_val, sizeof(str_val),
			       "0x%02x: ", index);
		strncpy(str_out, str_val, len);
		p += len;

		for (i = 0 ; i < 16 ; i++) {
			len = snprintf(str_val, sizeof(str_val), "%02x ",
				       rtw_read8(padapter, index + i));
			strncpy(p, str_val, len);
			p += len;
		}
		RTW_INFO("%s\n", str_out);
		_rtw_memset(&str_out, '\0', sizeof(str_out));
	}
}

/*
 * Description:
 *	Call this function to make sure power on successfully
 *
 * Return:
 *	_SUCCESS	enable success
 *	_FAIL	enable fail
 */
bool sdio_power_on_check(PADAPTER padapter) {
	u32 val_offset0, val_offset1, val_offset2, val_offset3;
	u32 val_mix = 0;
	u32 res = 0;
	bool ret = _FAIL;
	int index = 0;

	val_offset0 = rtw_read8(padapter, REG_CR);
	val_offset1 = rtw_read8(padapter, REG_CR + 1);
	val_offset2 = rtw_read8(padapter, REG_CR + 2);
	val_offset3 = rtw_read8(padapter, REG_CR + 3);

	if (val_offset0 == 0xEA || val_offset1 == 0xEA ||
	    val_offset2 == 0xEA || val_offset3 == 0xEA) {
		RTW_INFO("%s: power on fail, do Power on again\n", __func__);
		return ret;
	}

	val_mix = val_offset3 << 24 | val_mix;
	val_mix = val_offset2 << 16 | val_mix;
	val_mix = val_offset1 << 8 | val_mix;
	val_mix = val_offset0 | val_mix;

	res = rtw_read32(padapter, REG_CR);

	RTW_INFO("%s: val_mix:0x%08x, res:0x%08x\n", __func__, val_mix, res);

	while (index < 100) {
		if (res == val_mix) {
			RTW_INFO("%s: 0x100 the result of cmd52 and cmd53 is the same.\n", __func__);
			ret = _SUCCESS;
			break;
		} else {
			RTW_INFO("%s: 0x100 cmd52 and cmd53 is not the same(index:%d).\n", __func__, index);
			res = rtw_read32(padapter, REG_CR);
			index++;
			ret = _FAIL;
		}
	}

	if (ret) {
		index = 0;
		while (index < 100) {
			rtw_write32(padapter, 0x1B8, 0x12345678);
			res = rtw_read32(padapter, 0x1B8);
			if (res == 0x12345678) {
				RTW_INFO("%s: 0x1B8 test Pass.\n", __func__);
				ret = _SUCCESS;
				break;
			} else {
				index++;
				RTW_INFO("%s: 0x1B8 test Fail(index: %d).\n", __func__, index);
				ret = _FAIL;
			}
		}
	} else
		RTW_INFO("%s: fail at cmd52, cmd53.\n", __func__);

	if (ret == _FAIL)
		dump_mac_page0(padapter);

	return ret;
}

u8 rtw_hal_sdio_max_txoqt_free_space(_adapter *padapter)
{
	HAL_DATA_TYPE	*pHalData = GET_HAL_DATA(padapter);

	if (pHalData->SdioTxOQTMaxFreeSpace < 8)
		pHalData->SdioTxOQTMaxFreeSpace = 8;

	return pHalData->SdioTxOQTMaxFreeSpace;
}

u8 rtw_hal_sdio_query_tx_freepage(_adapter *padapter, u8 PageIdx, u8 RequiredPageNum)
{
	HAL_DATA_TYPE	*pHalData = GET_HAL_DATA(padapter);

	if ((pHalData->SdioTxFIFOFreePage[PageIdx] + pHalData->SdioTxFIFOFreePage[PUBLIC_QUEUE_IDX]) >= (RequiredPageNum))
		return _TRUE;
	else
		return _FALSE;
}

void rtw_hal_sdio_update_tx_freepage(_adapter *padapter, u8 PageIdx, u8 RequiredPageNum)
{
	HAL_DATA_TYPE	*pHalData = GET_HAL_DATA(padapter);
	u8	DedicatedPgNum = 0;
	u8	RequiredPublicFreePgNum = 0;
	/* _irqL irql; */

	/* _enter_critical_bh(&pHalData->SdioTxFIFOFreePageLock, &irql); */

	DedicatedPgNum = pHalData->SdioTxFIFOFreePage[PageIdx];
	if (RequiredPageNum <= DedicatedPgNum)
		pHalData->SdioTxFIFOFreePage[PageIdx] -= RequiredPageNum;
	else {
		pHalData->SdioTxFIFOFreePage[PageIdx] = 0;
		RequiredPublicFreePgNum = RequiredPageNum - DedicatedPgNum;
		pHalData->SdioTxFIFOFreePage[PUBLIC_QUEUE_IDX] -= RequiredPublicFreePgNum;
	}

	/* _exit_critical_bh(&pHalData->SdioTxFIFOFreePageLock, &irql); */
}

void rtw_hal_set_sdio_tx_max_length(PADAPTER padapter, u8 numHQ, u8 numNQ, u8 numLQ, u8 numPubQ, u8 div_num)
{
	HAL_DATA_TYPE	*pHalData = GET_HAL_DATA(padapter);
	u32	page_size;
	u32	lenHQ, lenNQ, lenLQ;

	rtw_hal_get_def_var(padapter, HAL_DEF_TX_PAGE_SIZE, &page_size);

	lenHQ = ((numHQ + numPubQ) / div_num) * page_size;
	lenNQ = ((numNQ + numPubQ) / div_num) * page_size;
	lenLQ = ((numLQ + numPubQ) / div_num) * page_size;

	pHalData->sdio_tx_max_len[HI_QUEUE_IDX] = (lenHQ > MAX_XMITBUF_SZ) ? MAX_XMITBUF_SZ : lenHQ;
	pHalData->sdio_tx_max_len[MID_QUEUE_IDX] = (lenNQ > MAX_XMITBUF_SZ) ? MAX_XMITBUF_SZ : lenNQ;
	pHalData->sdio_tx_max_len[LOW_QUEUE_IDX] = (lenLQ > MAX_XMITBUF_SZ) ? MAX_XMITBUF_SZ : lenLQ;
	#ifdef DBG_TX_FREE_PAGE
	RTW_INFO("rtw_hal_set_sdio_tx_max_length div_num :%u  numHQ=%u numNQ=%u numLQ=%u numPubQ=%u\n",div_num ,numHQ,numNQ,numLQ,numPubQ);
	RTW_INFO("pHalData->sdio_tx_max_len[HI_QUEUE_IDX] :%u\n",pHalData->sdio_tx_max_len[HI_QUEUE_IDX] );
	RTW_INFO("pHalData->sdio_tx_max_len[MID_QUEUE_IDX] :%u\n",pHalData->sdio_tx_max_len[MID_QUEUE_IDX] );
	RTW_INFO("rtw_hal_set_sdio_tx_max_length pHalData->sdio_tx_max_len[LOW_QUEUE_IDX] :%u\n",pHalData->sdio_tx_max_len[LOW_QUEUE_IDX] );
	#endif
}

u32 rtw_hal_get_sdio_tx_max_length(PADAPTER padapter, u8 queue_idx)
{
	struct dvobj_priv	*pdvobjpriv = adapter_to_dvobj(padapter);
	HAL_DATA_TYPE	*pHalData = GET_HAL_DATA(padapter);
	u32	deviceId, max_len;


	deviceId = ffaddr2deviceId(pdvobjpriv, queue_idx);
	switch (deviceId) {
	case WLAN_TX_HIQ_DEVICE_ID:
		max_len = pHalData->sdio_tx_max_len[HI_QUEUE_IDX];
		break;

	case WLAN_TX_MIQ_DEVICE_ID:
		max_len = pHalData->sdio_tx_max_len[MID_QUEUE_IDX];
		break;

	case WLAN_TX_LOQ_DEVICE_ID:
		max_len = pHalData->sdio_tx_max_len[LOW_QUEUE_IDX];
		break;

	default:
		max_len = pHalData->sdio_tx_max_len[MID_QUEUE_IDX];
		break;
	}

	return max_len;
}

#ifdef CONFIG_SDIO_TX_ENABLE_AVAL_INT
#if defined(CONFIG_RTL8188F) || defined(CONFIG_RTL8188GTV) ||defined(CONFIG_RTL8188E) || defined(CONFIG_RTL8821A) || defined(CONFIG_RTL8192F)|| defined(CONFIG_RTL8723D)
void rtw_hal_sdio_avail_page_threshold_init(_adapter *adapter)
{
	HAL_DATA_TYPE *hal_data = GET_HAL_DATA(adapter);

	hal_data->sdio_avail_int_en_q = 0xFF;
	rtw_write32(adapter, REG_TQPNT1, 0xFFFFFFFF);
	rtw_write32(adapter, REG_TQPNT2, 0xFFFFFFFF);
	#ifdef CONFIG_RTL8192F
	rtw_write32(adapter, REG_TQPNT3_V1_8192F, 0xFFFFFFFF);
	#endif
}

void rtw_hal_sdio_avail_page_threshold_en(_adapter *adapter, u8 qidx, u8 page)
{
	HAL_DATA_TYPE *hal_data = GET_HAL_DATA(adapter);
	struct dvobj_priv *dvobj = adapter_to_dvobj(adapter);

	if (hal_data->sdio_avail_int_en_q != qidx) {
		u32 page_size;
		u32 tx_max_len;
		u32 threshold_reg[] = {REG_TQPNT1, REG_TQPNT1 + 2, REG_TQPNT2, REG_TQPNT2 + 2}; /* H, M, L, E */
		u8 dw_shift[] = {0, 16, 0, 16}; /* H, M, L, E */
		u32 threshold = 0;

		/* use same low-high threshold as page num from tx_max_len */
		if(dvobj->tx_aval_int_thr_mode == 0) /*default setting by requirement*/
			threshold = page;
		else if (dvobj->tx_aval_int_thr_mode == 1 && dvobj->tx_aval_int_thr_value)
			threshold = dvobj->tx_aval_int_thr_value;
		else {
			rtw_hal_get_def_var(adapter, HAL_DEF_TX_PAGE_SIZE, &page_size);
			tx_max_len = hal_data->sdio_tx_max_len[qidx];
			threshold = PageNum(tx_max_len, page_size);
		}
		threshold |= (threshold) << 8;

		if (hal_data->sdio_avail_int_en_q == 0xFF)
			rtw_write16(adapter, threshold_reg[qidx], threshold);
		else if (hal_data->sdio_avail_int_en_q >> 1 == qidx >> 1) {/* same dword */
			rtw_write16(adapter, threshold_reg[hal_data->sdio_avail_int_en_q], 0);
			rtw_write32(adapter, threshold_reg[qidx & 0xFE]
				, (0xFFFF << dw_shift[hal_data->sdio_avail_int_en_q]) | (threshold << dw_shift[qidx]));
		} else {
			rtw_write16(adapter, threshold_reg[hal_data->sdio_avail_int_en_q], 0);
			rtw_write16(adapter, threshold_reg[hal_data->sdio_avail_int_en_q], 0xFFFF);
			rtw_write16(adapter, threshold_reg[qidx], threshold);
		}

		hal_data->sdio_avail_int_en_q = qidx;

		#ifdef DBG_TX_FREE_PAGE
		RTW_INFO("DWQP enable avail page threshold %s:%u-%u\n", sdio_tx_queue_str(qidx)
			, threshold & 0xFF, threshold >> 8);
		#endif
	}
}
#endif
#endif /* CONFIG_SDIO_TX_ENABLE_AVAL_INT */

#ifdef CONFIG_FW_C2H_REG
void sd_c2h_hisr_hdl(_adapter *adapter)
{
	u8 c2h_evt[C2H_REG_LEN] = {0};
	u8 id, seq, plen;
	u8 *payload;

	if (rtw_hal_c2h_evt_read(adapter, c2h_evt) != _SUCCESS)
		goto exit;

	if (rtw_hal_c2h_reg_hdr_parse(adapter, c2h_evt, &id, &seq, &plen, &payload) != _SUCCESS)
		goto exit;
		
	if (rtw_hal_c2h_id_handle_directly(adapter, id, seq, plen, payload)) {
		/* Handle directly */
		rtw_hal_c2h_handler(adapter, id, seq, plen, payload);
		goto exit;
	}

	if (rtw_c2h_reg_wk_cmd(adapter, c2h_evt) != _SUCCESS)
		RTW_ERR("%s rtw_c2h_reg_wk_cmd fail\n", __func__);

exit:
	return;
}
#endif

#ifdef CONFIG_SDIO_CHK_HCI_RESUME

#ifndef SDIO_HCI_RESUME_PWR_RDY_TIMEOUT_MS
	#define SDIO_HCI_RESUME_PWR_RDY_TIMEOUT_MS 200
#endif
#ifndef DBG_SDIO_CHK_HCI_RESUME
	#define DBG_SDIO_CHK_HCI_RESUME 0
#endif

bool sdio_chk_hci_resume(struct intf_hdl *pintfhdl)
{
	_adapter *adapter = pintfhdl->padapter;
	u8 hci_sus_state;
	u8 sus_ctl, sus_ctl_ori = 0xEA;
	u8 do_leave = 0;
	systime start = 0, end = 0;
	u32 poll_cnt = 0;
	u8 timeout = 0;
	u8 sr = 0;
	s32 err = 0;

	rtw_hal_get_hwreg(adapter, HW_VAR_HCI_SUS_STATE, &hci_sus_state);
	if (hci_sus_state == HCI_SUS_LEAVE || hci_sus_state == HCI_SUS_ERR)
		goto no_hdl;

	err = sd_cmd52_read(pintfhdl, SDIO_LOCAL_CMD_ADDR(SDIO_REG_HSUS_CTRL), 1, &sus_ctl);
	if (err)
		goto exit;
	sus_ctl_ori = sus_ctl;

	if ((sus_ctl & HCI_RESUME_PWR_RDY) && !(sus_ctl & HCI_SUS_CTRL))
		goto exit;

	if (sus_ctl & HCI_SUS_CTRL) {
		sus_ctl &= ~(HCI_SUS_CTRL);
		err = sd_cmd52_write(pintfhdl, SDIO_LOCAL_CMD_ADDR(SDIO_REG_HSUS_CTRL), 1, &sus_ctl);
		if (err)
			goto exit;
	}

	do_leave = 1;

	/* polling for HCI_RESUME_PWR_RDY && !HCI_SUS_CTRL */
	start = rtw_get_current_time();
	while (1) {
		if (rtw_is_surprise_removed(adapter)) {
			sr = 1;
			break;
		}

		err = sd_cmd52_read(pintfhdl, SDIO_LOCAL_CMD_ADDR(SDIO_REG_HSUS_CTRL), 1, &sus_ctl);
		poll_cnt++;

		if (!err && (sus_ctl & HCI_RESUME_PWR_RDY) && !(sus_ctl & HCI_SUS_CTRL))
			break;

		if (rtw_get_passing_time_ms(start) > SDIO_HCI_RESUME_PWR_RDY_TIMEOUT_MS) {
			timeout = 1;
			break;
		}
	}
	end = rtw_get_current_time();

exit:

	if (DBG_SDIO_CHK_HCI_RESUME) {
		RTW_INFO(FUNC_ADPT_FMT" hci_sus_state:%u, sus_ctl:0x%02x(0x%02x), do_leave:%u, to:%u, err:%u\n"
			, FUNC_ADPT_ARG(adapter), hci_sus_state, sus_ctl, sus_ctl_ori, do_leave, timeout, err);
		if (start != 0 || end != 0) {
			RTW_INFO(FUNC_ADPT_FMT" polling %d ms, cnt:%u\n"
				, FUNC_ADPT_ARG(adapter), rtw_get_time_interval_ms(start, end), poll_cnt);
		}
	}

	if (timeout) {
		RTW_ERR(FUNC_ADPT_FMT" timeout(err:%d) sus_ctl:0x%02x\n"
			, FUNC_ADPT_ARG(adapter), err, sus_ctl);
	} else if (err) {
		RTW_ERR(FUNC_ADPT_FMT" err:%d\n"
			, FUNC_ADPT_ARG(adapter), err);
	}

no_hdl:
	return do_leave ? _TRUE : _FALSE;
}

void sdio_chk_hci_suspend(struct intf_hdl *pintfhdl)
{
#define SDIO_CHK_HCI_SUSPEND_POLLING 0

	_adapter *adapter = pintfhdl->padapter;
	u8 hci_sus_state;
	u8 sus_ctl, sus_ctl_ori = 0xEA;
	systime start = 0, end = 0;
	u32 poll_cnt = 0;
	u8 timeout = 0;
	u8 sr = 0;
	s32 err = 0;
	u8 device_id;
	u16 offset;

	rtw_hal_get_hwreg(adapter, HW_VAR_HCI_SUS_STATE, &hci_sus_state);
	if (hci_sus_state == HCI_SUS_LEAVE || hci_sus_state == HCI_SUS_LEAVING || hci_sus_state == HCI_SUS_ERR)
		goto no_hdl;

	err = sd_cmd52_read(pintfhdl, SDIO_LOCAL_CMD_ADDR(SDIO_REG_HSUS_CTRL), 1, &sus_ctl);
	if (err)
		goto exit;
	sus_ctl_ori = sus_ctl;

	if (!(sus_ctl & HCI_RESUME_PWR_RDY))
		goto exit;

	sus_ctl |= HCI_SUS_CTRL;
	err = sd_cmd52_write(pintfhdl, SDIO_LOCAL_CMD_ADDR(SDIO_REG_HSUS_CTRL), 1, &sus_ctl);
	if (err)
		goto exit;

#if SDIO_CHK_HCI_SUSPEND_POLLING
	/* polling for HCI_RESUME_PWR_RDY cleared */
	start = rtw_get_current_time();
	while (1) {
		if (rtw_is_surprise_removed(adapter)) {
			sr = 1;
			break;
		}

		err = sd_cmd52_read(pintfhdl, SDIO_LOCAL_CMD_ADDR(SDIO_REG_HSUS_CTRL), 1, &sus_ctl);
		poll_cnt++;

		if (!err && !(sus_ctl & HCI_RESUME_PWR_RDY))
			break;

		if (rtw_get_passing_time_ms(start) > SDIO_HCI_RESUME_PWR_RDY_TIMEOUT_MS) {
			timeout = 1;
			break;
		}
	}
	end = rtw_get_current_time();
#endif /* SDIO_CHK_HCI_SUSPEND_POLLING */

exit:

	if (DBG_SDIO_CHK_HCI_RESUME) {
		RTW_INFO(FUNC_ADPT_FMT" hci_sus_state:%u, sus_ctl:0x%02x(0x%02x), to:%u, err:%u\n"
			, FUNC_ADPT_ARG(adapter), hci_sus_state, sus_ctl, sus_ctl_ori, timeout, err);
		if (start != 0 || end != 0) {
			RTW_INFO(FUNC_ADPT_FMT" polling %d ms, cnt:%u\n"
				, FUNC_ADPT_ARG(adapter), rtw_get_time_interval_ms(start, end), poll_cnt);
		}
	}

#if SDIO_CHK_HCI_SUSPEND_POLLING
	if (timeout) {
		RTW_ERR(FUNC_ADPT_FMT" timeout(err:%d) sus_ctl:0x%02x\n"
			, FUNC_ADPT_ARG(adapter), err, sus_ctl);
	} else
#endif
		if (err) {
			RTW_ERR(FUNC_ADPT_FMT" err:%d\n"
				, FUNC_ADPT_ARG(adapter), err);
		}

no_hdl:
	return;
}
#endif /* CONFIG_SDIO_CHK_HCI_RESUME */


#ifdef CONFIG_SDIO_INDIRECT_ACCESS

/* program indirect access register in sdio local to read/write page0 registers */
#ifndef INDIRECT_ACCESS_TIMEOUT_MS
	#define INDIRECT_ACCESS_TIMEOUT_MS 200
#endif
#ifndef DBG_SDIO_INDIRECT_ACCESS
	#define DBG_SDIO_INDIRECT_ACCESS 0
#endif

s32 sdio_iread(PADAPTER padapter, u32 addr, u8 size, u8 *v)
{
	struct intf_hdl *pintfhdl = &padapter->iopriv.intf;
	_mutex *mutex = &adapter_to_dvobj(padapter)->sd_indirect_access_mutex;

	u8 val[4] = {0};
	u8 cmd[4] = {0}; /* mapping to indirect access register, little endien */
	systime start = 0, end = 0;
	u8 timeout = 0;
	u8 sr = 0;
	s32 err = 0;

	if (size == 1)
		SET_INDIRECT_REG_SIZE_1BYTE(cmd);
	else if (size == 2)
		SET_INDIRECT_REG_SIZE_2BYTE(cmd);
	else if (size == 4)
		SET_INDIRECT_REG_SIZE_4BYTE(cmd);

	SET_INDIRECT_REG_ADDR(cmd, addr);

	/* acquire indirect access lock */
	_enter_critical_mutex(mutex, NULL);

	if (DBG_SDIO_INDIRECT_ACCESS)
		RTW_INFO(FUNC_ADPT_FMT" cmd:%02x %02x %02x %02x\n", FUNC_ADPT_ARG(padapter), cmd[0], cmd[1], cmd[2], cmd[3]);

	err = sd_cmd52_write(pintfhdl, SDIO_LOCAL_CMD_ADDR(SDIO_REG_INDIRECT_REG_CFG), 3, cmd);
	if (err)
		goto exit;

	/* trigger */
	SET_INDIRECT_REG_READ(cmd);

	if (DBG_SDIO_INDIRECT_ACCESS)
		RTW_INFO(FUNC_ADPT_FMT" cmd:%02x %02x %02x %02x\n", FUNC_ADPT_ARG(padapter), cmd[0], cmd[1], cmd[2], cmd[3]);

	err = sd_cmd52_write(pintfhdl, SDIO_LOCAL_CMD_ADDR(SDIO_REG_INDIRECT_REG_CFG + 2), 1, cmd + 2);
	if (err)
		goto exit;

	/* polling for indirect access done */
	start = rtw_get_current_time();
	while (1) {
		if (rtw_is_surprise_removed(padapter)) {
			sr = 1;
			break;
		}

		err = sd_cmd52_read(pintfhdl, SDIO_LOCAL_CMD_ADDR(SDIO_REG_INDIRECT_REG_CFG + 2), 1, cmd + 2);

		if (!err && GET_INDIRECT_REG_RDY(cmd))
			break;

		if (rtw_get_passing_time_ms(start) > INDIRECT_ACCESS_TIMEOUT_MS) {
			timeout = 1;
			break;
		}
	}
	end = rtw_get_current_time();

	if (timeout || sr)
		goto exit;

	/* read result */
	err = sd_cmd52_read(pintfhdl, SDIO_LOCAL_CMD_ADDR(SDIO_REG_INDIRECT_REG_DATA), size, val);
	if (size == 2)
		*((u16 *)(val)) = le16_to_cpu(*((u16 *)(val)));
	else if (size == 4)
		*((u32 *)(val)) = le32_to_cpu(*((u32 *)(val)));

	if (DBG_SDIO_INDIRECT_ACCESS) {
		if (size == 1)
			RTW_INFO(FUNC_ADPT_FMT" val:0x%02x\n", FUNC_ADPT_ARG(padapter), *((u8 *)(val)));
		else if (size == 2)
			RTW_INFO(FUNC_ADPT_FMT" val:0x%04x\n", FUNC_ADPT_ARG(padapter), *((u16 *)(val)));
		else if (size == 4)
			RTW_INFO(FUNC_ADPT_FMT" val:0x%08x\n", FUNC_ADPT_ARG(padapter), *((u32 *)(val)));
	}

exit:
	/* release indirect access lock */
	_exit_critical_mutex(mutex, NULL);

	if (DBG_SDIO_INDIRECT_ACCESS) {
		RTW_INFO(FUNC_ADPT_FMT" addr:0x%0x size:%u, cmd:%02x %02x %02x %02x, to:%u, err:%u\n"
			, FUNC_ADPT_ARG(padapter), addr, size, cmd[0], cmd[1], cmd[2], cmd[3], timeout, err);
		if (start != 0 || end != 0) {
			RTW_INFO(FUNC_ADPT_FMT" polling %d ms\n"
				, FUNC_ADPT_ARG(padapter), rtw_get_time_interval_ms(start, end));
		}
	}

	if (timeout) {
		RTW_ERR(FUNC_ADPT_FMT" addr:0x%0x timeout(err:%d), cmd\n"
			, FUNC_ADPT_ARG(padapter), addr, err);
		if (!err)
			err = -1; /* just for return value */
	} else if (err) {
		RTW_ERR(FUNC_ADPT_FMT" addr:0x%0x err:%d\n"
			, FUNC_ADPT_ARG(padapter), addr, err);
	} else if (sr) {
		/* just for return value */
		err = -1;
	}

	if (!err && !timeout && !sr)
		_rtw_memcpy(v, val, size);

	return err;
}

s32 sdio_iwrite(PADAPTER padapter, u32 addr, u8 size, u8 *v)
{
	struct intf_hdl *pintfhdl = &padapter->iopriv.intf;
	_mutex *mutex = &adapter_to_dvobj(padapter)->sd_indirect_access_mutex;

	u8 val[4] = {0};
	u8 cmd[4] = {0}; /* mapping to indirect access register, little endien */
	systime start = 0, end = 0;
	u8 timeout = 0;
	u8 sr = 0;
	s32 err = 0;

	if (size == 1)
		SET_INDIRECT_REG_SIZE_1BYTE(cmd);
	else if (size == 2)
		SET_INDIRECT_REG_SIZE_2BYTE(cmd);
	else if (size == 4)
		SET_INDIRECT_REG_SIZE_4BYTE(cmd);

	SET_INDIRECT_REG_ADDR(cmd, addr);

	/* acquire indirect access lock */
	_enter_critical_mutex(mutex, NULL);

	if (DBG_SDIO_INDIRECT_ACCESS)
		RTW_INFO(FUNC_ADPT_FMT" cmd:%02x %02x %02x %02x\n", FUNC_ADPT_ARG(padapter), cmd[0], cmd[1], cmd[2], cmd[3]);

	err = sd_cmd52_write(pintfhdl, SDIO_LOCAL_CMD_ADDR(SDIO_REG_INDIRECT_REG_CFG), 3, cmd);
	if (err)
		goto exit;

	/* data to write */
	_rtw_memcpy(val, v, size);

	if (DBG_SDIO_INDIRECT_ACCESS) {
		if (size == 1)
			RTW_INFO(FUNC_ADPT_FMT" val:0x%02x\n", FUNC_ADPT_ARG(padapter), *((u8 *)(val)));
		else if (size == 2)
			RTW_INFO(FUNC_ADPT_FMT" val:0x%04x\n", FUNC_ADPT_ARG(padapter), *((u16 *)(val)));
		else if (size == 4)
			RTW_INFO(FUNC_ADPT_FMT" val:0x%08x\n", FUNC_ADPT_ARG(padapter), *((u32 *)(val)));
	}

	if (size == 2)
		*((u16 *)(val)) = cpu_to_le16(*((u16 *)(val)));
	else if (size == 4)
		*((u32 *)(val)) = cpu_to_le32(*((u32 *)(val)));

	err = sd_cmd52_write(pintfhdl, SDIO_LOCAL_CMD_ADDR(SDIO_REG_INDIRECT_REG_DATA), size, val);
	if (err)
		goto exit;

	/* trigger */
	SET_INDIRECT_REG_WRITE(cmd);

	if (DBG_SDIO_INDIRECT_ACCESS)
		RTW_INFO(FUNC_ADPT_FMT" cmd:%02x %02x %02x %02x\n", FUNC_ADPT_ARG(padapter), cmd[0], cmd[1], cmd[2], cmd[3]);

	err = sd_cmd52_write(pintfhdl, SDIO_LOCAL_CMD_ADDR(SDIO_REG_INDIRECT_REG_CFG + 2), 1, cmd + 2);
	if (err)
		goto exit;

	/* polling for indirect access done */
	start = rtw_get_current_time();
	while (1) {
		if (rtw_is_surprise_removed(padapter)) {
			sr = 1;
			break;
		}

		err = sd_cmd52_read(pintfhdl, SDIO_LOCAL_CMD_ADDR(SDIO_REG_INDIRECT_REG_CFG + 2), 1, cmd + 2);

		if (!err && GET_INDIRECT_REG_RDY(cmd))
			break;

		if (rtw_get_passing_time_ms(start) > INDIRECT_ACCESS_TIMEOUT_MS) {
			timeout = 1;
			break;
		}
	}
	end = rtw_get_current_time();

	if (timeout || sr)
		goto exit;

exit:
	/* release indirect access lock */
	_exit_critical_mutex(mutex, NULL);

	if (DBG_SDIO_INDIRECT_ACCESS) {
		RTW_INFO(FUNC_ADPT_FMT" addr:0x%0x size:%u, cmd:%02x %02x %02x %02x, to:%u, err:%u\n"
			, FUNC_ADPT_ARG(padapter), addr, size, cmd[0], cmd[1], cmd[2], cmd[3], timeout, err);
		if (start != 0 || end != 0) {
			RTW_INFO(FUNC_ADPT_FMT" polling %d ms\n"
				, FUNC_ADPT_ARG(padapter), rtw_get_time_interval_ms(start, end));
		}
	}

	if (timeout) {
		RTW_ERR(FUNC_ADPT_FMT" addr:0x%0x timeout(err:%d), cmd\n"
			, FUNC_ADPT_ARG(padapter), addr, err);
		if (!err)
			err = -1; /* just for return value */
	} else if (err) {
		RTW_ERR(FUNC_ADPT_FMT" addr:0x%0x err:%d\n"
			, FUNC_ADPT_ARG(padapter), addr, err);
	} else if (sr) {
		/* just for return value */
		err = -1;
	}

	return err;
}

u8 sdio_iread8(struct intf_hdl *pintfhdl, u32 addr)
{
	u8 val;

	if (sdio_iread(pintfhdl->padapter, addr, 1, (u8 *)&val) != 0)
		val = SDIO_ERR_VAL8;

	return val;
}

u16 sdio_iread16(struct intf_hdl *pintfhdl, u32 addr)
{
	u16 val;

	if (sdio_iread(pintfhdl->padapter, addr, 2, (u8 *)&val) != 0)
		val = SDIO_ERR_VAL16;

	return val;
}

u32 sdio_iread32(struct intf_hdl *pintfhdl, u32 addr)
{
	u32 val;

	if (sdio_iread(pintfhdl->padapter, addr, 4, (u8 *)&val) != 0)
		val = SDIO_ERR_VAL32;

	return val;
}

s32 sdio_iwrite8(struct intf_hdl *pintfhdl, u32 addr, u8 val)
{
	return sdio_iwrite(pintfhdl->padapter, addr, 1, (u8 *)&val);
}

s32 sdio_iwrite16(struct intf_hdl *pintfhdl, u32 addr, u16 val)
{
	return sdio_iwrite(pintfhdl->padapter, addr, 2, (u8 *)&val);
}

s32 sdio_iwrite32(struct intf_hdl *pintfhdl, u32 addr, u32 val)
{
	return sdio_iwrite(pintfhdl->padapter, addr, 4, (u8 *)&val);
}
#endif /* CONFIG_SDIO_INDIRECT_ACCESS */
u32 cmd53_4byte_alignment(struct intf_hdl *pintfhdl, u32 addr)
{
	u32 addr_rdr;
	u32 value;

	value = 0;
	addr_rdr = addr % 4;

	if (addr_rdr) {
		u8 shift_bit;

		shift_bit = addr_rdr * 8;
		value = (sd_read32(pintfhdl, (addr - addr_rdr), NULL)) >> shift_bit;
	} else
		value = sd_read32(pintfhdl, addr, NULL);
	
	return value;
}

#endif /* !RTW_HALMAC */

#ifndef CONFIG_SDIO_TX_TASKLET
#ifdef SDIO_FREE_XMIT_BUF_SEMA
void _rtw_sdio_free_xmitbuf_sema_up(struct xmit_priv *xmit)
{
	_rtw_up_sema(&xmit->sdio_free_xmitbuf_sema);
}

void _rtw_sdio_free_xmitbuf_sema_down(struct xmit_priv *xmit)
{
	_rtw_down_sema(&xmit->sdio_free_xmitbuf_sema);
}

#ifdef DBG_SDIO_FREE_XMIT_BUF_SEMA
void dbg_rtw_sdio_free_xmitbuf_sema_up(struct xmit_priv *xmit, const char *caller)
{
	/* just for debug usage only, pleae take care for the different of count implementaton */
	RTW_INFO("%s("ADPT_FMT") before up sdio_free_xmitbuf_sema, count:%u\n"
		, caller, ADPT_ARG(xmit->adapter), xmit->sdio_free_xmitbuf_sema.count);
	_rtw_sdio_free_xmitbuf_sema_up(xmit);
}

void dbg_rtw_sdio_free_xmitbuf_sema_down(struct xmit_priv *xmit, const char *caller)
{
	/* just for debug usage only, pleae take care for the different of count implementaton */
	RTW_INFO("%s("ADPT_FMT") before down sdio_free_xmitbuf_sema, count:%u\n"
		, caller, ADPT_ARG(xmit->adapter), xmit->sdio_free_xmitbuf_sema.count);
	_rtw_sdio_free_xmitbuf_sema_down(xmit);
}
#endif /* DBG_SDIO_FREE_XMIT_BUF_SEMA */

#endif /* SDIO_FREE_XMIT_BUF_SEMA */
#endif /* !CONFIG_SDIO_TX_TASKLET */

s32 sdio_initrecvbuf(struct recv_buf *recvbuf, _adapter *adapter)
{
	_rtw_init_listhead(&recvbuf->list);
#ifdef PLATFORM_WINDOWS
	_rtw_spinlock_init(&recvbuf->recvbuf_lock);
#endif
	recvbuf->adapter = adapter;

	return _SUCCESS;
}

void sdio_freerecvbuf(struct recv_buf *recvbuf)
{
#ifdef PLATFORM_WINDOWS
	_rtw_spinlock_free(&recvbuf->recvbuf_lock);
#endif
}

#ifdef CONFIG_SDIO_RECVBUF_PWAIT
void dump_recvbuf_pwait_conf(void *sel, struct recv_priv *recvpriv)
{
	struct rtw_pwait_conf *conf = &recvpriv->recvbuf_pwait.conf;

	RTW_PRINT_SEL(sel, "%-4s %-10s %-10s\n"
		, "type", "time", "cnt_lmt");
	RTW_PRINT_SEL(sel, "%4s %10d %10d\n"
		, rtw_pwait_type_str(conf->type), conf->wait_time, conf->wait_cnt_lmt);
}

#ifdef CONFIG_SDIO_RECVBUF_PWAIT_RUNTIME_ADJUST
int recvbuf_pwait_config_req(struct recv_priv *recvpriv, enum rtw_pwait_type type, s32 time, s32 cnt_lmt)
{
	struct recv_buf *rbuf;
	struct rtw_pwait_conf *conf;
	int ret = _FAIL;

	rbuf = rtw_malloc(sizeof(*rbuf) + sizeof(struct rtw_pwait_conf));
	if (!rbuf)
		goto exit;

	sdio_initrecvbuf(rbuf, recvpriv->adapter);
	rbuf->type = RBUF_TYPE_PWAIT_ADJ;
	rbuf->pbuf = ((u8*)rbuf) + sizeof(*rbuf);
	conf = (struct rtw_pwait_conf *)rbuf->pbuf;
	conf->type = type;
	conf->wait_time = time;
	conf->wait_cnt_lmt = cnt_lmt;

	ret = rtw_enqueue_recvbuf(rbuf, &recvpriv->free_recv_buf_queue);
	if (0 && ret == _SUCCESS) {
		RTW_INFO("request recvbuf_pwait with type=%s time=%d cnt_lmt=%d\n"
			, rtw_pwait_type_str(type), time, cnt_lmt);
	}

exit:
	return ret;
}

int recvbuf_pwait_config_hdl(struct recv_priv *recvpriv, struct recv_buf *rbuf)
{
	struct rtw_pwait_conf *conf = (struct rtw_pwait_conf *)rbuf->pbuf;
	int ret = rtw_pwctx_config(&recvpriv->recvbuf_pwait, conf->type, conf->wait_time, conf->wait_cnt_lmt);

	if (0 && ret == _SUCCESS) {
		RTW_INFO("config recvbuf_pwait with type=%s time=%d cnt_lmt=%d\n"
			, rtw_pwait_type_str(conf->type), conf->wait_time, conf->wait_cnt_lmt);
	}
	sdio_freerecvbuf(rbuf);
	rtw_mfree(rbuf, sizeof(*rbuf) + sizeof(*conf));

	return ret;
}
#endif /* CONFIG_SDIO_RECVBUF_PWAIT_RUNTIME_ADJUST */
#endif /* CONFIG_SDIO_RECVBUF_PWAIT */

