/** @file gpio.c
 * Module handling General Purpose Input/Output (GPIO) pins
 *
 * The module provides functions to configure the gpio pins and read from/
 * write to them
 */

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

#include "gpio.h"
#include "gpio_regs.h"

#include "rcc.h"


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

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

static volatile struct GPIO* regs = (struct GPIO*)GPIO_BASE_ADDRESS;


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

/**
 * Use the provided mask to generate its equivalent with 3 bits of padding
 * between each initial bit. This new mask matches the registers layout and
 * allow for direct application of the mode and config. Outputs are disabled
 * prior to confiiguration to avoid any glitches. The relevant clocks are
 * automatically enabled for the ports used
 */
void gpio_configure(enum GpioPort port, enum GpioPin pin_mask,
		enum GpioMode mode, enum GpioConfig config)
{
	//ensure gpio port is enabled
	rcc_enable(RCC_AHB_NONE, RCC_APB1_NONE, RCC_APB2_IOPA << port);

	//reset outputs before configuring anything
	regs->PORTS[port].BRR.word = pin_mask;

	//clear config for selected port, then apply new config, 8 first pins
	uint32_t mask = 0;
	for (uint8_t i=0; i<8; ++i)
	{
		if (pin_mask & (0x1 << i))
		{
			mask |= 0x1 << (i * 4);
		}
	}
	regs->PORTS[port].CRL.word &= ~(0xF * mask);
	regs->PORTS[port].CRL.word |= ((config << 2) | (mode << 0)) * mask;

	//clear config for selected port, then apply new config, 8 last pins
	mask = 0;
	pin_mask = pin_mask >> 8;
	for (uint8_t i=0; i<8; ++i)
	{
		if (pin_mask & (0x1 << i))
		{
			mask |= 0x1 << (i * 4);
		}
	}
	regs->PORTS[port].CRH.word &= ~(0xF * mask);
	regs->PORTS[port].CRH.word |= ((config << 2) | (mode << 0)) * mask;
}

void gpio_reset(enum GpioPort port, enum GpioPin pin_mask)
{
	gpio_configure(port, pin_mask, GPIO_MODE_INPUT, GPIO_CONFIG_IN_FLOATING);
}

void gpio_write(enum GpioPort port, enum GpioPin pin_mask, bool value)
{
	if (value) {
		regs->PORTS[port].BSRR.word = pin_mask;
	} else {
		regs->PORTS[port].BRR.word = pin_mask;
	}
}

bool gpio_read(enum GpioPort port, enum GpioPin pin_mask)
{
	return (regs->PORTS[port].IDR.word & pin_mask) == pin_mask;
}

void gpio_reset_peripheral(void)
{
	rcc_disable(RCC_AHB_NONE, RCC_APB1_NONE, RCC_APB2_IOPA | RCC_APB2_IOPB
			| RCC_APB2_IOPC | RCC_APB2_IOPD | RCC_APB2_IOPE | RCC_APB2_IOPF
			| RCC_APB2_IOPG);
}


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

