/**
 * \file
 *
 * \brief XMEGA architecture specific IOPORT service implementation header file.
 *
 * Copyright (c) 2012-2013 Atmel Corporation. All rights reserved.
 *
 * \asf_license_start
 *
 * \page License
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * 3. The name of Atmel may not be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * 4. This software may only be redistributed and used in connection with an
 *    Atmel microcontroller product.
 *
 * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * \asf_license_stop
 *
 */
#ifndef IOPORT_XMEGA_H
#define IOPORT_XMEGA_H

#define IOPORT_CREATE_PIN(port, pin) ((IOPORT_ ## port) * 8 + (pin))
#define IOPORT_BASE_ADDRESS 0x600
#define IOPORT_PORT_OFFSET  0x20

/** \name IOPORT port numbers */
/** @{ */
#if !XMEGA_B3
#  define IOPORT_PORTA  0
#endif

#define IOPORT_PORTB    1
#define IOPORT_PORTC    2
#define IOPORT_PORTD    3

#if !XMEGA_B3
#  define IOPORT_PORTE  4
#endif

#if XMEGA_A1 || XMEGA_A1U || XMEGA_A3 || XMEGA_A3U || XMEGA_A3B || XMEGA_A3BU ||\
	XMEGA_C3 || XMEGA_D3
#  define IOPORT_PORTF  5
#endif

#if XMEGA_B1 || XMEGA_B3
#  define IOPORT_PORTG  6
#endif

#if XMEGA_A1 || XMEGA_A1U
#  define IOPORT_PORTH  7
#  define IOPORT_PORTJ  8
#  define IOPORT_PORTK  9
#endif

#if XMEGA_B1 || XMEGA_B3
#  define IOPORT_PORTM  11
#endif

#if XMEGA_A1 || XMEGA_A1U
#  define IOPORT_PORTQ  14
#endif

#define IOPORT_PORTR    15
/** @} */

/**
 * \weakgroup ioport_group
 * \section ioport_modes IOPORT Modes
 *
 * For details on these please see the XMEGA Manual.
 *
 * @{
 */

/** \name IOPORT Mode bit definitions */
/** @{ */
#define IOPORT_MODE_TOTEM           (0x00 << 3) /*!< Totem-pole */
#define IOPORT_MODE_BUSKEEPER       (0x01 << 3) /*!< Buskeeper */
#define IOPORT_MODE_PULLDOWN        (0x02 << 3) /*!< Pull-down */
#define IOPORT_MODE_PULLUP          (0x03 << 3) /*!< Pull-up */
#define IOPORT_MODE_WIREDOR         (0x04 << 3) /*!< Wired OR */
#define IOPORT_MODE_WIREDAND        (0x05 << 3) /*!< Wired AND */
#define IOPORT_MODE_WIREDORPULL     (0x06 << 3) /*!< Wired OR with pull-down */
#define IOPORT_MODE_WIREDANDPULL    (0x07 << 3) /*!< Wired AND with pull-up */
#define IOPORT_MODE_INVERT_PIN      (0x01 << 6) /*!< Invert output and input */
#define IOPORT_MODE_SLEW_RATE_LIMIT (0x01 << 7) /*!< Slew rate limiting */
/** @} */

/** @} */

typedef uint8_t ioport_mode_t;
typedef uint8_t ioport_pin_t;
typedef uint8_t ioport_port_t;
typedef uint8_t ioport_port_mask_t;

__always_inline static ioport_port_t arch_ioport_pin_to_port_id(ioport_pin_t pin)
{
	return pin >> 3;
}

__always_inline static PORT_t *arch_ioport_port_to_base(ioport_port_t port)
{
	return (PORT_t *)((uintptr_t)IOPORT_BASE_ADDRESS +
	       (port * IOPORT_PORT_OFFSET));
}

__always_inline static PORT_t *arch_ioport_pin_to_base(ioport_pin_t pin)
{
	return arch_ioport_port_to_base(arch_ioport_pin_to_port_id(pin));
}

__always_inline static ioport_port_mask_t arch_ioport_pin_to_mask(ioport_pin_t pin)
{
	return 1U << (pin & 0x07);
}

__always_inline static ioport_port_mask_t arch_ioport_pin_to_index(ioport_pin_t pin)
{
	return (pin & 0x07);
}

__always_inline static void arch_ioport_init(void)
{

}

__always_inline static void arch_ioport_enable_port(ioport_port_t port,
		ioport_port_mask_t mask)
{
	PORT_t *base = arch_ioport_port_to_base(port);
	volatile uint8_t *pin_ctrl = &base->PIN0CTRL;

	uint8_t flags = cpu_irq_save();

	for (uint8_t i = 0; i < 8; i++) {
		if (mask & arch_ioport_pin_to_mask(i)) {
			pin_ctrl[i] &= ~PORT_ISC_gm;
		}
	}

	cpu_irq_restore(flags);
}

__always_inline static void arch_ioport_enable_pin(ioport_pin_t pin)
{
	PORT_t *base = arch_ioport_pin_to_base(pin);
	volatile uint8_t *pin_ctrl =
			(&base->PIN0CTRL + arch_ioport_pin_to_index(pin));

	uint8_t flags = cpu_irq_save();

	*pin_ctrl &= ~PORT_ISC_gm;

	cpu_irq_restore(flags);
}

__always_inline static void arch_ioport_disable_port(ioport_port_t port,
		ioport_port_mask_t mask)
{
	PORT_t *base = arch_ioport_port_to_base(port);
	volatile uint8_t *pin_ctrl = &base->PIN0CTRL;

	uint8_t flags = cpu_irq_save();

	for (uint8_t i = 0; i < 8; i++) {
		if (mask & arch_ioport_pin_to_mask(i)) {
			pin_ctrl[i] |= PORT_ISC_INPUT_DISABLE_gc;
		}
	}

	cpu_irq_restore(flags);
}

__always_inline static void arch_ioport_disable_pin(ioport_pin_t pin)
{
	PORT_t *base = arch_ioport_pin_to_base(pin);
	volatile uint8_t *pin_ctrl =
			(&base->PIN0CTRL + arch_ioport_pin_to_index(pin));

	uint8_t flags = cpu_irq_save();

	*pin_ctrl |= PORT_ISC_INPUT_DISABLE_gc;

	cpu_irq_restore(flags);
}

__always_inline static void arch_ioport_set_port_mode(ioport_port_t port,
		ioport_port_mask_t mask, ioport_mode_t mode)
{
	PORT_t *base = arch_ioport_port_to_base(port);
	volatile uint8_t *pin_ctrl = &base->PIN0CTRL;
	uint8_t new_mode_bits = (mode & ~PORT_ISC_gm);

	uint8_t flags = cpu_irq_save();

	for (uint8_t i = 0; i < 8; i++) {
		if (mask & arch_ioport_pin_to_mask(i)) {
			pin_ctrl[i] = (pin_ctrl[i] & PORT_ISC_gm) | new_mode_bits;
		}
	}

	cpu_irq_restore(flags);
}

__always_inline static void arch_ioport_set_pin_mode(ioport_pin_t pin,
		ioport_mode_t mode)
{
	PORT_t *base = arch_ioport_pin_to_base(pin);
	volatile uint8_t *pin_ctrl =
			(&base->PIN0CTRL + arch_ioport_pin_to_index(pin));

	uint8_t flags = cpu_irq_save();

	*pin_ctrl &= PORT_ISC_gm;
	*pin_ctrl |= mode;

	cpu_irq_restore(flags);
}

__always_inline static void arch_ioport_set_port_dir(ioport_port_t port,
		ioport_port_mask_t mask, enum ioport_direction dir)
{
	PORT_t *base = arch_ioport_port_to_base(port);

	if (dir == IOPORT_DIR_OUTPUT) {
		base->DIRSET = mask;
	} else if (dir == IOPORT_DIR_INPUT) {
		base->DIRCLR = mask;
	}
}

__always_inline static void arch_ioport_set_pin_dir(ioport_pin_t pin,
		enum ioport_direction dir)
{
	PORT_t *base = arch_ioport_pin_to_base(pin);

	if (dir == IOPORT_DIR_OUTPUT) {
		base->DIRSET = arch_ioport_pin_to_mask(pin);
	} else if (dir == IOPORT_DIR_INPUT) {
		base->DIRCLR = arch_ioport_pin_to_mask(pin);
	}
}

__always_inline static void arch_ioport_set_pin_level(ioport_pin_t pin,
		bool level)
{
	PORT_t *base = arch_ioport_pin_to_base(pin);

	if (level) {
		base->OUTSET = arch_ioport_pin_to_mask(pin);
	} else {
		base->OUTCLR = arch_ioport_pin_to_mask(pin);
	}
}

__always_inline static void arch_ioport_set_port_level(ioport_port_t port,
		ioport_port_mask_t mask, ioport_port_mask_t level)
{
	PORT_t *base = arch_ioport_port_to_base(port);

	base->OUTSET = mask & level;
	base->OUTCLR = mask & ~level;
}

__always_inline static bool arch_ioport_get_pin_level(ioport_pin_t pin)
{
	PORT_t *base = arch_ioport_pin_to_base(pin);

	return base->IN & arch_ioport_pin_to_mask(pin);
}

__always_inline static ioport_port_mask_t arch_ioport_get_port_level(
		ioport_port_t port, ioport_port_mask_t mask)
{
	PORT_t *base = arch_ioport_port_to_base(port);

	return base->IN & mask;
}

__always_inline static void arch_ioport_toggle_pin_level(ioport_pin_t pin)
{
	PORT_t *base = arch_ioport_pin_to_base(pin);

	base->OUTTGL = arch_ioport_pin_to_mask(pin);
}

__always_inline static void arch_ioport_toggle_port_level(ioport_port_t port,
		ioport_port_mask_t mask)
{
	PORT_t *base = arch_ioport_port_to_base(port);

	base->OUTTGL = mask;
}

__always_inline static void arch_ioport_set_pin_sense_mode(ioport_pin_t pin,
		enum ioport_sense pin_sense)
{
	PORT_t *base = arch_ioport_pin_to_base(pin);
	volatile uint8_t *pin_ctrl =
			(&base->PIN0CTRL + arch_ioport_pin_to_index(pin));

	uint8_t flags = cpu_irq_save();

	*pin_ctrl &= ~PORT_ISC_gm;
	*pin_ctrl |= (pin_sense & PORT_ISC_gm);

	cpu_irq_restore(flags);
}

__always_inline static void arch_ioport_set_port_sense_mode(ioport_port_t port,
		ioport_port_mask_t mask, enum ioport_sense pin_sense)
{
	PORT_t *base = arch_ioport_port_to_base(port);
	volatile uint8_t *pin_ctrl = &base->PIN0CTRL;
	uint8_t new_sense_bits = (pin_sense & PORT_ISC_gm);

	uint8_t flags = cpu_irq_save();

	for (uint8_t i = 0; i < 8; i++) {
		if (mask & arch_ioport_pin_to_mask(i)) {
			pin_ctrl[i] = (pin_ctrl[i] & ~PORT_ISC_gm) | new_sense_bits;
		}
	}

	cpu_irq_restore(flags);
}

#endif /* IOPORT_XMEGA_H */
