/**
 * \file
 *
 * \brief SAM L21/L22/R30/R34/R35 Power functionality
 *
 * Copyright (c) 2014-2018 Microchip Technology Inc. and its subsidiaries.
 *
 * \asf_license_start
 *
 * \page License
 *
 * Subject to your compliance with these terms, you may use Microchip
 * software and any derivatives exclusively with Microchip products.
 * It is your responsibility to comply with third party license terms applicable
 * to your use of third party software (including open source software) that
 * may accompany Microchip software.
 *
 * THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES,
 * WHETHER EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE,
 * INCLUDING ANY IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY,
 * AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT WILL MICROCHIP BE
 * LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, INCIDENTAL OR CONSEQUENTIAL
 * LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND WHATSOEVER RELATED TO THE
 * SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS BEEN ADVISED OF THE
 * POSSIBILITY OR THE DAMAGES ARE FORESEEABLE.  TO THE FULLEST EXTENT
 * ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN ANY WAY
 * RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY,
 * THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE.
 *
 * \asf_license_stop
 *
 */
/*
 * Support and FAQ: visit <a href="https://www.microchip.com/support/">Microchip Support</a>
 */
#ifndef POWER_H_INCLUDED
#define POWER_H_INCLUDED

#include <compiler.h>
#include <clock.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
 * \addtogroup asfdoc_sam0_system_group
 * @{
 */

/**
 * \brief Device sleep modes.
 *
 * List of available sleep modes in the device. A table of clocks available in
 * different sleep modes can be found in \ref asfdoc_sam0_system_module_overview_sleep_mode.
 */
enum system_sleepmode {
	/** IDLE sleep mode */
	SYSTEM_SLEEPMODE_IDLE       = PM_SLEEPCFG_SLEEPMODE(0x2),
	/** STANDBY sleep mode */
	SYSTEM_SLEEPMODE_STANDBY    = PM_SLEEPCFG_SLEEPMODE_STANDBY,
	/** BACKUP sleep mode */
	SYSTEM_SLEEPMODE_BACKUP     = PM_SLEEPCFG_SLEEPMODE_BACKUP,
	/** OFF sleep mode */
	SYSTEM_SLEEPMODE_OFF        = PM_SLEEPCFG_SLEEPMODE_OFF,
};

/**
 * \brief Performance level.
 *
 * List of performance levels. Performance level technique consists of
 * adjusting the regulator output voltage to reduce power consumption.
 */
enum system_performance_level {
	/** Performance level 0 */
	SYSTEM_PERFORMANCE_LEVEL_0  = PM_PLCFG_PLSEL_PL0,
	/** Performance level 2 */
	SYSTEM_PERFORMANCE_LEVEL_2  = PM_PLCFG_PLSEL_PL2,
};

/**
 * \brief RAM Back-biasing mode.
 *
 * List of RAM back bias modes. By default, in standby sleep mode,
 * RAM is in low power mode (back biased) if its power domain is in
 * retention state. This behavior can be changed by configuring the Back Bias
 * bit groups in STDBYCFG(STDBYCFG.BBIASxx).
 */
enum system_ram_back_bias_mode {
	/** Retention Back biasing mode */
	SYSTEM_RAM_BACK_BIAS_RETENTION = 0,
	/** Standby Back Biasing mode */
	SYSTEM_RAM_BACK_BIAS_STANDBY,
	/** Standby OFF mode */
	SYSTEM_RAM_BACK_BIAS_STANDBY_OFF,
	/** Always OFF mode */
	SYSTEM_RAM_BACK_BIAS_OFF,
};

#if SAML21 || SAMR30 || (SAMR34) || (SAMR35)
/**
 * \brief Linked power domain.
 *
 * List of linked power domains. Power domains can be linked to each other.
 * It allows a power domain (PDn) to be kept in active state if the inferior
 * power domain (PDn-1) is in active state too.
 */
enum system_linked_power_domain {
	/** Power domains PD0/PD1/PD2 are not linked */
	SYSTEM_LINKED_POWER_DOMAIN_DEFAULT   = PM_STDBYCFG_LINKPD_DEFAULT_Val,
	/** Power domains PD0 and PD1 are linked */
	SYSTEM_LINKED_POWER_DOMAIN_PD01      = PM_STDBYCFG_LINKPD_PD01_Val,
	/** Power domains PD1 and PD2 are linked */
	SYSTEM_LINKED_POWER_DOMAIN_PD12      = PM_STDBYCFG_LINKPD_PD12_Val,
	/** All Power domains are linked */
	SYSTEM_LINKED_POWER_DOMAIN_PD012     = PM_STDBYCFG_LINKPD_PD012_Val,
};

#if (SAML21XXXB) || (SAMR30) || (SAMR34) || (SAMR35)
/**
 * \brief VREG switching mode.
 *
 * List of VREG switching modes.
 */
enum system_vreg_switch_mode {
	/** Automatic mode. */
	SYSTEM_SYSTEM_VREG_SWITCH_AUTO = 0,
	/** Performance oriented. */
	SYSTEM_SYSTEM_VREG_SWITCH_PERFORMANCE,
	/** Low Power consumption oriented. */
	SYSTEM_SYSTEM_VREG_SWITCH_LP,
};
#endif

/**
 * \brief Power domain.
 *
 * List of power domains. Power domain gating technique consists of turning
 * on or off power domain voltage to save power while keeping other domains
 * powered up.
 */
enum system_power_domain {
	/** All power domains switching are handled by hardware */
	SYSTEM_POWER_DOMAIN_DEFAULT = PM_STDBYCFG_PDCFG_DEFAULT_Val,
	/** Power domain 0 (PD0) is forced ACTIVE */
	SYSTEM_POWER_DOMAIN_PD0     = PM_STDBYCFG_PDCFG_PD0_Val,
	/** Power domain 0 and 1 (PD0 and PD1) are forced ACTIVE */
	SYSTEM_POWER_DOMAIN_PD01    = PM_STDBYCFG_PDCFG_PD01_Val,
	/** All power domains are forced ACTIVE */
	SYSTEM_POWER_DOMAIN_PD012   = PM_STDBYCFG_PDCFG_PD012_Val,
};
#endif

#if SAML22
/**
 * \brief Voltage Regulator switch in Standby mode.
 *
 */
enum system_vreg_switch_mode {
	/** Automatic mode. */
	SYSTEM_VREG_SWITCH_AUTO        = PM_STDBYCFG_VREGSMOD_AUTO_Val,
	/** Performance oriented. */
	SYSTEM_VREG_SWITCH_PERFORMANCE = PM_STDBYCFG_VREGSMOD_PERFORMANCE_Val,
	/** Low Power consumption oriented. */
	SYSTEM_VREG_SWITCH_LP          = PM_STDBYCFG_VREGSMOD_LP_Val,
};

#endif

/**
 * \brief Voltage regulator.
 *
 * Voltage regulators selection. In active mode, the voltage regulator
 * can be chosen on the fly between a LDO or a Buck converter.
 */
enum system_voltage_regulator_sel {
	/** The voltage regulator in active mode is a LDO voltage regulator */
	SYSTEM_VOLTAGE_REGULATOR_LDO    = SUPC_VREG_SEL_LDO_Val,
	/** The voltage regulator in active mode is a buck converter */
	SYSTEM_VOLTAGE_REGULATOR_BUCK   = SUPC_VREG_SEL_BUCK_Val,
};

/**
 * \brief Low power efficiency.
 *
 * Low power mode efficiency.
 */
enum system_voltage_regulator_low_power_efficiency {
	/** The voltage regulator in Low power mode has the default efficiency and
		support the whole VDD range (1.62V to 3.6V) */
	SYSTEM_VOLTAGE_REGULATOR_LOW_POWER_EFFICIENCY_DEFAULT,
	/** The voltage regulator in Low power mode has the highest efficiency and
		support the limited VDD range (2.5V to 3.6V) */
	SYSTEM_VOLTAGE_REGULATOR_LOW_POWER_EFFICIENCY_HIGHTEST,
};

/**
 * \brief Voltage reference value.
 *
 * Voltage references selection.
 */
enum system_voltage_references_sel {
	/** 1.0V voltage reference typical value */
	SYSTEM_VOLTAGE_REFERENCE_1V0    = SUPC_VREF_SEL_1V0_Val,
	/** 1.1V voltage reference typical value */
	SYSTEM_VOLTAGE_REFERENCE_1V1    = SUPC_VREF_SEL_1V1_Val,
	/** 1.2V voltage reference typical value */
	SYSTEM_VOLTAGE_REFERENCE_1V2    = SUPC_VREF_SEL_1V2_Val,
	/** 1.25V voltage reference typical value */
	SYSTEM_VOLTAGE_REFERENCE_1V25   = SUPC_VREF_SEL_1V25_Val,
	/** 2.0V voltage reference typical value */
	SYSTEM_VOLTAGE_REFERENCE_2V0    = SUPC_VREF_SEL_2V0_Val,
	/** 2.2V voltage reference typical value */
	SYSTEM_VOLTAGE_REFERENCE_2V2    = SUPC_VREF_SEL_2V2_Val,
	/** 2.4V voltage reference typical value */
	SYSTEM_VOLTAGE_REFERENCE_2V4    = SUPC_VREF_SEL_2V4_Val,
	/** 2.5V voltage reference typical value */
	SYSTEM_VOLTAGE_REFERENCE_2V5    = SUPC_VREF_SEL_2V5_Val,
};

/**
 * \brief Battery power switch configuration enum.
 *
 * Enum for Battery power switch modes.
 */
enum system_battery_power_switch {
	/** The backup domain is always supplied by main power */
	SYSTEM_BATTERY_POWER_SWITCH_NONE      = SUPC_BBPS_CONF_NONE_Val,
	/** The power switch is handled by the automatic power switch */
	SYSTEM_BATTERY_POWER_SWITCH_AUTOMATIC = SUPC_BBPS_CONF_APWS_Val,
	/** The backup domain is always supplied by battery backup power */
	SYSTEM_BATTERY_POWER_SWITCH_FORCED    = SUPC_BBPS_CONF_FORCED_Val,
	/** The power switch is handled by the BOD33 */
	SYSTEM_BATTERY_POWER_SWITCH_BOD33     = SUPC_BBPS_CONF_BOD33_Val,
};

/**
 * \brief Voltage reference.
 *
 * List of available voltage references (VREF) that may be used within the
 * device.
 */
enum system_voltage_reference {
	/** Temperature sensor voltage reference */
	SYSTEM_VOLTAGE_REFERENCE_TEMPSENSE,
	/** Voltage reference output */
	SYSTEM_VOLTAGE_REFERENCE_OUTPUT,
};

/**
 * \brief Backup IO enum.
 *
 * List of Backup input and output pins.
 * If enabled (\ref system_backup_pin_output_enable), the pins can be driven
 * by the SUPC.
 */
enum system_backup_pin {
	/** Power Supply OK status pin */
	SYSTEM_BACKUP_PIN_PSOK  = (0x1 << 0),
	/** Backup output pin 0  */
	SYSTEM_BACKUP_PIN_OUT_0 = (0x1 << 1),
	/** Backup output pin 1  */
	SYSTEM_BACKUP_PIN_OUT_1 = (0x1 << 2)
};

/**
 * \brief Standby configuration.
 *
 * Configuration structure for standby mode.
 */
struct system_standby_config {
#if SAML21 || SAMR30 || (SAMR34) || (SAMR35)
	/** Power domain. */
	enum system_power_domain  power_domain;
	/** Enable dynamic power gating for power domain 0 */
	bool enable_dpgpd0;
	/** Enable dynamic power gating for power domain 1 */
	bool enable_dpgpd1;
#if (SAML21XXXA)
	/** Automatic VREG switching disable. */
	bool disable_avregsd;
#else
	/** VREG switching mode */
	enum system_vreg_switch_mode vregs_mode;
#endif
	/** Linked power domain */
	enum system_linked_power_domain linked_power_domain;
#elif SAML22
	/** Regulator switch mode in standby. */
	enum system_vreg_switch_mode vreg_switch_mode;
#endif
	/** Back bias for HMCRAMCHS. */
	enum system_ram_back_bias_mode hmcramchs_back_bias;
	/** Back bias for HMCRAMCLP */
	enum system_ram_back_bias_mode hmcramclp_back_bias;
};

/**
 * \brief Voltage Regulator System (VREG) Control configuration.
 *
 * Configuration structure for VREG.
 */
struct system_voltage_regulator_config {
	/** Voltage scaling period */
	uint8_t  voltage_scale_period;
	/** Voltage scaling voltage step */
	uint8_t voltage_scale_step;
	/** Run in standby in standby sleep mode */
	bool run_in_standby;
	/** Voltage Regulator Selection */
	enum system_voltage_regulator_sel  regulator_sel;
	/** Low power efficiency */
	enum system_voltage_regulator_low_power_efficiency low_power_efficiency;
#if SAML22 || SAML21XXXB || (SAMR34J) || (SAMR35J)
	/** Run in standby in performance level 0. */
	bool run_in_standby_pl0;
#endif
};

/**
 * \brief Voltage References System (VREF) Control configuration.
 *
 * Configuration structure for VREF.
 */
struct system_voltage_references_config {
	/** Voltage References Selection */
	enum system_voltage_references_sel  sel;
	/** On Demand Control */
	bool on_demand;
	/** Run in standby */
	bool run_in_standby;
#if SAML22
	/** Temperature Sensor Selection. */
	bool temperature_sensor_sel;
#endif
};

/**
 * \brief Battery Backup Power Switch (BBPS) Control configuration.
 *
 * Configuration structure for Battery Backup Power Switch (BBPS).
 */
struct system_battery_backup_power_switch_config {
	/** Enable device wake up when BBPS switches from
		battery backup power to main power */
	bool wake_enabled;
	/** Battery backup power switch configuration */
	enum system_battery_power_switch battery_power_switch;
};

/**
 * \name Voltage Regulator
 * @{
 */

/**
 * \brief Retrieve the default configuration for voltage regulator.
 *
 * Fills a configuration structure with the default configuration:
 *   - Voltage scaling period is 1μs
 *   - Voltage scaling voltage step is 2*min_step
 *   - The voltage regulator is in low power mode in Standby sleep mode
 *   - The voltage regulator in active mode is an LDO voltage regulator
 *   - The voltage regulator in Low power mode has the default efficiency
 *
 * \param[out] config  Configuration structure to fill with default values
 */
static inline void system_voltage_regulator_get_config_defaults(
		struct system_voltage_regulator_config *const config)
{
	Assert(config);
	config->voltage_scale_period = 0;
	config->voltage_scale_step   = 0;
	config->run_in_standby       = false;
	config->regulator_sel        = SYSTEM_VOLTAGE_REGULATOR_LDO;
	config->low_power_efficiency = SYSTEM_VOLTAGE_REGULATOR_LOW_POWER_EFFICIENCY_DEFAULT;
#if SAML22 || SAML21XXXB || SAMR34J || SAMR35J
	config->run_in_standby_pl0   = false;
#endif
}

/**
 * \brief Configure voltage regulator.
 *
 * Configures voltage regulator with the given configuration.
 *
 * \param[in] config  Voltage regulator configuration structure containing
 *                    the new config
 */
static inline void system_voltage_regulator_set_config(
		struct system_voltage_regulator_config *const config)
{
	Assert(config);
	SUPC->VREG.bit.VSPER    = config->voltage_scale_period;
	SUPC->VREG.bit.VSVSTEP  = config->voltage_scale_step;
	SUPC->VREG.bit.RUNSTDBY = config->run_in_standby;
	SUPC->VREG.bit.SEL      = config->regulator_sel;
#if (SAML21XXXB) || (SAMR30) || (SAMR34) || (SAMR35)
	SUPC->VREG.bit.LPEFF    = config->low_power_efficiency;
#endif
#if SAML22 || SAML21XXXB || SAMR34J || SAMR35J
	SUPC->VREG.bit.STDBYPL0 = config->run_in_standby_pl0;
#endif
	while(!(SUPC->STATUS.reg & SUPC_STATUS_VREGRDY)) {
		;
	}
}

/**
* \brief Enable the selected voltage regulator.
 *
 * Enables the selected voltage regulator source.
 */
static inline void system_voltage_regulator_enable(void)
{
	SUPC->VREG.reg |= SUPC_VREG_ENABLE;
}

/**
 * \brief Disable the selected voltage regulator.
 *
 * Disables the selected voltage regulator.
 */
static inline void system_voltage_regulator_disable(void)
{
	SUPC->VREG.reg &= ~SUPC_VREG_ENABLE;
}

/**
 * @}
 */

/**
 * \name Voltage References
 * @{
 */

/**
 * \brief Retrieve the default configuration for voltage reference.
 *
 * Fill a configuration structure with the default configuration:
 *   - 1.0V voltage reference typical value
 *   - On demand control disabled
 *   - The voltage reference and the temperature sensor are halted during standby sleep mode
 *
 * \param[out] config  Configuration structure to fill with default values
 */
static inline void system_voltage_reference_get_config_defaults(
		struct system_voltage_references_config *const config)
{
	Assert(config);
	config->sel            = SYSTEM_VOLTAGE_REFERENCE_1V0;
	config->on_demand      = false;
	config->run_in_standby = false;
#if SAML22
	config->temperature_sensor_sel = false;
#endif
}

/**
 * \brief Configure voltage reference.
 *
 * Configures voltage reference with the given configuration.
 *
 * \param[in] config  Voltage reference configuration structure containing
 *                    the new config
 */
static inline void system_voltage_reference_set_config(
		struct system_voltage_references_config *const config)
{
	Assert(config);
	SUPC->VREF.bit.SEL      = config->sel;
	SUPC->VREF.bit.ONDEMAND = config->on_demand;
	SUPC->VREF.bit.RUNSTDBY = config->run_in_standby;
#if SAML22
	SUPC->VREF.bit.TSSEL    = config->temperature_sensor_sel;
#endif
}

/**
 * \brief Enable the selected voltage reference.
 *
 * Enables the selected voltage reference source, making the voltage reference
 * available on a pin as well as an input source to the analog peripherals.
 *
 * \param[in] vref  Voltage reference to enable
 */
static inline void system_voltage_reference_enable(
		const enum system_voltage_reference vref)
{
	switch (vref) {
		case SYSTEM_VOLTAGE_REFERENCE_TEMPSENSE:
			SUPC->VREF.reg |= SUPC_VREF_TSEN;
			break;
		case SYSTEM_VOLTAGE_REFERENCE_OUTPUT:
			SUPC->VREF.reg |= SUPC_VREF_VREFOE;
			break;
		default:
			Assert(false);
			return;
	}
}

/**
 * \brief Disable the selected voltage reference.
 *
 * Disables the selected voltage reference source.
 *
 * \param[in] vref  Voltage reference to disable
 */
static inline void system_voltage_reference_disable(
		const enum system_voltage_reference vref)
{
	switch (vref) {
		case SYSTEM_VOLTAGE_REFERENCE_TEMPSENSE:
			SUPC->VREF.reg &= ~SUPC_VREF_TSEN;
			break;
		case SYSTEM_VOLTAGE_REFERENCE_OUTPUT:
			SUPC->VREF.reg &= ~SUPC_VREF_VREFOE;
			break;
		default:
			Assert(false);
			return;
	}
}

/**
 * @}
 */

/**
 * \name Battery Backup Power Switch
 * @{
 */

/**
 * \brief Retrieve the default configuration for battery backup power switch control.
 *
 * Fills a configuration structure with the default configuration:
 *   - The main Power Supply OK status is not available on the PSOK pin
 *   - The device is not woken up when switched from battery backup power to main power
 *   - The backup domain is always supplied by main power
 *
 * \param[out] config  Configuration structure to fill with default values
 */
static inline void system_battery_backup_power_switch_get_config_defaults(
		struct system_battery_backup_power_switch_config *const config)
{
	Assert(config);
	config->wake_enabled         = false;
	config->battery_power_switch = SYSTEM_BATTERY_POWER_SWITCH_NONE;
}

/**
 * \brief Configure battery backup power switch.
 *
 * Configures battery backup power switch with the given configuration.
 *
 * \param[in] config  Battery backup power switch configuration structure containing
 *                    the new config
 */
static inline void system_battery_backup_power_switch_set_config(
		struct system_battery_backup_power_switch_config *const config)
{
	Assert(config);
	uint32_t new_config = SUPC->BBPS.reg & SUPC_BBPS_PSOKEN;

	if(config->wake_enabled) {
		new_config |= SUPC_BBPS_WAKEEN;
	}

	new_config |= SUPC_BBPS_CONF(config->battery_power_switch);

	SUPC->BBPS.reg = new_config;

	if (config->battery_power_switch == SYSTEM_BATTERY_POWER_SWITCH_AUTOMATIC) {
		while (!(SUPC->STATUS.reg & SUPC_STATUS_APWSRDY)) {
			;
		}
	}
}

/**
 * @}
 */

/**
 * \name Output Pins in Backup Mode
 * @{
 */

/**
 *  \brief Enable the backup pin output.
 *
 *  The output is enabled and driven by the SUPC.
 *
 *  \param[in] pin Backup pin index
 */
static inline void system_backup_pin_output_enable(
		enum system_backup_pin pin)
{
	if (pin == SYSTEM_BACKUP_PIN_PSOK) {
		SUPC->BBPS.reg |= SUPC_BBPS_PSOKEN;
	} else {
		SUPC->BKOUT.reg |= SUPC_BKOUT_EN(pin >> 1);
	}
}

/**
 *  \brief Disable the backup pin output.
 *
 *  The output is not enabled.
 *
 *  \param[in] pin Backup pin index
 */
static inline void system_backup_pin_output_disable(
		enum system_backup_pin pin)
{
	if (pin == SYSTEM_BACKUP_PIN_PSOK) {
		SUPC->BBPS.reg &= ~SUPC_BBPS_PSOKEN;
	} else {
		SUPC->BKOUT.reg &= ~SUPC_BKOUT_EN(pin >> 1);
	}
}

/**
 * \brief Check if backup pin output is enabled.
 *
 *  \param[in] pin Backup pin index
 *
 * \return The enabled status.
 * \retval true The output is enabled
 * \retval false The output is not enabled
 */
static inline bool system_backup_pin_output_is_enabled(
		enum system_backup_pin pin)
{
	bool enabled = false;

	if (pin == SYSTEM_BACKUP_PIN_PSOK) {
		if (SUPC->BBPS.reg & SUPC_BBPS_PSOKEN) {
			enabled = true;
		}
	} else {
		if (SUPC->BKOUT.reg & SUPC_BKOUT_EN(pin >> 1)) {
			enabled = true;
		}
	}
	return enabled;
}

/**
 *  \brief Enable the backup pin toggle on RTC event.
 *
 *  Toggle output on RTC event is enabled.
 *
 *  \param[in] pin Backup pin index
 */
static inline void system_backup_pin_output_enable_rtc_toggle(
		enum system_backup_pin pin)
{
	Assert(pin != SYSTEM_BACKUP_PIN_PSOK);

	SUPC->BKOUT.reg |= SUPC_BKOUT_RTCTGL(pin >> 1);
}

/**
 *  \brief Disable the backup pin toggle on RTC event.
 *
 *  Toggle output on RTC event is disabled.
 *
 *  \param[in] pin Backup pin index
 */
static inline void system_backup_pin_output_disable_rtc_toggle(
		enum system_backup_pin pin)
{
	Assert(pin != SYSTEM_BACKUP_PIN_PSOK);

	SUPC->BKOUT.reg &= ~SUPC_BKOUT_RTCTGL(pin >> 1);
}

/**
 *  \brief Set the backup pin.
 *
 *  Set the corresponding output pin.
 *
 *  \param[in] pin Backup pin index
 */
static inline void system_backup_pin_output_set(
		enum system_backup_pin pin)
{
	Assert(pin != SYSTEM_BACKUP_PIN_PSOK);

	SUPC->BKOUT.reg |= SUPC_BKOUT_SET(pin >> 1);
}

/**
 *  \brief Clear the backup pin.
 *
 *  Clear the corresponding output.
 *
 *  \param[in] pin Backup pin index
 */
static inline void system_backup_pin_output_clear(
		enum system_backup_pin pin)
{
	Assert(pin != SYSTEM_BACKUP_PIN_PSOK);

	SUPC->BKOUT.reg |= SUPC_BKOUT_CLR(pin >> 1);
}

/**
 *  \brief Get the backup I/O input values.
 *
 *  Get the backup I/O data input values. If the corresponding pin is enabled,
 *  the I/O input value is given on the pin.
 *
 *  \param[in] pin Backup pin index
 *
 * \return The backup I/O input level value.
 */
static inline bool system_backup_pin_output_get(enum system_backup_pin pin)
{
	Assert(pin != SYSTEM_BACKUP_PIN_PSOK);

	return (SUPC->BKIN.reg & SUPC_BKIN_BKIN(pin >> 1));
}

/**
 * @}
 */

/**
 * \name Device Sleep Control
 * @{
 */

/**
 * \brief Set the sleep mode of the device.
 *
 * Sets the sleep mode of the device; the configured sleep mode will be entered
 * upon the next call of the \ref system_sleep() function.
 *
 * For an overview of which systems are disabled in sleep for the different
 * sleep modes, see \ref asfdoc_sam0_system_module_overview_sleep_mode.
 *
 * \param[in] sleep_mode  Sleep mode to configure for the next sleep operation
 */
static inline void system_set_sleepmode(
	const enum system_sleepmode sleep_mode)
{
	PM->SLEEPCFG.reg = sleep_mode;
	while(PM->SLEEPCFG.reg != sleep_mode) ;
}

/**
 * \brief Put the system to sleep waiting for interrupt.
 *
 * Executes a device DSB (Data Synchronization Barrier) instruction to ensure
 * all ongoing memory accesses have completed. Further, a WFI (Wait For Interrupt)
 * instruction is executed to place the device into the sleep mode specified by
 * \ref system_set_sleepmode.
 */
static inline void system_sleep(void)
{
	__DSB();
	__WFI();
}

/**
 * @}
 */

/**
 * \name Performance Level Control
 * @{
 */

/**
 * \brief Switch performance level.
 *
 *  The bus frequency must be reduced prior to scaling down the performance level,
 *  in order to not exceed the maximum frequency allowed for the performance level.
 *
 *  When scaling up the performance level (for example from PL0 to PL2), the bus
 *  frequency can be increased first when the performance level transition is
 *  completed. Check the performance level status before increasing the frequency.
 *
 * \param[in] performance_level  Performance level to switch
 *
 * \retval STATUS_ERR_INVALID_ARG  Invalid parameter
 * \retval STATUS_OK               Successfully
 */
static inline enum status_code system_switch_performance_level(
					const enum system_performance_level performance_level)
{

	if (performance_level == (enum system_performance_level)PM->PLCFG.reg) {
		return STATUS_OK;
	}

#if SAML22 || SAML21XXXB || SAMR34J || SAMR35J
	if (PM->PLCFG.reg & PM_PLCFG_PLDIS) {
		return STATUS_ERR_INVALID_ARG;
	}
#endif

	/* Clear performance level status */
	PM->INTFLAG.reg = PM_INTFLAG_PLRDY;

	/* Switch performance level */
	PM->PLCFG.reg = performance_level;

	/* Waiting performance level ready */
	while (!PM->INTFLAG.reg) {
		;
	}
	return STATUS_OK;
}

#if SAML22 || SAML21XXXB || SAMR34J || SAMR35J
/**
 * \brief Enable performance level switch.
 *
 * Enable performance level switch.
 */
static inline void system_performance_level_enable(void)
{
	PM->PLCFG.reg &= ~PM_PLCFG_PLDIS;
}

/**
 * \brief Disable performance level switch.
 *
 * Disable performance level switch.
 */
static inline void system_performance_level_disable(void)
{
	PM->PLCFG.reg |= PM_PLCFG_PLDIS;
}
#endif

/**
 * \brief Get performance level.
 *
 * Get performance level.
 *
 * \return Current performance level.
 */
static inline enum system_performance_level system_get_performance_level(void)
{
	return (enum system_performance_level)PM->PLCFG.reg;
}

/**
 * \brief Get performance level status.
 *
 * Get performance level status.
 * \return Performance level status: Written to one when the performance level is ready.
 */
static inline uint8_t system_get_performance_level_status(void)
{
	return PM->INTFLAG.reg;
}

/**
 * \brief Clear performance level status.
 *
 * Clear performance level status.
 */
static inline void system_clear_performance_level_status(void)
{
	PM->INTFLAG.reg = PM_INTFLAG_PLRDY;
}

/**
 * @}
 */

/**
 * \name Standby Configuration
 * @{
 */

/**
 * \brief Retrieve the default configuration for standby.
 *
 * Fills a configuration structure with the default configuration for standby:
 *   - Retention back biasing mode for HMCRAMCLP
 *   - Retention back biasing mode for HMCRAMCHS
 *   - Power domains PD0/PD1/PD2 are not linked
 *   - Automatic VREG switching is used
 *   - Dynamic power gating for power domain 1 is disabled
 *   - Dynamic power gating for power domain 0 is disabled
 *   - All power domains switching are handled by hardware
 *
 * \param[out] config  Configuration structure to fill with default values
 */
static inline void system_standby_get_config_defaults(
		struct system_standby_config *const config)
{
	Assert(config);
#if SAML21 || SAMR30 || (SAMR34) || (SAMR35)
	config->power_domain        = SYSTEM_POWER_DOMAIN_DEFAULT;
	config->enable_dpgpd0       = false;
	config->enable_dpgpd1       = false;
#if (SAML21XXXB) || (SAMR30) || (SAMR34) || (SAMR35)
	config->vregs_mode          = SYSTEM_SYSTEM_VREG_SWITCH_AUTO;
#else
	config->disable_avregsd     = false;
#endif
	config->linked_power_domain = SYSTEM_LINKED_POWER_DOMAIN_DEFAULT;
#elif SAML22
	config->vreg_switch_mode    = SYSTEM_VREG_SWITCH_AUTO;
#endif
	config->hmcramchs_back_bias = SYSTEM_RAM_BACK_BIAS_RETENTION;
	config->hmcramclp_back_bias = SYSTEM_RAM_BACK_BIAS_RETENTION;
}

/**
 * \brief Configure standby mode.
 *
 * Configures standby with the given configuration.
 *
 * \param[in] config  Standby configuration structure containing
 *                    the new config
 */
static inline void system_standby_set_config(
		struct system_standby_config *const config)
{
	Assert(config);
#if SAML21 || SAMR30 || (SAMR34) || (SAMR35)
	PM->STDBYCFG.reg = PM_STDBYCFG_PDCFG(config->power_domain)
					 | (config->enable_dpgpd0 << PM_STDBYCFG_DPGPD0_Pos)
					 | (config->enable_dpgpd1 << PM_STDBYCFG_DPGPD1_Pos)
#if (SAML21XXXB) || (SAMR30) || (SAMR34) || (SAMR35)
					 | PM_STDBYCFG_VREGSMOD(config->vregs_mode)
#else
					 | (config->disable_avregsd << PM_STDBYCFG_AVREGSD_Pos)
#endif
					 | PM_STDBYCFG_LINKPD(config->linked_power_domain)
					 | PM_STDBYCFG_BBIASHS(config->hmcramchs_back_bias)
					 | PM_STDBYCFG_BBIASLP(config->hmcramclp_back_bias);
#elif SAML22
	PM->STDBYCFG.reg = PM_STDBYCFG_VREGSMOD(config->vreg_switch_mode) |
					 PM_STDBYCFG_BBIASHS(config->hmcramchs_back_bias);
#endif
}

/**
 * @}
 */

/**
 * \name I/O Retention
 * @{
 */

/**
 * \brief Enable I/O retention.
 *
 *  Enable I/O retention. After waking up from Backup mode, I/O lines are held
 *  until the bit is written to 0.
 */
static inline void system_io_retension_enable(void)
{
	PM->CTRLA.reg = PM_CTRLA_IORET;
}

/**
 * \brief Disable I/O retention.
 *
 * Disable IO retention. After waking up from Backup mode, I/O lines are not held.
 */
static inline void system_io_retension_disable(void)
{
	PM->CTRLA.reg = PM_CTRLA_MASK & (~PM_CTRLA_IORET);
}

/**
 * @}
 */

/** @} */

#ifdef __cplusplus
}
#endif

#endif /* POWER_H_INCLUDED */
