/*
 * ll_hsusart_impl.h
 *
 *  Created on: Oct 29, 2019
 *      Author: abody
 */
#include <string.h>
#include "ll_hsusart.h"

namespace f4ll {

template<typename T> static inline T RoundUpTo4(T input)
{
	return (input + 3) & (((T)-1) - 3);
}

LL_HsUsart::LL_HsUsart(USART_TypeDef *usart, DMA_TypeDef *dma, uint32_t streamRx, uint32_t streamTx)
: m_usart(usart)
, m_rxDma(dma, streamRx)
, m_txDma(dma, streamTx)
{
	LL_CrcHandler::Instance().AttachSlot(m_crcSlot);
	LL_DMA_EnableIT_TC(dma, streamRx);
	LL_DMA_EnableIT_TE(dma, streamRx);
	LL_DMA_EnableIT_TC(dma, streamTx);
	LL_DMA_EnableIT_TE(dma, streamTx);
	LL_USART_EnableIT_IDLE(usart);

	memset(&m_stats, 0, sizeof(m_stats));
}


uint8_t *LL_HsUsart::GetTxPacketBuffer(void)
{
	return m_txBuffer.packet.payload;
}


USART_TypeDef* LL_HsUsart::GetUsart(void)
{
	return m_usart;
}


LL_HsUsart::Stats const & LL_HsUsart::GetStats(void)
{
	return m_stats;
}


bool LL_HsUsart::IsTxBusy()
{
	return m_txBuffer.busy;
}


bool LL_HsUsart::IsTxFailed()
{
	return m_txBuffer.error;
}


bool LL_HsUsart::IsRxBusy(bool second)
{
	return m_rxBuffers[second].busy;
}


bool LL_HsUsart::IsRxFailed(bool second)
{
	return m_rxBuffers[second].error;

}

void LL_HsUsart::RxProcessed(bool second)
{
	m_rxBuffers[second].busy = false;
	m_rxBuffers[second].error = false;
}

void LL_HsUsart::SetCallback(IHsUsartCallback *callback, uintptr_t callbackParam)
{
	m_userCallback = callback;
	m_userCallbackParam = callbackParam;
}

void LL_HsUsart::BuildHeader(Packet &packet, uint8_t serialNo, uint8_t length)
{
	uint8_t hash = STARTMARKER;

	packet.header.startByte = STARTMARKER;
	packet.header.serial = serialNo;
	hash ^= serialNo;
	packet.header.payloadLength = length;
	hash ^= length;
	packet.header.hash = hash;
}


bool LL_HsUsart::CheckHeader(PacketHeader &header)
{
	return header.startByte == STARTMARKER && (header.startByte ^ header.serial ^ header.payloadLength) == header.hash;
}


void LL_HsUsart::PostPacket(uint8_t const *payload, uint8_t length, bool waitForCrcQueue)
{
	uint16_t payloadLength = RoundUpTo4((uint16_t)length);

	BuildHeader(m_txBuffer.packet, m_txSerialNo++, length);
	if(payload)
		memcpy(m_txBuffer.packet.payload, payload, length);
	m_txBuffer.requestedLength = sizeof(m_txBuffer.packet.header) + payloadLength + sizeof(uint32_t);
	m_txBuffer.busy = true;
	m_txBuffer.error = false;

	LL_CrcHandler::Instance().Enqueue(m_crcSlot, 0, &m_txBuffer.packet, sizeof(PacketHeader) + payloadLength,
			nullptr, reinterpret_cast<uintptr_t>(m_txBuffer.packet.payload + payloadLength));

	while(waitForCrcQueue && LL_CrcHandler::Instance().IsQueued(m_crcSlot, 0));

	LL_DMA_ConfigAddresses(m_txDma.GetDma(), m_txDma.GetStream(), reinterpret_cast<uint32_t>(&m_txBuffer.packet),
			LL_USART_DMA_GetRegAddr(m_usart), LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
	LL_DMA_SetDataLength(m_txDma.GetDma(), m_txDma.GetStream(), m_txBuffer.requestedLength);
	LL_USART_EnableDMAReq_TX(m_usart);
	LL_DMA_EnableStream(m_txDma.GetDma(), m_txDma.GetStream());

	++m_stats.sent;
}


void LL_HsUsart::SetupReceive()
{
	int packetIndex = m_rxBufferSelector;

	LL_DMA_ConfigAddresses(m_rxDma.GetDma(), m_rxDma.GetStream(), LL_USART_DMA_GetRegAddr(m_usart),
			reinterpret_cast<uint32_t>(&m_rxBuffers[packetIndex]), LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
	m_rxBuffers[packetIndex].requestedLength = sizeof(m_rxBuffers[packetIndex].packet);
	LL_DMA_SetDataLength(m_rxDma.GetDma(), m_rxDma.GetStream(), m_rxBuffers[packetIndex].requestedLength);	// payload already have extra room for hash
	LL_USART_EnableDMAReq_RX(m_usart);
	LL_USART_ClearFlag_ORE(m_usart);
	LL_DMA_EnableStream(m_rxDma.GetDma(), m_rxDma.GetStream());
}


void LL_HsUsart::UsartIrq(void)
{
	if(LL_USART_IsActiveFlag_IDLE(m_usart) && LL_USART_IsEnabledIT_IDLE(m_usart)) {	//	receiver idle
		LL_USART_ClearFlag_IDLE(m_usart);
		uint16_t rcvdLen = m_rxBuffers[m_rxBufferSelector].requestedLength - LL_DMA_GetDataLength(m_rxDma.GetDma(), m_rxDma.GetStream());
		if(rcvdLen >= sizeof(PacketHeader)) {
			if(CheckHeader(m_rxBuffers[m_rxBufferSelector].packet.header)) {
				if(rcvdLen >= sizeof(PacketHeader) +
						RoundUpTo4((uint16_t)m_rxBuffers[m_rxBufferSelector].packet.header.payloadLength)
						+ sizeof(uint32_t))
					LL_DMA_DisableStream(m_rxDma.GetDma(), m_rxDma.GetStream());
				else
					++m_stats.premature_payload;
			} else {
				m_rxBuffers[m_rxBufferSelector].error = 1;
				LL_DMA_DisableStream(m_rxDma.GetDma(), m_rxDma.GetStream());
			}
		} else
			++m_stats.premature_hdr;
	}
	else if(LL_USART_IsActiveFlag_TC(m_usart) && LL_USART_IsEnabledIT_TC(m_usart)) {	// transmission complete
		LL_USART_DisableIT_TC(m_usart);
		LL_USART_DisableDirectionTx(m_usart);	// enforcing an idle frame
		LL_USART_EnableDirectionTx(m_usart);
		m_txBuffer.busy = 0;
	}
}


void LL_HsUsart::RxDmaIrq()
{
	++m_stats.rcvd;
	if(*m_rxDma.GetIsReg() & m_rxDma.GetTcMask()) {
		*m_rxDma.GetIcfReg() = m_rxDma.GetTcMask();
		if(CheckHeader(m_rxBuffers[m_rxBufferSelector].packet.header))
			LL_CrcHandler::Instance().Enqueue(m_crcSlot, 1,
					&m_rxBuffers[m_rxBufferSelector].packet,
					sizeof(PacketHeader) + RoundUpTo4((uint16_t)m_rxBuffers[m_rxBufferSelector].packet.header.payloadLength),
					this, m_rxBufferSelector);
		else {
			++m_stats.hdrError;
			m_rxBuffers[m_rxBufferSelector].error = true;
		}
	} else if(*m_rxDma.GetIsReg() & m_rxDma.GetTeMask()) {
		*m_rxDma.GetIcfReg() = m_rxDma.GetTeMask();
		m_rxBuffers[m_rxBufferSelector].error = 1;
		++m_stats.dmaError;
	}

	m_rxBufferSelector = !m_rxBufferSelector;

	if(m_rxBuffers[m_rxBufferSelector].busy)
		++m_stats.overrun;
	SetupReceive();
}


void LL_HsUsart::CrcSucceeded(uintptr_t callbackParam, uint32_t crc, int prio)
{
	Buffer &buf(m_rxBuffers[static_cast<int>(callbackParam)]);

	buf.busy = 1;
	if(*(uint32_t*) (buf.packet.payload + RoundUpTo4((uint16_t)buf.packet.header.payloadLength)) != crc) {
		buf.error = 1;
		buf.errorInfo = crc;
		++m_stats.payloadErrror;
	}
	if(m_userCallback)
		buf.busy = !m_userCallback->PacketReceived(this, m_userCallbackParam, buf.packet);
}


void LL_HsUsart::CrcFailed(uintptr_t callbackParam, uint32_t crc, int prio)
{
	Buffer &buf(m_rxBuffers[static_cast<int>(callbackParam)]);
	buf.busy = buf.error = true;
	buf.errorInfo = 0;
	++m_stats.payloadErrror;
	if(m_userCallback)
		buf.busy = !m_userCallback->PacketReceived(this, m_userCallbackParam, buf.packet);
}


void LL_HsUsart::TxDmaIrq()
{
	if(*m_txDma.GetIsReg() & m_txDma.GetTcMask()) {	// DMA transfer complete
		*m_txDma.GetIcfReg() = m_txDma.GetTcMask();
		LL_USART_EnableIT_TC(m_usart);
		LL_DMA_DisableStream(m_txDma.GetDma(), m_txDma.GetStream());
	}
	else if(*m_txDma.GetIsReg() & m_txDma.GetTeMask()) {
		*m_txDma.GetIcfReg() = m_txDma.GetTeMask();
		m_txBuffer.error = 1;
		++m_stats.dmaError;
	}
	if(*m_txDma.GetIsReg() & m_txDma.GetFeMask())
		*m_txDma.GetIcfReg() = m_txDma.GetFeMask();
	if(*m_txDma.GetIsReg() & m_txDma.GetHtMask())
		*m_txDma.GetIcfReg() = m_txDma.GetHtMask();
	if(*m_txDma.GetIsReg() & m_txDma.GetDmeMask())
		*m_txDma.GetIcfReg() = m_txDma.GetDmeMask();
}


}	//	namespace f4ll
