/*
 * 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: sdio.c
 *
 * \brief: source file to handle SDIO line functionality.
 *
 * \n      This file handle the SDIO's device initialization as well as help to
 * \n      detect the SDIO device on board.
 *
 */
#include "Dm8127_Platform.h"
#include "sdio.h"

/***
 * \brief: Address read function.
 *
 * \b \descr: This function will take memory address as input and it gives
 * \n         the address value as a return.
 *
 * \param:    addr    [IN]    address to be read(pointer)
 *
 * \return: Value read from address
 */
UINT32 readl
(
	UINT32 *addr
)
{
	return (*(UINT32 *) addr);
}

/***
 * \brief: Address write function.
 *
 * \b \Descr: This function will take memory address as input and it writes the
 * \n         given value as data on the address.
 *
 * \param:   addr    [IN] address (pointer)
 *           data    [IN]  data to be written
 *
 * \return:void
 */
void writel
(
	UINT32 data,
	UINT32 *addr
)
{
	*(UINT32 *) addr = data;
}

/* Device clock enable */

/***
 * \brief: Enables Wlan clock
 *
 * \Param:  Sdio_base: [IN]	Base Address of the Wlan controller
 * \n       clock:     [IN]  Clock Frequency
 *
 *
 * \returns:   FAILED			On Failure
 *             SUCCESS         On Success
 */
SINT32 device_clock
(
	SDIO_controller_t *Sdio_base,
	UINT32 clock
)
{
	UINT32 val;
	UINT32 regval;

	/* disables, cen and set data timeout value */
	regval = readl(&Sdio_base->sysctl);
	regval &= ~(0xF0007);
	regval |= (0xe << 16);

	switch (clock) {
	case CLK_INITSEQ_WLAN:
		val = (REF_CLK * 1000 / (160) / 2);
		break;
	case CLK_INITSEQ_MMC:
			val = (REF_CLK * 1000 / (80 * 2));
			break;
	case CLK_400KHZ:
		val = (REF_CLK * 1000) / (1600); /* 1.6MHZ */
		break;
	case CLK_12MHZ:
		val = (REF_CLK * 1000 / (12000));
	    break;
	case CLK_24MHZ:
		val = (REF_CLK * 1000 / (24000));
		break;
	case CLK_4MHZ:
		val = (REF_CLK * 1000 / (4000));
		break;
	case CLK_2MHZ:
		val = (REF_CLK * 1000 / (2000));
		break;
	case CLK_48MHZ:
		val = (REF_CLK * 1000 / (48000));
		break;
	default:
		printf ("Clock Frequency not supported\n");
		return FAILURE;
	}

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

	regval |= (0x1); /* enable clock */
	writel(regval, &Sdio_base->sysctl);
	delay(4000);

	/* Wait until stable? */
	while ((readl(&Sdio_base->sysctl) & 0x2) == 0x0);

	writel(readl(&Sdio_base->sysctl) | 0x4, &Sdio_base->sysctl);
	delay(4000);

	return SUCCESS;
}
/***
 * \brief : Detect an SDIO (WLAN/MMC/SD) Card
 *
 * \Param : Sdio_base           Base Address of the SD/SDIO controller
 * \n       high_capacity_card  Returns 1 if a High Capacity SD card is found.
 *
 * \return: The Type of Card
 *
 * \n        (1):	SDIO Card is Found
 * \n        (2):	SD Card is Found   (SD_CARD)
 * \n        (3):	Wlan Card is Found
 * \n        (-1):	Card Not Found (UNKNOWN_CARD)
 */
SINT32 detect_card
(
	SDIO_controller_t *Sdio_base,
	SINT32 * high_capacity_card
)
{
	UINT32 ocr, ocr_rcvd;
	UINT32 rca;
	SINT32 cardtype ;
	SINT32 err;
	SINT32 retry;
	SINT32 io_funcs;
	SINT32 ver2_card = 0;

	/* Variable initialization */
	ocr      = INIT_VALUE;
	ocr_rcvd = INIT_VALUE;
	rca      = INIT_VALUE;
	retry    = INIT_VALUE;
	io_funcs = INIT_VALUE;
	ver2_card= INIT_VALUE;
	cardtype = UNKNOWN_CARD;
	err      = FAILED;

	device_clock(Sdio_base, CLK_400KHZ);

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

	err = send_cmd(Sdio_base, CMD5, 0x0000);
	if (!err) {
		ocr_rcvd =  readl(&Sdio_base->rsp10);
		io_funcs = (ocr_rcvd >> 28) & 0x7;

		do {
			err = send_cmd(Sdio_base, CMD5, ((ocr_rcvd >> 8) & 0xffffff));
			if (err)
				return -err;

		} while ( (readl(&Sdio_base->rsp10) & 0x80000000) == 0); /* Is card Ready? */
#ifdef DEBUG
		printf ("\tDetected an SDIO Card with %d functions\n", io_funcs);
#else
		platform_write("\nDetected an SDIO Card with %d functions", io_funcs);
#endif
		cardtype = SDIO_CARD;
	} else {
		/* Re-Init Card */
		writel(CMD0, &Sdio_base->cmd);
		while( (readl(&Sdio_base->stat) & 0x1) == 0);
		writel(0x1, &Sdio_base->stat);

		err = send_cmd(Sdio_base, CMD8, 0x000001AA);
		if (!err)
			ver2_card = 1;
		else
			ver2_card = 0;

		ocr = 0x1FF << 15;
		err = send_cmd(Sdio_base, CMD55, 0x00000000);
		if (!err) {
			cardtype = SD_CARD;
			ocr = ocr | (ver2_card << 30);
			err = send_cmd(Sdio_base, ACMD41, ocr);
		} else {
			cardtype = Wlan_CARD;
			err = send_cmd(Sdio_base, CMD1, ocr);
		}

		if (err)
			return -err;

		ocr_rcvd =  readl(&Sdio_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(Sdio_base, CMD55, 0x00000000);

			err = send_cmd(Sdio_base, ACMD41, ocr);
			if (err)
				return -err;

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

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

		err = send_cmd(Sdio_base, CMD2, ocr_rcvd);
		if (err)
			return -err;

	}

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

	err = send_cmd(Sdio_base, CMD7, rca << 16); /* set rca */
	if (err)
		return -err;

	/* for SD CARD */
	/* Enable 4-Bit Mode */
	if ((SDIO_controller_t *)HSmmc_BASE1 == (SDIO_controller_t *)Sdio_base)
	{
		err = send_cmd(Sdio_base, CMD55, rca << 16);
		err = send_cmd(Sdio_base, ACMD6, 0xFFFFFFFE);
		writel(readl(&Sdio_base->hctl) | (1 << 1), &Sdio_base->hctl);
	}

		/* The WL1271 does not respond to any higher freq than 4MHZ during ID reading Phase */
		/* Setting the clock to 2MHZ during the ID reading Phase as at higher frequency card does not respond */
	err = device_clock(Sdio_base, CLK_24MHZ);
	if (err)
		return -err;

	return cardtype;
}


/***
 * \brief: Initialise the SD/SDIO Controller
 *
 * \Param:  sdio_base:	Base Address of the SD/SDIO controller
 *
 * \return:	void
 *
 */
void SDIO_DEV_init
(
	SDIO_controller_t *sdio_base
)
{
	UINT32 regval;

	/**
	 *  software reset is given to HSsdio_base0 (HSWlan module) to work with
	 *  thw WLAN module.
	 */
	regval = readl(&sdio_base->sysconfig);
	writel(regval | 0x2, &sdio_base->sysconfig);

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

	/* reset the host controller */
	regval = readl(&sdio_base->sysctl);
	regval |= (1 << 24); /* SRA */
	writel(regval, &sdio_base->sysctl);

	/* reset done? */
	while ((readl(&sdio_base->sysctl) & (1 << 24)) != 0x0);

	/* Support for 1.8V and 3V */
	//writel(0x6000000, &sdio_base->capa);
	writel(0x6E10080, &sdio_base->capa);

	/* 1 Bit Mode, 3V Bus Voltage (SVDS) and Bus Pwr Off */
	writel(0x00000C00, &sdio_base->hctl);

	/* Set Block Size to 512 Bytes */
	writel(0x200, &sdio_base->blk);

	/* Support for 1.8V, 3V cards */
	writel(readl(&sdio_base->capa) | 0x6000000, &sdio_base->capa);

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

	if ((SDIO_controller_t *)sdio_base == (SDIO_controller_t *)HSWlan_BASE0)
	{
		device_clock(sdio_base, CLK_INITSEQ_WLAN);
	}
	else
	{
		device_clock(sdio_base, CLK_INITSEQ_WLAN);
	}

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

	/* Enable Interrupts */
	writel(0x307F0033, &sdio_base->ie);

	/* Send Initialisation Sequence for 80 clock cycles */
	writel(0x00000602, &sdio_base->con);
	delay(10);

	/* Send CMD0 */
	writel(CMD0, &sdio_base->cmd);
	while( (readl(&sdio_base->stat) & 0x1) == 0);
	writel(0x1, &sdio_base->stat);

	/* Send CMD0 */
	writel(CMD0, &sdio_base->cmd);
	while( (readl(&sdio_base->stat) & 0x1) == 0);
	writel(0x1, &sdio_base->stat);

	/* End Init sequence */
	regval = readl(&sdio_base->con) & ~0x2;
	writel(regval, &sdio_base->con);
}

#if 1
/***
 * \brief: Send a command to sdio device
 *
 * \param:  sdio_base	Base Address of the SD/SDIO controller
 * \n 	    cmd          command
 * \n       arg			arguments to the command
 *
 *
 * \returns: SUCEESS 		On Success
 * \n        +ve number      On Failure (Contents of HS_STAT register)
 *
 */
SINT32 send_cmd
(
	SDIO_controller_t * Sdio_base,
	UINT32 cmd,
	UINT32 arg
)
{
	UINT32 regval;
	UINT32 status;

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

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

	/* wait until you are allowed to send cmd */
	while ((readl(&Sdio_base->pstate) & 0x2) == 0x2);

	writel(0x307F0037, &Sdio_base->stat);
	writel(0x200, &Sdio_base->blk);

	writel(arg, &Sdio_base->arg);
	writel(cmd, &Sdio_base->cmd);
	delay(0x1000);

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

		if (status & STAT_ERRI)
			return status;

		if (status & STAT_CC) { /* command complete */
			writel(STAT_CC, &Sdio_base->stat);
			return SUCCESS;
		}
	}
}
#endif

