#include "gpio.h"

gpio_pindef_t gpio_pins[] = {
	GPIO_TABLE(EXPAND_AS_DEFS)
};

void gpio_hw_config_pin(GPIO_TypeDef *port, uint8_t pin, uint16_t conf) {
	uint8_t shift;
	shift = pin * 2;

	port->MODER &= ~(GPIO_MODER_MASK << shift);
	port->MODER |= (uint32_t)((conf >> GPIO_MODE_CFG_SHIFT) & GPIO_MODER_MASK) << shift;
	port->OTYPER &= ~(GPIO_TYPER_MASK << pin);
	port->OTYPER |= (uint32_t)((conf >> GPIO_TYPE_CFG_SHIFT) & GPIO_TYPER_MASK) << pin;
	port->OSPEEDR &= ~(GPIO_SPEEDR_MASK << shift);
	port->OSPEEDR |= (uint32_t)((conf >> GPIO_SPEED_CFG_SHIFT) & GPIO_SPEEDR_MASK) << shift;
	port->PUPDR &= ~(GPIO_PUPDR_MASK << shift);
	port->PUPDR |= (uint32_t)((conf >> GPIO_PUPD_CFG_SHIFT) & GPIO_PUPDR_MASK) << shift;
}

void gpio_connect_af(gpio_t id, uint8_t af_n) {
	gpio_pindef_t *pin = &gpio_pins[id];
	uint8_t shift;
	if (pin->pin < 8) {
		shift = pin->pin * 4;
		pin->port->AFR[0] &= ~((uint32_t)GPIO_AFR_MASK << shift);
		pin->port->AFR[0] |= af_n << shift;
	}
	else {
		shift = (pin->pin - 8) * 4;
		pin->port->AFR[1] &= ~((uint32_t)GPIO_AFR_MASK << shift);
		pin->port->AFR[1] |= af_n << shift;
	}
}

/*
 * TODO add analog flag and setup ADC pins in a proper way
 */
static void gpio_set_config(gpio_t id) {
	gpio_pindef_t *pin = &gpio_pins[id];
	if (pin->flags & GPIO_AF) {
		uint8_t af_n = (uint8_t)(pin->flags >> _GPIO_AF_SHIFT);
		gpio_connect_af(id, af_n);
		gpio_hw_config_pin(pin->port, pin->pin, GPIO_MODE_AF_CFG | 
				GPIO_SPEED_HIGH_CFG);
	}
	else if (pin->flags & GPIO_IN)
		gpio_hw_config_pin(pin->port, pin->pin, GPIO_MODE_IN_CFG | 
				GPIO_SPEED_HIGH_CFG);
	else if (pin->flags & GPIO_IN_PU) {
		gpio_hw_config_pin(pin->port, pin->pin, GPIO_MODE_IN_CFG | GPIO_PU_CFG |
				GPIO_SPEED_HIGH_CFG);
		gpio_set(id, pin->flags & GPIO_SET);
	}
	else if (pin->flags & GPIO_OUT) {
		gpio_hw_config_pin(pin->port, pin->pin, GPIO_MODE_OUT_CFG |
				((pin->flags & GPIO_OD) ? 
				 GPIO_TYPE_OD_CFG : GPIO_TYPE_PP_CFG) |
				GPIO_SPEED_HIGH_CFG);
		gpio_set(id, pin->flags & GPIO_SET);
	}
}

void gpio_init(void) {
	uint32_t i;

	GPIO_DeInit(GPIOA);
	GPIO_DeInit(GPIOB);
	GPIO_DeInit(GPIOC);
	GPIO_DeInit(GPIOD);
	GPIO_DeInit(GPIOE);

	// configure clocks
	RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOBEN |
			RCC_AHB1ENR_GPIOCEN | RCC_AHB1ENR_GPIODEN |
			RCC_AHB1ENR_GPIOEEN;

	// configure gpios
	for (i = 0; i < GPIO_TOTAL_COUNT; i++) {
		if (gpio_pins[i].flags & GPIO_NOINIT)
			continue;
		else {
			gpio_set_config(i);
		}
	}
}

void gpio_set(gpio_t pin, bool value) {
	if (gpio_pins[pin].flags & GPIO_INV)
		value = !value;
	if (value)
		gpio_pins[pin].port->BSRRL = 1 << gpio_pins[pin].pin;
	else
		gpio_pins[pin].port->BSRRH = 1 << gpio_pins[pin].pin;
}

void gpio_invert_output(gpio_t pin) {

	gpio_pins[pin].port->ODR ^= 1 << gpio_pins[pin].pin;
}

bool gpio_get(gpio_t pin) {
	bool value;
	if (gpio_pins[pin].flags & GPIO_NOINIT) {
		return false;
	}
	else {
		value = (gpio_pins[pin].port->IDR & (1 << gpio_pins[pin].pin)) >> gpio_pins[pin].pin;
		return (gpio_pins[pin].flags & GPIO_INV) ? !value : value;
	}
}

bool gpio_is_output(gpio_t pin) {
	return (gpio_pins[pin].flags & GPIO_OUT) ? true : false;
}

void gpio_set_direction(gpio_t pin, bool out) {
	if (out) {
		gpio_pins[pin].flags &= ~(GPIO_IN | GPIO_IN_PU);
		gpio_pins[pin].flags |= GPIO_OUT;
	}
	else {
		gpio_pins[pin].flags &= ~(GPIO_OUT | GPIO_OD);
		gpio_pins[pin].flags |= GPIO_IN;
	}
	gpio_set_config(pin);
}

void gpio_test() {

}

typedef struct {
	gpio_pindef_t *gpio;
	void (*handler)(void);
} exti_handler_t;

