//..............................................................................
//
// SPI protocol decoder
//

import "Decoder.jnc"

//..............................................................................

class SpiDecoder: Decoder
{
protected:
	enum Code
	{
		Padding         = 0xff,
		BufferOverflow  = 0xaa,
		Start           = 0,
		MosiDataHi,     // 1
		MosiDataLo,     // 2
		MisoDataHi,     // 3
		MisoDataLo,     // 4
		Stop,           // 5
	}

	enum State
	{
		Idle,
		Transaction,
		MosiDataHi,
		MosiDataLo,
		MisoDataHi,
	}

protected:
	State m_state;
	uint8_t m_mosi;
	uint8_t m_miso;
	std.Buffer m_misoBuffer;
	uint_t* m_bitCount; // as to avoid "lifting" allocations

public:
	bool m_isBigEndian;
	uint_t m_dataBits;

public:
	construct (log.Writer* logWriter);

	override reset ()
	{
		m_state = State.Idle;
	}

	override decode (
		void const thin* p,
		size_t size
		);
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

SpiDecoder.construct (log.Writer* logWriter)
{
	basetype.construct (logWriter);
	m_misoBuffer.setSize (m_buffer.m_size / 2);
	m_bitCount = new uint_t;
}

SpiDecoder.decode (
	void const thin* p0,
	size_t size
	)
{
	uint64_t timestamp = sys.getTimestamp ();
	uchar_t const thin* p = p0;
	uchar_t const thin* end = p + size;

	uchar_t thin* mosi = m_buffer.m_p + m_size;
	uchar_t thin* miso = m_misoBuffer.m_p + m_size;
	uchar_t thin* lastMiso = m_misoBuffer.m_p + m_misoBuffer.m_size - 1;

	for (; p < end; p++)
	{
		uchar_t c = *p;

		switch (c)
		{
		case Code.Padding:
			continue;

		case Code.BufferOverflow:
			m_logWriter.write (timestamp, I2cSpiSnifferLogRecordCode.BufferOverflow);
			continue;
		}

		uchar_t code = (c & 0xf0) >> 4;
		switch (code)
		{
		case Code.Start:
			size_t size = mosi - (uchar_t*) m_buffer.m_p;
			if (size)
			{
				memcpy (m_buffer.m_p + size, m_misoBuffer.m_p, size);
				m_logWriter.write (timestamp, log.StdRecordCode.TxRx, m_buffer.m_p, 2 * size);
				mosi = m_buffer.m_p;
				miso = m_misoBuffer.m_p;
			}

			uint_t bitCount = c & 0x0f;
			if (bitCount)
			{
				*m_bitCount = bitCount;
				m_logWriter.write (timestamp, I2cSpiSnifferLogRecordCode.IncompleteByte, m_bitCount, sizeof (uint_t));
			}

			m_logWriter.write (timestamp, I2cSpiSnifferLogRecordCode.SpiStart, null, 0);
			m_state = State.Transaction;
			break;

		case Code.MosiDataHi:
			if (m_state >= State.MosiDataHi)
				writeLogUnexpectedCode (timestamp, code, m_state);

			if (m_isBigEndian)
				m_mosi = c << 4;
			else
				m_mosi = c & 0x0f;

			m_state = State.MosiDataHi;
			break;

		case Code.MosiDataLo:
			if (m_state != State.MisoDataHi)
				writeLogUnexpectedCode (timestamp, code, m_state);

			if (m_isBigEndian)
				m_mosi |= c & 0x0f;
			else
				m_mosi |= c << 4;

			*mosi = m_mosi;
			m_state = State.MosiDataLo;
			break;

		case Code.MisoDataHi:
			if (m_state != State.MosiDataHi)
				writeLogUnexpectedCode (timestamp, code, m_state);

			if (m_isBigEndian)
				m_miso = c << 4;
			else
				m_miso = c & 0x0f;

			m_state = State.MisoDataHi;
			break;

		case Code.MisoDataLo:
			if (m_state != State.MosiDataLo)
				writeLogUnexpectedCode (timestamp, code, m_state);

			if (m_isBigEndian)
				m_miso |= c & 0x0f;
			else
				m_miso |= c << 4;

			*miso = m_miso;

			if (miso < lastMiso)
			{
				mosi++;
				miso++;
			}
			else
			{
				size_t size = mosi - (uchar_t*) m_buffer.m_p;
				memcpy (m_buffer.m_p + size, m_misoBuffer.m_p, size);
				m_logWriter.write (timestamp, log.StdRecordCode.TxRx, m_buffer.m_p, 2 * size);
				mosi = m_buffer.m_p;
				miso = m_misoBuffer.m_p;
			}

			m_state = State.Transaction;
			break;

		case Code.Stop:
			size_t size = mosi - (uchar_t*) m_buffer.m_p;
			if (size)
			{
				memcpy (m_buffer.m_p + size, m_misoBuffer.m_p, size);
				m_logWriter.write (timestamp, log.StdRecordCode.TxRx, m_buffer.m_p, 2 * size);
				mosi = m_buffer.m_p;
				miso = m_misoBuffer.m_p;
			}

			uint_t bitCount = c & 0x0f;
			if (m_state == State.MosiDataHi || m_state == State.MisoDataHi)
				bitCount += 4;

			if (bitCount)
			{
				*m_bitCount = bitCount;
				m_logWriter.write (timestamp, I2cSpiSnifferLogRecordCode.IncompleteByte, m_bitCount, sizeof (uint_t));
			}

			m_logWriter.write (timestamp, I2cSpiSnifferLogRecordCode.SpiStop, null, 0);
			m_state = State.Idle;
			break;
		}
	}

	m_size = mosi - (uchar_t*) m_buffer.m_p;
}

//..............................................................................

