root/drivers/staging/rtl8723bs/hal/sdio_ops.c
// SPDX-License-Identifier: GPL-2.0
/******************************************************************************
 *
 * Copyright(c) 2007 - 2012 Realtek Corporation. All rights reserved.
 *
 *******************************************************************************/
#include <drv_types.h>
#include <rtl8723b_hal.h>

/*  */
/*  Description: */
/*      The following mapping is for SDIO host local register space. */
/*  */
/*  Creadted by Roger, 2011.01.31. */
/*  */
static void hal_sdio_get_cmd_addr_8723b(
        struct adapter *adapter,
        u8 device_id,
        u32 addr,
        u32 *cmdaddr
)
{
        switch (device_id) {
        case SDIO_LOCAL_DEVICE_ID:
                *cmdaddr = ((SDIO_LOCAL_DEVICE_ID << 13) | (addr & SDIO_LOCAL_MSK));
                break;

        case WLAN_IOREG_DEVICE_ID:
                *cmdaddr = ((WLAN_IOREG_DEVICE_ID << 13) | (addr & WLAN_IOREG_MSK));
                break;

        case WLAN_TX_HIQ_DEVICE_ID:
                *cmdaddr = ((WLAN_TX_HIQ_DEVICE_ID << 13) | (addr & WLAN_FIFO_MSK));
                break;

        case WLAN_TX_MIQ_DEVICE_ID:
                *cmdaddr = ((WLAN_TX_MIQ_DEVICE_ID << 13) | (addr & WLAN_FIFO_MSK));
                break;

        case WLAN_TX_LOQ_DEVICE_ID:
                *cmdaddr = ((WLAN_TX_LOQ_DEVICE_ID << 13) | (addr & WLAN_FIFO_MSK));
                break;

        case WLAN_RX0FF_DEVICE_ID:
                *cmdaddr = ((WLAN_RX0FF_DEVICE_ID << 13) | (addr & WLAN_RX0FF_MSK));
                break;

        default:
                break;
        }
}

static u8 get_deviceid(u32 addr)
{
        u8 devide_id;
        u16 pseudo_id;

        pseudo_id = (u16)(addr >> 16);
        switch (pseudo_id) {
        case 0x1025:
                devide_id = SDIO_LOCAL_DEVICE_ID;
                break;

        case 0x1026:
                devide_id = WLAN_IOREG_DEVICE_ID;
                break;

        case 0x1031:
                devide_id = WLAN_TX_HIQ_DEVICE_ID;
                break;

        case 0x1032:
                devide_id = WLAN_TX_MIQ_DEVICE_ID;
                break;

        case 0x1033:
                devide_id = WLAN_TX_LOQ_DEVICE_ID;
                break;

        case 0x1034:
                devide_id = WLAN_RX0FF_DEVICE_ID;
                break;

        default:
                devide_id = WLAN_IOREG_DEVICE_ID;
                break;
        }

        return devide_id;
}

static u32 _cvrt2ftaddr(const u32 addr, u8 *pdevice_id, u16 *poffset)
{
        u8 device_id;
        u16 offset;
        u32 ftaddr;

        device_id = get_deviceid(addr);
        offset = 0;

        switch (device_id) {
        case SDIO_LOCAL_DEVICE_ID:
                offset = addr & SDIO_LOCAL_MSK;
                break;

        case WLAN_TX_HIQ_DEVICE_ID:
        case WLAN_TX_MIQ_DEVICE_ID:
        case WLAN_TX_LOQ_DEVICE_ID:
                offset = addr & WLAN_FIFO_MSK;
                break;

        case WLAN_RX0FF_DEVICE_ID:
                offset = addr & WLAN_RX0FF_MSK;
                break;

        case WLAN_IOREG_DEVICE_ID:
        default:
                device_id = WLAN_IOREG_DEVICE_ID;
                offset = addr & WLAN_IOREG_MSK;
                break;
        }
        ftaddr = (device_id << 13) | offset;

        if (pdevice_id)
                *pdevice_id = device_id;
        if (poffset)
                *poffset = offset;

        return ftaddr;
}

static u8 sdio_read8(struct intf_hdl *intfhdl, u32 addr)
{
        u32 ftaddr;

        ftaddr = _cvrt2ftaddr(addr, NULL, NULL);

        return sd_read8(intfhdl, ftaddr, NULL);
}

static u16 sdio_read16(struct intf_hdl *intfhdl, u32 addr)
{
        u32 ftaddr;
        __le16 le_tmp;

        ftaddr = _cvrt2ftaddr(addr, NULL, NULL);
        sd_cmd52_read(intfhdl, ftaddr, 2, (u8 *)&le_tmp);

        return le16_to_cpu(le_tmp);
}

static u32 sdio_read32(struct intf_hdl *intfhdl, u32 addr)
{
        struct adapter *adapter;
        u8 mac_pwr_ctrl_on;
        u8 device_id;
        u16 offset;
        u32 ftaddr;
        u8 shift;
        u32 val;
        s32 __maybe_unused err;
        __le32 le_tmp;

        adapter = intfhdl->padapter;
        ftaddr = _cvrt2ftaddr(addr, &device_id, &offset);

        rtw_hal_get_hwreg(adapter, HW_VAR_APFM_ON_MAC, &mac_pwr_ctrl_on);
        if (
                ((device_id == WLAN_IOREG_DEVICE_ID) && (offset < 0x100)) ||
                (!mac_pwr_ctrl_on) ||
                (adapter_to_pwrctl(adapter)->fw_current_in_ps_mode)
        ) {
                err = sd_cmd52_read(intfhdl, ftaddr, 4, (u8 *)&le_tmp);
                return le32_to_cpu(le_tmp);
        }

        /*  4 bytes alignment */
        shift = ftaddr & 0x3;
        if (shift == 0) {
                val = sd_read32(intfhdl, ftaddr, NULL);
        } else {
                u8 *tmpbuf;

                tmpbuf = kmalloc(8, GFP_ATOMIC);
                if (!tmpbuf)
                        return SDIO_ERR_VAL32;

                ftaddr &= ~(u16)0x3;
                sd_read(intfhdl, ftaddr, 8, tmpbuf);
                memcpy(&le_tmp, tmpbuf + shift, 4);
                val = le32_to_cpu(le_tmp);

                kfree(tmpbuf);
        }
        return val;
}

static s32 sdio_readN(struct intf_hdl *intfhdl, u32 addr, u32 cnt, u8 *buf)
{
        struct adapter *adapter;
        u8 mac_pwr_ctrl_on;
        u8 device_id;
        u16 offset;
        u32 ftaddr;
        u8 shift;
        s32 err;

        adapter = intfhdl->padapter;
        err = 0;

        ftaddr = _cvrt2ftaddr(addr, &device_id, &offset);

        rtw_hal_get_hwreg(adapter, HW_VAR_APFM_ON_MAC, &mac_pwr_ctrl_on);
        if (
                ((device_id == WLAN_IOREG_DEVICE_ID) && (offset < 0x100)) ||
                (!mac_pwr_ctrl_on) ||
                (adapter_to_pwrctl(adapter)->fw_current_in_ps_mode)
        )
                return sd_cmd52_read(intfhdl, ftaddr, cnt, buf);

        /*  4 bytes alignment */
        shift = ftaddr & 0x3;
        if (shift == 0) {
                err = sd_read(intfhdl, ftaddr, cnt, buf);
        } else {
                u8 *tmpbuf;
                u32 n;

                ftaddr &= ~(u16)0x3;
                n = cnt + shift;
                tmpbuf = kmalloc(n, GFP_ATOMIC);
                if (!tmpbuf)
                        return -ENOMEM;

                err = sd_read(intfhdl, ftaddr, n, tmpbuf);
                if (!err)
                        memcpy(buf, tmpbuf + shift, cnt);
                kfree(tmpbuf);
        }
        return err;
}

static s32 sdio_write8(struct intf_hdl *intfhdl, u32 addr, u8 val)
{
        u32 ftaddr;
        s32 err;

        ftaddr = _cvrt2ftaddr(addr, NULL, NULL);
        sd_write8(intfhdl, ftaddr, val, &err);

        return err;
}

static s32 sdio_write16(struct intf_hdl *intfhdl, u32 addr, u16 val)
{
        u32 ftaddr;
        __le16 le_tmp;

        ftaddr = _cvrt2ftaddr(addr, NULL, NULL);
        le_tmp = cpu_to_le16(val);
        return sd_cmd52_write(intfhdl, ftaddr, 2, (u8 *)&le_tmp);
}

static s32 sdio_write32(struct intf_hdl *intfhdl, u32 addr, u32 val)
{
        struct adapter *adapter;
        u8 mac_pwr_ctrl_on;
        u8 device_id;
        u16 offset;
        u32 ftaddr;
        u8 shift;
        s32 err;
        __le32 le_tmp;

        adapter = intfhdl->padapter;
        err = 0;

        ftaddr = _cvrt2ftaddr(addr, &device_id, &offset);

        rtw_hal_get_hwreg(adapter, HW_VAR_APFM_ON_MAC, &mac_pwr_ctrl_on);
        if (
                ((device_id == WLAN_IOREG_DEVICE_ID) && (offset < 0x100)) ||
                (!mac_pwr_ctrl_on) ||
                (adapter_to_pwrctl(adapter)->fw_current_in_ps_mode)
        ) {
                le_tmp = cpu_to_le32(val);

                return sd_cmd52_write(intfhdl, ftaddr, 4, (u8 *)&le_tmp);
        }

        /*  4 bytes alignment */
        shift = ftaddr & 0x3;
        if (shift == 0) {
                sd_write32(intfhdl, ftaddr, val, &err);
        } else {
                le_tmp = cpu_to_le32(val);
                err = sd_cmd52_write(intfhdl, ftaddr, 4, (u8 *)&le_tmp);
        }
        return err;
}

static s32 sdio_writeN(struct intf_hdl *intfhdl, u32 addr, u32 cnt, u8 *buf)
{
        struct adapter *adapter;
        u8 mac_pwr_ctrl_on;
        u8 device_id;
        u16 offset;
        u32 ftaddr;
        u8 shift;
        s32 err;

        adapter = intfhdl->padapter;
        err = 0;

        ftaddr = _cvrt2ftaddr(addr, &device_id, &offset);

        rtw_hal_get_hwreg(adapter, HW_VAR_APFM_ON_MAC, &mac_pwr_ctrl_on);
        if (
                ((device_id == WLAN_IOREG_DEVICE_ID) && (offset < 0x100)) ||
                (!mac_pwr_ctrl_on) ||
                (adapter_to_pwrctl(adapter)->fw_current_in_ps_mode)
        )
                return sd_cmd52_write(intfhdl, ftaddr, cnt, buf);

        shift = ftaddr & 0x3;
        if (shift == 0) {
                err = sd_write(intfhdl, ftaddr, cnt, buf);
        } else {
                u8 *tmpbuf;
                u32 n;

                ftaddr &= ~(u16)0x3;
                n = cnt + shift;
                tmpbuf = kmalloc(n, GFP_ATOMIC);
                if (!tmpbuf)
                        return -ENOMEM;
                err = sd_read(intfhdl, ftaddr, 4, tmpbuf);
                if (err) {
                        kfree(tmpbuf);
                        return err;
                }
                memcpy(tmpbuf + shift, buf, cnt);
                err = sd_write(intfhdl, ftaddr, n, tmpbuf);
                kfree(tmpbuf);
        }
        return err;
}

static void sdio_read_mem(
        struct intf_hdl *intfhdl,
        u32 addr,
        u32 cnt,
        u8 *rmem
)
{
        sdio_readN(intfhdl, addr, cnt, rmem);
}

static void sdio_write_mem(
        struct intf_hdl *intfhdl,
        u32 addr,
        u32 cnt,
        u8 *wmem
)
{
        sdio_writeN(intfhdl, addr, cnt, wmem);
}

/*
 * Description:
 *Read from RX FIFO
 *Round read size to block size,
 *and make sure data transfer will be done in one command.
 *
 * Parameters:
 *intfhdl       a pointer of intf_hdl
 *addr          port ID
 *cnt                   size to read
 *rmem          address to put data
 *
 * Return:
 *_SUCCESS(1)           Success
 *_FAIL(0)              Fail
 */
static u32 sdio_read_port(
        struct intf_hdl *intfhdl,
        u32 addr,
        u32 cnt,
        u8 *mem
)
{
        struct adapter *adapter;
        struct sdio_data *psdio;
        struct hal_com_data *hal;
        s32 err;

        adapter = intfhdl->padapter;
        psdio = &adapter_to_dvobj(adapter)->intf_data;
        hal = GET_HAL_DATA(adapter);

        hal_sdio_get_cmd_addr_8723b(adapter, addr, hal->SdioRxFIFOCnt++, &addr);

        if (cnt > psdio->block_transfer_len)
                cnt = _RND(cnt, psdio->block_transfer_len);

        err = _sd_read(intfhdl, addr, cnt, mem);

        if (err)
                return _FAIL;
        return _SUCCESS;
}

/*
 * Description:
 *Write to TX FIFO
 *Align write size block size,
 *and make sure data could be written in one command.
 *
 * Parameters:
 *intfhdl       a pointer of intf_hdl
 *addr          port ID
 *cnt                   size to write
 *wmem          data pointer to write
 *
 * Return:
 *_SUCCESS(1)           Success
 *_FAIL(0)              Fail
 */
static u32 sdio_write_port(
        struct intf_hdl *intfhdl,
        u32 addr,
        u32 cnt,
        u8 *mem
)
{
        struct adapter *adapter;
        struct sdio_data *psdio;
        s32 err;
        struct xmit_buf *xmitbuf = (struct xmit_buf *)mem;

        adapter = intfhdl->padapter;
        psdio = &adapter_to_dvobj(adapter)->intf_data;

        if (!adapter->hw_init_completed)
                return _FAIL;

        cnt = round_up(cnt, 4);
        hal_sdio_get_cmd_addr_8723b(adapter, addr, cnt >> 2, &addr);

        if (cnt > psdio->block_transfer_len)
                cnt = _RND(cnt, psdio->block_transfer_len);

        err = sd_write(intfhdl, addr, cnt, xmitbuf->pdata);

        rtw_sctx_done_err(
                &xmitbuf->sctx,
                err ? RTW_SCTX_DONE_WRITE_PORT_ERR : RTW_SCTX_DONE_SUCCESS
        );

        if (err)
                return _FAIL;
        return _SUCCESS;
}

void sdio_set_intf_ops(struct adapter *adapter, struct _io_ops *ops)
{
        ops->_read8 = &sdio_read8;
        ops->_read16 = &sdio_read16;
        ops->_read32 = &sdio_read32;
        ops->_read_mem = &sdio_read_mem;
        ops->_read_port = &sdio_read_port;

        ops->_write8 = &sdio_write8;
        ops->_write16 = &sdio_write16;
        ops->_write32 = &sdio_write32;
        ops->_writeN = &sdio_writeN;
        ops->_write_mem = &sdio_write_mem;
        ops->_write_port = &sdio_write_port;
}

/*
 * Todo: align address to 4 bytes.
 */
static s32 _sdio_local_read(
        struct adapter *adapter,
        u32 addr,
        u32 cnt,
        u8 *buf
)
{
        struct intf_hdl *intfhdl;
        u8 mac_pwr_ctrl_on;
        s32 err;
        u8 *tmpbuf;
        u32 n;

        intfhdl = &adapter->iopriv.intf;

        hal_sdio_get_cmd_addr_8723b(adapter, SDIO_LOCAL_DEVICE_ID, addr, &addr);

        rtw_hal_get_hwreg(adapter, HW_VAR_APFM_ON_MAC, &mac_pwr_ctrl_on);
        if (!mac_pwr_ctrl_on)
                return _sd_cmd52_read(intfhdl, addr, cnt, buf);

        n = round_up(cnt, 4);
        tmpbuf = kmalloc(n, GFP_ATOMIC);
        if (!tmpbuf)
                return -ENOMEM;

        err = _sd_read(intfhdl, addr, n, tmpbuf);
        if (!err)
                memcpy(buf, tmpbuf, cnt);

        kfree(tmpbuf);

        return err;
}

/*
 * Todo: align address to 4 bytes.
 */
s32 sdio_local_read(
        struct adapter *adapter,
        u32 addr,
        u32 cnt,
        u8 *buf
)
{
        struct intf_hdl *intfhdl;
        u8 mac_pwr_ctrl_on;
        s32 err;
        u8 *tmpbuf;
        u32 n;

        intfhdl = &adapter->iopriv.intf;

        hal_sdio_get_cmd_addr_8723b(adapter, SDIO_LOCAL_DEVICE_ID, addr, &addr);

        rtw_hal_get_hwreg(adapter, HW_VAR_APFM_ON_MAC, &mac_pwr_ctrl_on);
        if (
                (!mac_pwr_ctrl_on) ||
                (adapter_to_pwrctl(adapter)->fw_current_in_ps_mode)
        )
                return sd_cmd52_read(intfhdl, addr, cnt, buf);

        n = round_up(cnt, 4);
        tmpbuf = kmalloc(n, GFP_ATOMIC);
        if (!tmpbuf)
                return -ENOMEM;

        err = sd_read(intfhdl, addr, n, tmpbuf);
        if (!err)
                memcpy(buf, tmpbuf, cnt);

        kfree(tmpbuf);

        return err;
}

/*
 * Todo: align address to 4 bytes.
 */
s32 sdio_local_write(
        struct adapter *adapter,
        u32 addr,
        u32 cnt,
        u8 *buf
)
{
        struct intf_hdl *intfhdl;
        u8 mac_pwr_ctrl_on;
        s32 err;
        u8 *tmpbuf;

        intfhdl = &adapter->iopriv.intf;

        hal_sdio_get_cmd_addr_8723b(adapter, SDIO_LOCAL_DEVICE_ID, addr, &addr);

        rtw_hal_get_hwreg(adapter, HW_VAR_APFM_ON_MAC, &mac_pwr_ctrl_on);
        if (
                (!mac_pwr_ctrl_on) ||
                (adapter_to_pwrctl(adapter)->fw_current_in_ps_mode)
        )
                return sd_cmd52_write(intfhdl, addr, cnt, buf);

        tmpbuf = kmalloc(cnt, GFP_ATOMIC);
        if (!tmpbuf)
                return -ENOMEM;

        memcpy(tmpbuf, buf, cnt);

        err = sd_write(intfhdl, addr, cnt, tmpbuf);

        kfree(tmpbuf);

        return err;
}

u8 SdioLocalCmd52Read1Byte(struct adapter *adapter, u32 addr)
{
        u8 val = 0;
        struct intf_hdl *intfhdl = &adapter->iopriv.intf;

        hal_sdio_get_cmd_addr_8723b(adapter, SDIO_LOCAL_DEVICE_ID, addr, &addr);
        sd_cmd52_read(intfhdl, addr, 1, &val);

        return val;
}

static u16 sdio_local_cmd52_read2byte(struct adapter *adapter, u32 addr)
{
        __le16 val = 0;
        struct intf_hdl *intfhdl = &adapter->iopriv.intf;

        hal_sdio_get_cmd_addr_8723b(adapter, SDIO_LOCAL_DEVICE_ID, addr, &addr);
        sd_cmd52_read(intfhdl, addr, 2, (u8 *)&val);

        return le16_to_cpu(val);
}

static u32 sdio_local_cmd53_read4byte(struct adapter *adapter, u32 addr)
{

        u8 mac_pwr_ctrl_on;
        u32 val = 0;
        struct intf_hdl *intfhdl = &adapter->iopriv.intf;
        __le32 le_tmp;

        hal_sdio_get_cmd_addr_8723b(adapter, SDIO_LOCAL_DEVICE_ID, addr, &addr);
        rtw_hal_get_hwreg(adapter, HW_VAR_APFM_ON_MAC, &mac_pwr_ctrl_on);
        if (!mac_pwr_ctrl_on || adapter_to_pwrctl(adapter)->fw_current_in_ps_mode) {
                sd_cmd52_read(intfhdl, addr, 4, (u8 *)&le_tmp);
                val = le32_to_cpu(le_tmp);
        } else {
                val = sd_read32(intfhdl, addr, NULL);
        }
        return val;
}

void SdioLocalCmd52Write1Byte(struct adapter *adapter, u32 addr, u8 v)
{
        struct intf_hdl *intfhdl = &adapter->iopriv.intf;

        hal_sdio_get_cmd_addr_8723b(adapter, SDIO_LOCAL_DEVICE_ID, addr, &addr);
        sd_cmd52_write(intfhdl, addr, 1, &v);
}

static void sdio_local_cmd52_write4byte(struct adapter *adapter, u32 addr, u32 v)
{
        struct intf_hdl *intfhdl = &adapter->iopriv.intf;
        __le32 le_tmp;

        hal_sdio_get_cmd_addr_8723b(adapter, SDIO_LOCAL_DEVICE_ID, addr, &addr);
        le_tmp = cpu_to_le32(v);
        sd_cmd52_write(intfhdl, addr, 4, (u8 *)&le_tmp);
}

static s32 read_interrupt_8723b_sdio(struct adapter *adapter, u32 *phisr)
{
        u32 hisr, himr;
        u8 val8, hisr_len;

        if (!phisr)
                return false;

        himr = GET_HAL_DATA(adapter)->sdio_himr;

        /*  decide how many bytes need to be read */
        hisr_len = 0;
        while (himr) {
                hisr_len++;
                himr >>= 8;
        }

        hisr = 0;
        while (hisr_len != 0) {
                hisr_len--;
                val8 = SdioLocalCmd52Read1Byte(adapter, SDIO_REG_HISR + hisr_len);
                hisr |= (val8 << (8 * hisr_len));
        }

        *phisr = hisr;

        return true;
}

/*  */
/*      Description: */
/*              Initialize SDIO Host Interrupt Mask configuration variables for future use. */
/*  */
/*      Assumption: */
/*              Using SDIO Local register ONLY for configuration. */
/*  */
/*      Created by Roger, 2011.02.11. */
/*  */
void InitInterrupt8723BSdio(struct adapter *adapter)
{
        struct hal_com_data *haldata;

        haldata = GET_HAL_DATA(adapter);
        haldata->sdio_himr = (u32)(SDIO_HIMR_RX_REQUEST_MSK     |
                                   SDIO_HIMR_AVAL_MSK           |
                                   0);
}

/*  */
/*      Description: */
/*              Initialize System Host Interrupt Mask configuration variables for future use. */
/*  */
/*      Created by Roger, 2011.08.03. */
/*  */
void InitSysInterrupt8723BSdio(struct adapter *adapter)
{
        struct hal_com_data *haldata;

        haldata = GET_HAL_DATA(adapter);

        haldata->SysIntrMask = (0);
}

/*  */
/*      Description: */
/*              Enalbe SDIO Host Interrupt Mask configuration on SDIO local domain. */
/*  */
/*      Assumption: */
/*              1. Using SDIO Local register ONLY for configuration. */
/*              2. PASSIVE LEVEL */
/*  */
/*      Created by Roger, 2011.02.11. */
/*  */
void EnableInterrupt8723BSdio(struct adapter *adapter)
{
        struct hal_com_data *haldata;
        __le32 himr;
        u32 tmp;

        haldata = GET_HAL_DATA(adapter);

        himr = cpu_to_le32(haldata->sdio_himr);
        sdio_local_write(adapter, SDIO_REG_HIMR, 4, (u8 *)&himr);

        /*  Update current system IMR settings */
        tmp = rtw_read32(adapter, REG_HSIMR);
        rtw_write32(adapter, REG_HSIMR, tmp | haldata->SysIntrMask);

        /*  */
        /*  <Roger_Notes> There are some C2H CMDs have been sent before system interrupt is enabled, e.g., C2H, CPWM. */
        /*  So we need to clear all C2H events that FW has notified, otherwise FW won't schedule any commands anymore. */
        /*  2011.10.19. */
        /*  */
        rtw_write8(adapter, REG_C2HEVT_CLEAR, C2H_EVT_HOST_CLOSE);
}

/*  */
/*      Description: */
/*              Disable SDIO Host IMR configuration to mask unnecessary interrupt service. */
/*  */
/*      Assumption: */
/*              Using SDIO Local register ONLY for configuration. */
/*  */
/*      Created by Roger, 2011.02.11. */
/*  */
void DisableInterrupt8723BSdio(struct adapter *adapter)
{
        __le32 himr;

        himr = cpu_to_le32(SDIO_HIMR_DISABLED);
        sdio_local_write(adapter, SDIO_REG_HIMR, 4, (u8 *)&himr);
}

/*  */
/*      Description: */
/*              Using 0x100 to check the power status of FW. */
/*  */
/*      Assumption: */
/*              Using SDIO Local register ONLY for configuration. */
/*  */
/*      Created by Isaac, 2013.09.10. */
/*  */
u8 CheckIPSStatus(struct adapter *adapter)
{
        if (rtw_read8(adapter, 0x100) == 0xEA)
                return true;
        else
                return false;
}

static struct recv_buf *sd_recv_rxfifo(struct adapter *adapter, u32 size)
{
        u32 readsize, ret;
        u8 *readbuf;
        struct recv_priv *recv_priv;
        struct recv_buf *recvbuf;

        /*  Patch for some SDIO Host 4 bytes issue */
        /*  ex. RK3188 */
        readsize = round_up(size, 4);

        /* 3 1. alloc recvbuf */
        recv_priv = &adapter->recvpriv;
        recvbuf = rtw_dequeue_recvbuf(&recv_priv->free_recv_buf_queue);
        if (!recvbuf) {
                netdev_err(adapter->pnetdev, "%s: alloc recvbuf FAIL!\n",
                           __func__);
                return NULL;
        }

        /* 3 2. alloc skb */
        if (!recvbuf->pskb) {
                SIZE_PTR tmpaddr = 0;
                SIZE_PTR alignment = 0;

                recvbuf->pskb = __dev_alloc_skb(MAX_RECVBUF_SZ + RECVBUFF_ALIGN_SZ, GFP_ATOMIC);
                if (!recvbuf->pskb)
                        return NULL;

                recvbuf->pskb->dev = adapter->pnetdev;

                tmpaddr = (SIZE_PTR)recvbuf->pskb->data;
                alignment = tmpaddr & (RECVBUFF_ALIGN_SZ - 1);
                skb_reserve(recvbuf->pskb, (RECVBUFF_ALIGN_SZ - alignment));
        }

        /* 3 3. read data from rxfifo */
        readbuf = recvbuf->pskb->data;
        ret = sdio_read_port(&adapter->iopriv.intf, WLAN_RX0FF_DEVICE_ID, readsize, readbuf);
        if (ret == _FAIL)
                return NULL;

        /* 3 4. init recvbuf */
        recvbuf->len = size;
        recvbuf->phead = recvbuf->pskb->head;
        recvbuf->pdata = recvbuf->pskb->data;
        skb_set_tail_pointer(recvbuf->pskb, size);
        recvbuf->ptail = skb_tail_pointer(recvbuf->pskb);
        recvbuf->pend = skb_end_pointer(recvbuf->pskb);

        return recvbuf;
}

static void sd_rxhandler(struct adapter *adapter, struct recv_buf *recvbuf)
{
        struct recv_priv *recv_priv;
        struct __queue *pending_queue;

        recv_priv = &adapter->recvpriv;
        pending_queue = &recv_priv->recv_buf_pending_queue;

        /* 3 1. enqueue recvbuf */
        rtw_enqueue_recvbuf(recvbuf, pending_queue);

        /* 3 2. schedule tasklet */
        tasklet_schedule(&recv_priv->recv_tasklet);
}

void sd_int_dpc(struct adapter *adapter)
{
        struct hal_com_data *hal;
        struct dvobj_priv *dvobj;
        struct intf_hdl *intfhdl = &adapter->iopriv.intf;
        struct pwrctrl_priv *pwrctl;

        hal = GET_HAL_DATA(adapter);
        dvobj = adapter_to_dvobj(adapter);
        pwrctl = dvobj_to_pwrctl(dvobj);

        if (hal->sdio_hisr & SDIO_HISR_AVAL) {
                u8 freepage[4];

                _sdio_local_read(adapter, SDIO_REG_FREE_TXPG, 4, freepage);
                complete(&(adapter->xmitpriv.xmit_comp));
        }

        if (hal->sdio_hisr & SDIO_HISR_CPWM1) {
                timer_delete_sync(&(pwrctl->pwr_rpwm_timer));

                SdioLocalCmd52Read1Byte(adapter, SDIO_REG_HCPWM1_8723B);

                _set_workitem(&(pwrctl->cpwm_event));
        }

        if (hal->sdio_hisr & SDIO_HISR_TXERR) {
                u8 *status;
                u32 addr;

                status = kmalloc(4, GFP_ATOMIC);
                if (status) {
                        addr = REG_TXDMA_STATUS;
                        hal_sdio_get_cmd_addr_8723b(adapter, WLAN_IOREG_DEVICE_ID, addr, &addr);
                        _sd_read(intfhdl, addr, 4, status);
                        _sd_write(intfhdl, addr, 4, status);
                        kfree(status);
                }
        }

        if (hal->sdio_hisr & SDIO_HISR_C2HCMD) {
                struct c2h_evt_hdr_88xx *c2h_evt;

                c2h_evt = kzalloc(16, GFP_ATOMIC);
                if (c2h_evt) {
                        if (c2h_evt_read_88xx(adapter, (u8 *)c2h_evt) == _SUCCESS) {
                                if (c2h_id_filter_ccx_8723b((u8 *)c2h_evt)) {
                                        /* Handle CCX report here */
                                        rtw_hal_c2h_handler(adapter, (u8 *)c2h_evt);
                                        kfree(c2h_evt);
                                } else {
                                        rtw_c2h_wk_cmd(adapter, (u8 *)c2h_evt);
                                }
                        } else {
                                kfree(c2h_evt);
                        }
                } else {
                        /* Error handling for malloc fail */
                        rtw_cbuf_push(adapter->evtpriv.c2h_queue, NULL);
                        _set_workitem(&adapter->evtpriv.c2h_wk);
                }
        }

        if (hal->sdio_hisr & SDIO_HISR_RX_REQUEST) {
                struct recv_buf *recvbuf;
                int alloc_fail_time = 0;
                u32 hisr;

                hal->sdio_hisr ^= SDIO_HISR_RX_REQUEST;
                do {
                        hal->SdioRxFIFOSize = sdio_local_cmd52_read2byte(adapter, SDIO_REG_RX0_REQ_LEN);
                        if (hal->SdioRxFIFOSize != 0) {
                                recvbuf = sd_recv_rxfifo(adapter, hal->SdioRxFIFOSize);
                                if (recvbuf)
                                        sd_rxhandler(adapter, recvbuf);
                                else {
                                        alloc_fail_time++;
                                        if (alloc_fail_time >= 10)
                                                break;
                                }
                                hal->SdioRxFIFOSize = 0;
                        } else
                                break;

                        hisr = 0;
                        read_interrupt_8723b_sdio(adapter, &hisr);
                        hisr &= SDIO_HISR_RX_REQUEST;
                        if (!hisr)
                                break;
                } while (1);
        }
}

void sd_int_hdl(struct adapter *adapter)
{
        struct hal_com_data *hal;

        if (
                (adapter->bDriverStopped) || (adapter->bSurpriseRemoved)
        )
                return;

        hal = GET_HAL_DATA(adapter);

        hal->sdio_hisr = 0;
        read_interrupt_8723b_sdio(adapter, &hal->sdio_hisr);

        if (hal->sdio_hisr & hal->sdio_himr) {
                u32 v32;

                hal->sdio_hisr &= hal->sdio_himr;

                /*  clear HISR */
                v32 = hal->sdio_hisr & MASK_SDIO_HISR_CLEAR;
                if (v32)
                        sdio_local_cmd52_write4byte(adapter, SDIO_REG_HISR, v32);

                sd_int_dpc(adapter);
        }
}

/*  */
/*      Description: */
/*              Query SDIO Local register to query current the number of Free TxPacketBuffer page. */
/*  */
/*      Assumption: */
/*              1. Running at PASSIVE_LEVEL */
/*              2. RT_TX_SPINLOCK is NOT acquired. */
/*  */
/*      Created by Roger, 2011.01.28. */
/*  */
u8 HalQueryTxBufferStatus8723BSdio(struct adapter *adapter)
{
        struct hal_com_data *hal;
        u32 numof_free_page;

        hal = GET_HAL_DATA(adapter);

        numof_free_page = sdio_local_cmd53_read4byte(adapter, SDIO_REG_FREE_TXPG);

        memcpy(hal->SdioTxFIFOFreePage, &numof_free_page, 4);

        return true;
}

/* Read the TX OQT free page count from the SDIO local register. */
void HalQueryTxOQTBufferStatus8723BSdio(struct adapter *adapter)
{
        struct hal_com_data *haldata = GET_HAL_DATA(adapter);

        haldata->SdioTxOQTFreeSpace = SdioLocalCmd52Read1Byte(adapter, SDIO_REG_OQT_FREE_PG);
}