// Copyright 2020 Embedded Artistry LLC
// SPDX-License-Identifier: MIT

#include "STM32DMA.hpp"
#include <array>
#include <cassert>
#include <nvic.hpp>
#include <stm32h7xx_ll_dma.h>
#include <volatile.hpp>

const std::array<DMA_TypeDef* const, STM32DMA::device::MAX_DMA> dma_devices = {DMA1, DMA2};

const std::array<std::array<volatile uint32_t* const, STM32DMA::channel::MAX_CH>,
				 STM32DMA::device::MAX_DMA>
	dma_ifcr_register = {std::array<volatile uint32_t* const, STM32DMA::channel::MAX_CH>{
							 &DMA1->LIFCR, &DMA1->LIFCR, &DMA1->LIFCR, &DMA1->LIFCR, &DMA1->HIFCR,
							 &DMA1->HIFCR, &DMA1->HIFCR, &DMA1->HIFCR},
						 std::array<volatile uint32_t* const, STM32DMA::channel::MAX_CH>{
							 &DMA2->LIFCR, &DMA2->LIFCR, &DMA2->LIFCR, &DMA2->LIFCR, &DMA2->HIFCR,
							 &DMA2->HIFCR, &DMA2->HIFCR, &DMA2->HIFCR}};

const std::array<std::array<volatile uint32_t* const, STM32DMA::channel::MAX_CH>,
				 STM32DMA::device::MAX_DMA>
	dma_isr_register = {std::array<volatile uint32_t* const, STM32DMA::channel::MAX_CH>{
							&DMA1->LISR, &DMA1->LISR, &DMA1->LISR, &DMA1->LISR, &DMA1->HISR,
							&DMA1->HISR, &DMA1->HISR, &DMA1->HISR},
						std::array<volatile uint32_t* const, STM32DMA::channel::MAX_CH>{
							&DMA2->LISR, &DMA2->LISR, &DMA2->LISR, &DMA2->LISR, &DMA2->HISR,
							&DMA2->HISR, &DMA2->HISR, &DMA2->HISR}};

constexpr std::array<uint32_t const, STM32DMA::channel::MAX_CH> transfer_complete_flags = {
	DMA_LISR_TCIF0, DMA_LISR_TCIF1, DMA_LISR_TCIF2, DMA_LISR_TCIF3,
	DMA_HISR_TCIF4, DMA_HISR_TCIF5, DMA_HISR_TCIF6, DMA_HISR_TCIF7};

constexpr std::array<uint32_t const, STM32DMA::channel::MAX_CH> transfer_error_flags = {
	DMA_LISR_TEIF0, DMA_LISR_TEIF1, DMA_LISR_TEIF2, DMA_LISR_TEIF3,
	DMA_HISR_TEIF4, DMA_HISR_TEIF5, DMA_HISR_TEIF6, DMA_HISR_TEIF7};

constexpr std::array<uint32_t const, STM32DMA::channel::MAX_CH> clear_transfer_error_flags = {
	DMA_LIFCR_CTEIF0, DMA_LIFCR_CTEIF1, DMA_LIFCR_CTEIF2, DMA_LIFCR_CTEIF3,
	DMA_HIFCR_CTEIF4, DMA_HIFCR_CTEIF5, DMA_HIFCR_CTEIF6, DMA_HIFCR_CTEIF7};

constexpr std::array<uint32_t const, STM32DMA::channel::MAX_CH> clear_transfer_complete_flags = {
	DMA_LIFCR_CTCIF0, DMA_LIFCR_CTCIF1, DMA_LIFCR_CTCIF1, DMA_LIFCR_CTCIF3,
	DMA_HIFCR_CTCIF4, DMA_HIFCR_CTCIF5, DMA_HIFCR_CTCIF6, DMA_HIFCR_CTCIF7};

constexpr std::array<std::array<IRQn_Type, STM32DMA::channel::MAX_CH>, STM32DMA::device::MAX_DMA>
	irq_num = {
		std::array<IRQn_Type, STM32DMA::channel::MAX_CH>{
			DMA1_Stream0_IRQn, DMA1_Stream1_IRQn, DMA1_Stream2_IRQn, DMA1_Stream3_IRQn,
			DMA1_Stream4_IRQn, DMA1_Stream5_IRQn, DMA1_Stream6_IRQn, DMA1_Stream7_IRQn},
		std::array<IRQn_Type, STM32DMA::channel::MAX_CH>{
			DMA2_Stream0_IRQn, DMA2_Stream1_IRQn, DMA2_Stream2_IRQn, DMA2_Stream3_IRQn,
			DMA2_Stream4_IRQn, DMA2_Stream5_IRQn, DMA2_Stream6_IRQn, DMA2_Stream7_IRQn},
};

static std::array<std::array<STM32DMA::cb_t, STM32DMA::channel::MAX_CH>, STM32DMA::device::MAX_DMA>
	irq_handlers = {};

static inline bool check_dma_complete_flag(STM32DMA::device dev, STM32DMA::channel chan)
{
	return READ_BIT(*dma_isr_register[dev][chan], transfer_complete_flags[chan]);
}

static inline bool check_dma_error_flag(STM32DMA::device dev, STM32DMA::channel chan)
{
	return READ_BIT(*dma_isr_register[dev][chan], transfer_error_flags[chan]);
}

static inline void clear_transfer_complete_flag(STM32DMA::device dev, STM32DMA::channel chan)
{
	embutil::volatile_store(dma_ifcr_register[dev][chan], clear_transfer_complete_flags[chan]);
}

static inline void clear_transfer_error_flag(STM32DMA::device dev, STM32DMA::channel chan)
{
	embutil::volatile_store(dma_ifcr_register[dev][chan], clear_transfer_error_flags[chan]);
}

extern "C" void DMA1_Stream0_IRQHandler();
extern "C" void DMA1_Stream1_IRQHandler();
extern "C" void DMA1_Stream2_IRQHandler();
extern "C" void DMA1_Stream3_IRQHandler();
extern "C" void DMA1_Stream4_IRQHandler();
extern "C" void DMA1_Stream5_IRQHandler();
extern "C" void DMA1_Stream6_IRQHandler();
extern "C" void DMA1_Stream7_IRQHandler();
extern "C" void DMA2_Stream0_IRQHandler();
extern "C" void DMA2_Stream1_IRQHandler();
extern "C" void DMA2_Stream2_IRQHandler();
extern "C" void DMA2_Stream3_IRQHandler();
extern "C" void DMA2_Stream4_IRQHandler();
extern "C" void DMA2_Stream5_IRQHandler();
extern "C" void DMA2_Stream6_IRQHandler();
extern "C" void DMA2_Stream7_IRQHandler();

static void dma_handler(STM32DMA::device dev, STM32DMA::channel chan)
{
	auto handler = irq_handlers[dev][chan];
	auto* inst = dma_devices[dev];
	STM32DMA::status status;
	assert(handler); // check to see if a valid handler is registered

	if(check_dma_complete_flag(dev, chan))
	{
		clear_transfer_complete_flag(dev, chan);
		status = STM32DMA::status::ok;
	}
	else if(check_dma_error_flag(dev, chan))
	{
		clear_transfer_error_flag(dev, chan);
		status = STM32DMA::status::error;
	}
	else
	{
		assert(0); // Case not handled!
	}

	LL_DMA_DisableStream(inst, chan);
	handler(status);
}

void DMA1_Stream0_IRQHandler()
{
	dma_handler(STM32DMA::device::dma1, STM32DMA::channel::CH0);
}

void DMA1_Stream1_IRQHandler()
{
	dma_handler(STM32DMA::device::dma1, STM32DMA::channel::CH1);
}

void DMA1_Stream2_IRQHandler()
{
	dma_handler(STM32DMA::device::dma1, STM32DMA::channel::CH2);
}

void DMA1_Stream3_IRQHandler()
{
	dma_handler(STM32DMA::device::dma1, STM32DMA::channel::CH3);
}

void DMA1_Stream4_IRQHandler()
{
	dma_handler(STM32DMA::device::dma1, STM32DMA::channel::CH4);
}

void DMA1_Stream5_IRQHandler()
{
	dma_handler(STM32DMA::device::dma1, STM32DMA::channel::CH5);
}

void DMA1_Stream6_IRQHandler()
{
	dma_handler(STM32DMA::device::dma1, STM32DMA::channel::CH6);
}

void DMA1_Stream7_IRQHandler()
{
	dma_handler(STM32DMA::device::dma1, STM32DMA::channel::CH7);
}

void DMA2_Stream0_IRQHandler()
{
	dma_handler(STM32DMA::device::dma2, STM32DMA::channel::CH0);
}

void DMA2_Stream1_IRQHandler()
{
	dma_handler(STM32DMA::device::dma2, STM32DMA::channel::CH1);
}

void DMA2_Stream2_IRQHandler()
{
	dma_handler(STM32DMA::device::dma2, STM32DMA::channel::CH2);
}

void DMA2_Stream3_IRQHandler()
{
	dma_handler(STM32DMA::device::dma2, STM32DMA::channel::CH3);
}

void DMA2_Stream4_IRQHandler()
{
	dma_handler(STM32DMA::device::dma2, STM32DMA::channel::CH4);
}

void DMA2_Stream5_IRQHandler()
{
	dma_handler(STM32DMA::device::dma2, STM32DMA::channel::CH5);
}

void DMA2_Stream6_IRQHandler()
{
	dma_handler(STM32DMA::device::dma2, STM32DMA::channel::CH6);
}

void DMA2_Stream7_IRQHandler()
{
	dma_handler(STM32DMA::device::dma2, STM32DMA::channel::CH7);
}

void STM32DMA::setAddresses(void* source_address, void* dest_address, size_t transfer_size) noexcept
{
	auto* inst = dma_devices[device_];
	assert(inst); // Instance invalid

	// This function can only be called if channel disabled
	assert(LL_DMA_IsEnabledStream(inst, channel_) == false);
	assert(source_address && dest_address && transfer_size); // need non-nullptr and non-zero data

	LL_DMA_ConfigAddresses(inst, channel_, reinterpret_cast<uint32_t>(source_address),
						   reinterpret_cast<uint32_t>(dest_address),
						   LL_DMA_GetDataTransferDirection(inst, channel_));
	LL_DMA_SetDataLength(inst, channel_, transfer_size);
}

void STM32DMA::enable() noexcept
{
	auto* inst = dma_devices[device_];
	assert(inst); // Check for invalid device instance
	assert(LL_DMA_IsEnabledStream(inst, channel_) == false);

	enableInterrupts();
	LL_DMA_EnableStream(inst, channel_);
}

void STM32DMA::disable() noexcept
{
	auto* inst = dma_devices[device_];
	assert(inst); // Check for invalid device instance

	disableInterrupts();
	LL_DMA_DisableStream(inst, channel_);
}

void STM32DMA::start() noexcept
{
	auto* inst = dma_devices[device_];
	assert(inst && configuration_);

	LL_DMA_ConfigTransfer(inst, channel_, configuration_);
	LL_DMA_SetMemoryBurstxfer(inst, channel_, burst_configuration_);
	LL_DMA_ConfigFifo(inst, channel_, fifo_mode_, fifo_threshold_);
	LL_DMA_SetPeriphRequest(inst, channel_, mux_request_);
	LL_DMA_EnableBufferableTransfer(inst, channel_);

	started_ = true;
}

void STM32DMA::stop() noexcept
{
	disable();
	started_ = false;
}

void STM32DMA::enableInterrupts() noexcept
{
	auto irq = irq_num[device_][channel_];
	assert(irq); // Check that channel is supported

	// Disabling DMA sets the TCIF interrupt flag, so we need to clear it
	clear_transfer_complete_flag(device_, channel_);
	NVICControl::clear(irq_num[device_][channel_]);

	NVICControl::priority(irq, 4); // TODO: how to configure priority for the driver?
	NVICControl::enable(irq);

	// Enable complete/error interrupts
	LL_DMA_EnableIT_TC(dma_devices[device_], channel_);
	LL_DMA_EnableIT_TE(dma_devices[device_], channel_);
}

void STM32DMA::disableInterrupts() noexcept
{
	auto irq = irq_num[device_][channel_];
	assert(irq); // Check that channel is supported
	NVICControl::disable(irq);

	// Disable complete/error interrupts
	// Enable complete/error interrupts
	LL_DMA_DisableIT_TC(dma_devices[device_], channel_);
	LL_DMA_DisableIT_TE(dma_devices[device_], channel_);
}

void STM32DMA::registerCallback(const STM32DMA::cb_t& callback) noexcept
{
	irq_handlers[device_][channel_] = callback;
}

void STM32DMA::registerCallback(STM32DMA::cb_t&& callback) noexcept
{
	irq_handlers[device_][channel_] = std::move(callback);
}

STM32DMA::~STM32DMA()
{
	disable();
	stop();
}
