/** @file dma_mbuf.h
 * Module handling Direct Memory Access controller's multibuffer system
 *
 * The module provides a convenient tool to send data to a peripheral in a
 * buffered, low-altency, low-cpu usage, non-blocking way
 */

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

#include "dma_mbuf.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_MEM \
					| DMA_CONFIG_INC_MEM | DMA_CONFIG_MSIZE_8BITS)

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


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

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

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

	buffer->raw_buffer = raw_buffer;

	buffer->buffer_size = buffer_size / 2;

	buffer->byte_index = 0;
	buffer->buffer_index = 0;
	buffer->dma = param->dma;
	buffer->channel = param->channel;

	buffer->dma_running = false;

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

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

	//if the current buffer is full, give up
	if (buffer->byte_index >= buffer->buffer_size) {
		return 1;
	}

	//write the byte
	buffer->raw_buffer[buffer->buffer_index * buffer->buffer_size
		+ buffer->byte_index] = byte;
	++buffer->byte_index;
	return 0;
}

uint32_t dma_mbuf_switch(volatile struct DmaMultiBuffer* buffer)
{
	error_assert(buffer != nullptr);

	//no data to send, stop here
	if (buffer->byte_index == 0) {
		return 0;
	}

	//dma already running, give up
	if (buffer->dma_running) {
		return 1;
	}

	//start a new transfer
	buffer->dma_running = true;
	dma_start(buffer->dma, buffer->channel,
			&buffer->raw_buffer[buffer->buffer_index * buffer->buffer_size],
			buffer->byte_index);

	buffer->byte_index = 0;
	++buffer->buffer_index;
	if (buffer->buffer_index > 1) {
		buffer->buffer_index = 0;
	}

	return 0;
}


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

/**
 * Callback called on DMA TX tranfert's completion. Stops the DMA and notifies
 * the buffer that the transfer is done.
 */
static void mbuf_callback(enum DmaIRQSource src, volatile void* param)
{
	(void)src; //only transfer complete expected
	error_assert(param != nullptr);

	volatile struct DmaMultiBuffer* buffer = param;
	dma_stop(buffer->dma, buffer->channel);
	buffer->dma_running = false;
}

