/******************************************************************************
 *
 * Copyright(c) 2015 - 2019 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 _RTW_SDIO_C_

#include <drv_types.h>		/* struct dvobj_priv and etc. */
#include <drv_types_sdio.h>	/* RTW_SDIO_ADDR_CMD52_GEN */

/*
 * Description:
 *	Use SDIO cmd52 or cmd53 to read/write data
 *
 * Parameters:
 *	d	pointer of device object(struct dvobj_priv)
 *	addr	SDIO address, 17 bits
 *	buf	buffer for I/O
 *	len	length
 *	write	0:read, 1:write
 *	cmd52	0:cmd52, 1:cmd53
 *
 * Return:
 *	_SUCCESS	I/O ok.
 *	_FAIL		I/O fail.
 */
static u8 sdio_io(struct dvobj_priv *d, u32 addr, void *buf, size_t len, u8 write, u8 cmd52)
{
#ifdef DBG_SDIO
#if (DBG_SDIO >= 3)
	struct sdio_data *sdio;
#endif /* DBG_SDIO >= 3 */
#endif /* DBG_SDIO */
	u32 addr_drv;	/* address with driver defined bit */
	int err;
	u8 retry = 0;
	u8 stop_retry = _FALSE;	/* flag for stopping retry or not */


#ifdef DBG_SDIO
#if (DBG_SDIO >= 3)
	sdio = &d->intf_data;
#endif /* DBG_SDIO >= 3 */
#endif /* DBG_SDIO */

	if (rtw_is_surprise_removed(dvobj_get_primary_adapter(d))) {
		RTW_ERR("%s: bSurpriseRemoved, skip %s 0x%05x, %zu bytes\n",
			__FUNCTION__, write?"write":"read", addr, len);
		return _FAIL;
	}

	addr_drv = addr;
	if (cmd52)
		addr_drv = RTW_SDIO_ADDR_CMD52_GEN(addr_drv);

	do {
		if (write)
			err = d->intf_ops->write(d, addr_drv, buf, len, 0);
		else
			err = d->intf_ops->read(d, addr_drv, buf, len, 0);
		if (!err) {
			if (retry) {
				RTW_INFO("%s: Retry %s OK! addr=0x%05x %zu bytes, retry=%u,%u\n",
					 __FUNCTION__, write?"write":"read",
					 addr, len, retry, ATOMIC_READ(&d->continual_io_error));
				RTW_INFO_DUMP("Data: ", buf, len);
			}
			rtw_reset_continual_io_error(d);
			break;
		}
		RTW_ERR("%s: %s FAIL! error(%d) addr=0x%05x %zu bytes, retry=%u,%u\n",
			__FUNCTION__, write?"write":"read", err, addr, len,
			retry, ATOMIC_READ(&d->continual_io_error));

#ifdef DBG_SDIO
#if (DBG_SDIO >= 3)
		if (sdio->dbg_enable) {
			if (sdio->err_test && sdio->err_test_triggered)
				sdio->err_test = 0;

			if (sdio->err_stop) {
				RTW_ERR("%s: I/O error! Set surprise remove flag ON!\n",
					__FUNCTION__);
				rtw_set_surprise_removed(dvobj_get_primary_adapter(d));
				return _FAIL;
			}
		}
#endif /* DBG_SDIO >= 3 */
#endif /* DBG_SDIO */

		retry++;
		stop_retry = rtw_inc_and_chk_continual_io_error(d);
		if ((err == -1) || (stop_retry == _TRUE) || (retry > SD_IO_TRY_CNT)) {
			/* critical error, unrecoverable */
			RTW_ERR("%s: Fatal error! Set surprise remove flag ON! (retry=%u,%u)\n",
				__FUNCTION__, retry, ATOMIC_READ(&d->continual_io_error));
			rtw_set_surprise_removed(dvobj_get_primary_adapter(d));
			return _FAIL;
		}

		/* WLAN IOREG or SDIO Local */
		if ((addr & 0x10000) || !(addr & 0xE000)) {
			RTW_WARN("%s: Retry %s addr=0x%05x %zu bytes, retry=%u,%u\n",
				 __FUNCTION__, write?"write":"read", addr, len,
				 retry, ATOMIC_READ(&d->continual_io_error));
			continue;
		}
		return _FAIL;
	} while (1);

	return _SUCCESS;
}

u8 rtw_sdio_read_cmd52(struct dvobj_priv *d, u32 addr, void *buf, size_t len)
{
	return sdio_io(d, addr, buf, len, 0, 1);
}

u8 rtw_sdio_read_cmd53(struct dvobj_priv *d, u32 addr, void *buf, size_t len)
{
	return sdio_io(d, addr, buf, len, 0, 0);
}

u8 rtw_sdio_write_cmd52(struct dvobj_priv *d, u32 addr, void *buf, size_t len)
{
	return sdio_io(d, addr, buf, len, 1, 1);
}

u8 rtw_sdio_write_cmd53(struct dvobj_priv *d, u32 addr, void *buf, size_t len)
{
	return sdio_io(d, addr, buf, len, 1, 0);
}

u8 rtw_sdio_f0_read(struct dvobj_priv *d, u32 addr, void *buf, size_t len)
{
	int err;
	u8 ret;


	ret = _SUCCESS;
	addr = RTW_SDIO_ADDR_F0_GEN(addr);

	err = d->intf_ops->read(d, addr, buf, len, 0);
	if (err)
		ret = _FAIL;

	return ret;
}
