//! \file stm32f4_gpio.c
//! \author tkl
//! \date Mai 8, 2012
//! \brief Source file of the stm32f4 architecture dependent gpio driver.
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>

#include "stm32f4xx.h"

#include "gpio.h"
#include "stm32f4_gpio.h"

//! \brief Contains data for a callback for an external interrupt.
typedef struct {
	gpio_ext_it_cb_t callback;	//!< The call back to be executed.
	void *param;				//!< Parameter for the callback
}exti_cb_list_t;

//! \brief Contains call back data for all 16 exti lines.
static struct {
	exti_cb_list_t callback_list[16];	//!< Call back data list for the exti lines.
}gpio_obj;

static uint8_t gpio_bin2dec(uint16_t bin)
{
	uint8_t ret = 0;
	switch(bin) {
		case 0x0001: ret = 0;	break;
		case 0x0002: ret = 1;	break;
		case 0x0004: ret = 2;	break;
		case 0x0008: ret = 3;	break;
		case 0x0010: ret = 4;	break;
		case 0x0020: ret = 5;	break;
		case 0x0040: ret = 6;	break;
		case 0x0080: ret = 7;	break;
		case 0x0100: ret = 8;	break;
		case 0x0200: ret = 9;	break;
		case 0x0400: ret = 10;	break;
		case 0x0800: ret = 11;	break;
		case 0x1000: ret = 12;	break;
		case 0x2000: ret = 13;	break;
		case 0x4000: ret = 14;	break;
		case 0x8000: ret = 15;	break;
	}
	return ret;
}

static void gpio_init(const struct stm32f4_gpio *gpio)
{
		uint8_t m_port = 0;
		uint8_t m_pin = 0;
		uint32_t clock = 0;
	if(gpio == NULL)
		return;

	if(gpio->port == GPIOA) {
		clock = RCC_AHB1Periph_GPIOA;
		m_port = EXTI_PortSourceGPIOA;
	}
	else if(gpio->port == GPIOB) {
		clock = RCC_AHB1Periph_GPIOB;
		m_port = EXTI_PortSourceGPIOB;
	}
	else if(gpio->port == GPIOC) {
		clock = RCC_AHB1Periph_GPIOC;
		m_port = EXTI_PortSourceGPIOC;
	}
	else if(gpio->port == GPIOD) {
		clock = RCC_AHB1Periph_GPIOD;
		m_port = EXTI_PortSourceGPIOD;
	}
	else if(gpio->port == GPIOE) {
		clock = RCC_AHB1Periph_GPIOE;
		m_port = EXTI_PortSourceGPIOE;
	}
	else if(gpio->port == GPIOF) {
		clock = RCC_AHB1Periph_GPIOF;
		m_port = EXTI_PortSourceGPIOF;
	}
	else if(gpio->port == GPIOG) {
		clock = RCC_AHB1Periph_GPIOG;
		m_port = EXTI_PortSourceGPIOG;
	}
	else if(gpio->port == GPIOH) {
		clock = RCC_AHB1Periph_GPIOH;
		m_port = EXTI_PortSourceGPIOH;
	}
	else if(gpio->port == GPIOI) {
		clock = RCC_AHB1Periph_GPIOI;
		m_port = EXTI_PortSourceGPIOI;
	}

	RCC_AHB1PeriphClockCmd(clock, ENABLE);
	m_pin = gpio_bin2dec(gpio->pin->GPIO_Pin);

	SYSCFG_EXTILineConfig(m_port, m_pin);
}

int stm32f4_gpio_open(const void *gpio)
{
	struct stm32f4_gpio *this;
	uint8_t m_pin = 0;

	if(gpio == NULL)
		return -1;

	this = (struct stm32f4_gpio *)gpio;
	gpio_init(this);
	m_pin = gpio_bin2dec(this->pin->GPIO_Pin);

	GPIO_Init(this->port, (GPIO_InitTypeDef*)this->pin);

	if(this->ext_it_cb != NULL) {
		gpio_obj.callback_list[m_pin].callback = this->ext_it_cb;
		gpio_obj.callback_list[m_pin].param = this->param;
	}

	if((this->exti != NULL) && (this->nvic != NULL)) {
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
		EXTI_Init((EXTI_InitTypeDef*)this->exti);
		NVIC_Init((NVIC_InitTypeDef*)this->nvic);
	}

	return 0;
}

int stm32f4_gpio_close(const void *gpio)
{
	if(gpio == NULL)
		return -1;
	// TODO: deinit exti, nvic & gpio
	return 0;
}

char stm32f4_gpio_read(const void *gpio)
{
	struct stm32f4_gpio *this;
	if(gpio == NULL)
		return 0;

	this = (struct stm32f4_gpio *)gpio;
	return GPIO_ReadOutputDataBit(this->port, this->pin->GPIO_Pin);
}

void stm32f4_gpio_write(const void *gpio, char byte) {
	struct stm32f4_gpio *this;
	if(gpio == NULL)
		return;

	this = (struct stm32f4_gpio *)gpio;
	GPIO_WriteBit(this->port, this->pin->GPIO_Pin, (BitAction)byte);
}

void stm32f4_gpio_toggle(const void *gpio)
{
	struct stm32f4_gpio *this;
	if(gpio == NULL)
		return;

	this = (struct stm32f4_gpio *)gpio;
	BitAction act = Bit_SET;
	if(GPIO_ReadOutputDataBit(this->port, this->pin->GPIO_Pin))
		act = Bit_RESET;

	GPIO_WriteBit(this->port, this->pin->GPIO_Pin, act);
}

int stm32f4_gpio_set_exti_callback(const void *gpio,
		const void *callback, const void *param)
{
	struct stm32f4_gpio *this;
	uint8_t pin;
	if((gpio == NULL) || (callback == NULL))
		return -1;

	this = (struct stm32f4_gpio *)gpio;
	pin = gpio_bin2dec(this->exti->EXTI_Line);

	gpio_obj.callback_list[pin].callback = (gpio_ext_it_cb_t)callback;
	gpio_obj.callback_list[pin].param = (void*)param;

	return 0;
}

//! \brief The ISR for the EXTI0_IRQn interrupt.
void EXTI0_IRQHandler(void)
{
	if(gpio_obj.callback_list[0].callback != NULL) {
		gpio_ext_it_cb_t cb = gpio_obj.callback_list[0].callback;
		void *param = gpio_obj.callback_list[0].param;
		cb(param);
	}
	EXTI_ClearITPendingBit(EXTI_Line0);
}

//! \brief The ISR for the EXTI1_IRQn interrupt.
void EXTI1_IRQHandler(void)
{
	if(gpio_obj.callback_list[1].callback != NULL) {
		gpio_ext_it_cb_t cb = gpio_obj.callback_list[1].callback;
		void *param = gpio_obj.callback_list[1].param;
		cb(param);
	}
	EXTI_ClearITPendingBit(EXTI_Line1);
}

//! \brief The ISR for the EXTI2_IRQn interrupt.
void EXTI2_IRQHandler(void)
{
	if(gpio_obj.callback_list[2].callback != NULL) {
		gpio_ext_it_cb_t cb = gpio_obj.callback_list[2].callback;
		void *param = gpio_obj.callback_list[2].param;
		cb(param);
	}
	EXTI_ClearITPendingBit(EXTI_Line2);
}

//! \brief The ISR for the EXTI3_IRQn interrupt.
void EXTI3_IRQHandler(void)
{
	if(gpio_obj.callback_list[3].callback != NULL) {
		gpio_ext_it_cb_t cb = gpio_obj.callback_list[3].callback;
		void *param = gpio_obj.callback_list[3].param;
		cb(param);
	}
	EXTI_ClearITPendingBit(EXTI_Line3);
}

//! \brief The ISR for the EXTI4_IRQn interrupt.
void EXTI4_IRQHandler(void)
{
	if(gpio_obj.callback_list[4].callback != NULL) {
		gpio_ext_it_cb_t cb = gpio_obj.callback_list[4].callback;
		void *param = gpio_obj.callback_list[4].param;
		cb(param);
	}
	EXTI_ClearITPendingBit(EXTI_Line4);
}

//! \brief The ISR for the EXTI9_5_IRQn interrupt.
void EXTI9_5_IRQHandler(void) {
	uint32_t exti = 0;
	uint8_t pin;
	if(EXTI_GetITStatus(EXTI_Line6))
		exti = EXTI_Line6;

	pin = gpio_bin2dec(exti);
	if(gpio_obj.callback_list[pin].callback != NULL) {
		gpio_ext_it_cb_t cb = gpio_obj.callback_list[pin].callback;
		void *param = gpio_obj.callback_list[pin].param;
		cb(param);
	}
	EXTI_ClearITPendingBit(exti);
}

//! \brief The ISR for the EXTI15_10_IRQn interrupt.
void EXTI15_10_IRQHandler(void) {
	// TODO: detect & clear pending bit
}