//*****************************************************************************
//
//  sdhost.c
//
//  Driver for the SD Host (SDHost) Interface
//
//  Copyright (C) 2014 Texas Instruments Incorporated - http://www.ti.com/
//
//
//  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.
//
//*****************************************************************************

//*****************************************************************************
//
//! \addtogroup Secure_Digital_Host_api
//! @{
//
//*****************************************************************************

#include "inc/hw_types.h"
#include "inc/hw_memmap.h"
#include "inc/hw_mmchs.h"
#include "inc/hw_ints.h"
#include "inc/hw_apps_config.h"
#include "interrupt.h"
#include "sdhost.h"


//*****************************************************************************
//
//! Configures SDHost module.
//!
//! \param ulBase is the base address of SDHost module.
//!
//! This function configures the SDHost module, enabling internal sub-modules.
//!
//! \return None.
//
//*****************************************************************************
void
SDHostInit(unsigned long ulBase)
{
  //
  // Assert module reset
  //
  HWREG(ulBase + MMCHS_O_SYSCONFIG) = 0x2;

  //
  // Wait for soft reset to complete
  //
  while( !(HWREG(ulBase + MMCHS_O_SYSCONFIG) & 0x1) )
  {

  }

  //
  // Assert internal reset
  //
  HWREG(ulBase + MMCHS_O_SYSCTL) |= (1 << 24);

  //
  // Wait for Reset to complete
  //
  while( (HWREG(ulBase + MMCHS_O_SYSCTL) & (0x1 << 24)) )
  {

  }

  //
  // Set capability register, 1.8 and 3.0 V
  //
  HWREG(ulBase + MMCHS_O_CAPA) = (0x7 <<24);

  //
  // Select bus voltage, 3.0 V
  //
  HWREG(ulBase + MMCHS_O_HCTL) |= 0x7 << 9;

  //
  // Power up the bus
  //
  HWREG(ulBase + MMCHS_O_HCTL) |= 1 << 8;

  //
  // Wait for power on
  //
  while( !(HWREG(ulBase + MMCHS_O_HCTL) & (1<<8)) )
  {

  }

  HWREG(ulBase + MMCHS_O_CON) |= 1 << 21;

  //
  // Un-mask all events
  //
  HWREG(ulBase + MMCHS_O_IE) = 0xFFFFFFFF;
}


//*****************************************************************************
//
//! Resets SDHost command line
//!
//! \param ulBase is the base address of SDHost module.
//!
//! This function assers a soft reset for the command line
//!
//! \return None.
//
//*****************************************************************************
void
SDHostCmdReset(unsigned long ulBase)
{
  HWREG(ulBase + MMCHS_O_SYSCTL) |= 1 << 25;
  while( (HWREG(ulBase + MMCHS_O_SYSCTL) & (1 << 25)) )
  {

  }
}

//*****************************************************************************
//
//! Sends command over SDHost interface
//!
//! \param ulBase is the base address of SDHost module.
//! \param ulCmd is the command to send.
//! \param ulArg is the argument for the command.
//!
//! This function send command to the attached card over the SDHost interface.
//!
//! The \e ulCmd parameter can be one of \b SDHOST_CMD_0 to \b SDHOST_CMD_63.
//! It can be logically ORed with one or more of the following:
//! - \b SDHOST_MULTI_BLK for multi-block transfer
//! - \b SDHOST_WR_CMD          if command is followed by write data
//! - \b SDHOST_RD_CMD          if command is followed by read data
//! - \b SDHOST_DMA_EN          if SDHost need to generate DMA request.
//! - \b SDHOST_RESP_LEN_136    if 136 bit response is expected
//! - \b SDHOST_RESP_LEN_48     if 48 bit response is expected
//! - \b SDHOST_RESP_LEN_48B    if 48 bit response with busy bit is expected
//!
//! The parameter \e ulArg is the argument for the command
//!
//! \return Returns 0 on success, -1 otherwise.
//
//*****************************************************************************
long
SDHostCmdSend(unsigned long ulBase, unsigned long ulCmd, unsigned ulArg)
{
  //
  // Set Data Timeout
  //
  HWREG(ulBase + MMCHS_O_SYSCTL) |= 0x000E0000;

  //
  // Check for cmd inhabit
  //
  if( (HWREG(ulBase + MMCHS_O_PSTATE) & 0x1))
  {
    return -1;
  }

  //
  // Set the argument
  //
  HWREG(ulBase + MMCHS_O_ARG) = ulArg;

  //
  // Send the command
  //
  HWREG(ulBase + MMCHS_O_CMD) = ulCmd;

  return 0;
}

//*****************************************************************************
//
//! Writes a data word into the SDHost write buffer.
//!
//! \param ulBase is the base address of SDHost module.
//! \param ulData is data word to be transfered.
//!
//! This function writes a single data word into the SDHost write buffer. The
//! function returns \b true if there was a space available in the buffer else
//! returns \b false.
//!
//! \return Return \b true on success, \b false otherwise.
//
//*****************************************************************************
tBoolean
SDHostDataNonBlockingWrite(unsigned long ulBase, unsigned long ulData)
{

  //
  // See if there is a space in the write buffer
  //
  if( (HWREG(ulBase + MMCHS_O_PSTATE) & (1<<10)) )
  {
    //
    // Write the data into the buffer
    //
    HWREG(ulBase + MMCHS_O_DATA) = ulData;

    //
    // Success.
    //
    return(true);
  }
  else
  {
    //
    // No free sapce, failure.
    //
    return(false);
  }
}

//*****************************************************************************
//
//! Waits to write a data word into the SDHost write buffer.
//!
//! \param ulBase is the base address of SDHost module.
//! \param ulData is data word to be transfered.
//!
//! This function writes \e ulData into the SDHost write buffer. If there is no
//! space in the write buffer this function waits until there is a space
//! available before returning.
//!
//! \return None.
//
//*****************************************************************************
void
SDHostDataWrite(unsigned long ulBase, unsigned long ulData)
{
  //
  // Wait until space is available
  //
  while( !(HWREG(ulBase + MMCHS_O_PSTATE) & (1<<10)) )
  {

  }

  //
  // Write the data
  //
  HWREG(ulBase + MMCHS_O_DATA) = ulData;
}


//*****************************************************************************
//
//! Waits for a data word from the SDHost read buffer
//!
//! \param ulBase is the base address of SDHost module.
//! \param pulData is pointer to read data variable.
//!
//! This function reads a single data word from the SDHost read buffer. If there
//! is no data available in the buffer the function will wait until a data
//! word is received before returning.
//!
//! \return None.
//
//*****************************************************************************
void
SDHostDataRead(unsigned long ulBase, unsigned long *pulData)
{
  //
  // Wait until data is available
  //
  while( !(HWREG(ulBase + MMCHS_O_PSTATE) & (1<<11)) )
  {

  }

  //
  // Read the data
  //
  *pulData = HWREG(ulBase + MMCHS_O_DATA);
}

//*****************************************************************************
//
//! Reads single data word from the SDHost read buffer
//!
//! \param ulBase is the base address of SDHost module.
//! \param pulData is pointer to read data variable.
//!
//! This function reads a data word from the SDHost read buffer. The
//! function returns \b true if there was data available in to buffer else
//! returns \b false.
//!
//! \return Return \b true on success, \b false otherwise.
//
//*****************************************************************************
tBoolean
SDHostDataNonBlockingRead(unsigned long ulBase, unsigned long *pulData)
{

  //
  // See if there is any data in the read buffer.
  //
  if( (HWREG(ulBase + MMCHS_O_PSTATE) & (1<11)) )
  {
    //
    // Read the data word.
    //
    *pulData = HWREG(ulBase + MMCHS_O_DATA);

    //
    // Success
    //
    return(true);
  }
  else
  {
    //
    // No data available, failure.
    //
    return(false);
  }
}


//*****************************************************************************
//
//! Registers the interrupt handler for SDHost interrupt
//!
//! \param ulBase is the base address of SDHost module
//! \param pfnHandler is a pointer to the function to be called when the
//! SDHost interrupt occurs.
//!
//! This function does the actual registering of the interrupt handler.  This
//! function enables the global interrupt in the interrupt controller; specific
//! SDHost interrupts must be enabled via SDHostIntEnable(). It is the
//! interrupt handler's responsibility to clear the interrupt source.
//!
//! \sa IntRegister() for important information about registering interrupt
//! handlers.
//!
//! \return None.
//
//*****************************************************************************
void
SDHostIntRegister(unsigned long ulBase, void (*pfnHandler)(void))
{
  //
  // Register the interrupt handler.
  //
  IntRegister(INT_MMCHS, pfnHandler);

  //
  // Enable the SDHost interrupt.
  //
  IntEnable(INT_MMCHS);
}

//*****************************************************************************
//
//! Unregisters the interrupt handler for SDHost interrupt
//!
//! \param ulBase is the base address of SDHost module
//!
//! This function does the actual unregistering of the interrupt handler.  It
//! clears the handler to be called when a SDHost interrupt occurs.  This
//! function also masks off the interrupt in the interrupt controller so that
//! the interrupt handler no longer is called.
//!
//! \sa IntRegister() for important information about registering interrupt
//! handlers.
//!
//! \return None.
//
//*****************************************************************************
void
SDHostIntUnregister(unsigned long ulBase)
{
  //
  // Disable the SDHost interrupt.
  //
  IntDisable(INT_MMCHS);

  //
  // Unregister the interrupt handler.
  //
  IntUnregister(INT_MMCHS);
}

//*****************************************************************************
//
//! Enable individual interrupt source for the specified SDHost
//!
//! \param ulBase is the base address of SDHost module.
//! \param ulIntFlags is a bit mask of the interrupt sources to be enabled.
//!
//! This function enables the indicated SDHost interrupt sources.  Only the
//! sources that are enabled can be reflected to the processor interrupt;
//! disabled sources have no effect on the processor.
//!
//! The \e ulIntFlags parameter is the logical OR of any of the following:
//! - \b SDHOST_INT_CC    Command Complete interrupt
//! - \b SDHOST_INT_TC    Transfer Complete interrupt
//! - \b SDHOST_INT_BWR   Buffer Write Ready interrupt
//! - \b SDHOST_INT_BRR   Buffer Read Ready interrupt
//! - \b SDHOST_INT_ERRI  Error interrupt
//! - \b SDHOST_INT_CTO   Command Timeout error interrupt
//! - \b SDHOST_INT_CEB   Command End Bit error interrupt
//! - \b SDHOST_INT_DTO   Data Timeout error interrupt
//! - \b SDHOST_INT_DCRC  Data CRC error interrupt
//! - \b SDHOST_INT_DEB   Data End Bit error
//! - \b SDHOST_INT_CERR  Cart Status Error interrupt
//! - \b SDHOST_INT_BADA  Bad Data error interrupt
//! - \b SDHOST_INT_DMARD Read DMA done interrupt
//! - \b SDHOST_INT_DMAWR Write DMA done interrupt
//!
//!     Note that SDHOST_INT_ERRI can only be used with \sa SDHostIntStatus()
//! and is internally logical OR of all error status bits. Setting this bit
//! alone as \e ulIntFlags doesn't generates any interrupt.
//!
//! \return None.
//
//*****************************************************************************
void
SDHostIntEnable(unsigned long ulBase,unsigned long ulIntFlags)
{
  //
  // Enable DMA done interrupts
  //
  HWREG(APPS_CONFIG_BASE + APPS_CONFIG_O_DMA_DONE_INT_MASK_CLR) =
                                                          (ulIntFlags >> 30);

  //
  // Enable the individual interrupt sources
  //
  HWREG(ulBase + MMCHS_O_ISE) |= (ulIntFlags & 0x3FFFFFFF);
}

//*****************************************************************************
//
//! Enable individual interrupt source for the specified SDHost
//!
//! \param ulBase is the base address of SDHost module.
//! \param ulIntFlags is a bit mask of the interrupt sources to be enabled.
//!
//! This function disables the indicated SDHost interrupt sources.  Only the
//! sources that are enabled can be reflected to the processor interrupt;
//! disabled sources have no effect on the processor.
//!
//! The \e ulIntFlags parameter has the same definition as the \e ulIntFlags
//! parameter to SDHostIntEnable().
//!
//! \return None.
//
//*****************************************************************************
void
SDHostIntDisable(unsigned long ulBase,unsigned long ulIntFlags)
{
  //
  // Disable DMA done interrupts
  //
  HWREG(APPS_CONFIG_BASE + APPS_CONFIG_O_DMA_DONE_INT_MASK_SET) =
                                                          (ulIntFlags >> 30);
  //
  // Disable the individual interrupt sources
  //
  HWREG(ulBase + MMCHS_O_ISE) &= ~(ulIntFlags & 0x3FFFFFFF);
}

//*****************************************************************************
//
//! Gets the current interrupt status.
//!
//! \param ulBase is the base address of SDHost module.
//!
//! This function returns the interrupt status for the specified SDHost.
//!
//! \return Returns the current interrupt status, enumerated as a bit field of
//! values described in SDHostIntEnable().
//
//*****************************************************************************
unsigned long
SDHostIntStatus(unsigned long ulBase)
{
  unsigned long ulIntStatus;

  //
  // Get DMA done interrupt status
  //
  ulIntStatus = HWREG(APPS_CONFIG_BASE + APPS_CONFIG_O_DMA_DONE_INT_STS_RAW);
  ulIntStatus = (ulIntStatus << 30);

  //
  // Return the status of individual interrupt sources
  //
  ulIntStatus |= (HWREG(ulBase + MMCHS_O_STAT) & 0x3FFFFFFF);

  return(ulIntStatus);
}

//*****************************************************************************
//
//! Clears the individual interrupt sources.
//!
//! \param ulBase is the base address of SDHost module.
//! \param ulIntFlags is a bit mask of the interrupt sources to be cleared.
//!
//! The specified SDHost interrupt sources are cleared, so that they no longer
//! assert.  This function must be called in the interrupt handler to keep the
//! interrupt from being recognized again immediately upon exit.
//!
//! The \e ulIntFlags parameter has the same definition as the \e ulIntFlags
//! parameter to SDHostIntEnable().
//!
//! \return None.
//
//*****************************************************************************
void
SDHostIntClear(unsigned long ulBase,unsigned long ulIntFlags)
{
  //
  // Clear DMA done interrupts
  //
  HWREG(APPS_CONFIG_BASE + APPS_CONFIG_O_DMA_DONE_INT_ACK) =
                                                          (ulIntFlags >> 30);
  //
  // Clear the individual interrupt sources
  //
  HWREG(ulBase + MMCHS_O_STAT) = (ulIntFlags & 0x3FFFFFFF);
}

//*****************************************************************************
//
//! Sets the card status error mask.
//!
//! \param ulBase is the base address of SDHost module
//! \param ulErrMask is the bit mask of card status errors to be enabled
//!
//! This function sets the card status error mask for response type R1, R1b,
//! R5, R5b and R6 response. The parameter \e ulErrMask is the bit mask of card
//! status errors to be enabled, if the corresponding bits in the 'card status'
//! field of a respose are set then the host controller indicates a card error
//! interrupt status. Only bits referenced as type E (error) in status field in
//! the response can set a card status error.
//!
//! \return None
//
//*****************************************************************************
void
SDHostCardErrorMaskSet(unsigned long ulBase, unsigned long ulErrMask)
{
  //
  // Set the card status error mask
  //
  HWREG(ulBase + MMCHS_O_CSRE) = ulErrMask;
}


//*****************************************************************************
//
//! Gets the card status error mask.
//!
//! \param ulBase is the base address of SDHost module
//!
//! This function gets the card status error mask for response type R1, R1b,
//! R5, R5b and R6 response.
//!
//! \return Returns the current card status error.
//
//*****************************************************************************
unsigned long
SDHostCardErrorMaskGet(unsigned long ulBase)
{
  //
  // Return the card status error mask
  //
  return(HWREG(ulBase + MMCHS_O_CSRE));
}

//*****************************************************************************
//
//! Sets the SD Card clock.
//!
//! \param ulBase is the base address of SDHost module
//! \param ulSDHostClk is the rate of clock supplied to SDHost module
//! \param ulCardClk is the required SD interface clock
//!
//! This function configures the SDHost interface to supply the specified clock
//! to the connected card.
//!
//! \return None.
//
//*****************************************************************************
void
SDHostSetExpClk(unsigned long ulBase, unsigned long ulSDHostClk,
             unsigned long ulCardClk)
{
  unsigned long ulDiv;

  //
  // Disable card clock
  //
  HWREG(ulBase + MMCHS_O_SYSCTL) &= ~0x4;

  //
  // Enable internal clock
  //
  HWREG(ulBase + MMCHS_O_SYSCTL) |= 0x1;

  ulDiv = ((ulSDHostClk/ulCardClk) & 0x3FF);

  //
  // Set clock divider,
  //
  HWREG(ulBase + MMCHS_O_SYSCTL) = ((HWREG(ulBase + MMCHS_O_SYSCTL) &
                                     ~0x0000FFC0)| (ulDiv) << 6);

  //
  // Wait for clock to stablize
  //
  while( !(HWREG(ulBase + MMCHS_O_SYSCTL) & 0x2) )
  {

  }

  //
  // Enable card clock
  //
  HWREG(ulBase + MMCHS_O_SYSCTL) |= 0x4;
}

//*****************************************************************************
//
//! Get the response for the last command.
//!
//! \param ulBase is the base address of SDHost module
//! \param ulRespnse is 128-bit response.
//!
//! This function gets the response from the SD card for the last command
//! send.
//!
//! \return None.
//
//*****************************************************************************
void
SDHostRespGet(unsigned long ulBase, unsigned long ulRespnse[4])
{

  //
  // Read the responses.
  //
  ulRespnse[0] = HWREG(ulBase + MMCHS_O_RSP10);
  ulRespnse[1] = HWREG(ulBase + MMCHS_O_RSP32);
  ulRespnse[2] = HWREG(ulBase + MMCHS_O_RSP54);
  ulRespnse[3] = HWREG(ulBase + MMCHS_O_RSP76);

}

//*****************************************************************************
//
//! Set the block size for data transfer
//!
//! \param ulBase is the base address of SDHost module
//! \param ulBlkSize is the transfer block size in bytes
//!
//! This function sets the block size the data transfer.
//!
//! The parameter \e ulBlkSize is size of each data block in bytes.
//! This should be in range 0 - 2^10.
//!
//! \return None.
//
//*****************************************************************************
void
SDHostBlockSizeSet(unsigned long ulBase, unsigned short ulBlkSize)
{
  //
  // Set the block size
  //
  HWREG(ulBase + MMCHS_O_BLK) = ((HWREG(ulBase + MMCHS_O_BLK) & 0x00000FFF)|
                                  (ulBlkSize & 0xFFF));
}

//*****************************************************************************
//
//! Set the block size and count for data transfer
//!
//! \param ulBase is the base address of SDHost module
//! \param ulBlkCount is the number of blocks
//!
//! This function sets block count for the data transfer. This needs to be set
//! for each block transfer. \sa SDHostBlockSizeSet()
//!
//! \return None.
//
//*****************************************************************************
void
SDHostBlockCountSet(unsigned long ulBase, unsigned short ulBlkCount)
{
  unsigned long ulRegVal;

  //
  // Read the current value
  //
  ulRegVal = HWREG(ulBase + MMCHS_O_BLK);

  //
  // Set the number of blocks
  //
  HWREG(ulBase + MMCHS_O_BLK) = ((ulRegVal & 0x0000FFFF)|
                                  (ulBlkCount << 16));
}

//*****************************************************************************
//
// Close the Doxygen group.
//! @}
//
//*****************************************************************************
