/******************************************************************************
 *
 * Copyright(c) 2016 - 2018 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 "halmac_usb_88xx.h"

#if HALMAC_88XX_SUPPORT

enum usb_burst_size {
	USB_BURST_SIZE_3_0 = 0x0,
	USB_BURST_SIZE_2_0_HS = 0x1,
	USB_BURST_SIZE_2_0_FS = 0x2,
	USB_BURST_SIZE_2_0_OTHERS = 0x3,
	USB_BURST_SIZE_UNDEFINE = 0x7F,
};

/**
 * init_usb_cfg_88xx() - init USB
 * @adapter : the adapter of halmac
 * Author : KaiYuan Chang/Ivan Lin
 * Return : enum halmac_ret_status
 * More details of status code can be found in prototype document
 */
enum halmac_ret_status
init_usb_cfg_88xx(struct halmac_adapter *adapter)
{
	u8 value8 = 0;
	struct halmac_api *api = (struct halmac_api *)adapter->halmac_api;

	PLTFM_MSG_TRACE("[TRACE]%s ===>\n", __func__);

	value8 |= (BIT_DMA_MODE | (0x3 << BIT_SHIFT_BURST_CNT));

	if (HALMAC_REG_R8(REG_SYS_CFG2 + 3) == 0x20) {
		 /* usb3.0 */
		value8 |= (USB_BURST_SIZE_3_0 << BIT_SHIFT_BURST_SIZE);
	} else {
		if ((HALMAC_REG_R8(REG_USB_USBSTAT) & 0x3) == 0x1)/* usb2.0 */
			value8 |= USB_BURST_SIZE_2_0_HS << BIT_SHIFT_BURST_SIZE;
		else /* usb1.1 */
			value8 |= USB_BURST_SIZE_2_0_FS << BIT_SHIFT_BURST_SIZE;
	}

	HALMAC_REG_W8(REG_RXDMA_MODE, value8);
	HALMAC_REG_W16(REG_TXDMA_OFFSET_CHK,
		       HALMAC_REG_R16(REG_TXDMA_OFFSET_CHK) | BIT_DROP_DATA_EN);

	PLTFM_MSG_TRACE("[TRACE]%s <===\n", __func__);

	return HALMAC_RET_SUCCESS;
}

/**
 * deinit_usb_cfg_88xx() - deinit USB
 * @adapter : the adapter of halmac
 * Author : KaiYuan Chang/Ivan Lin
 * Return : enum halmac_ret_status
 * More details of status code can be found in prototype document
 */
enum halmac_ret_status
deinit_usb_cfg_88xx(struct halmac_adapter *adapter)
{
	return HALMAC_RET_SUCCESS;
}

/**
 * cfg_usb_rx_agg_88xx() - config rx aggregation
 * @adapter : the adapter of halmac
 * @halmac_rx_agg_mode
 * Author : KaiYuan Chang/Ivan Lin
 * Return : enum halmac_ret_status
 * More details of status code can be found in prototype document
 */
enum halmac_ret_status
cfg_usb_rx_agg_88xx(struct halmac_adapter *adapter,
		    struct halmac_rxagg_cfg *cfg)
{
	u8 dma_usb_agg;
	u8 size;
	u8 timeout;
	u8 agg_enable;
	u32 value32;
	struct halmac_api *api = (struct halmac_api *)adapter->halmac_api;

	PLTFM_MSG_TRACE("[TRACE]%s ===>\n", __func__);

	dma_usb_agg = HALMAC_REG_R8(REG_RXDMA_AGG_PG_TH + 3);
	agg_enable = HALMAC_REG_R8(REG_TXDMA_PQ_MAP);

	switch (cfg->mode) {
	case HALMAC_RX_AGG_MODE_NONE:
		agg_enable &= ~BIT_RXDMA_AGG_EN;
		break;
	case HALMAC_RX_AGG_MODE_DMA:
		agg_enable |= BIT_RXDMA_AGG_EN;
		dma_usb_agg |= BIT(7);
		break;

	case HALMAC_RX_AGG_MODE_USB:
		agg_enable |= BIT_RXDMA_AGG_EN;
		dma_usb_agg &= ~BIT(7);
		break;
	default:
		PLTFM_MSG_ERR("[ERR]unsupported mode\n");
		agg_enable &= ~BIT_RXDMA_AGG_EN;
		break;
	}

	if (cfg->threshold.drv_define == _FALSE) {
		if (HALMAC_REG_R8(REG_SYS_CFG2 + 3) == 0x20) {
			/* usb3.0 */
			size = 0x5;
			timeout = 0xA;
		} else {
			/* usb2.0 */
			size = 0x5;
			timeout = 0x20;
		}
	} else {
		size = cfg->threshold.size;
		timeout = cfg->threshold.timeout;
	}

	value32 = HALMAC_REG_R32(REG_RXDMA_AGG_PG_TH);
	if (cfg->threshold.size_limit_en == _FALSE)
		HALMAC_REG_W32(REG_RXDMA_AGG_PG_TH, value32 & ~BIT_EN_PRE_CALC);
	else
		HALMAC_REG_W32(REG_RXDMA_AGG_PG_TH, value32 | BIT_EN_PRE_CALC);

	HALMAC_REG_W8(REG_TXDMA_PQ_MAP, agg_enable);
	HALMAC_REG_W8(REG_RXDMA_AGG_PG_TH + 3, dma_usb_agg);
	HALMAC_REG_W16(REG_RXDMA_AGG_PG_TH,
		       (u16)(size | (timeout << BIT_SHIFT_DMA_AGG_TO_V1)));

	PLTFM_MSG_TRACE("[TRACE]%s <===\n", __func__);

	return HALMAC_RET_SUCCESS;
}

/**
 * reg_r8_usb_88xx() - read 1byte register
 * @adapter : the adapter of halmac
 * @offset : register offset
 * Author : KaiYuan Chang/Ivan Lin
 * Return : enum halmac_ret_status
 * More details of status code can be found in prototype document
 */
u8
reg_r8_usb_88xx(struct halmac_adapter *adapter, u32 offset)
{
	u8 value8;

	value8 = PLTFM_REG_R8(offset);

	return value8;
}

/**
 * reg_w8_usb_88xx() - write 1byte register
 * @adapter : the adapter of halmac
 * @offset : register offset
 * @value : register value
 * Author : KaiYuan Chang/Ivan Lin
 * Return : enum halmac_ret_status
 * More details of status code can be found in prototype document
 */
enum halmac_ret_status
reg_w8_usb_88xx(struct halmac_adapter *adapter, u32 offset, u8 value)
{
	PLTFM_REG_W8(offset, value);

	return HALMAC_RET_SUCCESS;
}

/**
 * reg_r16_usb_88xx() - read 2byte register
 * @adapter : the adapter of halmac
 * @offset : register offset
 * Author : KaiYuan Chang/Ivan Lin
 * Return : enum halmac_ret_status
 * More details of status code can be found in prototype document
 */
u16
reg_r16_usb_88xx(struct halmac_adapter *adapter, u32 offset)
{
	u16 value16;

	value16 = PLTFM_REG_R16(offset);

	return value16;
}

/**
 * reg_w16_usb_88xx() - write 2byte register
 * @adapter : the adapter of halmac
 * @offset : register offset
 * @value : register value
 * Author : KaiYuan Chang/Ivan Lin
 * Return : enum halmac_ret_status
 * More details of status code can be found in prototype document
 */
enum halmac_ret_status
reg_w16_usb_88xx(struct halmac_adapter *adapter, u32 offset, u16 value)
{
	PLTFM_REG_W16(offset, value);

	return HALMAC_RET_SUCCESS;
}

/**
 * reg_r32_usb_88xx() - read 4byte register
 * @adapter : the adapter of halmac
 * @offset : register offset
 * Author : KaiYuan Chang/Ivan Lin
 * Return : enum halmac_ret_status
 * More details of status code can be found in prototype document
 */
u32
reg_r32_usb_88xx(struct halmac_adapter *adapter, u32 offset)
{
	u32 value32;

	value32 = PLTFM_REG_R32(offset);

	return value32;
}

/**
 * reg_w32_usb_88xx() - write 4byte register
 * @adapter : the adapter of halmac
 * @offset : register offset
 * @value : register value
 * Author : KaiYuan Chang/Ivan Lin
 * Return : enum halmac_ret_status
 * More details of status code can be found in prototype document
 */
enum halmac_ret_status
reg_w32_usb_88xx(struct halmac_adapter *adapter, u32 offset, u32 value)
{
	PLTFM_REG_W32(offset, value);

	return HALMAC_RET_SUCCESS;
}

/**
 * set_usb_bulkout_num_88xx() - inform bulk-out num
 * @adapter : the adapter of halmac
 * @bulkout_num : usb bulk-out number
 * Author : KaiYuan Chang
 * Return : enum halmac_ret_status
 * More details of status code can be found in prototype document
 */
enum halmac_ret_status
set_usb_bulkout_num_88xx(struct halmac_adapter *adapter, u8 num)
{
	adapter->bulkout_num = num;

	return HALMAC_RET_SUCCESS;
}

/**
 * get_usb_bulkout_id_88xx() - get bulk out id for the TX packet
 * @adapter : the adapter of halmac
 * @buf : tx packet, include txdesc
 * @size : tx packet size
 * @id : usb bulk-out id
 * Author : KaiYuan Chang
 * Return : enum halmac_ret_status
 * More details of status code can be found in prototype document
 */
enum halmac_ret_status
get_usb_bulkout_id_88xx(struct halmac_adapter *adapter, u8 *buf, u32 size,
			u8 *id)
{
	enum halmac_qsel queue_sel;
	enum halmac_dma_mapping dma_mapping;

	PLTFM_MSG_TRACE("[TRACE]%s ===>\n", __func__);

	if (!buf) {
		PLTFM_MSG_ERR("[ERR]buf is NULL!!\n");
		return HALMAC_RET_DATA_BUF_NULL;
	}

	if (size == 0) {
		PLTFM_MSG_ERR("[ERR]size is 0!!\n");
		return HALMAC_RET_DATA_SIZE_INCORRECT;
	}

	queue_sel = (enum halmac_qsel)GET_TX_DESC_QSEL(buf);

	switch (queue_sel) {
	case HALMAC_QSEL_VO:
	case HALMAC_QSEL_VO_V2:
		dma_mapping = adapter->pq_map[HALMAC_PQ_MAP_VO];
		break;
	case HALMAC_QSEL_VI:
	case HALMAC_QSEL_VI_V2:
		dma_mapping = adapter->pq_map[HALMAC_PQ_MAP_VI];
		break;
	case HALMAC_QSEL_BE:
	case HALMAC_QSEL_BE_V2:
		dma_mapping = adapter->pq_map[HALMAC_PQ_MAP_BE];
		break;
	case HALMAC_QSEL_BK:
	case HALMAC_QSEL_BK_V2:
		dma_mapping = adapter->pq_map[HALMAC_PQ_MAP_BK];
		break;
	case HALMAC_QSEL_MGNT:
		dma_mapping = adapter->pq_map[HALMAC_PQ_MAP_MG];
		break;
	case HALMAC_QSEL_HIGH:
	case HALMAC_QSEL_BCN:
	case HALMAC_QSEL_CMD:
		dma_mapping = HALMAC_DMA_MAPPING_HIGH;
		break;
	default:
		PLTFM_MSG_ERR("[ERR]Qsel is out of range\n");
		return HALMAC_RET_QSEL_INCORRECT;
	}

	switch (dma_mapping) {
	case HALMAC_DMA_MAPPING_HIGH:
		*id = 0;
		break;
	case HALMAC_DMA_MAPPING_NORMAL:
		*id = 1;
		break;
	case HALMAC_DMA_MAPPING_LOW:
		*id = 2;
		break;
	case HALMAC_DMA_MAPPING_EXTRA:
		*id = 3;
		break;
	default:
		PLTFM_MSG_ERR("[ERR]out of range\n");
		return HALMAC_RET_DMA_MAP_INCORRECT;
	}

	PLTFM_MSG_TRACE("[TRACE]%s <===\n", __func__);

	return HALMAC_RET_SUCCESS;
}

/**
 * cfg_txagg_usb_align_88xx() -config sdio bus tx agg alignment
 * @adapter : the adapter of halmac
 * @enable : function enable(1)/disable(0)
 * @align_size : sdio bus tx agg alignment size (2^n, n = 3~11)
 * Author : Soar Tu
 * Return : enum halmac_ret_status
 * More details of status code can be found in prototype document
 */
enum halmac_ret_status
cfg_txagg_usb_align_88xx(struct halmac_adapter *adapter, u8 enable,
			 u16 align_size)
{
	return HALMAC_RET_NOT_SUPPORT;
}

/**
 * tx_allowed_usb_88xx() - check tx status
 * @adapter : the adapter of halmac
 * @buf : tx packet, include txdesc
 * @size : tx packet size, include txdesc
 * Author : Ivan Lin
 * Return : enum halmac_ret_status
 * More details of status code can be found in prototype document
 */
enum halmac_ret_status
tx_allowed_usb_88xx(struct halmac_adapter *adapter, u8 *buf, u32 size)
{
	return HALMAC_RET_NOT_SUPPORT;
}

/**
 * usb_indirect_reg_r32_88xx() - read MAC reg by SDIO reg
 * @adapter : the adapter of halmac
 * @offset : register offset
 * Author : Soar
 * Return : enum halmac_ret_status
 * More details of status code can be found in prototype document
 */
u32
usb_indirect_reg_r32_88xx(struct halmac_adapter *adapter, u32 offset)
{
	return 0xFFFFFFFF;
}

/**
 * usb_reg_rn_88xx() - read n byte register
 * @adapter : the adapter of halmac
 * @offset : register offset
 * @size : register value size
 * @value : register value
 * Author : Soar
 * Return : enum halmac_ret_status
 * More details of status code can be found in prototype document
 */
enum halmac_ret_status
usb_reg_rn_88xx(struct halmac_adapter *adapter, u32 offset, u32 size,
		u8 *value)
{
	return HALMAC_RET_NOT_SUPPORT;
}

/**
 * get_usb_tx_addr_88xx() - get CMD53 addr for the TX packet
 * @adapter : the adapter of halmac
 * @buf : tx packet, include txdesc
 * @size : tx packet size
 * @pcmd53_addr : cmd53 addr value
 * Author : KaiYuan Chang/Ivan Lin
 * Return : enum halmac_ret_status
 * More details of status code can be found in prototype document
 */
enum halmac_ret_status
get_usb_tx_addr_88xx(struct halmac_adapter *adapter, u8 *buf, u32 size,
		     u32 *cmd53_addr)
{
	return HALMAC_RET_NOT_SUPPORT;
}

enum halmac_ret_status
set_usb_mode_88xx(struct halmac_adapter *adapter, enum halmac_usb_mode mode)
{
	u32 usb_tmp;
	struct halmac_api *api = (struct halmac_api *)adapter->halmac_api;
	enum halmac_usb_mode cur_mode;

	cur_mode = (HALMAC_REG_R8(REG_SYS_CFG2 + 3) == 0x20) ?
					HALMAC_USB_MODE_U3 : HALMAC_USB_MODE_U2;

	/*check if HW supports usb2_usb3 switch*/
	usb_tmp = HALMAC_REG_R32(REG_PAD_CTRL2);
	if (_FALSE == (BIT_GET_USB23_SW_MODE_V1(usb_tmp) |
	    (usb_tmp & BIT_USB3_USB2_TRANSITION))) {
		PLTFM_MSG_ERR("[ERR]u2/u3 switch\n");
		return HALMAC_RET_USB2_3_SWITCH_UNSUPPORT;
	}

	if (mode == cur_mode) {
		PLTFM_MSG_ERR("[ERR]u2/u3 unchange\n");
		return HALMAC_RET_USB_MODE_UNCHANGE;
	}

	/* Enable IO wrapper timeout */
	if (adapter->chip_id == HALMAC_CHIP_ID_8822B ||
	    adapter->chip_id == HALMAC_CHIP_ID_8821C)
		HALMAC_REG_W8_CLR(REG_SW_MDIO + 3, BIT(0));

	usb_tmp &= ~(BIT_USB23_SW_MODE_V1(0x3));

	if (mode == HALMAC_USB_MODE_U2)
		HALMAC_REG_W32(REG_PAD_CTRL2,
			       usb_tmp |
			       BIT_USB23_SW_MODE_V1(HALMAC_USB_MODE_U2) |
			       BIT_RSM_EN_V1);
	else
		HALMAC_REG_W32(REG_PAD_CTRL2,
			       usb_tmp |
			       BIT_USB23_SW_MODE_V1(HALMAC_USB_MODE_U3) |
			       BIT_RSM_EN_V1);

	HALMAC_REG_W8(REG_PAD_CTRL2 + 1, 4);
	HALMAC_REG_W16_SET(REG_SYS_PW_CTRL, BIT_APFM_OFFMAC);
	PLTFM_DELAY_US(1000);
	HALMAC_REG_W32_SET(REG_PAD_CTRL2, BIT_NO_PDN_CHIPOFF_V1);

	return HALMAC_RET_SUCCESS;
}

enum halmac_ret_status
usbphy_write_88xx(struct halmac_adapter *adapter, u8 addr, u16 data, u8 speed)
{
	struct halmac_api *api = (struct halmac_api *)adapter->halmac_api;

	if (speed == HAL_INTF_PHY_USB3) {
		HALMAC_REG_W8(0xff0d, (u8)data);
		HALMAC_REG_W8(0xff0e, (u8)(data >> 8));
		HALMAC_REG_W8(0xff0c, addr | BIT(7));
	} else if (speed == HAL_INTF_PHY_USB2) {
		HALMAC_REG_W8(0xfe41, (u8)data);
		HALMAC_REG_W8(0xfe40, addr);
		HALMAC_REG_W8(0xfe42, 0x81);
	} else {
		PLTFM_MSG_ERR("[ERR]Error USB Speed !\n");
		return HALMAC_RET_NOT_SUPPORT;
	}

	return HALMAC_RET_SUCCESS;
}

u16
usbphy_read_88xx(struct halmac_adapter *adapter, u8 addr, u8 speed)
{
	struct halmac_api *api = (struct halmac_api *)adapter->halmac_api;
	u16 value = 0;

	if (speed == HAL_INTF_PHY_USB3) {
		HALMAC_REG_W8(0xff0c, addr | BIT(6));
		value = (u16)(HALMAC_REG_R32(0xff0c) >> 8);
	} else if (speed == HAL_INTF_PHY_USB2) {
		if (addr >= 0xE0 && addr <= 0xFF)
			addr -= 0x20;
		if (addr >= 0xC0 && addr <= 0xDF) {
			HALMAC_REG_W8(0xfe40, addr);
			HALMAC_REG_W8(0xfe42, 0x81);
			value = HALMAC_REG_R8(0xfe43);
		} else {
			PLTFM_MSG_ERR("[ERR]phy offset\n");
			return HALMAC_RET_NOT_SUPPORT;
		}
	} else {
		PLTFM_MSG_ERR("[ERR]usb speed !\n");
		return HALMAC_RET_NOT_SUPPORT;
	}

	return value;
}

#endif /* HALMAC_88XX_SUPPORT */
