/**************************************************************************/
/*! 
    @file     ssp.c
    @author   K. Townsend (microBuilder.eu)
    @date     22 March 2010
    @version  0.10

    @section DESCRIPTION

    Generic code for SSP/SPI communications.  By default, the SSP block
    is initialised in SPI master mode.

    @section Example

    @code
    #include "core/cpu/cpu.h"
    #include "core/ssp/ssp.h"
    ...
    cpuInit();
    sspInit(0, sspClockPolarity_High, sspClockPhase_RisingEdge);
    ...
    uint8_t request[SSP_FIFOSIZE];
    uint8_t response[SSP_FIFOSIZE];
  
    // Send 0x9C to the slave device and wait for a response
    request[0] = 0x80 | 0x1C;
    // Toggle the select pin
    ssp0Select();
    // Send 1 byte from the request buffer
    sspSend(0, (uint8_t *)&request, 1);
    // Receive 1 byte into the response buffer
    sspReceive(0, (uint8_t *)&response, 1);
    // Reset the select pin
    ssp0Deselect();
    // Print the results
    debug_printf("Ox%x ", response[0]);
    @endcode
	
    @section LICENSE

    Software License Agreement (BSD License)

    Copyright (c) 2010, microBuilder SARL
    All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are met:
    1. Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.
    2. 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.
    3. Neither the name of the copyright holders 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 ''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 HOLDER 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.
*/
/**************************************************************************/
#include "ssp.h"
#include "core/gpio/gpio.h"

/* Statistics for all interrupts */
volatile uint32_t interruptRxStat = 0;
volatile uint32_t interruptOverRunStat = 0;
volatile uint32_t interruptRxTimeoutStat = 0;

/**************************************************************************/
/*! 
    @brief SSP0 interrupt handler for SPI communication

    The algorithm is, if RXFIFO is at least half full, 
    start receive until it's empty; if TXFIFO is at least
    half empty, start transmit until it's full.
    This will maximize the use of both FIFOs and performance.
*/
/**************************************************************************/
void SSP_IRQHandler (void)
{
  uint32_t regValue;

  regValue = SSP_SSP0MIS;

  /* Check for overrun interrupt */
  if ( regValue & SSP_SSP0MIS_RORMIS_FRMRCVD )
  {
    interruptOverRunStat++;
    SSP_SSP0ICR = SSP_SSP0ICR_RORIC_CLEAR;      // Clear interrupt
  }

  /* Check for timeout interrupt */
  if ( regValue & SSP_SSP0MIS_RTMIS_NOTEMPTY )
  {
    interruptRxTimeoutStat++;
    SSP_SSP0ICR = SSP_SSP0ICR_RTIC_CLEAR;       // Clear interrupt
  }

  /* Check if Rx buffer is at least half-full */
  if ( regValue & SSP_SSP0MIS_RXMIS_HALFFULL )
  {
    // ToDo: Receive until it's empty
    interruptRxStat++;
  }
  return;
}

/**************************************************************************/
/*! 
    @brief Initialises the SSP0 port

    By default, SSP0 is set to SPI frame-format with 8-bit data.  Pin 2.11
    is routed to serve as serial clock (SCK), and SSEL (0.2) is set to
    GPIO to allow manual control of when the SPI port is enabled or
    disabled.  Overrun and timeout interrupts are both enabled.

    @param[in]  portNum
                The SPI port to use (0..1)
    @param[in]  polarity
                Indicates whether the clock should be held high
                (sspClockPolarity_High) or low (sspClockPolarity_Low)
                when inactive.
    @param[in]  phase
                Indicates whether new bits start on the leading
                (sspClockPhase_RisingEdge) or falling
                (sspClockPhase_FallingEdge) edge of clock transitions.

    @note   sspSelect() and sspDeselect() macros have been defined in 
            ssp.h to control the SSEL line without having to know the 
            specific pin being used.
*/
/**************************************************************************/
void sspInit (uint8_t portNum, sspClockPolarity_t polarity, sspClockPhase_t phase)
{
  gpioInit();

  if (portNum == 0)
  {
    /* Reset SSP */
    SCB_PRESETCTRL &= ~SCB_PRESETCTRL_SSP0_MASK;
    SCB_PRESETCTRL |= SCB_PRESETCTRL_SSP0_RESETDISABLED;
  
    /* Enable AHB clock to the SSP domain. */
    SCB_SYSAHBCLKCTRL |= (SCB_SYSAHBCLKCTRL_SSP0);
  
    /* Divide by 1 (SSPCLKDIV also enables to SSP CLK) */
    SCB_SSP0CLKDIV = SCB_SSP0CLKDIV_DIV1;
  
    /* Set P0.8 to SSP MISO */
    IOCON_PIO0_8 &= ~IOCON_PIO0_8_FUNC_MASK;
    IOCON_PIO0_8 |= IOCON_PIO0_8_FUNC_MISO0;
  
    /* Set P0.9 to SSP MOSI */
    IOCON_PIO0_9 &= ~IOCON_PIO0_9_FUNC_MASK;
    IOCON_PIO0_9 |= IOCON_PIO0_9_FUNC_MOSI0;

    /* Set 2.11 to SSP SCK (0.6 and 0.10 can also be used) */ 
    #ifdef CFG_SSP0_SCKPIN_2_11
    IOCON_SCKLOC = IOCON_SCKLOC_SCKPIN_PIO2_11; 
    IOCON_PIO2_11 = IOCON_PIO2_11_FUNC_SCK0;  
    #endif

    /* Set 0.6 to SSP SCK (2.11 and 0.10 can also be used) */ 
    #ifdef CFG_SSP0_SCKPIN_0_6
    IOCON_SCKLOC = IOCON_SCKLOC_SCKPIN_PIO0_6; 
    IOCON_PIO0_6 = IOCON_PIO0_6_FUNC_SCK;
    #endif

    /* Set P0.2/SSEL to GPIO output and high */
    IOCON_PIO0_2 &= ~IOCON_PIO0_2_FUNC_MASK;
    IOCON_PIO0_2 |= IOCON_PIO0_2_FUNC_GPIO;
    gpioSetDir(SSP0_CSPORT, SSP0_CSPIN, 1);
    gpioSetValue(SSP0_CSPORT, SSP0_CSPIN, 1);
    gpioSetPullup(&IOCON_PIO0_2, gpioPullupMode_Inactive);  // Board has external pull-up
  
    /* If SSP0CLKDIV = DIV1 -- (PCLK / (CPSDVSR × [SCR+1])) = (72,000,000 / (2 x [8 + 1])) = 4.0 MHz */
    uint32_t configReg = ( SSP_SSP0CR0_DSS_8BIT   // Data size = 8-bit
                  | SSP_SSP0CR0_FRF_SPI           // Frame format = SPI
                  | SSP_SSP0CR0_SCR_8);           // Serial clock rate = 8
  
    // Set clock polarity
    if (polarity == sspClockPolarity_High)
      configReg |= SSP_SSP0CR0_CPOL_HIGH;     // Clock polarity = High between frames
    else
      configReg &= ~SSP_SSP0CR0_CPOL_MASK;    // Clock polarity = Low between frames
  
    // Set edge transition
    if (phase == sspClockPhase_FallingEdge)
      configReg |= SSP_SSP0CR0_CPHA_SECOND;   // Clock out phase = Trailing edge clock transition
    else
      configReg &= ~SSP_SSP0CR0_CPHA_MASK;    // Clock out phase = Leading edge clock transition
  
    // Assign config values to SSP0CR0
    SSP_SSP0CR0 = configReg;
  
    /* Clock prescale register must be even and at least 2 in master mode */
    SSP_SSP0CPSR = SSP_SSP0CPSR_CPSDVSR_DIV2;
  
    /* Clear the Rx FIFO */
    uint8_t i, Dummy=Dummy;
    for ( i = 0; i < SSP_FIFOSIZE; i++ )
    {
      Dummy = SSP_SSP0DR;
    }
  
    /* Enable the SSP Interrupt */
    NVIC_EnableIRQ(SSP_IRQn);
  
    /* Set SSPINMS registers to enable interrupts
     * enable all error related interrupts        */
    SSP_SSP0IMSC = ( SSP_SSP0IMSC_RORIM_ENBL      // Enable overrun interrupt
                   | SSP_SSP0IMSC_RTIM_ENBL);     // Enable timeout interrupt
  
    /* Enable device and set it to master mode, no loopback */
    SSP_SSP0CR1 = SSP_SSP0CR1_SSE_ENABLED | SSP_SSP0CR1_MS_MASTER | SSP_SSP0CR1_LBM_NORMAL;
  }

  return;
}

/**************************************************************************/
/*! 
    @brief Sends a block of data to the SSP0 port

    @param[in]  portNum
                The SPI port to use (0..1)
    @param[in]  buf
                Pointer to the data buffer
    @param[in]  length
                Block length of the data buffer
*/
/**************************************************************************/
void sspSend (uint8_t portNum, const uint8_t *buf, uint32_t length)
{
  uint32_t i;
  uint8_t Dummy = Dummy;

  if (portNum == 0)
  {
    for (i = 0; i < length; i++)
    {
      /* Move on only if NOT busy and TX FIFO not full. */
      while ((SSP_SSP0SR & (SSP_SSP0SR_TNF_NOTFULL | SSP_SSP0SR_BSY_BUSY)) != SSP_SSP0SR_TNF_NOTFULL);
      SSP_SSP0DR = *buf;
      buf++;
  
      while ( (SSP_SSP0SR & (SSP_SSP0SR_BSY_BUSY|SSP_SSP0SR_RNE_NOTEMPTY)) != SSP_SSP0SR_RNE_NOTEMPTY );
      /* Whenever a byte is written, MISO FIFO counter increments, Clear FIFO 
      on MISO. Otherwise, when SSP0Receive() is called, previous data byte
      is left in the FIFO. */
      Dummy = SSP_SSP0DR;
    }
  }

  return; 
}

/**************************************************************************/
/*! 
    @brief Receives a block of data from the SSP0 port

    @param[in]  portNum
                The SPI port to use (0..1)
    @param[in]  buf
                Pointer to the data buffer
    @param[in]  length
                Block length of the data buffer
*/
/**************************************************************************/
void sspReceive(uint8_t portNum, uint8_t *buf, uint32_t length)
{
  uint32_t i;

  if (portNum == 0)
  {
    for ( i = 0; i < length; i++ )
    {
      /* As long as the receive FIFO is not empty, data can be received. */
      SSP_SSP0DR = 0xFF;
  
      /* Wait until the Busy bit is cleared */
      while ( (SSP_SSP0SR & (SSP_SSP0SR_BSY_BUSY|SSP_SSP0SR_RNE_NOTEMPTY)) != SSP_SSP0SR_RNE_NOTEMPTY );
      
      *buf = SSP_SSP0DR;
      buf++;
    }
  }

  return; 
}


/**************************************************************************/
/*! 
    @brief Sends a block of data to the SSP0 port and receives the
           answer back into the same buffer.

    @param[in]  portNum
                The SPI port to use (0..1)
    @param[in]  buf
                Pointer to the data buffer
    @param[in]  length
                Block length of the data buffer
*/
/**************************************************************************/
void sspSendReceive(uint8_t portNum, uint8_t *buf, uint32_t length)
{
  uint32_t i;
  uint8_t Dummy = Dummy;

  if (portNum == 0)
  {
    for (i = 0; i < length; i++)
    {
      /* Move on only if NOT busy and TX FIFO not full. */
      while ((SSP_SSP0SR & (SSP_SSP0SR_TNF_NOTFULL | SSP_SSP0SR_BSY_BUSY)) != SSP_SSP0SR_TNF_NOTFULL);
      SSP_SSP0DR = *buf;
  
      while ( (SSP_SSP0SR & (SSP_SSP0SR_BSY_BUSY|SSP_SSP0SR_RNE_NOTEMPTY)) != SSP_SSP0SR_RNE_NOTEMPTY );
      /* Whenever a byte is written, MISO FIFO counter increments, Clear FIFO 
      on MISO. Otherwise, when SSP0Receive() is called, previous data byte
      is left in the FIFO. */
      *buf = SSP_SSP0DR;
      buf++;
    }
  }

  return; 
}

