/*
 * Copyright (c) 2010 by Cristian Maglie <c.maglie@arduino.cc>
 * Copyright (c) 2014 by Paul Stoffregen <paul@pjrc.com> (Transaction API)
 * Copyright (c) 2014 by Matthijs Kooijman <matthijs@stdin.nl> (SPISettings AVR)
 * Copyright (c) 2014 by Andrew J. Kroll <xxxajk@gmail.com> (atomicity fixes)
 * SPI Master library for arduino.
 *
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of either the GNU General Public License version 2
 * or the GNU Lesser General Public License version 2.1, both as
 * published by the Free Software Foundation.
 */

 /*
 * Arduino srl - www.arduino.org
 * 2016 Jun 9: Edited Francesco Alessi (alfran) - francesco@arduino.org
 */

#ifndef _SPI_H_INCLUDED
#define _SPI_H_INCLUDED

#include <Arduino.h>

#define SPI2_CLK_ENABLE()                __HAL_RCC_SPI2_CLK_ENABLE()
#define SPI2_SCK_CLK_ENABLE()            __HAL_RCC_GPIOD_CLK_ENABLE()
#define SPI2_MISO_CLK_ENABLE()           __HAL_RCC_GPIOB_CLK_ENABLE()
#define SPI2_MOSI_CLK_ENABLE()           __HAL_RCC_GPIOC_CLK_ENABLE()

#define SPI2_FORCE_RESET()               __HAL_RCC_SPI2_FORCE_RESET()
#define SPI2_RELEASE_RESET()             __HAL_RCC_SPI2_RELEASE_RESET()

/* Definition for SPI2 Pins */
#define SPI2_SCK_PIN                     GPIO_PIN_3
#define SPI2_SCK_GPIO_PORT               GPIOD
#define SPI2_SCK_AF                      GPIO_AF5_SPI2
#define SPI2_MISO_PIN                    GPIO_PIN_14
#define SPI2_MISO_GPIO_PORT              GPIOB
#define SPI2_MISO_AF                     GPIO_AF5_SPI2
#define SPI2_MOSI_PIN                    GPIO_PIN_3
#define SPI2_MOSI_GPIO_PORT              GPIOC
#define SPI2_MOSI_AF                     GPIO_AF5_SPI2

#define SPI1_CLK_ENABLE()                __HAL_RCC_SPI1_CLK_ENABLE()
#define SPI1_SCK_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
#define SPI1_MISO_CLK_ENABLE()           __HAL_RCC_GPIOB_CLK_ENABLE()
#define SPI1_MOSI_CLK_ENABLE()           __HAL_RCC_GPIOB_CLK_ENABLE()
#define SPI1_NSS_CLK_ENABLE()            __HAL_RCC_GPIOA_CLK_ENABLE()

#define SPI1_FORCE_RESET()               __HAL_RCC_SPI1_FORCE_RESET()
#define SPI1_RELEASE_RESET()             __HAL_RCC_SPI1_RELEASE_RESET()

/* Definition for SPI2 Pins */
#define SPI1_SCK_PIN                     GPIO_PIN_3
#define SPI1_SCK_GPIO_PORT               GPIOB
#define SPI1_SCK_AF                      GPIO_AF5_SPI1
#define SPI1_MISO_PIN                    GPIO_PIN_4
#define SPI1_MISO_GPIO_PORT              GPIOB
#define SPI1_MISO_AF                     GPIO_AF5_SPI1
#define SPI1_MOSI_PIN                    GPIO_PIN_5
#define SPI1_MOSI_GPIO_PORT              GPIOB
#define SPI1_MOSI_AF                     GPIO_AF5_SPI1

#define SPI1_NSS_PIN                     GPIO_PIN_15
#define SPI1_NSS_GPIO_PORT               GPIOA
#define SPI1_NSS_AF                      GPIO_AF5_SPI1

#ifndef LSBFIRST
#define LSBFIRST 0
#endif
#ifndef MSBFIRST
#define MSBFIRST 1
#endif

#define SPI_CLOCK_DIV2   SPI_BAUDRATEPRESCALER_2
#define SPI_CLOCK_DIV4   SPI_BAUDRATEPRESCALER_4
#define SPI_CLOCK_DIV8   SPI_BAUDRATEPRESCALER_8
#define SPI_CLOCK_DIV16  SPI_BAUDRATEPRESCALER_16
#define SPI_CLOCK_DIV32  SPI_BAUDRATEPRESCALER_32
#define SPI_CLOCK_DIV64  SPI_BAUDRATEPRESCALER_64
#define SPI_CLOCK_DIV128 SPI_BAUDRATEPRESCALER_128
#define SPI_CLOCK_DIV256 SPI_BAUDRATEPRESCALER_256

#define SPI_MODE0 0x00
#define SPI_MODE1 0x04
#define SPI_MODE2 0x08
#define SPI_MODE3 0x0C

enum SPITransferMode {
	SPI_CONTINUE,
	SPI_LAST
};

class SPISettings {
public:
  SPISettings(uint32_t clock, uint8_t bitOrder, uint8_t dataMode)
  {
    init_AlwaysInline(clock, bitOrder, dataMode);
  }
  SPISettings()
  {
    init_AlwaysInline(4000000, MSBFIRST, SPI_MODE0);
  }
private:
  void init_AlwaysInline(uint32_t clock, uint8_t bitOrder, uint8_t dataMode)
    __attribute__((__always_inline__)) {

    /* Select the Bit Order */
    if(bitOrder == LSBFIRST)
    {
      FirstBit = SPI_FIRSTBIT_LSB;
    }
    else
    {
      FirstBit = SPI_FIRSTBIT_MSB;
    }

    /* Select the SPI Communication Mode */
    if(dataMode == SPI_MODE0)
    {
      CLKPhase          = SPI_PHASE_1EDGE;
      CLKPolarity       = SPI_POLARITY_LOW;
    }
    else if(dataMode == SPI_MODE1)
    {
      CLKPhase          = SPI_PHASE_2EDGE;
      CLKPolarity       = SPI_POLARITY_LOW;
    }
    else if(dataMode == SPI_MODE2)
    {
      CLKPhase          = SPI_PHASE_1EDGE;
      CLKPolarity       = SPI_POLARITY_HIGH;
    }
    else
    {
      CLKPhase          = SPI_PHASE_2EDGE;
      CLKPolarity       = SPI_POLARITY_HIGH;
    }

    /* Select the Clock Divider */
    BaudRatePrescaler = (((HAL_RCC_GetPCLK1Freq()) / 2  ) < clock) ? SPI_CLOCK_DIV2
                      : (((HAL_RCC_GetPCLK1Freq()) / 4  ) < clock) ? SPI_CLOCK_DIV4
                      : (((HAL_RCC_GetPCLK1Freq()) / 8  ) < clock) ? SPI_CLOCK_DIV8
                      : (((HAL_RCC_GetPCLK1Freq()) / 16 ) < clock) ? SPI_CLOCK_DIV16
                      : (((HAL_RCC_GetPCLK1Freq()) / 32 ) < clock) ? SPI_CLOCK_DIV32
                      : (((HAL_RCC_GetPCLK1Freq()) / 64 ) < clock) ? SPI_CLOCK_DIV64
                      : (((HAL_RCC_GetPCLK1Freq()) / 128) < clock) ? SPI_CLOCK_DIV128
                      :                                              SPI_CLOCK_DIV256;
  }

  uint32_t FirstBit;
  uint32_t CLKPhase;
  uint32_t CLKPolarity;
  uint32_t BaudRatePrescaler;

  friend class SPIClass;
};

class SPIClass {
public:
  /* Constructor */
  SPIClass(SPI_TypeDef *spiInstance);
  /* Initialize the SPI peripheral */
  void begin();

  /* Initialize the SPI peripheral and SS pin */
  void begin(uint8_t slaveSelectPin);

  /* Initialize the SPI peripheral with settings */
  void beginTransaction(SPISettings settings);

  /* Initialize the SPI peripheral */
  void endTransaction();

  /* Begin the transfer */
  uint8_t transfer(uint8_t data);
  uint16_t transfer16(uint16_t data);
  void transfer(void *buf, size_t count);
  uint8_t transfer(uint8_t slaveSelectPin, uint8_t val, SPITransferMode transferMode);

  /* End the transfer */
  void end();

  void end(uint8_t slaveSelectPin);

  /* Set Bit Order */
  void setBitOrder(uint8_t bitOrder);

  /* Set Clock Divider */
  void setClockDivider(uint8_t clockDiv);

  /* Set Communication Mode */
  void setDataMode(uint8_t dataMode);

private:

  SPI_HandleTypeDef hSPIx;

};

extern SPIClass SPI;
extern SPIClass SPI7;

#endif