/** @file exti.c
 * Module handling EXTernal Interrupt lines
 *
 * The module provides functions to configure the exti lines to generate events
 * or interrupts
 */

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

#include "exti.h"
#include "exti_regs.h"

#include "nvic.h"


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

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

static volatile struct EXTI* regs = (struct EXTI*)EXTI_BASE_ADDRESS;
static ExtiCallback callbacks[19];


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

void exti_configure(enum ExtiLine line_mask, enum ExtiConfig config_mask,
		ExtiCallback callback)
{
	exti_reset(line_mask);

	//configure edge detections
	if (config_mask & EXTI_CONFIG_RISING_EDGE) {
		regs->RTSR.word |= line_mask;
	} else {
		regs->RTSR.word &= ~line_mask;
	}

	if (config_mask & EXTI_CONFIG_FALLING_EDGE) {
		regs->FTSR.word |= line_mask;
	} else {
		regs->FTSR.word &= ~line_mask;
	}

	//reconfigure events/irqs
	regs->EMR.word |= line_mask;
	if (callback) {
		//register callbacks for each line selected
		for (uint8_t i = 0; i < 19;  ++i) {
			if (line_mask & (0x1 << i)) {
				callbacks[i] = callback;
			}
		}

		//enable interrupts lines in nvic
		for (uint8_t i = 0; i < 5; ++i) {
			if (line_mask & (0x1 << i)) {
				nvic_enable(NVIC_IRQ_EXTI0 + i);
			}
		}
		if (line_mask & 0x3e0) {
			nvic_enable(NVIC_IRQ_EXTI9_5);
		}
		if (line_mask & 0x7c00) {
			nvic_enable(NVIC_IRQ_EXTI15_10);
		}
		if (line_mask & EXTI_LINE_PVD) {
			nvic_enable(NVIC_IRQ_PVD);
		}
		if (line_mask & EXTI_LINE_RTC) {
			nvic_enable(NVIC_IRQ_RTC_ALARM);
		}
		if (line_mask & EXTI_LINE_USB) {
			nvic_enable(NVIC_IRQ_USB_WAKEUP);
		}

		//enable irqs in last to avoid triggers during config
		regs->IMR.word |= line_mask;
	}
}

void exti_reset(enum ExtiLine line_mask)
{
	//disable events/irqs
	regs->IMR.word &= ~line_mask;
	regs->EMR.word &= ~line_mask;

	//clear any pending bits
	regs->PR.word |= line_mask;

	//disable interrupts lines in nvic
	for (uint8_t i = 0; i < 5; ++i) {
		if (line_mask & (0x1 << i)) {
			nvic_disable(NVIC_IRQ_EXTI0 + i);
		}
	}
	if (line_mask & 0x3e0) {
		nvic_disable(NVIC_IRQ_EXTI9_5);
	}
	if (line_mask & 0x7c00) {
		nvic_disable(NVIC_IRQ_EXTI15_10);
	}

	//disable events/irqs
	regs->IMR.word &= ~line_mask;
	regs->EMR.word &= ~line_mask;

	//clear any pending bits
	regs->PR.word |= line_mask;

	//disable interrupts lines in nvic
	if (line_mask & EXTI_LINE_PVD) {
		nvic_disable(NVIC_IRQ_PVD);
	}
	if (line_mask & EXTI_LINE_RTC) {
		nvic_disable(NVIC_IRQ_RTC_ALARM);
	}
	if (line_mask & EXTI_LINE_USB) {
		nvic_disable(NVIC_IRQ_USB_WAKEUP);
	}
}


void exti_reset_peripheral(void)
{
	//reset peripheral config
	regs->IMR.word = 0;
	regs->EMR.word = 0;
	regs->RTSR.word = 0;
	regs->FTSR.word = 0;
	regs->SWIER.word = 0;
	regs->PR.word = 0;

	//disable interrupts in nvic
	for (uint8_t i = 0; i < 5; ++i) {
		nvic_enable(NVIC_IRQ_EXTI0 + i);
	}
	nvic_enable(NVIC_IRQ_EXTI9_5);
	nvic_enable(NVIC_IRQ_EXTI15_10);
	nvic_disable(NVIC_IRQ_PVD);
	nvic_disable(NVIC_IRQ_RTC_ALARM);
	nvic_disable(NVIC_IRQ_USB_WAKEUP);
}

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

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

void hdr_exti0(void)
{
	nvic_clear_pending(NVIC_IRQ_EXTI0);
	regs->PR.PR0 = 1;
	callbacks[0]();
}

void hdr_exti1(void)
{
	nvic_clear_pending(NVIC_IRQ_EXTI1);
	regs->PR.PR1 = 1;
	callbacks[1]();
}

void hdr_exti2(void)
{
	nvic_clear_pending(NVIC_IRQ_EXTI2);
	regs->PR.PR2 = 1;
	callbacks[2]();
}

void hdr_exti3(void)
{
	nvic_clear_pending(NVIC_IRQ_EXTI3);
	regs->PR.PR3 = 1;
	callbacks[3]();
}

void hdr_exti4(void)
{
	nvic_clear_pending(NVIC_IRQ_EXTI4);
	regs->PR.PR4 = 1;
	callbacks[4]();
}

void hdr_exti9_5(void)
{
	nvic_clear_pending(NVIC_IRQ_EXTI9_5);
	for (uint8_t i = 5; i < 10; ++i) {
		if (regs->PR.word & (0x1 << i)) {
			regs->PR.word |= 0x1 << i;
			callbacks[i]();
		}
	}
}

void hdr_exti15_10(void)
{
	nvic_clear_pending(NVIC_IRQ_EXTI15_10);
	for (uint8_t i = 10; i < 16; ++i) {
		if (regs->PR.word & (0x1 << i)) {
			regs->PR.word |= 0x1 << i;
			callbacks[i]();
		}
	}
}

void hdr_pvd(void)
{
	nvic_clear_pending(NVIC_IRQ_PVD);
	regs->PR.PR16 = 1;
	callbacks[16]();
}

void hdr_rtc_alarm(void)
{
	nvic_clear_pending(NVIC_IRQ_RTC_ALARM);
	regs->PR.PR17 = 1;
	callbacks[17]();
}

void hdr_usb_wakeup(void)
{
	nvic_clear_pending(NVIC_IRQ_USB_WAKEUP);
	regs->PR.PR18 = 1;
	callbacks[18]();
}

