/** @file dma_cbuf.c
 * Module handling Direct Memory Access controller's circular system
 *
 * The module provides a convenient tool to receive data from a peripheral in a
 * buffered, low-latency, low-cpu usage, non-blocking way
 */

//--includes--------------------------------------------------------------------

#include "dma_cbuf.h"
#include "error.h"


//--local definitions-----------------------------------------------------------

/**
 * DMA configuration to be used for the buffer. Additionnal configuration may be
 * added by the peripherals used
 */
#define DMA_CONFIG (DMA_CONFIG_IRQ_COMPLETE | DMA_CONFIG_FROM_PERIPH  \
					| DMA_CONFIG_CIRCULAR | DMA_CONFIG_INC_MEM        \
					| DMA_CONFIG_MSIZE_8BITS)

static void cbuf_callback(enum DmaIRQSource src, volatile void* param);


//--local variables-------------------------------------------------------------

//--public functions------------------------------------------------------------

void dma_cbuf_configure(volatile struct DmaCircBuffer* buffer,
		const struct DmaParam* param, enum DmaConfig priority,
		volatile void* raw_buffer, uint16_t buffer_size)
{
	error_assert(buffer != nullptr);
	error_assert(param != nullptr);
	error_assert(raw_buffer != nullptr);

	buffer->buffer = raw_buffer;

	buffer->size = buffer_size;
	buffer->begin = 0;

	buffer->dma = param->dma;
	buffer->channel = param->channel;

	buffer->dma_looped = false;

	dma_configure(buffer->dma, buffer->channel,
			DMA_CONFIG | param->config | priority,
			param->periph, cbuf_callback, buffer);
	dma_start(buffer->dma, buffer->channel, buffer->buffer, buffer->size);
}

uint32_t dma_cbuf_read_byte(volatile struct DmaCircBuffer* buffer,
		uint8_t* byte)
{
	error_assert(buffer != nullptr);
	error_assert(byte != nullptr);

	//retreive the current end of the buffer based on the DMA's progress
	uint16_t end = buffer->size
		- dma_get_remaining(buffer->dma, buffer->channel);

	//check for bytes to read and overflow
	if ((end > buffer->begin) && buffer->dma_looped) {
		//TODO overflow
		buffer->begin = end;
	} else if ((buffer->begin == end) && !buffer->dma_looped) {
		//TODO no data
		return 1;
	}

	//read the oldest byte and advance the buffer
	uint8_t* raw_buffer = (uint8_t*)buffer->buffer;
	*byte = raw_buffer[buffer->begin];
	++buffer->begin;
	if (buffer->begin >= buffer->size) {
		buffer->begin = 0;
		buffer->dma_looped = false;
	}

	return 0;
}

//--local functions-------------------------------------------------------------

/**
 * Callback called on DMA RX tranfert's completion. Sets a flag needed to
 * properly handle the circular buffer
 */
static void cbuf_callback(enum DmaIRQSource src, volatile void* param)
{
	(void)src; //only transfer complete expected
	error_assert(param != nullptr);

	volatile struct DmaCircBuffer* buffer = param;
	buffer->dma_looped = true;
}

