/******************************************************************************
 *
 * 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 "sta_sch.h"

static u32 poll_own_chk(struct mac_ax_adapter *adapter, u32 addr)
{
	u32 cnt = 100;
	u32 ck;
	u32 val32;
	struct mac_ax_intf_ops *ops = adapter_to_intf_ops(adapter);

	if (addr == R_AX_SS_SRAM_CTRL_1 || addr == R_AX_SS_LINK_INFO ||
	    addr == R_AX_SS_SRAM_CTRL_0 || addr == R_AX_SS_LINK_ADD ||
	    addr == R_AX_SS_LINK_DEL || addr == R_AX_SS_LINK_SEARCH)
		ck = B_AX_SS_OWN;
	else
		ck = 0;

	while (--cnt) {
		val32 = MAC_REG_R32(addr);
		if ((val32 & ck) == 0)
			break;
		PLTFM_DELAY_US(1);
	}
	if (!cnt) {
		PLTFM_MSG_ERR("[STASCH]poll_own fail!!\n");
		return MACPOLLTO;
	}

	if (addr == R_AX_SS_SRAM_CTRL_1 || addr == R_AX_SS_LINK_INFO ||
	    addr == R_AX_SS_LINK_ADD || addr == R_AX_SS_LINK_DEL) {
		val32 = MAC_REG_R32(addr);
		if (val32 & (BIT(29) | BIT(30))) {
			PLTFM_MSG_ERR("[STASCH]poll status fail!!\n");
			return MACPROCERR;
		}
	}
	return MACSUCCESS;
}

static u32 poll_own_val(struct mac_ax_adapter *adapter, u32 addr, u32 *r_val)
{
	u32 cnt = 100;
	u32 ck;
	struct mac_ax_intf_ops *ops = adapter_to_intf_ops(adapter);

	if (addr == R_AX_SS_SRAM_CTRL_1 || addr == R_AX_SS_LINK_INFO ||
	    addr == R_AX_SS_SRAM_CTRL_0 || addr == R_AX_SS_LINK_ADD ||
	    addr == R_AX_SS_LINK_DEL || addr == R_AX_SS_LINK_SEARCH)
		ck = B_AX_SS_OWN;
	else
		ck = 0;

	while (--cnt) {
		*r_val = MAC_REG_R32(addr);
		if ((*r_val & ck) == 0)
			break;
		PLTFM_DELAY_US(1);
	}
	if (!cnt) {
		PLTFM_MSG_ERR("[STASCH]poll_own fail!!\n");
		return MACPOLLTO;
	}

	if (addr == R_AX_SS_SRAM_CTRL_1 || addr == R_AX_SS_LINK_INFO ||
	    addr == R_AX_SS_LINK_ADD || addr == R_AX_SS_LINK_DEL) {
		if (*r_val & (BIT(29) | BIT(30))) {
			PLTFM_MSG_ERR("[STASCH]poll status fail!!\n");
			return MACPROCERR;
		}
	}
	return MACSUCCESS;
}

static u32 sram1_ctrl_val(struct mac_ax_adapter *adapter, u32 cmd, u32 *r_val)
{
	struct mac_ax_intf_ops *ops = adapter_to_intf_ops(adapter);

	MAC_REG_W32(R_AX_SS_SRAM_CTRL_1, cmd);

	return poll_own_val(adapter, R_AX_SS_SRAM_CTRL_1, r_val);
}

static u32 get_link_info(struct mac_ax_adapter *adapter, u32 cmd, u32 *r_val)
{
	struct mac_ax_intf_ops *ops = adapter_to_intf_ops(adapter);

	MAC_REG_W32(R_AX_SS_LINK_INFO, cmd);

	return poll_own_val(adapter, R_AX_SS_LINK_INFO, r_val);
}

static u32 add_to_link(struct mac_ax_adapter *adapter,
		       struct mac_ax_ss_link_info *link)
{
	struct mac_ax_intf_ops *ops = adapter_to_intf_ops(adapter);

	MAC_REG_W32(R_AX_SS_LINK_ADD, B_AX_SS_OWN |
		    (link->ul ? B_AX_SS_UL : 0) |
		    SET_WORD(link->wmm, B_AX_SS_WMM) |
		    SET_WORD(link->ac, B_AX_SS_AC) |
		    SET_WORD(link->macid2, B_AX_SS_MACID_2) |
		    SET_WORD(link->macid1, B_AX_SS_MACID_1) |
		    SET_WORD(link->macid0, B_AX_SS_MACID_0));
	return poll_own_chk(adapter, R_AX_SS_LINK_ADD);
}

static u32 del_from_link(struct mac_ax_adapter *adapter,
			 struct mac_ax_ss_link_info *link)
{
	struct mac_ax_intf_ops *ops = adapter_to_intf_ops(adapter);

	MAC_REG_W32(R_AX_SS_LINK_DEL, B_AX_SS_OWN |
		    (link->ul ? B_AX_SS_UL : 0) |
		    SET_WORD(link->wmm, B_AX_SS_WMM) |
		    SET_WORD(link->ac, B_AX_SS_AC) |
		    SET_WORD(link->macid2, B_AX_SS_MACID_2) |
		    SET_WORD(link->macid1, B_AX_SS_MACID_1) |
		    SET_WORD(link->macid0, B_AX_SS_MACID_0));
	return poll_own_chk(adapter, R_AX_SS_LINK_DEL);
}

static u32 clean_sta_link(struct mac_ax_adapter *adapter,
			  struct mac_ax_ss_link_info *link)
{
	u32 val32;
	u8 wmm, ac;
	u32 cnt = adapter->hw_info->macid_num + 1;
	u32 cmd;
	u32 ret;

	if (link->ul) {
		wmm = 0;
		ac = 0;
	} else {
		wmm = link->wmm;
		ac = link->ac;
	}

	do {
		cmd = B_AX_SS_OWN | (link->ul ? B_AX_SS_UL : 0) |
		      SET_WORD(wmm, B_AX_SS_WMM) | SET_WORD(ac, B_AX_SS_AC);
		ret = get_link_info(adapter, cmd, &val32);
		if (ret != MACSUCCESS)
			return ret;

		link->link_head = GET_FIELD(val32, B_AX_SS_LINK_HEAD);
		link->link_tail = GET_FIELD(val32, B_AX_SS_LINK_TAIL);
		link->link_len = GET_FIELD(val32, B_AX_SS_LINK_LEN);
		link->macid0 = link->link_head;

		if (link->macid0 != 0xFF) {
			ret = del_from_link(adapter, link);
			if (ret != MACSUCCESS)
				return ret;
			cnt--;
		} else {
			break;
		}
	} while (link->link_len || cnt);

	if (!cnt) {
		PLTFM_MSG_ERR("[STASCH]link error!!\n");
		return MACSSLINK;
	}

	return MACSUCCESS;
}

static u32 wmm_vld_chk(struct mac_ax_adapter *adapter, u8 *vld, u8 wmm)
{
	u8 wmm_num;

	switch (adapter->hw_info->chip_id) {
	case MAC_AX_CHIP_ID_8852A:
		wmm_num = STA_SCH_WMM_NUM_8852A;
		break;
	case MAC_AX_CHIP_ID_8852B:
		wmm_num = STA_SCH_WMM_NUM_8852B;
		break;
	case MAC_AX_CHIP_ID_8852C:
		wmm_num = STA_SCH_WMM_NUM_8852C;
		break;
	case MAC_AX_CHIP_ID_8192XB:
		wmm_num = STA_SCH_WMM_NUM_8192XB;
		break;
	default:
		return MACCHIPID;
	}

	if (wmm < wmm_num)
		*vld = 1;
	else
		*vld = 0;

	return MACSUCCESS;
}

static u32 ul_vld_chk(struct mac_ax_adapter *adapter, u8 *vld)
{
	u8 ul_support;

	switch (adapter->hw_info->chip_id) {
	case MAC_AX_CHIP_ID_8852A:
		ul_support = STA_SCH_UL_SUPPORT_8852A;
		break;
	case MAC_AX_CHIP_ID_8852B:
		ul_support = STA_SCH_UL_SUPPORT_8852B;
		break;
	case MAC_AX_CHIP_ID_8852C:
		ul_support = STA_SCH_UL_SUPPORT_8852C;
		break;
	case MAC_AX_CHIP_ID_8192XB:
		ul_support = STA_SCH_UL_SUPPORT_8192XB;
		break;
	default:
		return MACCHIPID;
	}

	if (ul_support)
		*vld = 1;
	else
		*vld = 0;

	return MACSUCCESS;
}

static u32 get_sta_link(struct mac_ax_adapter *adapter,
			struct mac_ax_ss_link_info *link)
{
	u32 val32, i;
	u8 macid, wmm, ac;
	u32 ret;
	u32 cmd;
	u16 macid_num = adapter->hw_info->macid_num;
	u16 id_empty = (macid_num << 1) - 1;

	if (link->ul) {
		wmm = 0;
		ac = 0;
	} else {
		wmm = link->wmm;
		ac = link->ac;
	}

	cmd = B_AX_SS_OWN | (link->ul ? B_AX_SS_UL : 0) |
	      SET_WORD(wmm, B_AX_SS_WMM) | SET_WORD(ac, B_AX_SS_AC);
	ret = get_link_info(adapter, cmd, &val32);
	if (ret != MACSUCCESS)
		return ret;

	link->link_head = GET_FIELD(val32, B_AX_SS_LINK_HEAD);
	link->link_tail = GET_FIELD(val32, B_AX_SS_LINK_TAIL);
	link->link_len = GET_FIELD(val32, B_AX_SS_LINK_LEN);
	macid = link->link_head;

	if (link->link_head == id_empty) {
		if (link->link_len) {
			PLTFM_MSG_ERR("empty link_len error!!");
			return MACSSLINK;
		}
	} else {
		i = 0;
		do {
			link->link_list[i] = macid;
			link->link_bitmap[macid] = 1;
			cmd = B_AX_SS_OWN |
			      SET_WORD(CTRL1_R_NEXT_LINK, B_AX_SS_CMD_SEL) |
			      SET_WORD(ac, B_AX_SS_AC) |
			      (link->ul ? BIT(23) : 0) | macid;
			ret = sram1_ctrl_val(adapter, cmd, &val32);
			if (ret != MACSUCCESS)
				return ret;

			macid = GET_FIELD(val32, B_AX_SS_VALUE);
			if (macid == id_empty) {
				if (link->link_list[i] != link->link_tail) {
					PLTFM_MSG_ERR("[STASCH]link_tail error!!\n");
					return MACSSLINK;
				}
				if (i >= link->link_len) {
					PLTFM_MSG_ERR("[STASCH]link_len error!!\n");
					return MACSSLINK;
				}
				break;
			}
			i++;
		} while (i < SS_LINK_SIZE);
	}

	return MACSUCCESS;
}

u32 sta_link_cfg(struct mac_ax_adapter *adapter,
		 struct mac_ax_ss_link_info *link,
		 enum mac_ax_ss_link_cfg cfg)
{
	u32 ret;
	u8 vld;

	if (link->ul) {
		ret = ul_vld_chk(adapter, &vld);
		if (ret != MACSUCCESS)
			return ret;
		if (vld == 0)
			return MACSUCCESS;
	}

	ret = wmm_vld_chk(adapter, &vld, link->wmm);
	if (ret != MACSUCCESS)
		return ret;
	if (vld == 0)
		return MACSUCCESS;

	switch (cfg) {
	case MAC_AX_SS_LINK_CFG_GET:
		ret = get_sta_link(adapter, link);
		break;
	case MAC_AX_SS_LINK_CFG_ADD:
		ret = add_to_link(adapter, link);
		break;
	case MAC_AX_SS_LINK_CFG_DEL:
		ret = del_from_link(adapter, link);
		break;
	case MAC_AX_SS_LINK_CFG_CLEAN:
		ret = clean_sta_link(adapter, link);
		break;
	}

	return ret;
}

void set_ss_wmm_tbl(struct mac_ax_adapter *adapter,
		    struct mac_ax_ss_wmm_tbl_ctrl *ctrl)
{
	u32 val32;
	struct mac_ax_intf_ops *ops = adapter_to_intf_ops(adapter);

	val32 = MAC_REG_R32(R_AX_SS_CTRL);
	switch (ctrl->wmm) {
	case 0:
		val32 = SET_CLR_WORD(val32, ctrl->wmm_mapping,
				     B_AX_SS_WMM_SEL_0);
		break;
	case 1:
		val32 = SET_CLR_WORD(val32, ctrl->wmm_mapping,
				     B_AX_SS_WMM_SEL_1);
		break;
	case 2:
		val32 = SET_CLR_WORD(val32, ctrl->wmm_mapping,
				     B_AX_SS_WMM_SEL_2);
		break;
	case 3:
		val32 = SET_CLR_WORD(val32, ctrl->wmm_mapping,
				     B_AX_SS_WMM_SEL_3);
		break;
	default:
		break;
	}
	MAC_REG_W32(R_AX_SS_CTRL, val32);
}

void ss_wmm_tbl_cfg(struct mac_ax_adapter *adapter,
		    struct mac_ax_ss_wmm_tbl_ctrl *ctrl,
		    enum mac_ax_ss_wmm_tbl_cfg cfg)
{
	set_ss_wmm_tbl(adapter, ctrl);
}

u32 switch_wmm_macid(struct mac_ax_adapter *adapter,
		     struct mac_ax_ss_link_info *link,
		     enum mac_ax_ss_wmm_tbl src_link,
		     enum mac_ax_ss_wmm_tbl dst_link)
{
	u32 ret;

	link->wmm = src_link;
	ret = sta_link_cfg(adapter, link, MAC_AX_SS_LINK_CFG_DEL);
	if (ret != MACSUCCESS)
		return ret;
	link->wmm = dst_link;
	ret = sta_link_cfg(adapter, link, MAC_AX_SS_LINK_CFG_ADD);
	if (ret != MACSUCCESS)
		return ret;

	return MACSUCCESS;
}

static u32 chk_role_wmm(struct mac_ax_adapter *adapter, u8 *pmacid, u8 src_wmm)
{
	struct mac_role_tbl *role;
	u8 wmm;

	role = mac_role_srch(adapter, *pmacid);
	if (!role)
		return MACNOITEM;

	wmm = role->info.dbcc_role ?
	      *(adapter->dbcc_info->dbcc_wmm_list + *pmacid) : role->wmm;

	if (wmm != src_wmm)
		*pmacid = 0xFF;

	return MACSUCCESS;
}

static u32 switch_wmm_link(struct mac_ax_adapter *adapter,
			   enum mac_ax_ss_wmm_tbl src_link,
			   enum mac_ax_ss_wmm_tbl dst_link,
			   enum mac_ax_ss_wmm src_wmm)
{
	struct mac_ax_ss_link_info link;
	u32 ret;
	u32 i;

	link.ul = 0;
	for (link.ac = 0; link.ac < 4; link.ac++) {
		PLTFM_MEMSET(link.link_list, 0xFF, SS_LINK_SIZE);
		link.wmm = src_link;
		ret = sta_link_cfg(adapter, &link, MAC_AX_SS_LINK_CFG_GET);
		if (ret != MACSUCCESS) {
			PLTFM_MSG_ERR("sta_link_cfg %d\n", ret);
			return ret;
		}
		for (i = 0; i < link.link_len; i += 3) {
			link.macid0 = link.link_list[i];
			link.macid1 = link.link_list[i + 1];
			link.macid2 = link.link_list[i + 2];
			if (link.macid0 != 0xFF) {
				ret = chk_role_wmm(adapter, &link.macid0, src_wmm);
				if (ret != MACSUCCESS)
					return ret;
			}
			if (link.macid1 != 0xFF) {
				ret = chk_role_wmm(adapter, &link.macid1, src_wmm);
				if (ret != MACSUCCESS)
					return ret;
			}
			if (link.macid2 != 0xFF) {
				ret = chk_role_wmm(adapter, &link.macid2, src_wmm);
				if (ret != MACSUCCESS)
					return ret;
			}
			if (link.macid0 != 0xFF || link.macid1 != 0xFF ||
			    link.macid2 != 0xFF) {
				ret = switch_wmm_macid(adapter, &link, src_link,
						       dst_link);
				if (ret != MACSUCCESS)
					return ret;
			}
		}
	}
	return MACSUCCESS;
}

u32 mac_ss_wmm_sta_move(struct mac_ax_adapter *adapter,
			enum mac_ax_ss_wmm src_wmm,
			enum mac_ax_ss_wmm_tbl dst_link)
{
	struct mac_ax_ss_wmm_tbl_ctrl ctrl;
	u32 ret;

	switch (src_wmm) {
	case MAC_AX_SS_WMM0:
		ctrl.wmm = 0;
		break;
	case MAC_AX_SS_WMM1:
		ctrl.wmm = 1;
		break;
	case MAC_AX_SS_WMM2:
		ctrl.wmm = 2;
		break;
	case MAC_AX_SS_WMM3:
		ctrl.wmm = 3;
		break;
	default:
		return MACFUNCINPUT;
	}
	ret = get_ss_wmm_tbl(adapter, &ctrl);
	if (ret != MACSUCCESS) {
		PLTFM_MSG_ERR("get_ss_wmm_tbl %d\n", ret);
		return ret;
	}

	if (ctrl.wmm_mapping == dst_link)
		return MACARDYDONE;

	ret = switch_wmm_link(adapter, ctrl.wmm_mapping, dst_link, src_wmm);
	if (ret != MACSUCCESS) {
		PLTFM_MSG_ERR("switch_wmm_link %d\n", ret);
		return ret;
	}

	ctrl.wmm_mapping = dst_link;
	ss_wmm_tbl_cfg(adapter, &ctrl, MAC_AX_SS_WMM_TBL_SET);

	return MACSUCCESS;
}

u32 mac_ss_wmm_map_upd(struct mac_ax_adapter *adapter,
		       enum mac_ax_ss_wmm src_wmm,
		       enum mac_ax_ss_wmm_tbl dst_link,
		       u8 chk_emp)
{
	struct mac_ax_ss_wmm_tbl_ctrl ctrl;
	struct mac_ax_ss_link_info link;
	u32 ret;

	switch (src_wmm) {
	case MAC_AX_SS_WMM0:
		ctrl.wmm = 0;
		break;
	case MAC_AX_SS_WMM1:
		ctrl.wmm = 1;
		break;
	case MAC_AX_SS_WMM2:
		ctrl.wmm = 2;
		break;
	case MAC_AX_SS_WMM3:
		ctrl.wmm = 3;
		break;
	default:
		return MACFUNCINPUT;
	}
	ret = get_ss_wmm_tbl(adapter, &ctrl);
	if (ret != MACSUCCESS) {
		PLTFM_MSG_ERR("get_ss_wmm_tbl %d\n", ret);
		return ret;
	}

	if (ctrl.wmm_mapping == dst_link)
		return MACSUCCESS;

	link.ul = 0;
	for (link.ac = 0; link.ac < 4; link.ac++) {
		if (!chk_emp)
			break;
		PLTFM_MEMSET(link.link_list, 0xFF, SS_LINK_SIZE);
		link.wmm = ctrl.wmm_mapping;
		ret = sta_link_cfg(adapter, &link, MAC_AX_SS_LINK_CFG_GET);
		if (ret != MACSUCCESS) {
			PLTFM_MSG_ERR("sta_link_cfg %d\n", ret);
			return ret;
		}
		if (link.link_len) {
			PLTFM_MSG_ERR("wmm%d link%d non empty %d\n",
				      link.wmm, link.ac, link.link_len);
			return MACCMP;
		}
	}

	ctrl.wmm_mapping = dst_link;
	ss_wmm_tbl_cfg(adapter, &ctrl, MAC_AX_SS_WMM_TBL_SET);

	return MACSUCCESS;
}

