/**
 * \file
 *
 * \brief Watchdog driver for the AVR32 WDT.
 *
 * - Compiler:           IAR EWAVR32 and GNU GCC for AVR32
 * - Supported devices:  All AVR32 devices with a WDT module
 * - AppNote:
 *
 * \author               Atmel Corporation: http://www.atmel.com \n
 *                       Support and FAQ: http://support.atmel.no/
 *
 * \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 AVR 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.
 */
#include <debug.h>
#include <io.h>
#include <util.h>
#include <watchdog.h>
#include <chip/clk.h>

#define WDT_CTRL		0x00		//!< Control Register
# define WDT_CTRL_KEY1			(0x55 << 24)	//!< 1st write key
# define WDT_CTRL_KEY2			(0xaa << 24)	//!< 2nd write key
# define WDT_CTRL_PSEL(x)		((x) << 8)	//!< Prescale Select
# define WDT_CTRL_EN			(1 << 0)	//!< WDT Enable
#define WDT_CLR			0x04		//!< Clear Register

/**
 * \internal
 * \brief Return the value of WDT register \a reg
 */
#define wdt_read_reg(reg)					\
	mmio_read32((void *)(WDT_BASE + WDT_##reg))

/**
 * \internal
 * \brief Write the value \a val to WDT register \a reg
 */
#define wdt_write_reg(reg, val)					\
	mmio_write32((void *)(WDT_BASE + WDT_##reg), val)

/**
 * \brief Initialize the watchdog.
 *
 * \note The actual timeout will never be less than the specified value,
 * but may at worst be slightly more than twice the value specified.
 *
 * \param timeout_ms The minimum timeout in milliseconds after which the
 * watchdog will expire.
 */
void watchdog_set_timeout(unsigned long timeout_ms)
{
	unsigned long	cycles;
	unsigned int	psel;

	cycles = (PM_RCOSC_MAX_RATE * timeout_ms) / 1000;
	psel = ilog2(cycles);

	dbg_info("WDT: setting %lu ms timeout (requested %lu ms; psel=%u)\n",
			((1UL << (psel + 1)) * 1000) / PM_RCOSC_MAX_RATE,
			timeout_ms, psel);

	wdt_write_reg(CTRL, WDT_CTRL_KEY1 | WDT_CTRL_PSEL(psel));
	wdt_write_reg(CTRL, WDT_CTRL_KEY2 | WDT_CTRL_PSEL(psel));
}

/**
 * \brief Enable the watchdog.
 *
 * After calling this function, the watchdog will be counting and the
 * chip will be reset if watchdog_reset() isn't called regularly before
 * the timeout expires.
 */
void watchdog_enable(void)
{
	uint32_t	ctrl;

	ctrl = wdt_read_reg(CTRL) | WDT_CTRL_EN;
	wdt_write_reg(CTRL, WDT_CTRL_KEY1 | ctrl);
	wdt_write_reg(CTRL, WDT_CTRL_KEY2 | ctrl);
}

/**
 * \brief Enable the watchdog.
 *
 * After calling this function, the watchdog will stop counting and will
 * not reset the chip even if watchdog_reset() isn't called regularly.
 */
void watchdog_disable(void)
{
	uint32_t	ctrl;

	ctrl = wdt_read_reg(CTRL) & ~WDT_CTRL_EN;
	wdt_write_reg(CTRL, WDT_CTRL_KEY1 | ctrl);
	wdt_write_reg(CTRL, WDT_CTRL_KEY2 | ctrl);
}

/**
 * \brief Reset the watchdog.
 *
 * This function will reset the watchdog counter and must be called
 * regularly while the watchdog is running. If the counter reaches the
 * configured timeout, the chip will be reset.
 */
void watchdog_reset(void)
{
	wdt_write_reg(CLR, 0);
}
