/** @file dma.h
 * Module handling Direct Memory Access controller
 *
 * The module provides functions to configure the dma channels and controller
 * transfers
 */

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

#include "dma.h"
#include "dma_regs.h"

#include "nvic.h"
#include "rcc.h"

#include "stddef.h"


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

static void configure_dma(volatile struct DMA* dma, enum DmaChannel channel,
		enum DmaConfig config_mask, volatile void* periph);
static void start_dma(volatile struct DMA* dma, enum DmaChannel channel,
		volatile void* mem, uint16_t size);

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

static volatile struct DMA* const dma1 = (struct DMA*)DMA1_BASE_ADDRESS;
static volatile struct DMA* const dma2 = (struct DMA*)DMA2_BASE_ADDRESS;
static DmaCallback dma1_callbacks[7];
static volatile void* dma1_cb_params[7];
static DmaCallback dma2_callbacks[5];
static volatile void* dma2_cb_params[5];


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

void dma_configure(enum DmaPeriph dma, enum DmaChannel channel,
		enum DmaConfig config_mask, volatile void* periph,
		DmaCallback callback, volatile void* cb_param)
{
	//reset peripheral first, to ensure proper configuration
	dma_reset(dma, channel);

	switch (dma) {
		case DMA_PERIPH_1:
			rcc_enable(RCC_AHB_DMA1, RCC_APB1_NONE, RCC_APB2_NONE);
			configure_dma(dma1, channel, config_mask, periph);
			if (callback) {
				dma1_callbacks[channel] = callback;
				dma1_cb_params[channel] = cb_param;
				nvic_enable(NVIC_IRQ_DMA1_CHANNEL1 + channel);
			}
			break;
		case DMA_PERIPH_2:
			rcc_enable(RCC_AHB_DMA2, RCC_APB1_NONE, RCC_APB2_NONE);
			configure_dma(dma2, channel, config_mask, periph);
			if (callback) {
				dma2_callbacks[channel] = callback;
				dma2_cb_params[channel] = cb_param;
				nvic_enable(NVIC_IRQ_DMA2_CHANNEL1 + channel);
			}
			break;
		default:
			break;
	}
}

void dma_reset(enum DmaPeriph dma, enum DmaChannel channel)
{
	volatile struct DMA* periph;

	//first, disable IRQs
	switch (dma) {
		case DMA_PERIPH_1:
			periph = dma1;
			dma1_callbacks[channel] = NULL;
			nvic_disable(NVIC_IRQ_DMA1_CHANNEL1 + channel);
			break;
		case DMA_PERIPH_2:
			periph = dma2;
			dma2_callbacks[channel] = NULL;
			nvic_disable(NVIC_IRQ_DMA2_CHANNEL1 + channel);
			break;
		default:
			return;
			break;
	}

	//then, set all registers to reset value
	volatile struct DMA_CHANNEL* regs = &periph->CHANNELS[channel];
	regs->CCR.word = 0;
	regs->CNDTR.word = 0;
	regs->CMAR = 0;
	regs->CPAR = 0;
}

void dma_start(enum DmaPeriph dma, enum DmaChannel channel,
		volatile void* mem, uint16_t size)
{
	switch (dma) {
		case DMA_PERIPH_1:
			if (dma1_callbacks[channel]) {
				nvic_enable(NVIC_IRQ_DMA1_CHANNEL1 + channel);
			}
			start_dma(dma1, channel, mem, size);
			break;
		case DMA_PERIPH_2:
			if (dma2_callbacks[channel]) {
				nvic_enable(NVIC_IRQ_DMA2_CHANNEL1 + channel);
			}
			start_dma(dma2, channel, mem, size);
			break;
		default:
			return;
			break;
	}
}

void dma_stop(enum DmaPeriph dma, enum DmaChannel channel)
{
	switch (dma) {
		case DMA_PERIPH_1:
			dma1->CHANNELS[channel].CCR.EN = 0;
			if (dma1_callbacks[channel]) {
				nvic_disable(NVIC_IRQ_DMA1_CHANNEL1 + channel);
			}
			break;
		case DMA_PERIPH_2:
			dma2->CHANNELS[channel].CCR.EN;
			if (dma2_callbacks[channel]) {
				nvic_disable(NVIC_IRQ_DMA2_CHANNEL1 + channel);
			}
			break;
		default:
			return;
			break;
	}
}

void dma_enter_critical(enum DmaPeriph dma, enum DmaChannel channel)
{
	switch (dma) {
		case DMA_PERIPH_1:
			nvic_disable(NVIC_IRQ_DMA1_CHANNEL1 + channel);
			break;
		case DMA_PERIPH_2:
			nvic_disable(NVIC_IRQ_DMA2_CHANNEL1 + channel);
			break;
		default:
			return;
			break;
	}
}

void dma_exit_critical(enum DmaPeriph dma, enum DmaChannel channel)
{
	switch (dma) {
		case DMA_PERIPH_1:
			nvic_enable(NVIC_IRQ_DMA1_CHANNEL1 + channel);
			break;
		case DMA_PERIPH_2:
			nvic_enable(NVIC_IRQ_DMA2_CHANNEL1 + channel);
			break;
		default:
			return;
			break;
	}
}

uint16_t dma_get_remaining(enum DmaPeriph dma, enum DmaChannel channel)
{
	switch (dma) {
		case DMA_PERIPH_1:
			return dma1->CHANNELS[channel].CNDTR.NDT;
			break;
		case DMA_PERIPH_2:
			return dma2->CHANNELS[channel].CNDTR.NDT;
			break;
		default:
			return 0;
			break;
	}
}

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

/**
 * Applies the given configuration mask to the given DMA channel
 */
static void configure_dma(volatile struct DMA* dma, enum DmaChannel channel,
		enum DmaConfig config_mask, volatile void* periph)
{
	volatile struct DMA_CHANNEL* regs = &dma->CHANNELS[channel];

	//registers should already be at reset value, apply new config
	regs->CCR.word = config_mask;
	regs->CPAR = (uint32_t)periph;
}

/**
 * Starts the given DMA channel using the given parameters
 */
static void start_dma(volatile struct DMA* dma, enum DmaChannel channel,
		volatile void* mem, uint16_t size)
{
	volatile struct DMA_CHANNEL* regs = &dma->CHANNELS[channel];

	//registers should already be configured, apply transfer config
	regs->CNDTR.NDT = size;
	regs->CMAR = (uint32_t)mem;

	//only start transfer when everything is configured
	regs->CCR.EN = 1;
}

//--ISRs------------------------------------------------------------------------

void hdr_dma1_channel1(void)
{
	nvic_clear_pending(NVIC_IRQ_DMA1_CHANNEL1);

	enum DmaIRQSource src = (dma1->IFCR.word >> 1) & 0x7;
	dma1->IFCR.CGIF1 = 1;

	dma1_callbacks[0](src, dma1_cb_params[0]);
}

void hdr_dma1_channel2(void)
{
	nvic_clear_pending(NVIC_IRQ_DMA1_CHANNEL2);

	enum DmaIRQSource src = (dma1->IFCR.word >> 5) & 0x7;
	dma1->IFCR.CGIF2 = 1;

	dma1_callbacks[1](src, dma1_cb_params[1]);
}

void hdr_dma1_channel3(void)
{
	nvic_clear_pending(NVIC_IRQ_DMA1_CHANNEL3);

	enum DmaIRQSource src = (dma1->IFCR.word >> 9) & 0x7;
	dma1->IFCR.CGIF3 = 1;

	dma1_callbacks[2](src, dma1_cb_params[2]);
}

void hdr_dma1_channel4(void)
{
	nvic_clear_pending(NVIC_IRQ_DMA1_CHANNEL4);

	enum DmaIRQSource src = (dma1->IFCR.word >> 13) & 0x7;
	dma1->IFCR.CGIF4 = 1;

	dma1_callbacks[3](src, dma1_cb_params[3]);
}

void hdr_dma1_channel5(void)
{
	nvic_clear_pending(NVIC_IRQ_DMA1_CHANNEL5);

	enum DmaIRQSource src = (dma1->IFCR.word >> 17) & 0x7;
	dma1->IFCR.CGIF5 = 1;

	dma1_callbacks[4](src, dma1_cb_params[4]);
}

void hdr_dma1_channel6(void)
{
	nvic_clear_pending(NVIC_IRQ_DMA1_CHANNEL6);

	enum DmaIRQSource src = (dma1->IFCR.word >> 21) & 0x7;
	dma1->IFCR.CGIF6 = 1;

	dma1_callbacks[5](src, dma1_cb_params[5]);
}

void hdr_dma1_channel7(void)
{
	nvic_clear_pending(NVIC_IRQ_DMA1_CHANNEL7);

	enum DmaIRQSource src = (dma1->IFCR.word >> 25) & 0x7;
	dma1->IFCR.CGIF7 = 1;

	dma1_callbacks[6](src, dma1_cb_params[6]);
}

void hdr_dma2_channel1(void)
{
	nvic_clear_pending(NVIC_IRQ_DMA2_CHANNEL1);

	enum DmaIRQSource src = (dma2->IFCR.word >> 1) & 0x7;
	dma2->IFCR.CGIF1 = 1;

	dma2_callbacks[0](src, dma2_cb_params[0]);
}

void hdr_dma2_channel2(void)
{
	nvic_clear_pending(NVIC_IRQ_DMA2_CHANNEL2);

	enum DmaIRQSource src = (dma2->IFCR.word >> 5) & 0x7;
	dma2->IFCR.CGIF2 = 1;

	dma2_callbacks[1](src, dma2_cb_params[1]);
}

void hdr_dma2_channel3(void)
{
	nvic_clear_pending(NVIC_IRQ_DMA2_CHANNEL3);

	enum DmaIRQSource src = (dma2->IFCR.word >> 9) & 0x7;
	dma2->IFCR.CGIF3 = 1;

	dma2_callbacks[2](src, dma2_cb_params[2]);
}

void hdr_dma2_channel4_5(void)
{
	nvic_clear_pending(NVIC_IRQ_DMA2_CHANNEL4_5);

	enum DmaIRQSource src = (dma2->IFCR.word >> 13) & 0x7;
	if (src != 0) {
		dma2->IFCR.CGIF4 = 1;
		dma1_callbacks[3](src, dma2_cb_params[3]);
	}

	src = (dma2->IFCR.word >> 17) & 0x7;
	if (src != 0) {
		dma2->IFCR.CGIF5 = 1;
		dma1_callbacks[4](src, dma2_cb_params[4]);
	}
}

