/*
 * SdioAdapter.c
 *
 * Copyright(c) 1998 - 2009 Texas Instruments. All rights reserved.
 * Copyright(c) 2008 - 2009 Google, Inc. All rights reserved.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *  * Neither the name Texas Instruments nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/** \file   SdioAdapter.c
 *  \brief  The SDIO driver adapter. Platform dependent.
 *
 * An adaptation layer between the lower SDIO driver (in BSP) and the upper Sdio
 * Used for issuing all SDIO transaction types towards the lower SDIO-driver.
 * Makes the decision whether to use Sync or Async transaction, and reflects it
 *     by the return value and calling its callback in case of Async.
 *
 *  \see    SdioAdapter.h, SdioDrv.c & h
 */

#ifdef CONFIG_MMC_EMBEDDED_SDIO
#include <linux/kernel.h>
#include <linux/mutex.h>
#include <linux/mmc/core.h>
#include <linux/mmc/card.h>
#include <linux/mmc/sdio_func.h>
#include <linux/mmc/sdio_ids.h>
#include "TxnDefs.h"

#define TI_SDIO_DEBUG

#define TIWLAN_MMC_MAX_DMA                 8192

int wifi_set_carddetect( int on );

static struct sdio_func *tiwlan_func = NULL;
static struct completion sdio_wait;

ETxnStatus sdioAdapt_TransactBytes (unsigned int  uFuncId,
                                    unsigned int  uHwAddr,
                                    void *        pHostAddr,
                                    unsigned int  uLength,
                                    unsigned int  bDirection,
                                    unsigned int  bMore);

static int sdio_wifi_probe(struct sdio_func *func,
                           const struct sdio_device_id *id)
{
        int rc;

        printk("%s: %d\n", __FUNCTION__, func->class);

        if (func->class != SDIO_CLASS_WLAN)
                return -EINVAL;

        sdio_claim_host(func);

        rc = sdio_enable_func(func);
        if (rc)
                goto err1;
        rc = sdio_set_block_size(func, 512);

        if (rc) {
                printk("%s: Unable to set blocksize\n", __FUNCTION__);
                goto err2;
        }

        tiwlan_func = func;
        complete(&sdio_wait);
        return 0;
err2:
        sdio_disable_func(func);
err1:
        sdio_release_host(func);
        complete(&sdio_wait);
        return rc;
}

static void sdio_wifi_remove(struct sdio_func *func)
{
}

static const struct sdio_device_id sdio_wifi_ids[] = {
        { SDIO_DEVICE_CLASS(SDIO_CLASS_WLAN)    },
        {                                       },
};

MODULE_DEVICE_TABLE(sdio, sdio_wifi_ids);

static struct sdio_driver sdio_wifi_driver = {
        .probe          = sdio_wifi_probe,
        .remove         = sdio_wifi_remove,
        .name           = "sdio_wifi",
        .id_table       = sdio_wifi_ids,
};

ETxnStatus sdioAdapt_TransactBytes (unsigned int  uFuncId,
                                    unsigned int  uHwAddr,
                                    void *        pHostAddr,
                                    unsigned int  uLength,
                                    unsigned int  bDirection,
                                    unsigned int  bMore);

int sdioAdapt_ConnectBus (void *        fCbFunc,
                          void *        hCbArg,
                          unsigned int  uBlkSizeShift,
                          unsigned int  uSdioThreadPriority,
                          unsigned char **pTxDmaSrcAddr)
{
	int rc;

	init_completion(&sdio_wait);
	wifi_set_carddetect( 1 );
	rc = sdio_register_driver(&sdio_wifi_driver);
	if (rc < 0) {
		printk(KERN_ERR "%s: Fail to register sdio_wifi_driver\n", __func__);
		return rc;
	}
	if (!wait_for_completion_timeout(&sdio_wait, msecs_to_jiffies(10000))) {
		printk(KERN_ERR "%s: Timed out waiting for device detect\n", __func__);
		sdio_unregister_driver(&sdio_wifi_driver);
		return -ENODEV;
	}
	/* Provide the DMA buffer address to the upper layer so it will use it as the transactions host buffer. */
	if (pTxDmaSrcAddr) { /* Dm: check what to do with it */
		*pTxDmaSrcAddr = kmalloc(TIWLAN_MMC_MAX_DMA, GFP_KERNEL | GFP_DMA);
	}
	return 0;
}

int sdioAdapt_DisconnectBus (void)
{
	if (tiwlan_func) {
		sdio_disable_func( tiwlan_func );
		sdio_release_host( tiwlan_func );
	}
	wifi_set_carddetect( 0 );
	sdio_unregister_driver(&sdio_wifi_driver);
	return 0;
}

ETxnStatus sdioAdapt_TransactBytes (unsigned int  uFuncId,
                                    unsigned int  uHwAddr,
                                    void *        pHostAddr,
                                    unsigned int  uLength,
                                    unsigned int  bDirection,
                                    unsigned int  bMore)
{
	unsigned char *pData = pHostAddr;
	unsigned int i;
	int rc = 0, final_rc = 0;

	for (i = 0; i < uLength; i++) {
		if( bDirection ) {
			if (uFuncId == 0)
				*pData = (unsigned char)sdio_f0_readb(tiwlan_func, uHwAddr, &rc);
			else
				*pData = (unsigned char)sdio_readb(tiwlan_func, uHwAddr, &rc);
		}
		else {
			if (uFuncId == 0)
				sdio_f0_writeb(tiwlan_func, *pData, uHwAddr, &rc);
			else
				sdio_writeb(tiwlan_func, *pData, uHwAddr, &rc);
		}
		if( rc ) {
			final_rc = rc;
		}
#ifdef TI_SDIO_DEBUG
		printk(KERN_INFO "%c52: [0x%x](%u) %c 0x%x\n", (bDirection ? 'R' : 'W'), uHwAddr, uLength, (bDirection ? '=' : '<'), (unsigned)*pData);
#endif
		uHwAddr++;
		pData++;
	}
	/* If failed return ERROR, if succeeded return COMPLETE */
	if (final_rc) {
		return TXN_STATUS_ERROR;
	}
	return TXN_STATUS_COMPLETE;
}

ETxnStatus sdioAdapt_Transact (unsigned int  uFuncId,
                               unsigned int  uHwAddr,
                               void *        pHostAddr,
                               unsigned int  uLength,
                               unsigned int  bDirection,
                               unsigned int  bBlkMode,
                               unsigned int  bFixedAddr,
                               unsigned int  bMore)
{
	int rc;

	if (uFuncId == 0)
		return sdioAdapt_TransactBytes (uFuncId, uHwAddr, pHostAddr,
						uLength, bDirection, bMore);
	if (bDirection) {
		if (bFixedAddr)
			rc = sdio_memcpy_fromio(tiwlan_func, pHostAddr, uHwAddr, uLength);
		else
			rc = sdio_readsb(tiwlan_func, pHostAddr, uHwAddr, uLength);

	}
	else {
		if (bFixedAddr)
			rc = sdio_memcpy_toio(tiwlan_func, uHwAddr, pHostAddr, uLength);
		else
			rc = sdio_writesb(tiwlan_func, uHwAddr, pHostAddr, uLength);
	}
#ifdef TI_SDIO_DEBUG
	if (uLength == 1)
	        printk(KERN_INFO "%c53: [0x%x](%u) %c 0x%x\n", (bDirection ? 'R' : 'W'), uHwAddr, uLength, (bDirection ? '=' : '<'), (unsigned)(*(char *)pHostAddr));
	else if (uLength == 2)
	        printk(KERN_INFO "%c53: [0x%x](%u) %c 0x%x\n", (bDirection ? 'R' : 'W'), uHwAddr, uLength, (bDirection ? '=' : '<'), (unsigned)(*(short *)pHostAddr));
	else if (uLength == 4)
	        printk(KERN_INFO "%c53: [0x%x](%u) %c 0x%x\n", (bDirection ? 'R' : 'W'), uHwAddr, uLength, (bDirection ? '=' : '<'), (unsigned)(*(long *)pHostAddr));
	else
		printk(KERN_INFO "%c53: [0x%x](%u) F[%d] B[%d] I[%d] = %d\n", (bDirection ? 'R' : 'W'), uHwAddr, uLength, uFuncId, bBlkMode, bFixedAddr, rc);
#endif
	/* If failed return ERROR, if succeeded return COMPLETE */
	if (rc) {
		return TXN_STATUS_ERROR;
	}
	return TXN_STATUS_COMPLETE;
}

#else

#include "SdioDrvDbg.h"
#include "TxnDefs.h"
#include "SdioAdapter.h"
#include "SdioDrv.h"
#include "bmtrace_api.h"
#include <linux/slab.h>

#ifdef SDIO_1_BIT /* see also in SdioDrv.c */
#define SDIO_BITS_CODE   0x80 /* 1 bits */
#else
#define SDIO_BITS_CODE   0x82 /* 4 bits */
#endif

static unsigned char *pDmaBufAddr = 0;

/************************************************************************
 * Defines
 ************************************************************************/
/* Sync/Async Threshold */
#ifdef FULL_ASYNC_MODE
#define SYNC_ASYNC_LENGTH_THRESH	0     /* Use Async for all transactions */
#else
#define SYNC_ASYNC_LENGTH_THRESH	360   /* Use Async for transactions longer than this threshold (in bytes) */
#endif

#define MAX_RETRIES                 10

#define MAX_BUS_TXN_SIZE            8192  /* Max bus transaction size in bytes (for the DMA buffer allocation) */

/* For block mode configuration */
#define FN0_FBR2_REG_108                    0x210
#define FN0_FBR2_REG_108_BIT_MASK           0xFFF

int sdioAdapt_ConnectBus (void *        fCbFunc,
                          void *        hCbArg,
                          unsigned int  uBlkSizeShift,
                          unsigned int  uSdioThreadPriority,
                          unsigned char **pRxDmaBufAddr,
                          unsigned int  *pRxDmaBufLen,
                          unsigned char **pTxDmaBufAddr,
                          unsigned int  *pTxDmaBufLen)
{
	unsigned char  uByte;
	unsigned long  uLong;
	unsigned long  uCount = 0;
	unsigned int   uBlkSize = 1 << uBlkSizeShift;
	int            iStatus;

	if (uBlkSize < SYNC_ASYNC_LENGTH_THRESH)
	{
		PERR1("%s(): Block-Size should be bigger than SYNC_ASYNC_LENGTH_THRESH!!\n", __FUNCTION__ );
	}

	/* Enabling clocks if thet are not enabled */
	sdioDrv_clk_enable();

	/* Allocate a DMA-able buffer and provide it to the upper layer to be used for all read and write transactions */
	if (pDmaBufAddr == 0) /* allocate only once (in case this function is called multiple times) */
	{
		pDmaBufAddr = kmalloc(MAX_BUS_TXN_SIZE, GFP_KERNEL | GFP_DMA);
		if (pDmaBufAddr == 0)
		{
			iStatus = -1;
			goto fail;
		}
	}
	*pRxDmaBufAddr = *pTxDmaBufAddr = pDmaBufAddr;
	*pRxDmaBufLen  = *pTxDmaBufLen  = MAX_BUS_TXN_SIZE;

	/* Init SDIO driver and HW */
	iStatus = sdioDrv_ConnectBus (fCbFunc, hCbArg, uBlkSizeShift, uSdioThreadPriority);
	if (iStatus) { goto fail; }

	/* Send commands sequence: 0, 5, 3, 7 */
	iStatus = sdioDrv_ExecuteCmd (SD_IO_GO_IDLE_STATE, 0, MMC_RSP_NONE, &uByte, sizeof(uByte));
	if (iStatus)
	{
		printk("%s %d command number: %d failed\n", __FUNCTION__, __LINE__, SD_IO_GO_IDLE_STATE);
		goto fail;
	}

	iStatus = sdioDrv_ExecuteCmd (SDIO_CMD5, VDD_VOLTAGE_WINDOW, MMC_RSP_R4, &uByte, sizeof(uByte));
	if (iStatus) {
		printk("%s %d command number: %d failed\n", __FUNCTION__, __LINE__, SDIO_CMD5);
		goto fail;
	}

	iStatus = sdioDrv_ExecuteCmd (SD_IO_SEND_RELATIVE_ADDR, 0, MMC_RSP_R6, &uLong, sizeof(uLong));
	if (iStatus) {
		printk("%s %d command number: %d failed\n", __FUNCTION__, __LINE__, SD_IO_SEND_RELATIVE_ADDR);
		goto fail;
	}

	iStatus = sdioDrv_ExecuteCmd (SD_IO_SELECT_CARD, uLong, MMC_RSP_R6, &uByte, sizeof(uByte));
	if (iStatus) {
		printk("%s %d command number: %d failed\n", __FUNCTION__, __LINE__, SD_IO_SELECT_CARD);
		goto fail;
	}

    /* NOTE:
     * =====
     * Each of the following loops is a workaround for a HW bug that will be solved in PG1.1 !!
     * Each write of CMD-52 to function-0 should use it as follows:
     * 1) Write the desired byte using CMD-52
     * 2) Read back the byte using CMD-52
     * 3) Write two dummy bytes to address 0xC8 using CMD-53
     * 4) If the byte read in step 2 is different than the written byte repeat the sequence
     */

	/* set device side bus width to 4 bit (for 1 bit write 0x80 instead of 0x82) */
	do
	{
		uByte = SDIO_BITS_CODE;
		iStatus = sdioDrv_WriteSyncBytes (TXN_FUNC_ID_CTRL, CCCR_BUS_INTERFACE_CONTOROL, &uByte, 1, 1);
		if (iStatus) { goto fail; }

		iStatus = sdioDrv_ReadSyncBytes (TXN_FUNC_ID_CTRL, CCCR_BUS_INTERFACE_CONTOROL, &uByte, 1, 1);
		if (iStatus) { goto fail; }
        
		iStatus = sdioDrv_WriteSync (TXN_FUNC_ID_CTRL, 0xC8, &uLong, 2, 1, 1);
		if (iStatus) { goto fail; }

		uCount++;

	} while ((uByte != SDIO_BITS_CODE) && (uCount < MAX_RETRIES));

	uCount = 0;

	/* allow function 2 */
	do
	{
		uByte = 4;
		iStatus = sdioDrv_WriteSyncBytes (TXN_FUNC_ID_CTRL, CCCR_IO_ENABLE, &uByte, 1, 1);
		if (iStatus) { goto fail; }

		iStatus = sdioDrv_ReadSyncBytes (TXN_FUNC_ID_CTRL, CCCR_IO_ENABLE, &uByte, 1, 1);
		if (iStatus) { goto fail; }
        
		iStatus = sdioDrv_WriteSync (TXN_FUNC_ID_CTRL, 0xC8, &uLong, 2, 1, 1);
		if (iStatus) { goto fail; }

		uCount++;

	} while ((uByte != 4) && (uCount < MAX_RETRIES));


#ifdef SDIO_IN_BAND_INTERRUPT

	uCount = 0;

	do
	{
		uByte = 3;
		iStatus = sdioDrv_WriteSyncBytes (TXN_FUNC_ID_CTRL, CCCR_INT_ENABLE, &uByte, 1, 1);
		if (iStatus) { goto fail; }

		iStatus = sdioDrv_ReadSyncBytes (TXN_FUNC_ID_CTRL, CCCR_INT_ENABLE, &uByte, 1, 1);
		if (iStatus) { goto fail; }

		iStatus = sdioDrv_WriteSync (TXN_FUNC_ID_CTRL, 0xC8, &uLong, 2, 1, 1);
		if (iStatus) { goto fail; }

		uCount++;

	} while ((uByte != 3) && (uCount < MAX_RETRIES));


#endif

	uCount = 0;

	/* set block size for SDIO block mode */
	do
	{
		uLong = uBlkSize;
		iStatus = sdioDrv_WriteSync (TXN_FUNC_ID_CTRL, FN0_FBR2_REG_108, &uLong, 2, 1, 1);
		if (iStatus) { goto fail; }

		iStatus = sdioDrv_ReadSync (TXN_FUNC_ID_CTRL, FN0_FBR2_REG_108, &uLong, 2, 1, 1);
		if (iStatus) { goto fail; }

		iStatus = sdioDrv_WriteSync (TXN_FUNC_ID_CTRL, 0xC8, &uLong, 2, 1, 1);
		if (iStatus) { goto fail; }

		uCount++;

	} while (((uLong & FN0_FBR2_REG_108_BIT_MASK) != uBlkSize) && (uCount < MAX_RETRIES));

	if (uCount >= MAX_RETRIES)
	{
		/* Failed to write CMD52_WRITE to function 0 */
		iStatus = (int)uCount;
	}

fail:
	/* Disable the clocks for now */
	sdioDrv_clk_disable();

	return iStatus;
}


int sdioAdapt_DisconnectBus (void)
{
	if (pDmaBufAddr)
	{
		kfree (pDmaBufAddr);
		pDmaBufAddr = 0;
	}

	return sdioDrv_DisconnectBus ();
}

ETxnStatus sdioAdapt_Transact (unsigned int  uFuncId,
                               unsigned int  uHwAddr,
                               void *        pHostAddr,
                               unsigned int  uLength,
                               unsigned int  bDirection,
                               unsigned int  bBlkMode,
                               unsigned int  bFixedAddr,
                               unsigned int  bMore)
{
	int iStatus;

	/* If transction length is below threshold, use Sync methods */
	if (uLength < SYNC_ASYNC_LENGTH_THRESH)
	{
		/* Call read or write Sync method */
		if (bDirection)
		{
			CL_TRACE_START_L2();
			iStatus = sdioDrv_ReadSync (uFuncId, uHwAddr, pHostAddr, uLength, bFixedAddr, bMore);
			CL_TRACE_END_L2("tiwlan_drv.ko", "INHERIT", "SDIO", ".ReadSync");
		}
		else
		{
			CL_TRACE_START_L2();
			iStatus = sdioDrv_WriteSync (uFuncId, uHwAddr, pHostAddr, uLength, bFixedAddr, bMore);
			CL_TRACE_END_L2("tiwlan_drv.ko", "INHERIT", "SDIO", ".WriteSync");
		}

		/* If failed return ERROR, if succeeded return COMPLETE */
		if (iStatus)
		{
			return TXN_STATUS_ERROR;
		}
		return TXN_STATUS_COMPLETE;
	}

	/* If transction length is above threshold, use Async methods */
	else
	{
		/* Call read or write Async method */
		if (bDirection)
		{
			CL_TRACE_START_L2();
			iStatus = sdioDrv_ReadAsync (uFuncId, uHwAddr, pHostAddr, uLength, bBlkMode, bFixedAddr, bMore);
			CL_TRACE_END_L2("tiwlan_drv.ko", "INHERIT", "SDIO", ".ReadAsync");
		}
		else
		{
			CL_TRACE_START_L2();
			iStatus = sdioDrv_WriteAsync (uFuncId, uHwAddr, pHostAddr, uLength, bBlkMode, bFixedAddr, bMore);
			CL_TRACE_END_L2("tiwlan_drv.ko", "INHERIT", "SDIO", ".WriteAsync");
		}

		/* If failed return ERROR, if succeeded return PENDING */
		if (iStatus)
		{
			return TXN_STATUS_ERROR;
		}
		return TXN_STATUS_PENDING;
	}
}
         
ETxnStatus sdioAdapt_TransactBytes (unsigned int  uFuncId,
                                    unsigned int  uHwAddr,
                                    void *        pHostAddr,
                                    unsigned int  uLength,
                                    unsigned int  bDirection,
                                    unsigned int  bMore)
{
	static unsigned int lastMore = 0;
	int iStatus;

	if ((bMore == 1) || (lastMore == bMore))
	{
		sdioDrv_clk_enable();
	}

	/* Call read or write bytes Sync method */
	if (bDirection)
	{
		iStatus = sdioDrv_ReadSyncBytes (uFuncId, uHwAddr, pHostAddr, uLength, bMore);
	}
	else
	{
		iStatus = sdioDrv_WriteSyncBytes (uFuncId, uHwAddr, pHostAddr, uLength, bMore);
	}

	if (bMore == 0)
	{
		sdioDrv_clk_disable();
	}
	lastMore = bMore;

	/* If failed return ERROR, if succeeded return COMPLETE */
	if (iStatus)
	{
		return TXN_STATUS_ERROR;
	}
	return TXN_STATUS_COMPLETE;
}
#endif
