/* Arduino DigitalIO Library
 * Copyright (C) 2013 by William Greiman
 *
 * This file is part of the Arduino DigitalIO Library
 *
 * This Library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This Library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the Arduino DigitalIO Library.  If not, see
 * <http://www.gnu.org/licenses/>.
 */
/**
 * @file
 * @brief  Software SPI.
 *
 * @defgroup softSPI Software SPI
 * @ingroup internals
 * @details  Software SPI Template Class.
 * @{
 */

#ifndef SoftSPI_h
#define SoftSPI_h
#include "DigitalPin.h"
//------------------------------------------------------------------------------
/** Nop for timing. */
#define nop asm volatile ("nop\n\t")
//------------------------------------------------------------------------------
/** Pin Mode for MISO is input.*/
const bool MISO_MODE  = false;
/** Pullups disabled for MISO are disabled. */
const bool MISO_LEVEL = false;
/** Pin Mode for MOSI is output.*/
const bool MOSI_MODE  = true;
/** Pin Mode for SCK is output. */
const bool SCK_MODE   = true;
//------------------------------------------------------------------------------
/**
 * @class SoftSPI
 * @brief Fast software SPI.
 */
template<uint8_t MisoPin, uint8_t MosiPin, uint8_t SckPin, uint8_t Mode = 0>
class SoftSPI
{
public:
	//----------------------------------------------------------------------------
	/** Initialize SoftSPI pins. */
	void begin()
	{
		fastPinConfig(MisoPin, MISO_MODE, MISO_LEVEL);
		fastPinConfig(MosiPin, MOSI_MODE, !MODE_CPHA(Mode));
		fastPinConfig(SckPin, SCK_MODE, MODE_CPOL(Mode));
	}
	//----------------------------------------------------------------------------
	/** Soft SPI receive byte.
	 * @return Data byte received.
	 */
	ALWAYS_INLINE uint8_t receive()
	{
		uint8_t data = 0;
		receiveBit(7, &data);
		receiveBit(6, &data);
		receiveBit(5, &data);
		receiveBit(4, &data);
		receiveBit(3, &data);
		receiveBit(2, &data);
		receiveBit(1, &data);
		receiveBit(0, &data);
		return data;
	}
	//----------------------------------------------------------------------------
	/** Soft SPI send byte.
	 * @param[in] data Data byte to send.
	 */
	ALWAYS_INLINE void send(uint8_t data)
	{
		sendBit(7, data);
		sendBit(6, data);
		sendBit(5, data);
		sendBit(4, data);
		sendBit(3, data);
		sendBit(2, data);
		sendBit(1, data);
		sendBit(0, data);
	}
	//----------------------------------------------------------------------------
	/** Soft SPI transfer byte.
	 * @param[in] txData Data byte to send.
	 * @return Data byte received.
	 */
	ALWAYS_INLINE uint8_t transfer(uint8_t txData)
	{
		uint8_t rxData = 0;
		transferBit(7, &rxData, txData);
		transferBit(6, &rxData, txData);
		transferBit(5, &rxData, txData);
		transferBit(4, &rxData, txData);
		transferBit(3, &rxData, txData);
		transferBit(2, &rxData, txData);
		transferBit(1, &rxData, txData);
		transferBit(0, &rxData, txData);
		return rxData;
	}

private:
	//----------------------------------------------------------------------------
	ALWAYS_INLINE bool MODE_CPHA(uint8_t mode)
	{
		return (mode & 1) != 0;
	}
	ALWAYS_INLINE bool MODE_CPOL(uint8_t mode)
	{
		return (mode & 2) != 0;
	}
	ALWAYS_INLINE void receiveBit(uint8_t bit, uint8_t* data)
	{
		if (MODE_CPHA(Mode)) {
			fastDigitalWrite(SckPin, !MODE_CPOL(Mode));
		}
		nop;
		nop;
		fastDigitalWrite(SckPin,
		                 MODE_CPHA(Mode) ? MODE_CPOL(Mode) : !MODE_CPOL(Mode));
		if (fastDigitalRead(MisoPin)) {
			*data |= 1 << bit;
		}
		if (!MODE_CPHA(Mode)) {
			fastDigitalWrite(SckPin, MODE_CPOL(Mode));
		}
	}
	//----------------------------------------------------------------------------
	ALWAYS_INLINE void sendBit(uint8_t bit, uint8_t data)
	{
		if (MODE_CPHA(Mode)) {
			fastDigitalWrite(SckPin, !MODE_CPOL(Mode));
		}
		fastDigitalWrite(MosiPin, data & (1 << bit));
		fastDigitalWrite(SckPin,
		                 MODE_CPHA(Mode) ? MODE_CPOL(Mode) : !MODE_CPOL(Mode));
		nop;
		nop;
		if (!MODE_CPHA(Mode)) {
			fastDigitalWrite(SckPin, MODE_CPOL(Mode));
		}
	}
	//----------------------------------------------------------------------------
	ALWAYS_INLINE void transferBit(uint8_t bit, uint8_t* rxData, uint8_t txData)
	{
		if (MODE_CPHA(Mode)) {
			fastDigitalWrite(SckPin, !MODE_CPOL(Mode));
		}
		fastDigitalWrite(MosiPin, txData & (1 << bit));
		fastDigitalWrite(SckPin,
		                 MODE_CPHA(Mode) ? MODE_CPOL(Mode) : !MODE_CPOL(Mode));
		if (fastDigitalRead(MisoPin)) {
			*rxData |= 1 << bit;
		}
		if (!MODE_CPHA(Mode)) {
			fastDigitalWrite(SckPin, MODE_CPOL(Mode));
		}
	}
	//----------------------------------------------------------------------------
};
#endif  // SoftSPI_h
/** @} */
