/**************************************************************************//**
\file  spi.c

\brief SPI interface routines.

\author
    Atmel Corporation: http://www.atmel.com \n
    Support email: avr@atmel.com

  Copyright (c) 2008-2015, Atmel Corporation. All rights reserved.
  Licensed under Atmel's Limited License Agreement (BitCloudTM).

\internal
  History:
    24/08/07 A. Khromykh - Created
*******************************************************************************/
/******************************************************************************
                   Includes section
******************************************************************************/
#include <spiFlasher.h>
#include <atomic.h>
#include <uart.h>
#include <fcpu.h>
#include <gpio.h>
#include <spi.h>
#include <irq.h>

/******************************************************************************
                   Define(s) section
******************************************************************************/
#define SPI_BAUDRATE(freq_value)  ((uint32_t)(F_CPU / freq_value))
#define SPI_DLYBS(delay_value)    ((uint32_t)(delay_value * F_CPU) + 1ul)
#define SPI_DLYBCT(delay_value)   ((uint32_t)(delay_value * F_CPU / 32) + 1ul)
#define HAL_SPI_PACK_PARAMETER(freq_value, valid_delay_value, cons_delay_value) \
                              ((SPI_BAUDRATE(freq_value) << 8) |                \
                               (SPI_DLYBS(valid_delay_value) << 16) |           \
                               (SPI_DLYBCT(cons_delay_value) << 24))

#define CS0    0
#define CS1    1
#define CS2    2
#define CS3    3

#define DEFAULT_CS  CS0

#define XMEGA_SPI_FREQUENCY   200000ul
#define NPCS_TO_SPCK_XMEGA  0.0000001
#define DELAY_BETWEEN_CONSECUTIVE_TRANSFERS_XMEGA  0.0001

#define DATA_SIZE_FROM_XMEGA      4

/******************************************************************************
                   Types section
******************************************************************************/
// type of access
typedef enum
{
  SPI_READ,
  SPI_WRITE
} SpiAccessType_t;

/******************************************************************************
                   Global variable section
******************************************************************************/
uint8_t spiBuffer[RX_BUFFER_LENGTH];

/******************************************************************************
                   Implementations section
******************************************************************************/
/*****************************************************************************
***************** SPI1 implementation ****************************************
******************************************************************************/
/**************************************************************************//**
  \brief New data are received
******************************************************************************/
void spi1RxEndIrqHandler(void)
{
  // free slave device
  GPIO_SPI1_SS_set();
  irq1EnableInterrupt();
  uart0SendData(spiBuffer, DATA_SIZE_FROM_XMEGA);
}

/**************************************************************************//**
  \brief Previous data are transmitted
******************************************************************************/
void spi1TxEndIrqHandler(void)
{
  // free slave device
  GPIO_SPI1_SS_set();
}

/******************************************************************************
  interrupt handler spi1
******************************************************************************/
void spi1Handler(void)
{
  // get spi status register
  uint32_t status = AT91C_BASE_SPI1->SPI_SR;

  if ((status & AT91C_SPI_RXBUFF) && (AT91C_BASE_SPI1->SPI_IMR & AT91C_SPI_RXBUFF))
  {
    AT91C_BASE_SPI1->SPI_IDR = AT91C_SPI_RXBUFF;
    // transaction is ended
    setTask(SPI_RX_END);
  } else if ((status & AT91C_SPI_TXBUFE) && (AT91C_BASE_SPI1->SPI_IMR & AT91C_SPI_TXBUFE))
  {
    AT91C_BASE_SPI1->SPI_IDR = AT91C_SPI_TXBUFE;
    AT91C_BASE_SPI1->SPI_IER = AT91C_SPI_TXEMPTY;
  }
  else if ((status & AT91C_SPI_TXEMPTY) && (AT91C_BASE_SPI1->SPI_IMR & AT91C_SPI_TXEMPTY))
  {
    AT91C_BASE_SPI1->SPI_IDR = AT91C_SPI_TXEMPTY;
    // transmit buffer and shift register are empty
    setTask(UART_TX_END);
  }
}

/******************************************************************************
  Performs hardware initialization of SPI1 interface.
******************************************************************************/
void spi1Config(void)
{
  /* Configure PIOs for SPI */
  AT91C_BASE_PIOA->PIO_BSR = AT91C_PA23_SPI1_MOSI | AT91C_PA24_SPI1_MISO |
                             AT91C_PA22_SPI1_SPCK;
  AT91C_BASE_PIOA->PIO_PDR = AT91C_PA23_SPI1_MOSI | AT91C_PA24_SPI1_MISO |
                             AT91C_PA22_SPI1_SPCK;

  /* enable the clock of SPI */
  AT91C_BASE_PMC->PMC_PCER = 1ul << AT91C_ID_SPI1;

  /* initialization spi */
  /* reset and disable  */
  AT91C_BASE_SPI1->SPI_CR = AT91C_SPI_SPIDIS | AT91C_SPI_SWRST;
  /* setup spi mode master, fixed mode */
  AT91C_BASE_SPI1->SPI_MR = AT91C_SPI_MSTR | AT91C_SPI_MODFDIS;
  uint32_t packParam = HAL_SPI_PACK_PARAMETER(XMEGA_SPI_FREQUENCY,
                                              NPCS_TO_SPCK_XMEGA,
                                              DELAY_BETWEEN_CONSECUTIVE_TRANSFERS_XMEGA);  // spi timing parameter cs
  /* setup parameters for default chip select */
  AT91C_BASE_SPI1->SPI_CSR[DEFAULT_CS] = AT91C_SPI_NCPHA | AT91C_SPI_BITS_8 | packParam;

  /* disable all spi interrupt */
  AT91C_BASE_SPI1->SPI_IDR = ALL_PERIPHERIAL_INTERRUPT_DISABLE;

  /* Enable interrupts */
  /* Disable the interrupt on the interrupt controller */
  AT91C_BASE_AIC->AIC_IDCR = 1 << AT91C_ID_SPI1;
  /* Save the interrupt handler routine pointer and the interrupt priority */
  AT91C_BASE_AIC->AIC_SVR[AT91C_ID_SPI1] = (uint32_t)spi1Handler;
  /* Store the Source Mode Register */
  AT91C_BASE_AIC->AIC_SMR[AT91C_ID_SPI1] = AT91C_AIC_SRCTYPE_HIGH_LEVEL | AT91C_AIC_PRIOR_LOWEST;
  /* Clear the interrupt on the interrupt controller */
  AT91C_BASE_AIC->AIC_ICCR = 1 << AT91C_ID_SPI1;
  /* Enable the interrupt on the interrupt controller */
  AT91C_BASE_AIC->AIC_IECR = 1 << AT91C_ID_SPI1;

  /* enable spi DMA */
  AT91C_BASE_SPI1->SPI_PTCR = AT91C_PDC_RXTEN | AT91C_PDC_TXTEN;

  /* enable spi */
  AT91C_BASE_SPI1->SPI_CR = AT91C_SPI_SPIEN;

  GPIO_SPI1_SS_set();
  GPIO_SPI1_SS_make_out();
}

/******************************************************************************
starts spi data transaction
Parameters:
   accessType - type of spi transaction
   data - pointer to data buffer.
   size - size of data buffer.
******************************************************************************/
void spi1StartDataTransaction(SpiAccessType_t accessType, uint8_t *data, uint16_t size)
{
  uint32_t dummy;

  /* sometimes that bit is not clear. */
  while (AT91C_BASE_SPI1->SPI_SR & AT91C_SPI_RDRF)
  dummy = AT91C_BASE_SPI1->SPI_RDR;
  (void)dummy;

  if (SPI_READ == accessType)
  {
    /* pointer to received buffer */
    AT91C_BASE_SPI1->SPI_RPR = (uint32_t)data;
    /* data size */
    AT91C_BASE_SPI1->SPI_RCR = size;
  }
  /* pointer to transmitted buffer */
  AT91C_BASE_SPI1->SPI_TPR = (uint32_t)data;
  /* data size. Start bus transaction. */
  AT91C_BASE_SPI1->SPI_TCR = size;

  if (SPI_READ == accessType)
  {
    /* enable interrupt when received buffer is full. */
    AT91C_BASE_SPI1->SPI_IER = AT91C_SPI_RXBUFF;
  }
  else
  {
    /* enable interrupt when transmitted buffer is empty. */
    AT91C_BASE_SPI1->SPI_IER = AT91C_SPI_TXBUFE;
  }
}

/**************************************************************************//**
  \brief Send data by DMA of SPI1

  \param
    data - data pointer
    size - data size (in bytes)
******************************************************************************/
void spi1SendData(uint8_t* data, uint32_t size)
{
  if (NULL == data)
     return;

  memcpy(spiBuffer, data, size);
  // select slave device
  GPIO_SPI1_SS_clr();
  spi1StartDataTransaction(SPI_WRITE, spiBuffer, size);
}

/**************************************************************************//**
  \brief Get data by DMA of SPI1

  \param
    size - data size (in bytes)
******************************************************************************/
void spi1GetData(void)
{
  // select slave device
  GPIO_SPI1_SS_clr();
  spi1StartDataTransaction(SPI_READ, spiBuffer, DATA_SIZE_FROM_XMEGA);
}

// eof spi.c
