/*******************************************************************************
 * Copyright (c) 2016 - 2018 Johannes Messmer (admin@jomess.com), fortiss GmbH
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *   Johannes Messmer - initial API and implementation and/or initial documentation
 *   Jose Cabral - Cleaning of namespaces
 *******************************************************************************/

#include "spi.h"
#include <sstream>
#include <string>

unsigned long const EmbrickSPIHandler::scmDefaultSpiSpeed = 300000;
unsigned long const EmbrickSPIHandler::scmMaxSpiSpeed = 700000;
char const EmbrickSPIHandler::mSpiMode = SPI_CPHA;
char const EmbrickSPIHandler::mSpiBitOrder = 0; // MSB first

const char * const EmbrickSPIHandler::scmFailedToInitHandler = "Failed to init spidev handler. Check if spi is enabled.";
const char * const EmbrickSPIHandler::scmFailedToConfigMode = "Failed to config spi write mode.";
const char * const EmbrickSPIHandler::scmFailedToConfigBitOrder = "Failed to config spi bit order.";
const char * const EmbrickSPIHandler::scmFailedToConfigSpeed = "Failed to config spi speed.";
const char * const EmbrickSPIHandler::scmFailedToTestBus = "Failed to send test byte to spi.";
const char * const EmbrickSPIHandler::scmFailedToTransferBuffer = "Failed to transfer buffer via spi.";

EmbrickSPIHandler::EmbrickSPIHandler(unsigned int paInterface) :
    mError(0) {
  // Convert int to string
  std::ostringstream interfaceStream;
  interfaceStream << paInterface;
  std::string interfaceStr = interfaceStream.str();

  std::string spidev = "/dev/spidev" + interfaceStr + ".0";
  init(spidev.c_str());
}

EmbrickSPIHandler::~EmbrickSPIHandler() {
  deInit();
}

void EmbrickSPIHandler::init(const char* paSpidev) {
  // Init spidev
  DEVLOG_DEBUG("emBrick[SPIHandler]: Open spidev '%s'\n", paSpidev);
  mFd = open(paSpidev, O_RDWR);

  if(mFd < 0) {
    return fail(scmFailedToInitHandler);
  }
  // Set mode to SPI_CPOL and SPI_CPHA
  if(!config(SPI_IOC_WR_MODE, SPI_IOC_RD_MODE, mSpiMode)) {
    return fail(scmFailedToConfigMode);
  }
  // Set MSB (Most Significant Bit First)
  if(!config(SPI_IOC_WR_LSB_FIRST, SPI_IOC_RD_LSB_FIRST, mSpiBitOrder)) {
    return fail(scmFailedToConfigBitOrder);
  }
  // Set speed
  if(!config(SPI_IOC_WR_MAX_SPEED_HZ, SPI_IOC_RD_MAX_SPEED_HZ, scmMaxSpiSpeed)) {
    return fail(scmFailedToConfigSpeed);
  }
  mSpiSpeed = scmDefaultSpiSpeed;

  // Test
  unsigned char testByte[1] = { 0 };
  if(!transfer(testByte, testByte, 1)) {
    return fail(scmFailedToTestBus);
  }
  DEVLOG_INFO("emBrick[SPIHandler]: Ready.\n");
}

void EmbrickSPIHandler::deInit() {
  // Close handler if open
  if(mFd >= 0) {
    close(mFd);
  }
}

template<typename T> bool EmbrickSPIHandler::config(unsigned int paConfig, unsigned int paConfigVerify, T paValue) {
  int status;

  // Set config via ioctl
  status = ioctl(mFd, paConfig, &paValue);
  if(status < 0) {
    return false;
  }
  // Verify config
  T valueCheck;
  status = ioctl(mFd, paConfigVerify, &valueCheck);
  if(status < 0) {
    return false;
  }
  return valueCheck == paValue;
}

void EmbrickSPIHandler::fail(const char* paReason) {
  mError = paReason;
  DEVLOG_ERROR("emBrick[SPIHandler]: %s\n", mError);
}

bool EmbrickSPIHandler::transfer(unsigned char* paSendBuffer, unsigned char* paReceiveBuffer, int paLength) {

  struct spi_ioc_transfer msg;

  msg.tx_buf = (unsigned long) paSendBuffer;
  msg.rx_buf = (unsigned long) paReceiveBuffer;
  msg.len = paLength;
  msg.delay_usecs = 0;
  msg.speed_hz = mSpiSpeed;
  msg.bits_per_word = 8;
  msg.cs_change = 0;
  msg.pad = 0;

  // Send data to spi bus
  int status = ioctl(mFd, SPI_IOC_MESSAGE(1), &msg);
  if(status < 0) {
    fail(scmFailedToTransferBuffer);
    return false;
  }

  return true;
}

void EmbrickSPIHandler::setSpeed(const unsigned long paSpeed) {
  mSpiSpeed = paSpeed;
}

