/** 
 * \file pio.h
 * \brief pio header file
 * \details File contains structures, enumerations and functions declarations for Parallel Input/Output Controller
 *
 * \author Juraj Golej
 * \version 1.0
 *
 * \todo [pio] open drain mode
 */


#ifndef PIO_H_
#define PIO_H_

#include "sam.h"
#include <stdbool.h>
#include <stdlib.h>
#include <clocks/clocks/clocks.h>
#include <utils/retcode/retcode.h>
#include <utils/linearlist/linearlist.h>
#include <utils/assert/arm_assert.h>

/** 
 * \brief Pull mode enumeration
 */
enum PIO_pull_mode {
	PIO_PULLOFF,	///< Pull-up or pull-down resistor isn't connected
	PIO_PULLUP,		///< Pull-up resistor
	PIO_PULLDOWN,	///< Pull-down resistor
}; /* enum PIO_pull_mode */

/** 
 * \brief Peripheral group enumeration
 */
enum PIO_peripheral {
	PIO_PER_A,	///< Peripheral group A
	PIO_PER_B,	///< Peripheral group B
	PIO_PER_C,	///< Peripheral group C
	PIO_PER_D,	///< Peripheral group D
}; /* enum PIO_peripheral */

/** 
 * \brief External interrupt type enumeration
 */
enum PIO_irq_type {
	PIO_IRQ_BOTH_EDGE,		///< Interrupt request on both edges
	PIO_IRQ_RISING_EDGE,	///< Interrupt request on rising edges
	PIO_IRQ_FALLING_EDGE,	///< Interrupt request on falling edges
	PIO_IRQ_LOW_LEVEL,		///< Interrupt request on low level
	PIO_IRQ_HIGH_LEVEL,		///< Interrupt request on high level
}; /* enum PIO_irq_type */

struct PIO_irq;

/**
 * \brief Interrupt user defined function (callback)
 * 
 * \param[in] pinCode Pin on which there was interrupt event
 */
typedef void (*PIO_irq_cb)(struct PIO_irq* const module);

/**
 * \brief External IRQ struct
 *
 * \see PIO_PIN
 */
struct PIO_irq {
	struct linearlist_node element;	///< Linear list node (useful in linear list)

	uint8_t pin;					///< Pin number
	Pio* port;						///< Port pointer
	PIO_irq_cb callback;			///< User defined function
}; /* struct PIO_irq */

/**
 * \brief Create pin code from port (PIOA, PIOB...) and pin (0, 1, 2 .. 31)
 * 
 * \param[in] port Port (PIOA, PIOB...)
 * \param[in] pin Pin (0, 1, 2 .. 31)
 * 
 * \return \b uint8_t Pin code
 * 
 * \see PIO_PORT
 * \see PIO_PIN
 */
static inline uint8_t PIO(Pio* port, uint8_t pin)
{
	return ((((((uint32_t)port - (uint32_t)PIOA) / 0x200) & 0x07) << 5) + (pin & 0x1F));
}

/**
 * \brief Get port pointer from pin code
 * 
 * \param[in] pinCode Pin code
 * 
 * \return \b Pio* Pointer on port 
 * 
 * \see PIO
 * \see PIO_PIN
 */
static inline Pio* PIO_PORT(uint8_t pinCode)
{
	return (Pio*)((uint32_t)PIOA + (uint32_t)(pinCode >> 5) * 0x200);
}

/**
 * \brief Get pin number from pin code
 * 
 * \param[in] pinCode Pin code
 * 
 * \return \b uint8_t Pin number
 * 
 * \see PIO
 * \see PIO_PORT
 */
static inline uint8_t PIO_PIN(uint8_t pinCode)
{
	return (pinCode & 0x1F);
}

/**
 * \brief Get PIO peripheral hardware ID
 * 
 * \param[in] periphery Pointer on PIO periphery
 * 
 * \return \b int16_t Peripheral ID or -1, if can't find this \c periphery
 */
static inline int16_t PIO_getPeripheralHWID(Pio* periphery)
{
	switch((uint32_t)periphery) {
		case (uint32_t)PIOA:
			return ID_PIOA;
		case (uint32_t)PIOB:
			return ID_PIOB;
		case (uint32_t)PIOC:
			return ID_PIOC;
		case (uint32_t)PIOD:
			return ID_PIOD;
		case (uint32_t)PIOE:
			return ID_PIOE;
		default:
			return -1;
	}
}

/**
 * \brief Set pin as output
 * 
 * \param[in] pinCode Pin code
 */
static inline void PIO_output_enable(const uint8_t pinCode)
{
	PIO_PORT(pinCode)->PIO_OER = (1 << PIO_PIN(pinCode));
}

/**
 * \brief Set pin as input
 *
 * \param[in] pinCode Pin code
 */
static inline void PIO_output_disable(const uint8_t pinCode)
{
	PIO_PORT(pinCode)->PIO_ODR = (1 << PIO_PIN(pinCode));
}

/**
 * \brief Get pin output status
 * 
 * \param[in] pinCode Pin code
 *
 * \return \b bool Output status
 * \retval true Pin is set as output
 * \retval false Pin is set as input
 */
static inline bool PIO_output_status(const uint8_t pinCode)
{
	if(PIO_PORT(pinCode)->PIO_OSR & (1 << PIO_PIN(pinCode))) {
		return true;
	}
	return false;
}

/**
 * \brief Set pin to high level
 *
 * \param[in] pinCode Pin code
 */
static inline void PIO_setpin_high(const uint8_t pinCode)
{
	PIO_PORT(pinCode)->PIO_SODR = (1 << PIO_PIN(pinCode));
}

/**
 * \brief Set pin to low level
 *
 * \param[in] pinCode Pin code
 */
static inline void PIO_setpin_low(const uint8_t pinCode)
{
	PIO_PORT(pinCode)->PIO_CODR = (1 << PIO_PIN(pinCode));
}

/**
 * \brief Get pin output level status
 * 
 * \param[in] pinCode Pin code
 *
 * \return \b bool Output level status
 * \retval true Pin level is set to high
 * \retval false Pin is set to low
 */
static inline bool PIO_setpin_status(const uint8_t pinCode)
{
	if(PIO_PORT(pinCode)->PIO_ODSR & (1 << PIO_PIN(pinCode))) {
		return true;
	}
	return false;
}

/**
 * \brief Set pin pull value
 *
 * \param[in] pinCode Pin code
 * \param[in] mode Pull mode
 */
static inline void PIO_pull(const uint8_t pinCode, const enum PIO_pull_mode mode)
{
	switch(mode) {
		case PIO_PULLUP:
			PIO_PORT(pinCode)->PIO_PPDDR = (1 << PIO_PIN(pinCode));
			PIO_PORT(pinCode)->PIO_PUER = (1 << PIO_PIN(pinCode));
			break;
		case PIO_PULLDOWN:
			PIO_PORT(pinCode)->PIO_PUDR = (1 << PIO_PIN(pinCode));
			PIO_PORT(pinCode)->PIO_PPDER = (1 << PIO_PIN(pinCode));
			break;
		case PIO_PULLOFF:
		default:
			PIO_PORT(pinCode)->PIO_PUDR = (1 << PIO_PIN(pinCode));
			PIO_PORT(pinCode)->PIO_PPDDR = (1 << PIO_PIN(pinCode));
			break;
	}
}

/**
 * \brief Get pin pull status
 * 
 * \param[in] pinCode Pin code
 *
 * \return \b enum \b PIO_pull_mode Pin pull mode 
 */
static inline enum PIO_pull_mode PIO_pull_status(const uint8_t pinCode)
{
	if(!(PIO_PORT(pinCode)->PIO_PUSR & (1 << PIO_PIN(pinCode)))) {
		return PIO_PULLUP;
	}
	else if(!(PIO_PORT(pinCode)->PIO_PPDSR & (1 << PIO_PIN(pinCode)))) {
		return PIO_PULLDOWN;
	}
	
	return PIO_PULLOFF;
}


/**
 * \brief Configure pin and assign pin to peripheral (peripheral group)
 * 
 * \param[in] pinCode Pin code
 * \param[in] group Peripheral group
 */
static inline void PIO_peripheral_configure(const uint8_t pinCode, const enum PIO_peripheral group)
{
	Pio* port = PIO_PORT(pinCode);
	uint8_t pin = PIO_PIN(pinCode);

	switch(group) {
		case PIO_PER_A:
			port->PIO_ABCDSR[0] &= ~(1 << pin);
			port->PIO_ABCDSR[1] &= ~(1 << pin);
			break;
		case PIO_PER_B:
			port->PIO_ABCDSR[0] |= (1 << pin);
			port->PIO_ABCDSR[1] &= ~(1 << pin);
			break;
		case PIO_PER_C:
			port->PIO_ABCDSR[0] &= ~(1 << pin);
			port->PIO_ABCDSR[1] |= (1 << pin);
			break;
		case PIO_PER_D:
			port->PIO_ABCDSR[0] |= (1 << pin);
			port->PIO_ABCDSR[1] |= (1 << pin);
			break;
		default:
			break;
	}

	port->PIO_PDR = (1 << pin);
}

/**
 * \brief Connect pin to peripheral output
 *
 * \param[in] pinCode Pin code
 */
static inline void PIO_peripheral_enable(const uint8_t pinCode)
{
	PIO_PORT(pinCode)->PIO_PDR = (1 << PIO_PIN(pinCode));
}

/**
 * \brief Disconnect pin from peripheral (peripheral group)
 *
 * \param[in] pinCode Pin code
 */
static inline void PIO_peripheral_disable(const uint8_t pinCode)
{
	PIO_PORT(pinCode)->PIO_PER = (1 << PIO_PIN(pinCode));
}

/**
 * \brief Get pin peripheral assignment status
 * 
 * \param[in] pinCode Pin code
 *
 * \return \b bool Peripheral assignment status
 * \retval true Pin is assignment to peripheral
 * \retval false Pin is in general input/output mode
 */
static inline bool	PIO_peripheral_status(const uint8_t pinCode)
{
	if(PIO_PORT(pinCode)->PIO_PSR & (1 << PIO_PIN(pinCode))) {
		return false;
	}
	return true;
}

/**
 * \brief Get pin level
 * 
 * \param[in] pinCode Pin code
 *
 * \return \b bool Pin level
 * \retval true Pin level is high
 * \retval false Pin level is low
 */
static inline bool PIO_pin_level(const uint8_t pinCode)
{
	if(PIO_PORT(pinCode)->PIO_PDSR & (1 << PIO_PIN(pinCode))) {
		return true;
	}
	return false;
}

/**
 * \brief Enable glitch filter
 * 
 * \param[in] pinCode Pin code
 *
 * \note Glitch with a duration of less than 1/2 master clock period will be ignored
 */
static inline void PIO_filter_glitch_enable(const uint8_t pinCode)
{
	PIO_PORT(pinCode)->PIO_IFSCDR = (1 << PIO_PIN(pinCode));
	PIO_PORT(pinCode)->PIO_IFER = (1 << PIO_PIN(pinCode));
}

/**
 * \brief Enable debouncer filter
 * 
 * \param[in] pinCode Pin code
 * \param[in] periods Number of SLCK periods 
 *
 * \note Impulse with a duration of less than ((DIV+1)*2)*tSLCK will be ignored
 */
static inline void PIO_filter_debouncer_enable(const uint8_t pinCode, uint16_t periods)
{
	PIO_PORT(pinCode)->PIO_SCDR = periods;
	PIO_PORT(pinCode)->PIO_IFSCER = (1 << PIO_PIN(pinCode));
	PIO_PORT(pinCode)->PIO_IFER = (1 << PIO_PIN(pinCode));
}

/**
 * \brief Disable pin filter
 * 
 * \param[in] pinCode Pin code
 */
static inline void PIO_filter_disable(const uint8_t pinCode)
{
	PIO_PORT(pinCode)->PIO_IFDR = (1 << PIO_PIN(pinCode));
}

/**
 * \brief Configure external interrupt 
 * 
 * \param[in] module Pointer on module
 * \param[in] pinCode Pin code
 * \param[in] irqType Interrupt type
 * \param[in] callback User defined callback function
 * 
 * \return \b enum \b retCode Return code value
 * \retval ENone Everything OK
 * \retval EInvalidParameter \c irqType invalid parameter or \c callback is \c NULL
 */
enum retCode PIO_interrupt_init(struct PIO_irq* const module, const uint8_t pinCode, enum PIO_irq_type irqType, PIO_irq_cb callback);

/**
 * \brief Deinitialize external interrupt 
 * 
 * \param[in] module Pointer on module
 * 
 * \return \b enum \b retCode Return code value
 * \retval ENone Everything OK
 * \retval EDenied Module isn't initialized
 */
enum retCode PIO_interrupt_deinit(struct PIO_irq* const module);

/**
 * \brief Enable external interrupt 
 * 
 * \param[in] module Pointer on module
 */
static inline void PIO_interrupt_enable(struct PIO_irq* const module)
{
	/* Check NULL pointers */
	Assert(module);
	module->port->PIO_IER = (1 << module->pin);
}

/**
 * \brief Disable external interrupt 
 * 
 * \param[in] module Pointer on module
 */
static inline void PIO_interrupt_disable(struct PIO_irq* const module)
{
	/* Check NULL pointers */
	Assert(module);
	module->port->PIO_IDR = (1 << module->pin);
}

/**
 * \brief Get external interrupt enable status
 * 
 * \param[in] module Pointer on module
 *
 * \return \b bool External interrupt enable status
 * \retval true External interrupt is enabled 
 * \retval false External interrupt is disabled
 */
static inline bool PIO_interrupt_status(struct PIO_irq* const module)
{
	Assert(module);
	Try(module->port->PIO_IMR & (1 << module->pin), true);
	return false;
}

#endif /* PIO_H_ */