/******************************************************************************
 *
 * Copyright(c) 2019 Realtek Corporation. All rights reserved.
 *
 * 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.
 *
 ******************************************************************************/
#include "rx_forwarding.h"

#if MAC_AX_FW_REG_OFLD
u32 mac_set_rx_forwarding(struct mac_ax_adapter *adapter,
			  struct mac_ax_rx_fwd_ctrl_t *rf_ctrl_p)
{
	u32 ret = 0;
	u8 *buf;
#if MAC_AX_PHL_H2C
	struct rtw_h2c_pkt *h2cb;
#else
	struct h2c_buf *h2cb;
#endif
	struct fwcmd_rx_fwd *rx_fwd;
	struct mac_ax_af_ud_ctrl_t *af_ud;
	struct mac_ax_pm_cam_ctrl_t *pm_cam;

	if (!rf_ctrl_p)
		return MACNPTR;

	h2cb = h2cb_alloc(adapter, H2CB_CLASS_DATA);
	if (!h2cb)
		return MACNPTR;

	buf = h2cb_put(h2cb, sizeof(struct fwcmd_rx_fwd));
	if (!buf) {
		ret = MACNOBUF;
		goto fail;
	}

	rx_fwd = (struct fwcmd_rx_fwd *)buf;
	rx_fwd->dword0 =
	cpu_to_le32(SET_WORD(rf_ctrl_p->type, FWCMD_H2C_RX_FWD_TYPE) |
		    SET_WORD(rf_ctrl_p->frame, FWCMD_H2C_RX_FWD_FRAME) |
		    SET_WORD(rf_ctrl_p->fwd_tg, FWCMD_H2C_RX_FWD_FWD_TG));

	af_ud = &rf_ctrl_p->af_ud_ctrl;
	rx_fwd->dword1 =
	cpu_to_le32(SET_WORD(af_ud->index, FWCMD_H2C_RX_FWD_AF_UD_INDEX) |
		    SET_WORD(af_ud->fwd_tg, FWCMD_H2C_RX_FWD_AF_UD_FWD_TG) |
		    SET_WORD(af_ud->category, FWCMD_H2C_RX_FWD_AF_UD_CATEGORY) |
		    SET_WORD(af_ud->action_field,
			     FWCMD_H2C_RX_FWD_AF_UD_ACTION_FIELD));

	pm_cam = &rf_ctrl_p->pm_cam_ctrl;
	rx_fwd->dword2 =
	cpu_to_le32((pm_cam->valid ? FWCMD_H2C_RX_FWD_PM_CAM_VALID : 0) |
		    SET_WORD(pm_cam->type, FWCMD_H2C_RX_FWD_PM_CAM_TYPE) |
		    SET_WORD(pm_cam->subtype, FWCMD_H2C_RX_FWD_PM_CAM_SUBTYPE) |
		    (pm_cam->skip_mac_iv_hdr ?
		     FWCMD_H2C_RX_FWD_PM_CAM_SKIP_MAC_IV_HDR : 0) |
		    SET_WORD(pm_cam->target_ind,
			     FWCMD_H2C_RX_FWD_PM_CAM_TARGET_IND) |
		    SET_WORD(pm_cam->entry_index,
			     FWCMD_H2C_RX_FWD_PM_CAM_INDEX) |
		    SET_WORD(pm_cam->crc16, FWCMD_H2C_RX_FWD_PM_CAM_CRC16));

	rx_fwd->dword3 =
	cpu_to_le32(SET_WORD(pm_cam->pld_mask0,
			     FWCMD_H2C_RX_FWD_PM_CAM_PLD_MASK0));

	rx_fwd->dword4 =
	cpu_to_le32(SET_WORD(pm_cam->pld_mask1,
			     FWCMD_H2C_RX_FWD_PM_CAM_PLD_MASK1));

	rx_fwd->dword5 =
	cpu_to_le32(SET_WORD(pm_cam->pld_mask2,
			     FWCMD_H2C_RX_FWD_PM_CAM_PLD_MASK2));

	rx_fwd->dword6 =
	cpu_to_le32(SET_WORD(pm_cam->pld_mask3,
			     FWCMD_H2C_RX_FWD_PM_CAM_PLD_MASK3));

	if (adapter->sm.fwdl == MAC_AX_FWDL_INIT_RDY) {
		ret = h2c_pkt_set_hdr(adapter, h2cb,
				      FWCMD_TYPE_H2C,
				      FWCMD_H2C_CAT_MAC,
				      FWCMD_H2C_CL_FW_OFLD,
				      FWCMD_H2C_FUNC_RX_FWD,
				      0,
				      1);
		if (ret)
			goto fail;

		ret = h2c_pkt_build_txd(adapter, h2cb);
		if (ret)
			goto fail;

#if MAC_AX_PHL_H2C
		ret = PLTFM_TX(h2cb);
#else
		ret = PLTFM_TX(h2cb->data, h2cb->len);
#endif
		if (ret)
			goto fail;
	} else {
		ret = MACFWNONRDY;
		goto fail;
	}

	ret = MACSUCCESS;
fail:
	h2cb_free(adapter, h2cb);

	return ret;
}

#else
static inline u32 af_fwd_cfg(struct mac_ax_adapter *adapter,
			     enum mac_ax_action_frame frame,
			     enum mac_ax_fwd_target fwd_tg)
{
	u32 val32;
	struct mac_ax_intf_ops *ops = adapter_to_intf_ops(adapter);

	val32 = MAC_REG_R32(R_AX_ACTION_FWD0);
	switch (frame) {
	case MAC_AX_AF_CSA:
		val32 = SET_CLR_WORD(val32, fwd_tg, B_AX_FWD_CSA);
		break;
	case MAC_AX_AF_ADDTS_REQ:
		val32 = SET_CLR_WORD(val32, fwd_tg, B_AX_FWD_ADDTS_REQ);
		break;
	case MAC_AX_AF_ADDTS_RES:
		val32 = SET_CLR_WORD(val32, fwd_tg, B_AX_FWD_ADDTS_RES);
		break;
	case MAC_AX_AF_DELTS:
		val32 = SET_CLR_WORD(val32, fwd_tg, B_AX_FWD_DELTS);
		break;
	case MAC_AX_AF_ADDBA_REQ:
		val32 = SET_CLR_WORD(val32, fwd_tg, B_AX_FWD_ADDBA_REQ);
		break;
	case MAC_AX_AF_ADDBA_RES:
		val32 = SET_CLR_WORD(val32, fwd_tg, B_AX_FWD_ADDBA_RES);
		break;
	case MAC_AX_AF_DELBA:
		val32 = SET_CLR_WORD(val32, fwd_tg, B_AX_FWD_DELBA);
		break;
	case MAC_AX_AF_NCW:
		val32 = SET_CLR_WORD(val32, fwd_tg, B_AX_FWD_NCW);
		break;
	case MAC_AX_AF_GID_MGNT:
		val32 = SET_CLR_WORD(val32, fwd_tg, B_AX_FWD_GID_MGNT);
		break;
	case MAC_AX_AF_OP_MODE:
		val32 = SET_CLR_WORD(val32, fwd_tg, B_AX_FWD_OP_MODE);
		break;
	case MAC_AX_AF_CSI:
		val32 = SET_CLR_WORD(val32, fwd_tg, B_AX_FWD_CSI);
		break;
	case MAC_AX_AF_HT_CBFM:
		val32 = SET_CLR_WORD(val32, fwd_tg, B_AX_FWD_HT_CBFM);
		break;
	case MAC_AX_AF_VHT_CBFM:
		val32 = SET_CLR_WORD(val32, fwd_tg, B_AX_FWD_VHT_CBFM);
		break;
	default:
		return MACNOITEM;
	}
	MAC_REG_W32(R_AX_ACTION_FWD0, val32);
	return MACSUCCESS;
}

static inline u32 af_ud_fwd_cfg(struct mac_ax_adapter *adapter,
				struct mac_ax_af_ud_ctrl_t *af_ud_ctrl_p)
{
	u32 val32;
	u16 val16;
	struct mac_ax_intf_ops *ops = adapter_to_intf_ops(adapter);

	val32 = MAC_REG_R32(R_AX_ACTION_FWD1);
	switch (af_ud_ctrl_p->index) {
	case MAC_AX_AF_UD_0:
		val16 = MAC_REG_R16(R_AX_ACTION_FWD_CTRL0);
		val16 = SET_CLR_WORD(val16, af_ud_ctrl_p->category,
				     B_AX_FWD_ACTN_CAT0);
		val16 = SET_CLR_WORD(val16, af_ud_ctrl_p->action_field,
				     B_AX_FWD_ACTN_ACTN0);
		MAC_REG_W16(R_AX_ACTION_FWD_CTRL0, val16);
		val32 = SET_CLR_WORD(val32, af_ud_ctrl_p->fwd_tg,
				     B_AX_FWD_ACTN_CTRL0);
		break;
	case MAC_AX_AF_UD_1:
		val16 = MAC_REG_R16(R_AX_ACTION_FWD_CTRL1);
		val16 = SET_CLR_WORD(val16, af_ud_ctrl_p->category,
				     B_AX_FWD_ACTN_CAT1);
		val16 = SET_CLR_WORD(val16, af_ud_ctrl_p->action_field,
				     B_AX_FWD_ACTN_ACTN1);
		MAC_REG_W16(R_AX_ACTION_FWD_CTRL1, val16);
		val32 = SET_CLR_WORD(val32, af_ud_ctrl_p->fwd_tg,
				     B_AX_FWD_ACTN_CTRL1);
		break;
	case MAC_AX_AF_UD_2:
		val16 = MAC_REG_R16(R_AX_ACTION_FWD_CTRL2);
		val16 = SET_CLR_WORD(val16, af_ud_ctrl_p->category,
				     B_AX_FWD_ACTN_CAT2);
		val16 = SET_CLR_WORD(val16, af_ud_ctrl_p->action_field,
				     B_AX_FWD_ACTN_ACTN2);
		MAC_REG_W16(R_AX_ACTION_FWD_CTRL2, val16);
		val32 = SET_CLR_WORD(val32, af_ud_ctrl_p->fwd_tg,
				     B_AX_FWD_ACTN_CTRL2);
		break;
	case MAC_AX_AF_UD_3:
		val16 = MAC_REG_R16(R_AX_ACTION_FWD_CTRL3);
		val16 = SET_CLR_WORD(val16, af_ud_ctrl_p->category,
				     B_AX_FWD_ACTN_CAT3);
		val16 = SET_CLR_WORD(val16, af_ud_ctrl_p->action_field,
				     B_AX_FWD_ACTN_ACTN3);
		MAC_REG_W16(R_AX_ACTION_FWD_CTRL3, val16);
		val32 = SET_CLR_WORD(val32, af_ud_ctrl_p->fwd_tg,
				     B_AX_FWD_ACTN_CTRL3);
		break;
	default:
		return MACNOITEM;
	}
	MAC_REG_W32(R_AX_ACTION_FWD1, val32);
	return MACSUCCESS;
}

static inline u32 tf_fwd_cfg(struct mac_ax_adapter *adapter,
			     enum mac_ax_trigger_frame frame,
			     enum mac_ax_fwd_target fwd_tg)
{
	u32 val32;
	struct mac_ax_intf_ops *ops = adapter_to_intf_ops(adapter);

	val32 = MAC_REG_R32(R_AX_TF_FWD);
	switch (frame) {
	case MAC_AX_TF_BT:
		val32 = SET_CLR_WORD(val32, fwd_tg, B_AX_FWD_TF0);
		break;
	case MAC_AX_TF_BFRP:
		val32 = SET_CLR_WORD(val32, fwd_tg, B_AX_FWD_TF1);
		break;
	case MAC_AX_TF_MU_BAR:
		val32 = SET_CLR_WORD(val32, fwd_tg, B_AX_FWD_TF2);
		break;
	case MAC_AX_TF_MU_RTS:
		val32 = SET_CLR_WORD(val32, fwd_tg, B_AX_FWD_TF3);
		break;
	case MAC_AX_TF_BSRP:
		val32 = SET_CLR_WORD(val32, fwd_tg, B_AX_FWD_TF4);
		break;
	case MAC_AX_TF_GCR_MU_BAR:
		val32 = SET_CLR_WORD(val32, fwd_tg, B_AX_FWD_TF5);
		break;
	case MAC_AX_TF_BQRP:
		val32 = SET_CLR_WORD(val32, fwd_tg, B_AX_FWD_TF6);
		break;
	case MAC_AX_TF_NFRP:
		val32 = SET_CLR_WORD(val32, fwd_tg, B_AX_FWD_TF7);
		break;
	default:
		return MACNOITEM;
	}
	MAC_REG_W32(R_AX_TF_FWD, val32);
	return MACSUCCESS;
}

static inline u32 pm_cam_access_polling(struct mac_ax_adapter *adapter)
{
	u32 cnt = PM_CAM_WAIT_CNT;
	struct mac_ax_intf_ops *ops = adapter_to_intf_ops(adapter);

	while (cnt--) {
		if (!(MAC_REG_R32(R_AX_PLD_CAM_ACCESS) & B_AX_PLD_CAM_POLL))
			break;
		PLTFM_DELAY_US(PM_CAM_WAIT_US);
	}

	if (!++cnt) {
		PLTFM_MSG_ERR("[ERR]PM CAM access timeout\n");
		return MACPOLLTO;
	}
	return MACSUCCESS;
}

static inline u32 pm_cam_indirect_r(struct mac_ax_adapter *adapter,
				    u32 offset)
{
	u32 val32;
	struct mac_ax_intf_ops *ops = adapter_to_intf_ops(adapter);

	val32 = 0;
	val32 = SET_CLR_WORD(val32, offset, B_AX_PLD_CAM_OFFSET);
	val32 &= ~B_AX_PLD_CAM_CLR;
	val32 &= ~B_AX_PLD_CAM_RW;
	val32 |= B_AX_PLD_CAM_POLL;
	MAC_REG_W32(R_AX_PLD_CAM_ACCESS, val32);

	if (pm_cam_access_polling(adapter)) {
		PLTFM_MSG_ERR("[ERR]PM CAM read timeout\n");
		return MACSUCCESS;
	}

	return MAC_REG_R32(R_AX_PLD_CAM_RDATA);
}

static inline void pm_cam_indirect_w(struct mac_ax_adapter *adapter,
				     u32 offset, u32 data)
{
	u32 val32;
	struct mac_ax_intf_ops *ops = adapter_to_intf_ops(adapter);

	MAC_REG_W32(R_AX_PLD_CAM_WDATA, data);

	val32 = 0;
	val32 = SET_CLR_WORD(val32, offset, B_AX_PLD_CAM_OFFSET);
	val32 &= ~B_AX_PLD_CAM_CLR;
	val32 |= B_AX_PLD_CAM_RW;
	val32 |= B_AX_PLD_CAM_POLL;
	MAC_REG_W32(R_AX_PLD_CAM_ACCESS, val32);

	if (pm_cam_access_polling(adapter))
		PLTFM_MSG_ERR("[ERR]PM CAM write timeout\n");
}

static inline u32 pm_cam_fwd_cfg(struct mac_ax_adapter *adapter,
				 struct mac_ax_pm_cam_ctrl_t *pm_cam_ctrl_p)
{
	u32 val32, offset;
	struct mac_ax_intf_ops *ops = adapter_to_intf_ops(adapter);

	if (is_chip_id(adapter, MAC_AX_CHIP_ID_8852C) ||
	    is_chip_id(adapter, MAC_AX_CHIP_ID_8192XB)) {
		PLTFM_MSG_ERR("[ERR]%s PM_CAM API is removed in 52C & 92XB.\n"
			      , __func__);
		return MACSUCCESS;
	}

	if (!pm_cam_ctrl_p)
		return MACNPTR;

	if (pm_cam_ctrl_p->entry_index >= MAC_AX_PM_CAM_ENTRY_NUM_MAX)
		return MACNOITEM;

	// Set as indirect access and enable PM CAM
	val32 = MAC_REG_R32(R_AX_PLD_CAM_CTRL);
	val32 = SET_CLR_WORD(val32, 0xf, B_AX_PLD_CAM_RANGE);
	val32 |= B_AX_PLD_CAM_ACC;
	val32 |= B_AX_PLD_CAM_EN;
	MAC_REG_W32(R_AX_PLD_CAM_CTRL, val32);

	// offset unit: 4 bytes
	offset = (pm_cam_ctrl_p->entry_index *
		 MAC_AX_PM_CAM_ENTRY_CONTENT_SIZE) / 4;

	val32 = 0;
	val32 = SET_CLR_WORD(val32, pm_cam_ctrl_p->pld_mask0, PM_CAM_PLD_MASK0);
	pm_cam_indirect_w(adapter, offset + PM_CAM_OFFSET_DW1, val32);
	if (val32 != pm_cam_indirect_r(adapter, offset + PM_CAM_OFFSET_DW1)) {
		PLTFM_MSG_ERR("[ERR]PM CAM cfg fail 1\n");
		return MACRFPMCAM;
	}

	val32 = 0;
	val32 = SET_CLR_WORD(val32, pm_cam_ctrl_p->pld_mask1, PM_CAM_PLD_MASK1);
	pm_cam_indirect_w(adapter, offset + PM_CAM_OFFSET_DW2, val32);
	if (val32 != pm_cam_indirect_r(adapter, offset + PM_CAM_OFFSET_DW2)) {
		PLTFM_MSG_ERR("[ERR]PM CAM cfg fail 2\n");
		return MACRFPMCAM;
	}

	val32 = 0;
	val32 = SET_CLR_WORD(val32, pm_cam_ctrl_p->pld_mask2, PM_CAM_PLD_MASK2);
	pm_cam_indirect_w(adapter, offset + PM_CAM_OFFSET_DW3, val32);
	if (val32 != pm_cam_indirect_r(adapter, offset + PM_CAM_OFFSET_DW3)) {
		PLTFM_MSG_ERR("[ERR]PM CAM cfg fail 3\n");
		return MACRFPMCAM;
	}

	val32 = 0;
	val32 = SET_CLR_WORD(val32, pm_cam_ctrl_p->pld_mask3, PM_CAM_PLD_MASK3);
	pm_cam_indirect_w(adapter, offset + PM_CAM_OFFSET_DW4, val32);
	if (val32 != pm_cam_indirect_r(adapter, offset + PM_CAM_OFFSET_DW4)) {
		PLTFM_MSG_ERR("[ERR]PM CAM cfg fail 4\n");
		return MACRFPMCAM;
	}

	// PMCAM is not trigger-type reg!
	val32 = 0;
	val32 |= (pm_cam_ctrl_p->valid ? PM_CAM_VALID : 0);
	val32 = SET_CLR_WORD(val32, pm_cam_ctrl_p->type, PM_CAM_TYPE);
	val32 = SET_CLR_WORD(val32, pm_cam_ctrl_p->subtype, PM_CAM_SUBTYPE);
	val32 |= (pm_cam_ctrl_p->skip_mac_iv_hdr ? PM_CAM_SKIP_MAC_IV_HDR : 0);
	val32 = SET_CLR_WORD(val32, pm_cam_ctrl_p->target_ind,
			     PM_CAM_TARGET_IND);
	val32 = SET_CLR_WORD(val32, pm_cam_ctrl_p->crc16, PM_CAM_CRC16);
	pm_cam_indirect_w(adapter, offset, val32);
	if (val32 != pm_cam_indirect_r(adapter, offset)) {
		PLTFM_MSG_ERR("[ERR]PM CAM cfg fail 5, %x, %x\n",
			      val32, pm_cam_indirect_r(adapter, offset));
		return MACRFPMCAM;
	}

	return MACSUCCESS;
}

u32 mac_set_rx_forwarding(struct mac_ax_adapter *adapter,
			  struct mac_ax_rx_fwd_ctrl_t *rf_ctrl_p)
{
	u32 ret = 0;

	if (!rf_ctrl_p) {
		PLTFM_MSG_ERR("[ERR]%s NULL pointer!\n", __func__);
		return MACNPTR;
	}

	switch (rf_ctrl_p->type) {
	case MAC_AX_FT_ACTION:
		ret = af_fwd_cfg(adapter,
				 (enum mac_ax_action_frame)rf_ctrl_p->frame,
				 (enum mac_ax_fwd_target)rf_ctrl_p->fwd_tg);
		break;
	case MAC_AX_FT_ACTION_UD:
		ret = af_ud_fwd_cfg(adapter, &rf_ctrl_p->af_ud_ctrl);
		break;
	case MAC_AX_FT_TRIGGER:
		ret = tf_fwd_cfg(adapter,
				 (enum mac_ax_trigger_frame)rf_ctrl_p->frame,
				 (enum mac_ax_fwd_target)rf_ctrl_p->fwd_tg);
		break;
	case MAC_AX_FT_PM_CAM:
		// Don't suggest using indirect access.
		ret = pm_cam_fwd_cfg(adapter, &rf_ctrl_p->pm_cam_ctrl);
		break;
	}

	return ret;
}

#endif
