/*
 * Copyright (c) 2016, Texas Instruments Incorporated
 * 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 of Texas Instruments Incorporated 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 : sdmmc.c
 *
 * \brief: source file for sdmmc block read and write.
 *
 */
 
#include "dm8127_types.h"
#include "dm8127_Platform.h"
#include "stdio.h"
#include "sdmmc.h"
#include "sdio.h"
#if 0
/**
 *\brief :Enables mmc clock
 * 
 *\Params:
 *         mmc_base : Base Address of the MMC controller
 *         clock    : Clock Frequency
 * 
 *\Returns:
 *         -1			On Failure
 *          0         On Success
 */
SINT32 enable_mmc_clock(SDIO_controller_t *mmc_base, UINT32 clock)
{
	UINT32 val;
	UINT32 regval;
	
	/* disables, cen and set data timeout value */
	regval = readl(&mmc_base->sysctl);
	regval &= ~(0xF0007); 
	regval |= (0xe << 16); 

	switch (clock) {
	case CLK_INITSEQ:
		val = (SDMMC_REF_CLK * 1000 / (80 * 2)); 
		break;
	case CLK_400KHZ:
		val = (SDMMC_REF_CLK * 1000 / (1600) ); /* 1.6 MHZ */
		break;
	case CLK_12MHZ:
		val = (SDMMC_REF_CLK * 1000 / (12000));
		break;
	case CLK_24MHZ:
		val = (SDMMC_REF_CLK * 1000 / (24000));
		break;
	case CLK_48MHZ:
		val = (SDMMC_REF_CLK * 1000 / (48000));
		break;
	default:
		return FAILURE;
	}

	/* Select clock frequency */
	regval &= ~(0xffc0);
	regval |= (val << 6);

	regval |= (0x1); /* enable clock */
	writel(regval, &mmc_base->sysctl);
	delay(10);
	
	/* Wait until stable? */
	while ((readl(&mmc_base->sysctl) & 0x2) == 0x0);

	writel(readl(&mmc_base->sysctl) | 0x4, &mmc_base->sysctl);
	delay(1000);

	return SUCCESS;
}


/*
 * Initialise the SD/SDIO Controller
 * 
 * Parameters:
 * mmc_base:	Base Address of the SD/SDIO controller
 * 
 * Returns:		void
 * 
 */ 
void mmcinit (SDIO_controller_t *mmc_base)
{
	UINT32 regval;
	regval = 0;

	regval = readl(&mmc_base->sysconfig);
	writel(regval | 0x2, &mmc_base->sysconfig); /* Software reset */

	while ((readl(&mmc_base->sysstatus) & 0x1) == 0); /* reset status ? */

	regval = readl(&mmc_base->sysctl);
	regval |= (1 << 24); /* Software reset */
	writel(regval, &mmc_base->sysctl); /* reset the host controller */
	
	while ((readl(&mmc_base->sysctl) & (1 << 24)) != 0x0);/* reset done? */

	/* Support for 1.8V and 3V */
	//writel(0x6000000, &mmc_base->capa);
	writel(0x6E10080, &mmc_base->capa);
	
	/* 1 Bit Mode, 3V Bus Voltage (SVDS) and Bus Pwr Off */
	writel(0x00000C00, &mmc_base->hctl);
	
	/* Set Block Size to 512 Bytes */
	writel(0x200, &mmc_base->blk);
	
	/* Support for 1.8V, 3V cards */
	writel(readl(&mmc_base->capa) | 0x6000000, &mmc_base->capa);

	/* Functional Mode, No INIT Sequence */
	regval = readl(&mmc_base->con) & (0x3 << 9);
	writel(regval, &mmc_base->con);
	delay(1000);

	enable_mmc_clock(mmc_base, CLK_INITSEQ);
	
	/* Switch on Bus Power */
	writel(readl(&mmc_base->hctl) | (1 << 8), &mmc_base->hctl); 

	/* Enable Interrupts */
	writel(0x307F0033, &mmc_base->ie);
	
	/* Send Initialisation Sequence for 80 clock cycles */
	writel(0x00000602, &mmc_base->con);
	delay(1000);
	
	/* Send CMD0 */
	writel(SDMMC_CMD0, &mmc_base->cmd);
	while( (readl(&mmc_base->stat) & 0x1) == 0);
	writel(0x1, &mmc_base->stat);
	
	/* Send CMD0 */
	writel(SDMMC_CMD0, &mmc_base->cmd);
	while( (readl(&mmc_base->stat) & 0x1) == 0);
	writel(0x1, &mmc_base->stat);
	
	/* End Init sequence */
	regval = readl(&mmc_base->con) & ~0x2;
	writel(regval, &mmc_base->con);
}
#endif
#if 0
/*
 * Send a command
 * 
 * Parameters:
 * mmc_base:	Base Address of the SD/SDIO controller
 * cmd:			command
 * arg:			arguments to the command
 * 
 * Returns:		
 * 	0 		On Success
 *  > 0     On Failure (Contents of HS_STAT register)
 *  
 */ 
SINT32 send_cmd(SDIO_controller_t * mmc_base, UINT32 cmd, UINT32 arg)
{
	UINT32 regval;
	UINT32 status;

	status = 0;
	/* Software reset cmd and dat lines */
	regval = readl(&mmc_base->sysctl);
	regval |= 0x6000000;
	writel(regval, &mmc_base->sysctl);

	while( (readl(&mmc_base->sysctl) & 0x6000000) != 0);

	/* wait until you are allowed to send cmd */
	while ((readl(&mmc_base->pstate) & 0x2) == 0x2);
	
	writel(0x307F0037, &mmc_base->stat);
	writel(0x200, &mmc_base->blk);
	
	writel(arg, &mmc_base->arg);
	writel(cmd, &mmc_base->cmd);
	delay(0x1000);

	for ( ;; ) {
		do {
			status = readl(&mmc_base->stat);
		} while (status == 0);

		if ((status & STAT_ERRI))
			return status;

		if (status & STAT_CC) { /* command complete */
			writel(STAT_CC, &mmc_base->stat); 
			return SUCCESS;
		}
	}
}
#endif
#if 0
/*
 * Detect an SDIO/SD Card
 * 
 * Parameters:
 * mmc_base:			Base Address of the SD/SDIO controller
 * high_capacity_card:	Returns 1 if a High Capacity SD card is found.
 * 
 * Returns:	The Type of Card
 * 
 *  (1):	SDIO Card is Found
 *  (2):	SD Card is Found (SD_CARD)
 *  (3):	MMC Card is Found
 * (-1):	Card Not Found (UNKNOWN_CARD) 
 * 
 */ 
SINT32 detect_card (SDIO_controller_t *mmc_base, SINT32 * high_capacity_card)
{
	UINT32 ocr, ocr_rcvd;
	UINT32 rca;
	SINT32 cardtype = UNKNOWN_CARD;
	SINT32 err;
	SINT32 retry;
	SINT32 io_funcs;
	SINT32 ver2_card = 0;

	enable_mmc_clock(mmc_base, CLK_400KHZ);

	/* Initialise */
	writel(SDMMC_CMD0, &mmc_base->cmd);
	while( (readl(&mmc_base->stat) & 0x1) == 0);
	writel(0x1, &mmc_base->stat);

	err = send_cmd(mmc_base, SDMMC_CMD5, 0x0000);
	if (!err) {
		ocr_rcvd =  readl(&mmc_base->rsp10);
		
		io_funcs = (ocr_rcvd & 0x7000000) >> 28;
		if (io_funcs > 0)
			cardtype = SDIO_CARD;
		else
			cardtype = UNKNOWN_CARD;

		do {
			err = send_cmd(mmc_base, SDMMC_CMD5, ocr_rcvd >> 16 & 0xffff);		
			if (err)
				return -err;
		
		} while ( (readl(&mmc_base->rsp10) & 0x80000000) == 0); /* Is card Ready? */
		
		printf ("SDIO Card Detected with %d IO Functions\n", io_funcs);
	} else {
		/* Re-Init Card */
		writel(SDMMC_CMD0, &mmc_base->cmd);
		while( (readl(&mmc_base->stat) & 0x1) == 0);
		writel(0x1, &mmc_base->stat);
	
		err = send_cmd(mmc_base, SDMMC_CMD8, 0x000001AA);
		if (!err)
			ver2_card = 1;
		else
			ver2_card = 0;

		ocr = 0x1FF << 15;
		err = send_cmd(mmc_base, SDMMC_CMD55, 0x00000000);
		if (!err) {
			cardtype = SD_CARD;
			ocr = ocr | (ver2_card << 30);
			err = send_cmd(mmc_base, SDMMC_ACMD41, ocr);
		} else {
			cardtype = MMC_CARD;
			err = send_cmd(mmc_base, SDMMC_CMD1, ocr);
		}

		if (err)
			return -err;

		ocr_rcvd =  readl(&mmc_base->rsp10);
		if (ver2_card && (ocr_rcvd & 0x40000000))
			*high_capacity_card = 1;
		else
			*high_capacity_card = 0;

		/* is card is ready? */
		for (retry = 0; retry < 1000 && ((ocr_rcvd & 0x80000000) == 0); retry++) {
			if (cardtype == SD_CARD)
				send_cmd(mmc_base, SDMMC_CMD55, 0x00000000);

			err = send_cmd(mmc_base, SDMMC_ACMD41, ocr);
			if (err)
				return -err;

			ocr_rcvd =  readl(&mmc_base->rsp10);
		}

		if (ocr_rcvd & (1 << 31) == 0)
			return -1; /* card not ready yet */

		err = send_cmd(mmc_base, SDMMC_CMD2, ocr_rcvd);
		if (err)
			return -err;
	
	}

	if (cardtype == SD_CARD || cardtype == SDIO_CARD) {
		err = send_cmd(mmc_base, SDMMC_CMD3, 0x00000000); /* Get RCA */
		rca =  readl(&mmc_base->rsp10) >> 16;
	} else if (cardtype == MMC_CARD) {
		rca = 0x1234 << 16;
		err = send_cmd(mmc_base, MMC_CMD3, rca); /* Set RCA */
	}
	if (err)
		return -err;

	enable_mmc_clock(mmc_base, CLK_24MHZ);

	err = send_cmd(mmc_base, SDMMC_CMD7, rca << 16); /* set rca */
	if (err)
		return -err;
		
	/* Enable 4-Bit Mode */
	err = send_cmd(mmc_base, SDMMC_CMD55, rca << 16);
	err = send_cmd(mmc_base, SDMMC_ACMD6, 0xFFFFFFFE);
	writel(readl(&mmc_base->hctl) | (1 << 1), &mmc_base->hctl);	
		
		
	return cardtype;
}
#endif
/***
 * \brief: Read a Block
 * 
 * \Param: *mmc_base:            Base Address of the SD/SDIO controller
 * \n       high_capacity_card 	 1 if it is a High Capacity Card
 * \n       block:				 Block Address
 * \n       nBytes:				 size of the block
 * \n       buf:				 buffer to be read into
 * 
 * \returns:  -Ve				 On Error
 * \n          0				 On Success
 */ 
SINT32 bread
(
	SDIO_controller_t *mmc_base,
	SINT32 high_capacity,
	SINT32 block_num,
	UINT32 nbytes,
	UINT8 *buf
)
{
	UINT32 arg;
	UINT32 status;
	SINT32 err;
	SINT32 i;

	if (high_capacity)
		arg = block_num; /* sector mode */
	else
		arg = block_num * SECTOR_SIZE; /* byte mode */

	err = send_cmd(mmc_base, SDMMC_CMD17, arg);
	if (err)
		return -err;
	
	do {
		status = readl(&mmc_base->stat);
	} while ( (status & STAT_BRR) == 0); /* ready to read? */

	for (i = 0; i < SECTOR_SIZE / 4; i++) {
		do {
			status = readl(&mmc_base->stat);
		} while ( (status & STAT_BRR) == 0); /* ready to read? */

		*(UINT32 *)buf = readl(&mmc_base->data);
//		printf(" 0x%x\t",*buf);
		buf = buf + 4;
	}
	
	writel(readl(&mmc_base->stat) | STAT_BRR, &mmc_base->stat);
	
	return SUCCESS;
}

/***
 * \brief: Write a Block
 * 
 * \Param: * mmc_base:			Base Address of the SD/SDIO controller
 * \n       high_capacity_card:	1 if it is a High Capacity Card else, 0.
 * \n       block:				Block Address
 * \n       nBytes:				size of the block
 * \n       buf:				data to be written
 * 
 * \return:  -Ve					On Error
 * \n         0					On Success
 */ 
SINT32 bwrite
(
	SDIO_controller_t *mmc_base,
	SINT32 high_capacity,
	SINT32 block_num,
	UINT32 nbytes,
	UINT8 *buf
)
{
	UINT32 arg;
	UINT32 status;
	SINT32 err;
	SINT32 i;

	if (high_capacity)
		arg = block_num; /* sector mode */
	else
		arg = block_num * SECTOR_SIZE; /* byte mode */

	err = send_cmd(mmc_base, SDMMC_CMD24, arg);
	if (err)
		return -err;

	do {
		status = readl(&mmc_base->stat);
	} while ( (status & STAT_BWR) == 0); /* ready to write? */

	for (i = 0; i < SECTOR_SIZE / 4; i++) {
			writel( *(UINT32 *)buf, &mmc_base->data);
			buf = buf + 4;
	}
	
	writel(readl(&mmc_base->stat) | STAT_BWR, &mmc_base->stat);

	return SUCCESS;
}
