/**************************************************************************//**
 * @file
 * @brief SPI implementation of Board Control interface
 *        This implementation use the USART2 SPI interface to control board
 *        control registers. It works
 * @author Energy Micro AS
 * @version 2.0.1
 ******************************************************************************
 * @section License
 * <b>(C) Copyright 2012 Energy Micro AS, http://www.energymicro.com</b>
 *******************************************************************************
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 * 4. The source and compiled code may only be used on Energy Micro "EFM32"
 *    microcontrollers and "EFR4" radios.
 *
 * DISCLAIMER OF WARRANTY/LIMITATION OF REMEDIES: Energy Micro AS has no
 * obligation to support this Software. Energy Micro AS is providing the
 * Software "AS IS", with no express or implied warranties of any kind,
 * including, but not limited to, any implied warranties of merchantability
 * or fitness for any particular purpose or warranties against infringement
 * of any proprietary rights of a third party.
 *
 * Energy Micro AS will not be liable for any consequential, incidental, or
 * special damages, or any other relief, or for any claim by any third party,
 * arising from your use of this Software.
 *
 *****************************************************************************/

/***************************************************************************//**
 * @addtogroup BSP
 * @{
 ******************************************************************************/

#include <stdio.h>
#include "efm32.h"
#include "em_usart.h"
#include "em_gpio.h"
#include "em_cmu.h"
#include "dvk.h"
#include "dvk_bcregisters.h"

#ifdef _EFM32_TINY_FAMILY

/* USART used for SPI access */
#define USART_USED                USART0
#define USART_CLK                 cmuClock_USART0

/* GPIO pins used, please refer to DVK user guide. */
#define PIN_SPIBUS_CONNECT        13
#define PORT_SPIBUS_CONNECT       gpioPortC
#define PIN_SPI_TX                10
#define PORT_SPI_TX               gpioPortE
#define PIN_SPI_RX                11
#define PORT_SPI_RX               gpioPortE
#define PIN_SPI_CLK               12
#define PORT_SPI_CLK              gpioPortE
#define PIN_SPI_CS                13
#define PORT_SPI_CS               gpioPortE

#else

/* USART used for SPI access */
#define USART_USED                USART2
#define USART_CLK                 cmuClock_USART2

/* GPIO pins used, please refer to DVK user guide. */
#define PIN_SPIBUS_CONNECT        13
#define PORT_SPIBUS_CONNECT       gpioPortC
#define PIN_EBIBUS_CONNECT        12
#define PORT_EBIBUS_CONNECT       gpioPortC
#define PIN_SPI_TX                2
#define PORT_SPI_TX               gpioPortC
#define PIN_SPI_RX                3
#define PORT_SPI_RX               gpioPortC
#define PIN_SPI_CLK               4
#define PORT_SPI_CLK              gpioPortC
#define PIN_SPI_CS                5
#define PORT_SPI_CS               gpioPortC

#endif

static volatile uint16_t *lastAddr = NULL;

/**************************************************************************//**
 * @brief  Initializes SPI interface for access to FPGA registers
 *         for board control
 *****************************************************************************/
static void spiInit(void)
{
  USART_InitSync_TypeDef init = USART_INITSYNC_DEFAULT;

  /* Enable module clocks */
  CMU_ClockEnable(cmuClock_GPIO, true);
  CMU_ClockEnable(cmuClock_HFPER, true);
  CMU_ClockEnable(USART_CLK, true);

  /* Configure SPI bus connect pins, DOUT set to 0, disable EBI */
  GPIO_PinModeSet(PORT_SPIBUS_CONNECT, PIN_SPIBUS_CONNECT, gpioModePushPull, 0);
  GPIO_PinModeSet(PORT_EBIBUS_CONNECT, PIN_EBIBUS_CONNECT, gpioModePushPull, 1);
  
  /* Configure SPI pins */
  GPIO_PinModeSet(PORT_SPI_TX, PIN_SPI_TX, gpioModePushPull, 0);
  GPIO_PinModeSet(PORT_SPI_RX, PIN_SPI_RX, gpioModePushPull, 0);
  GPIO_PinModeSet(PORT_SPI_CLK, PIN_SPI_CLK, gpioModePushPull, 0);
  /* Keep CS high to not activate slave */
  GPIO_PinModeSet(PORT_SPI_CS, PIN_SPI_CS, gpioModePushPull, 1);

  /* Configure to use SPI master with manual CS */
  /* For now, configure SPI for worst case 32MHz clock in order to work for all */
  /* configurations. */
  init.refFreq = 32000000;
  init.baudrate = 7000000;
  USART_InitSync(USART_USED, &init);

  /* Enable pins at default location */
  USART_USED->ROUTE = USART_ROUTE_TXPEN | USART_ROUTE_RXPEN | USART_ROUTE_CLKPEN;
}

/**************************************************************************//**
 * @brief  Disables GPIO pins and USART from FPGA register access
 *****************************************************************************/
static void spiDisable(void)
{
  USART_Reset(USART_USED);

  /* Disable LCD_SELECT */
  GPIO_PinModeSet(gpioPortD, 13, gpioModeDisabled, 0);

  /* Disable SPI pins */
  GPIO_PinModeSet(PORT_SPIBUS_CONNECT, 13, gpioModeDisabled, 0);
  GPIO_PinModeSet(PORT_SPIBUS_CONNECT, 12, gpioModeDisabled, 0);
  GPIO_PinModeSet(PORT_SPI_TX, PIN_SPI_TX, gpioModeDisabled, 0);
  GPIO_PinModeSet(PORT_SPI_RX, PIN_SPI_RX, gpioModeDisabled, 0);
  GPIO_PinModeSet(PORT_SPI_CLK, PIN_SPI_CLK, gpioModeDisabled, 0);
  GPIO_PinModeSet(PORT_SPI_CS, PIN_SPI_CS, gpioModeDisabled, 0);

  /* Disable USART clock - we can't disable GPIO or HFPER as we don't know who else
   * might be using it */
  CMU_ClockEnable(USART_CLK, false);
}

/**************************************************************************//**
 * @brief  Perform SPI Transfer
 *****************************************************************************/
static uint16_t spiAccess(uint8_t spiaddr, uint8_t rw, uint16_t spidata)
{
  uint16_t      tmp;

  GPIO_PinOutClear(PORT_SPI_CS, PIN_SPI_CS);

  /* For every byte sent, one is received */

  /* Write SPI address */
  USART_Tx(USART_USED, (spiaddr & 0x3) | rw << 3);
  /* Just ignore data read back */
  USART_Rx(USART_USED);

  /* SPI data LSB */ 
  USART_Tx(USART_USED, spidata & 0xFF);
  tmp = (uint16_t)USART_Rx(USART_USED);

  /* SPI data MSB */ 
  USART_Tx(USART_USED, spidata >> 8);
  tmp |= (uint16_t)USART_Rx(USART_USED) << 8;

  GPIO_PinOutSet(PORT_SPI_CS, PIN_SPI_CS);

  return tmp;
}

/**************************************************************************//**
 * @brief  Performs SPI write to FPGA register
 * @param spiadr Address of register
 * @param spidata Data to write
 *****************************************************************************/
static void spiWrite(uint8_t spiadr, uint16_t spidata)
{
  spiAccess(spiadr, 0, spidata);
}

/**************************************************************************//**
 * @brief  Performs SPI read from FPGA register
 * @param spiadr Address of register
 * @param spidata Dummy data
 *****************************************************************************/
static uint16_t spiRead(uint8_t spiadr, uint16_t spidata)
{
  return spiAccess(spiadr, 1, spidata);
}

/**************************************************************************//**
 * @brief  Initializes DVK register access
*  @return true on success, false on failure
 *****************************************************************************/
bool DVK_SPI_init(void)
{
  uint16_t spiMagic;

  spiInit();
  /* Read "board control Magic" register to verify SPI is up and running */
  /*  if not FPGA is configured to be in EBI mode  */

  spiMagic = DVK_SPI_readRegister(BC_MAGIC);
  if(spiMagic != BC_MAGIC_VALUE)
  {
    return false;
  } 
  else
  {
    return true;
  }
}

/**************************************************************************//**
 * @brief  Disable and free up resources used by SPI board control access
 *****************************************************************************/
void DVK_SPI_disable(void)
{
  spiDisable();
}

/**************************************************************************//**
 * @brief  Perform read from DVK board control register
 * @param  addr Address of register to read from
 *****************************************************************************/
uint16_t DVK_SPI_readRegister(volatile uint16_t *addr)
{
  uint16_t data;

  if (addr != lastAddr)
  {
    spiWrite(0x00, 0xFFFF & ((uint32_t) addr));             /*LSBs of address*/
    spiWrite(0x01, 0xFF & ((uint32_t) addr >> 16));         /*MSBs of address*/
    spiWrite(0x02, (0x0C000000 & (uint32_t) addr) >> 26);   /*Chip select*/
  }
  /* Read twice */
  data     = spiRead(0x03, 0);
  data     = spiRead(0x03, 0);
  lastAddr = addr;
  return data;
}

/**************************************************************************//**
 * @brief  Perform write to DVK board control register
 * @param addr Address of register to write to
 * @param data 16-bit to  write into register
 *****************************************************************************/
void DVK_SPI_writeRegister(volatile uint16_t *addr, uint16_t data)
{
  if (addr != lastAddr)
  {
    spiWrite(0x00, 0xFFFF & ((uint32_t) addr));             /*LSBs of address*/
    spiWrite(0x01, 0xFF & ((uint32_t) addr >> 16));         /*MSBs of address*/
    spiWrite(0x02, (0x0C000000 & (uint32_t) addr) >> 26);   /*Chip select*/
  }
  spiWrite(0x03, data);                                     /*Data*/
  lastAddr = addr;
}

/** @} (end group BSP) */
