/***************************************************************************//**
 *   @file   spi.cpp
 *   @brief  Implementation of SPI No-OS platform driver interfaces
********************************************************************************
 * Copyright (c) 2019, 2020 Analog Devices, Inc.
 *
 * All rights reserved.
 *
 * This software is proprietary to Analog Devices, Inc. and its licensors.
 * By using this software you agree to the terms of the associated
 * Analog Devices Software License Agreement.
*******************************************************************************/

/******************************************************************************/
/***************************** Include Files **********************************/
/******************************************************************************/

#include <stdio.h>
#include <mbed.h>

#include "platform_drivers.h"
#include "spi_extra.h"

/******************************************************************************/
/********************** Macros and Constants Definitions **********************/
/******************************************************************************/

#define		SPI_16_BIT_FRAME		16		// SPI 16-bit frame size
#define		SPI_8_BIT_FRAME			8		// SPI 8-bit frame size

/******************************************************************************/
/********************** Variables and User defined data types *****************/
/******************************************************************************/

static uint8_t spi_format_bytes = SPI_16_BIT_FRAME;   	// SPI format

/******************************************************************************/
/************************ Functions Declarations ******************************/
/******************************************************************************/

/******************************************************************************/
/************************ Functions Definitions *******************************/
/******************************************************************************/

/**
 * @brief Initialize the SPI communication peripheral.
 * @param desc - The SPI descriptor.
 * @param init_param - The structure that contains the SPI parameters.
 * @return SUCCESS in case of success, FAILURE otherwise.
 */
int32_t spi_init_noos(struct spi_desc **desc,
		      const struct spi_init_param *param)
{
	mbed::SPI *spi;		// pointer to new spi instance
	DigitalOut *ss; 	// pointer to new SS instance
	mbed_spi_desc *mbed_desc;	// Pointer to mbed spi descriptor

	if (desc) {
		// Create the spi description object for the device
		spi_desc * new_desc = (spi_desc *)malloc(sizeof(spi_desc));
		if (new_desc == NULL) {
			return FAILURE;
		}

		new_desc->chip_select = param->chip_select;
		new_desc->mode = param->mode;
		new_desc->max_speed_hz = param->max_speed_hz;

		// Configure and instantiate SPI protocol
		spi = new SPI(
			(PinName)(((mbed_spi_init_param *)param->extra)->spi_mosi_pin),
			(PinName)(((mbed_spi_init_param *)param->extra)->spi_miso_pin),
			(PinName)(((mbed_spi_init_param *)param->extra)->spi_clk_pin));

		if (spi == NULL) {
			return FAILURE;
		}

		// Configure and instantiate slave select pin
		ss = new DigitalOut((PinName)(new_desc->chip_select));
		if (ss == NULL) {
			return FAILURE;
		}

		// Create the SPI extra descriptor object to store new SPI instances
		mbed_desc = (mbed_spi_desc *)malloc(sizeof(mbed_spi_desc));
		if (mbed_desc == NULL) {
			return FAILURE;
		}

		mbed_desc->spi_port = (SPI *)spi;
		mbed_desc->slave_select = (DigitalOut *)ss;
		new_desc->extra = (mbed_spi_desc *)mbed_desc;

		*desc = new_desc;

		/**
		    NOTE: Actual frequency of SPI clk will be somewhat device
		    dependent, relating to clock-settings, prescalars etc. If absolute
		    SPI frequency is required, consult your device documentation.
		  **/
		spi->frequency(param->max_speed_hz);
		spi->format(SPI_16_BIT_FRAME, param->mode);  // data write/read format
		spi_format_bytes = SPI_16_BIT_FRAME;
		spi->set_default_write_value(0x00);          // code to write when reading back
		ss->write(GPIO_HIGH);                        // set SS high

		return SUCCESS;
	}

	return FAILURE;
}


/**
 * @brief Free the resources allocated by spi_init().
 * @param desc - The SPI descriptor.
 * @return SUCCESS in case of success, FAILURE otherwise.
 */
int32_t spi_remove(struct spi_desc *desc)
{
	if (desc) {
		// Free the SPI port object
		if ((SPI *)(((mbed_spi_desc *)(desc->extra))->spi_port)) {
			delete((SPI *)(((mbed_spi_desc *)(desc->extra))->spi_port));
		}

		// Free the SS port object
		if ((DigitalOut *)(((mbed_spi_desc *)(desc->extra))->slave_select)) {
			delete((DigitalOut *)(((mbed_spi_desc *)(desc->extra))->slave_select));
		}

		// Free the SPI extra descriptor object
		if ((mbed_spi_desc *)(desc->extra)) {
			free((mbed_spi_desc *)(desc->extra));
		}

		// Free the SPI descriptor object
		free(desc);

		return SUCCESS;
	}

	return FAILURE;
}


/**
 * @brief Write and read data to/from SPI.
 * @param desc - The SPI descriptor.
 * @param data - The buffer with the transmitted/received data.
 * @param bytes_number - Number of bytes to write/read.
 * @return SUCCESS in case of success, FAILURE otherwise.
 */
int32_t spi_write_and_read(struct spi_desc *desc,
			   uint8_t *data,
			   uint16_t bytes_number)
{
	mbed::SPI *spi; 			// pointer to new spi instance
	mbed::DigitalOut *ss;  		// pointer to new SS instance
	uint16_t num_of_words;		// Number of words in SPI frame
	uint16_t rw_data;			// SPI read data (16-bit)
	uint16_t data_index = 0;	// Data index
	size_t byte;				// Byte read/write index

	if (desc) {
		spi = (SPI *)(((mbed_spi_desc *)(desc->extra))->spi_port);
		ss = (DigitalOut *)(((mbed_spi_desc *)(desc->extra))->slave_select);

		/* Get the total number of words (16-bit) */
		num_of_words = bytes_number / 2;

		/* Determine the data transmit/receive format based on parity of data */
		if (!(bytes_number % 2)) {
			if (spi_format_bytes != SPI_16_BIT_FRAME) {
				spi->format(SPI_16_BIT_FRAME, desc->mode);
				spi_format_bytes = SPI_16_BIT_FRAME;
			}
		} else {
			if (spi_format_bytes != SPI_8_BIT_FRAME) {
				spi->format(SPI_8_BIT_FRAME, desc->mode);
				spi_format_bytes = SPI_8_BIT_FRAME;
			}
		}

		ss->write(GPIO_LOW);

		/* **Note: It is not possible to change the format of data transfer when SPI
		 * communication is in progress. If format is attempted to change (from 8-bit
		 * to 16-bit or vice a versa), the SPI communication is reset and master generates
		 * a single Clock signal during format change. This triggers false transfer on slave
		 * which results into incorrect data transfer. For this reason, the bytes with even parity
		 * are transferred in 16-bit format and odd parity bytes are transferred in 8-bit format.
		 * Application layer doesn't have any control to stop SPI reset during format change. */

		if (!(bytes_number % 2)) {
			while (num_of_words) {
				/* Form a 16-bit data to be written (LE format) */
				rw_data = ((uint16_t)data[data_index + 1] | ((uint16_t)data[data_index] << 8));

				/* Transmit a 16-bit data over SPI */
				rw_data =  (uint16_t)spi->write(rw_data);

				/* Extract the MSB and LSB from 16-bit read data (LE format) */
				data[data_index++] = (uint8_t)(rw_data >> 8);
				data[data_index++] = (uint8_t)rw_data;

				num_of_words--;
			}
		} else {
			for (byte = 0; byte < bytes_number; byte++) {
				data[byte] =  spi->write(data[byte]);
			}
		}

		ss->write(GPIO_HIGH);

		return SUCCESS;
	}

	return FAILURE;
}
