/* Copyright 2016 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

/* Clocks and power management settings */

#include "chipset.h"
#include "clock.h"
#include "clock-l4.h"
#include "common.h"
#include "console.h"
#include "cpu.h"
#include "hooks.h"
#include "host_command.h"
#include "registers.h"
#include "rtc.h"
#include "timer.h"
#include "uart.h"
#include "util.h"

/* Console output macros */
#define CPUTS(outstr) cputs(CC_CLOCK, outstr)
#define CPRINTS(format, args...) cprints(CC_CLOCK, format, ## args)

/* High-speed oscillator is 16 MHz */
#define STM32_HSI_CLOCK 16000000
/* Multi-speed oscillator is 4 MHz by default */
#define STM32_MSI_CLOCK 4000000

/* Real Time Clock (RTC) */

#ifdef CONFIG_STM32_CLOCK_HSE_HZ
#define RTC_PREDIV_A 39
#define RTC_FREQ ((STM32L4_RTC_REQ) / (RTC_PREDIV_A + 1)) /* Hz */
#else /* from LSI clock */
#define RTC_PREDIV_A 1
#define RTC_FREQ (STM32L4_LSI_CLOCK / (RTC_PREDIV_A + 1)) /* Hz */
#endif
#define RTC_PREDIV_S (RTC_FREQ - 1)
/*
 * Scaling factor to ensure that the intermediate values computed from/to the
 * RTC frequency are fitting in a 32-bit integer.
 */
#define SCALING 1000

enum clock_osc {
	OSC_INIT = 0,	/* Uninitialized */
	OSC_HSI,	/* High-speed internal oscillator */
	OSC_MSI,	/* Multi-speed internal oscillator */
#ifdef STM32_HSE_CLOCK	/* Allows us to catch absence of HSE at comiple time */
	OSC_HSE,	/* High-speed external oscillator */
#endif
	OSC_PLL,	/* PLL */
};

static int freq = STM32_MSI_CLOCK;
static int current_osc;

int clock_get_freq(void)
{
	return freq;
}

int clock_get_timer_freq(void)
{
	return clock_get_freq();
}

void clock_wait_bus_cycles(enum bus_type bus, uint32_t cycles)
{
	volatile uint32_t unused __attribute__((unused));

	if (bus == BUS_AHB) {
		while (cycles--)
			unused = STM32_DMA1_REGS->isr;
	} else { /* APB */
		while (cycles--)
			unused = STM32_USART_BRR(STM32_USART1_BASE);
	}
}

static void clock_enable_osc(enum clock_osc osc)
{
	uint32_t ready;
	uint32_t on;

	switch (osc) {
	case OSC_HSI:
		ready = STM32_RCC_CR_HSIRDY;
		on = STM32_RCC_CR_HSION;
		break;
	case OSC_MSI:
		ready = STM32_RCC_CR_MSIRDY;
		on = STM32_RCC_CR_MSION;
		break;
#ifdef STM32_HSE_CLOCK
	case OSC_HSE:
#ifdef STM32_HSE_BYP
		STM32_RCC_CR |= STM32_RCC_CR_HSEBYP;
#endif
		ready = STM32_RCC_CR_HSERDY;
		on = STM32_RCC_CR_HSEON;
		break;
#endif
	case OSC_PLL:
		ready = STM32_RCC_CR_PLLRDY;
		on = STM32_RCC_CR_PLLON;
		break;
	default:
		return;
	}

	/* Enable HSI and wait for HSI to be ready */
	wait_for_ready(&STM32_RCC_CR, on, ready);
}

/* Switch system clock oscillator */
static void clock_switch_osc(enum clock_osc osc)
{
	uint32_t sw;
	uint32_t sws;
	uint32_t val;

	switch (osc) {
	case OSC_HSI:
		sw = STM32_RCC_CFGR_SW_HSI;
		sws = STM32_RCC_CFGR_SWS_HSI;
		break;
	case OSC_MSI:
		sw = STM32_RCC_CFGR_SW_MSI;
		sws = STM32_RCC_CFGR_SWS_MSI;
		break;
#ifdef STM32_HSE_CLOCK
	case OSC_HSE:
		sw = STM32_RCC_CFGR_SW_HSE;
		sws = STM32_RCC_CFGR_SWS_HSE;
		break;
#endif
	case OSC_PLL:
		sw = STM32_RCC_CFGR_SW_PLL;
		sws = STM32_RCC_CFGR_SWS_PLL;
		break;
	default:
		return;
	}
	val = STM32_RCC_CFGR;
	val &= ~STM32_RCC_CFGR_SW;
	val |= sw;
	STM32_RCC_CFGR = val;
	while ((STM32_RCC_CFGR & STM32_RCC_CFGR_SWS_MSK) != sws)
		;
}

/*
 * Configure PLL for HSE
 *
 * 1. Disable the PLL by setting PLLON to 0 in RCC_CR.
 * 2. Wait until PLLRDY is cleared. The PLL is now fully stopped.
 * 3. Change the desired parameter.
 * 4. Enable the PLL again by setting PLLON to 1.
 * 5. Enable the desired PLL outputs by configuring PLLPEN, PLLQEN, PLLREN
 *    in RCC_PLLCFGR.
 */
static int stm32_configure_pll(enum clock_osc osc,
			       uint8_t m, uint8_t n, uint8_t r)
{
	uint32_t val;
	bool pll_unchanged;
	int f;

	val = STM32_RCC_PLLCFGR;
	pll_unchanged = true;

	if (osc == OSC_HSI)
		if ((val & STM32_RCC_PLLCFGR_PLLSRC_MSK) !=
		    STM32_RCC_PLLCFGR_PLLSRC_HSI)
			pll_unchanged = false;

	if (osc == OSC_MSI)
		if ((val & STM32_RCC_PLLCFGR_PLLSRC_MSK) !=
		    STM32_RCC_PLLCFGR_PLLSRC_MSI)
			pll_unchanged = false;

#ifdef STM32_HSE_CLOCK
	if (osc == OSC_HSE)
		if ((val & STM32_RCC_PLLCFGR_PLLSRC_MSK) !=
		    STM32_RCC_PLLCFGR_PLLSRC_HSE)
			pll_unchanged = false;
#endif

	if ((val & STM32_RCC_PLLCFGR_PLLM_MSK) !=
	    ((m - 1) << STM32_RCC_PLLCFGR_PLLM_POS))
		pll_unchanged = false;

	if ((val & STM32_RCC_PLLCFGR_PLLN_MSK) !=
	    (n << STM32_RCC_PLLCFGR_PLLN_POS))
		pll_unchanged = false;

	if ((val & STM32_RCC_PLLCFGR_PLLR_MSK) !=
	    (((r >> 1) - 1) << STM32_RCC_PLLCFGR_PLLR_POS))
		pll_unchanged = false;

	if (pll_unchanged == true) {
		if (osc == OSC_HSI)
			f = STM32_HSI_CLOCK;
		else
			f = STM32_MSI_CLOCK;

		if (!(STM32_RCC_CR & STM32_RCC_CR_PLLRDY)) {
			STM32_RCC_CR |= STM32_RCC_CR_PLLON;
			STM32_RCC_PLLCFGR |= STM32_RCC_PLLCFGR_PLLREN;

			while ((STM32_RCC_CR & STM32_RCC_CR_PLLRDY) == 0)
				;
		}
		/* (f * n) shouldn't overflow based on their max values */
		return (f * n / m / r);
	}
	/* 1 */
	STM32_RCC_CR &= ~STM32_RCC_CR_PLLON;

	/* 2 */
	while (STM32_RCC_CR & STM32_RCC_CR_PLLRDY)
		;

	/* 3 */
	val = STM32_RCC_PLLCFGR;

	val &= ~STM32_RCC_PLLCFGR_PLLSRC_MSK;
	switch (osc) {
	case OSC_HSI:
		val |= STM32_RCC_PLLCFGR_PLLSRC_HSI;
		f = STM32_HSI_CLOCK;
		break;
	case OSC_MSI:
		val |= STM32_RCC_PLLCFGR_PLLSRC_MSI;
		f = STM32_MSI_CLOCK;
		break;
#ifdef STM32_HSE_CLOCK
	case OSC_HSE:
		val |= STM32_RCC_PLLCFGR_PLLSRC_HSE;
		f = STM32_HSE_CLOCK;
		break;
#endif
	default:
		return -1;
	}

	ASSERT(m > 0 && m < 9);
	val &= ~STM32_RCC_PLLCFGR_PLLM_MSK;
	val |= (m - 1) << STM32_RCC_PLLCFGR_PLLM_POS;

	/* Max and min values are from TRM */
	ASSERT(n > 7 && n < 87);
	val &= ~STM32_RCC_PLLCFGR_PLLN_MSK;
	val |= n << STM32_RCC_PLLCFGR_PLLN_POS;

	val &= ~STM32_RCC_PLLCFGR_PLLR_MSK;
	switch (r) {
	case 2:
		val |= 0 << STM32_RCC_PLLCFGR_PLLR_POS;
		break;
	case 4:
		val |= 1 << STM32_RCC_PLLCFGR_PLLR_POS;
		break;
	case 6:
		val |= 2 << STM32_RCC_PLLCFGR_PLLR_POS;
		break;
	case 8:
		val |= 3 << STM32_RCC_PLLCFGR_PLLR_POS;
		break;
	default:
		return -1;
	}

	STM32_RCC_PLLCFGR = val;

	/* 4 */
	clock_enable_osc(OSC_PLL);

	/* 5 */
	val = STM32_RCC_PLLCFGR;
	val |= 1 << STM32_RCC_PLLCFGR_PLLREN_POS;
	STM32_RCC_PLLCFGR = val;

	/* (f * n) shouldn't overflow based on their max values */
	return (f * n / m / r);
}

/**
 * Set system clock oscillator
 *
 * @param osc		Oscillator to use
 * @param pll_osc	Source oscillator for PLL. Ignored if osc is not PLL.
 */
static void clock_set_osc(enum clock_osc osc, enum clock_osc pll_osc)
{
	uint32_t val;

	if (osc == current_osc)
		return;

	if (current_osc != OSC_INIT)
		hook_notify(HOOK_PRE_FREQ_CHANGE);

	switch (osc) {
	case OSC_HSI:
		/* Ensure that HSI is ON */
		clock_enable_osc(osc);

		/* Set HSI as system clock after exiting stop mode */
		STM32_RCC_CFGR |= STM32_RCC_CFGR_STOPWUCK;

		/* Switch to HSI */
		clock_switch_osc(osc);

		/* Disable MSI */
		STM32_RCC_CR &= ~STM32_RCC_CR_MSION;

		freq = STM32_HSI_CLOCK;
		break;

	case OSC_MSI:
		/* Switch to MSI @ 1MHz */
		STM32_RCC_CR =
			(STM32_RCC_CR & ~STM32_RCC_ICSCR_MSIRANGE_MASK) |
			STM32_RCC_ICSCR_MSIRANGE_1MHZ;
		/* Ensure that MSI is ON */
		clock_enable_osc(osc);

		/*
		 * Set MSI as system clock after exiting stop mode
		 */
		STM32_RCC_CFGR &= ~STM32_RCC_CFGR_STOPWUCK;

		/* Switch to MSI */
		clock_switch_osc(osc);

		/* Disable HSI */
		STM32_RCC_CR &= ~STM32_RCC_CR_HSION;

		freq = STM32_MSI_CLOCK;
		break;

#ifdef STM32_HSE_CLOCK
	case OSC_HSE:
		/* Ensure that HSE is stable */
		clock_enable_osc(osc);

		/* Switch to HSE */
		clock_switch_osc(osc);

		/* Disable other clock sources */
		STM32_RCC_CR &= ~(STM32_RCC_CR_MSION | STM32_RCC_CR_HSION |
				STM32_RCC_CR_PLLON);

		freq = STM32_HSE_CLOCK;

		break;
#endif
	case OSC_PLL:
		/* Ensure that source clock is stable */
		if (pll_osc == OSC_INIT) {
			if ((STM32_RCC_CFGR & STM32_RCC_CFGR_SWS_MSK) !=
			    STM32_RCC_CFGR_SWS_PLL) {
				STM32_RCC_CFGR |= STM32_RCC_CFGR_STOPWUCK;
				clock_enable_osc(OSC_HSI);
				freq = stm32_configure_pll(OSC_HSI, STM32_PLLM,
							   STM32_PLLN,
							   STM32_PLLR);
			} else {
				/* already set PLL, skip */
				freq = STM32_HSI_CLOCK * STM32_PLLN /
				       STM32_PLLM / STM32_PLLR;
				break;
			}
		} else {
			clock_enable_osc(pll_osc);
			/* Configure PLLCFGR */
			freq = stm32_configure_pll(pll_osc, STM32_PLLM,
						   STM32_PLLN, STM32_PLLR);
		}
		ASSERT(freq > 0);

		/* Change to Range 1 if Freq > 26MHz */
		if (freq > 26000000U) {
			/* Set VCO range 1 */
			val = STM32_RCC_CR;
			val &= ~PWR_CR1_VOS_MSK;
			val |= PWR_CR1_VOS_0;
			STM32_RCC_CR = val;

			/*
			 * Set Flash latency according to frequency
			 */
			val = STM32_FLASH_ACR;
			val &= ~STM32_FLASH_ACR_LATENCY_MASK;
			if (freq <= 16000000U)	{
				val = val;
			} else if (freq <= 32000000U)	{
				val |= 1;
			} else if (freq <= 48000000U)	{
				val |= 2;
			} else if (freq <= 64000000U)	{
				val |= 3;
			} else if (freq <= 80000000U)	{
				val |= 4;
			} else	{
				val |= 4;
				CPUTS("Incorrect Frequency setting in VOS1!\n");
			}
			STM32_FLASH_ACR = val;
		} else	{
			val = STM32_FLASH_ACR;
			val &= ~STM32_FLASH_ACR_LATENCY_MASK;

			if (freq <= 6000000U) {
				val = val;
			} else if (freq <= 12000000U) {
				val |= 1;
			} else if (freq <= 18000000U) {
				val |= 2;
			} else if (freq <= 26000000U) {
				val |= 3;
			} else	{
				val |= 4;
				CPUTS("Incorrect Frequency setting in VOS2!\n");
			}
			STM32_FLASH_ACR = val;
		}

		while (val != STM32_FLASH_ACR)
			;

		/* Switch to PLL */
		clock_switch_osc(osc);

		/* TODO: Disable other sources */
		break;
	default:
		break;
	}

	/* Notify modules of frequency change unless we're initializing */
	if (current_osc != OSC_INIT) {
		current_osc = osc;
		hook_notify(HOOK_FREQ_CHANGE);
	} else {
		current_osc = osc;
	}
}

void clock_enable_module(enum module_id module, int enable)
{
	static uint32_t clock_mask;
	int new_mask;

	if (enable)
		new_mask = clock_mask | BIT(module);
	else
		new_mask = clock_mask & ~BIT(module);

	/* Only change clock if needed */
	if (new_mask != clock_mask) {
		if (module == MODULE_ADC) {
			STM32_RCC_APB2ENR |= STM32_RCC_PB2_SYSCFGEN;
			STM32_RCC_APB1ENR1 |= STM32_RCC_PB1_PWREN;

			/* ADC select bit 28/29 */
			STM32_RCC_CCIPR &= ~STM32_RCC_CCIPR_ADCSEL_MSK;
			STM32_RCC_CCIPR |= (STM32_RCC_CCIPR_ADCSEL_0 |
					    STM32_RCC_CCIPR_ADCSEL_1);
			/* ADC clock enable */
			if (enable)
				STM32_RCC_AHB2ENR |= STM32_RCC_HB2_ADC1;
			else
				STM32_RCC_AHB2ENR &= ~STM32_RCC_HB2_ADC1;
		} else if (module == MODULE_SPI_FLASH) {
			if (enable)
				STM32_RCC_APB1ENR1 |= STM32_RCC_PB1_SPI2;
			else
				STM32_RCC_APB1ENR1 &= ~STM32_RCC_PB1_SPI2;
		} else if (module == MODULE_SPI ||
			   module == MODULE_SPI_CONTROLLER) {
			if (enable)
				STM32_RCC_APB2ENR |= STM32_RCC_APB2ENR_SPI1EN;
			else if ((new_mask & (BIT(MODULE_SPI) |
					      BIT(MODULE_SPI_CONTROLLER))) == 0)
				STM32_RCC_APB2ENR &= ~STM32_RCC_APB2ENR_SPI1EN;
		}
	}

	clock_mask = new_mask;
}

void rtc_init(void)
{
	/* Enable RTC Alarm in EXTI */
	STM32_EXTI_RTSR |= EXTI_RTC_ALR_EVENT;
	task_enable_irq(STM32_IRQ_RTC_ALARM);

	/* RTC was initilized, avoid initialization again */
	if (STM32_RTC_ISR & STM32_RTC_ISR_INITS)
		return;

	rtc_unlock_regs();

	/* Enter RTC initialize mode */
	STM32_RTC_ISR |= STM32_RTC_ISR_INIT;
	while (!(STM32_RTC_ISR & STM32_RTC_ISR_INITF))
		;

	/* Set clock prescalars */
	STM32_RTC_PRER = (RTC_PREDIV_A << 16) | RTC_PREDIV_S;

	/* Start RTC timer */
	STM32_RTC_ISR &= ~STM32_RTC_ISR_INIT;
	while (STM32_RTC_ISR & STM32_RTC_ISR_INITF)
		;

	/* Enable RTC alarm interrupt */
	STM32_RTC_CR |= STM32_RTC_CR_ALRAIE | STM32_RTC_CR_BYPSHAD;

	rtc_lock_regs();
}

#if defined(CONFIG_CMD_RTC) || defined(CONFIG_HOSTCMD_RTC)
void rtc_set(uint32_t sec)
{
	struct rtc_time_reg rtc;

	sec_to_rtc(sec, &rtc);
	rtc_unlock_regs();

	/* Disable alarm */
	STM32_RTC_CR &= ~STM32_RTC_CR_ALRAE;

	/* Enter RTC initialize mode */
	STM32_RTC_ISR |= STM32_RTC_ISR_INIT;
	while (!(STM32_RTC_ISR & STM32_RTC_ISR_INITF))
		;

	/* Set clock prescalars */
	STM32_RTC_PRER = (RTC_PREDIV_A << 16) | RTC_PREDIV_S;

	STM32_RTC_TR = rtc.rtc_tr;
	STM32_RTC_DR = rtc.rtc_dr;
	/* Start RTC timer */
	STM32_RTC_ISR &= ~STM32_RTC_ISR_INIT;

	rtc_lock_regs();
}
#endif


void clock_init(void)
{
#ifdef STM32_HSE_CLOCK
	clock_set_osc(OSC_PLL, OSC_HSE);
#else
#ifdef STM32_USE_PLL
	clock_set_osc(OSC_PLL, OSC_INIT);
#else
	clock_set_osc(OSC_HSI, OSC_INIT);
#endif
#endif

#ifdef CONFIG_LOW_POWER_IDLE
	low_power_init();
	rtc_init();
#endif
}

static void clock_chipset_startup(void)
{
	/* Return to full speed */
	clock_enable_module(MODULE_CHIPSET, 1);
}
DECLARE_HOOK(HOOK_CHIPSET_STARTUP, clock_chipset_startup, HOOK_PRIO_DEFAULT);
DECLARE_HOOK(HOOK_CHIPSET_RESUME, clock_chipset_startup, HOOK_PRIO_DEFAULT);

static void clock_chipset_shutdown(void)
{
	/* Drop to lower clock speed if no other module requires full speed */
	clock_enable_module(MODULE_CHIPSET, 0);
}
DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, clock_chipset_shutdown, HOOK_PRIO_DEFAULT);
DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, clock_chipset_shutdown, HOOK_PRIO_DEFAULT);

static int command_clock(int argc, char **argv)
{
	if (argc >= 2) {
		if (!strcasecmp(argv[1], "hsi"))
			clock_set_osc(OSC_HSI, OSC_INIT);
		else if (!strcasecmp(argv[1], "msi"))
			clock_set_osc(OSC_MSI, OSC_INIT);
#ifdef STM32_HSE_CLOCK
		else if (!strcasecmp(argv[1], "hse"))
			clock_set_osc(OSC_HSE, OSC_INIT);
		else if (!strcasecmp(argv[1], "pll"))
			clock_set_osc(OSC_PLL, OSC_HSE);
#else
		else if (!strcasecmp(argv[1], "pll"))
			clock_set_osc(OSC_PLL, OSC_HSI);
#endif
		else
			return EC_ERROR_PARAM1;
	}

	ccprintf("Clock frequency is now %d Hz\n", freq);
	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(clock, command_clock,
			"hsi | msi"
#ifdef STM32_HSE_CLOCK
			" | hse | pll"
#endif
			,
			"Set clock frequency");

uint32_t rtcss_to_us(uint32_t rtcss)
{
	return ((RTC_PREDIV_S - (rtcss & 0x7FFF)) * (SECOND / SCALING) /
		(RTC_FREQ / SCALING));
}

uint32_t us_to_rtcss(uint32_t us)
{
	return (RTC_PREDIV_S -
		(us * (RTC_FREQ / SCALING) / (SECOND / SCALING)));
}


/* Convert decimal to BCD */
static uint8_t u8_to_bcd(uint8_t val)
{
	/* Fast division by 10 (when lacking HW div) */
	uint32_t quot = ((uint32_t)val * 0xCCCD) >> 19;
	uint32_t rem = val - quot * 10;

	return rem | (quot << 4);
}

/* Convert between RTC regs in BCD and seconds */
static uint32_t rtc_tr_to_sec(uint32_t rtc_tr)
{
	uint32_t sec;

	/* convert the hours field */
	sec = (((rtc_tr & RTC_TR_HT) >> RTC_TR_HT_POS) * 10 +
	       ((rtc_tr & RTC_TR_HU) >> RTC_TR_HU_POS)) * 3600;
	/* convert the minutes field */
	sec += (((rtc_tr & RTC_TR_MNT) >> RTC_TR_MNT_POS) * 10 +
		((rtc_tr & RTC_TR_MNU) >> RTC_TR_MNU_POS)) * 60;
	/* convert the seconds field */
	sec += ((rtc_tr & RTC_TR_ST) >> RTC_TR_ST_POS) *  10 +
	       (rtc_tr & RTC_TR_SU);
	return sec;
}

static uint32_t sec_to_rtc_tr(uint32_t sec)
{
	uint32_t rtc_tr;
	uint8_t hour;
	uint8_t min;

	sec %= SECS_PER_DAY;
	/* convert the hours field */
	hour = sec / 3600;
	rtc_tr = u8_to_bcd(hour) << 16;
	/* convert the minutes field */
	sec -= hour * 3600;
	min = sec / 60;
	rtc_tr |= u8_to_bcd(min) << 8;
	/* convert the seconds field */
	sec -= min * 60;
	rtc_tr |= u8_to_bcd(sec);

	return rtc_tr;
}

/* Register setup before RTC alarm is allowed for update */
static void pre_work_set_rtc_alarm(void)
{
	rtc_unlock_regs();

	/* Make sure alarm is disabled */
	STM32_RTC_CR &= ~STM32_RTC_CR_ALRAE;
	while (!(STM32_RTC_ISR & STM32_RTC_ISR_ALRAWF))
		;
	STM32_RTC_ISR &= ~STM32_RTC_ISR_ALRAF;
	STM32_EXTI_PR = BIT(18);
}

/* Register setup after RTC alarm is updated */
static void post_work_set_rtc_alarm(void)
{
	/* Enable alarm and alarm interrupt */
	STM32_EXTI_IMR |= BIT(18);
	STM32_EXTI_RTSR |= BIT(18);
	STM32_RTC_CR |= (STM32_RTC_CR_ALRAE);

	rtc_lock_regs();
}

#ifdef CONFIG_HOSTCMD_RTC
static struct wake_time host_wake_time;

bool is_host_wake_alarm_expired(timestamp_t ts)
{
	return host_wake_time.ts.val &&
	       timestamp_expired(host_wake_time.ts, &ts);
}

void restore_host_wake_alarm(void)
{
	if (!host_wake_time.ts.val)
		return;

	pre_work_set_rtc_alarm();

	/* Set alarm time */
	STM32_RTC_ALRMAR = host_wake_time.rtc_alrmar;

	post_work_set_rtc_alarm();
}

static uint32_t rtc_dr_to_sec(uint32_t rtc_dr)
{
	struct calendar_date time;
	uint32_t sec;

	time.year = (((rtc_dr & 0xf00000) >> 20) * 10 +
		    ((rtc_dr & 0xf0000) >> 16));
	time.month = (((rtc_dr & 0x1000) >> 12) * 10 +
		     ((rtc_dr & 0xf00) >> 8));
	time.day = ((rtc_dr & 0x30) >> 4) * 10 + (rtc_dr & 0xf);

	sec = date_to_sec(time);

	return sec;
}

static uint32_t sec_to_rtc_dr(uint32_t sec)
{
	struct calendar_date time;
	uint32_t rtc_dr;

	time = sec_to_date(sec);

	rtc_dr = u8_to_bcd(time.year) << 16;
	rtc_dr |= u8_to_bcd(time.month) << 8;
	rtc_dr |= u8_to_bcd(time.day);

	return rtc_dr;
}
#endif

uint32_t rtc_to_sec(const struct rtc_time_reg *rtc)
{
	uint32_t sec = 0;

#ifdef CONFIG_HOSTCMD_RTC
	sec = rtc_dr_to_sec(rtc->rtc_dr);
#endif
	return sec + (rtcss_to_us(rtc->rtc_ssr) / SECOND) +
	       rtc_tr_to_sec(rtc->rtc_tr);
}

void sec_to_rtc(uint32_t sec, struct rtc_time_reg *rtc)
{
	rtc->rtc_dr = 0;
#ifdef CONFIG_HOSTCMD_RTC
	rtc->rtc_dr = sec_to_rtc_dr(sec);
#endif
	rtc->rtc_tr = sec_to_rtc_tr(sec);
	rtc->rtc_ssr = 0;
}

/* Return sub-10-sec time diff between two rtc readings
 *
 * Note: this function assumes rtc0 was sampled before rtc1.
 * Additionally, this function only looks at the difference mod 10
 * seconds.
 */
uint32_t get_rtc_diff(const struct rtc_time_reg *rtc0,
		      const struct rtc_time_reg *rtc1)
{
	uint32_t rtc0_val, rtc1_val, diff;

	rtc0_val = (rtc0->rtc_tr & RTC_TR_SU) * SECOND +
		   rtcss_to_us(rtc0->rtc_ssr);
	rtc1_val = (rtc1->rtc_tr & RTC_TR_SU) * SECOND +
		   rtcss_to_us(rtc1->rtc_ssr);
	diff = rtc1_val;
	if (rtc1_val < rtc0_val) {
		/* rtc_ssr has wrapped, since we assume rtc0 < rtc1, add
		 * 10 seconds to get the correct value
		 */
		diff += 10 * SECOND;
	}
	diff -= rtc0_val;
	return diff;
}

void rtc_read(struct rtc_time_reg *rtc)
{
	/*
	 * Read current time synchronously. Each register must be read
	 * twice with identical values because glitches may occur for reads
	 * close to the RTCCLK edge.
	 */
	do {
		rtc->rtc_dr = STM32_RTC_DR;

		do {
			rtc->rtc_tr = STM32_RTC_TR;

			do {
				rtc->rtc_ssr = STM32_RTC_SSR;
			} while (rtc->rtc_ssr != STM32_RTC_SSR);

		} while (rtc->rtc_tr != STM32_RTC_TR);

	} while (rtc->rtc_dr != STM32_RTC_DR);
}

void set_rtc_alarm(uint32_t delay_s, uint32_t delay_us,
		   struct rtc_time_reg *rtc, uint8_t save_alarm)
{
	uint32_t alarm_sec = 0;
	uint32_t alarm_us = 0;

	if (delay_s == EC_RTC_ALARM_CLEAR && !delay_us) {
		reset_rtc_alarm(rtc);
		return;
	}

	/* Alarm timeout must be within 1 day (86400 seconds) */
	ASSERT((delay_s + delay_us / SECOND) < SECS_PER_DAY);

	pre_work_set_rtc_alarm();
	rtc_read(rtc);

	/* Calculate alarm time */
	alarm_sec = rtc_tr_to_sec(rtc->rtc_tr) + delay_s;

	if (delay_us) {
		alarm_us = rtcss_to_us(rtc->rtc_ssr) + delay_us;
		alarm_sec = alarm_sec + alarm_us / SECOND;
		alarm_us = alarm_us % SECOND;
	}

	/*
	 * If seconds is greater than 1 day, subtract by 1 day to deal with
	 * 24-hour rollover.
	 */
	if (alarm_sec >= SECS_PER_DAY)
		alarm_sec -= SECS_PER_DAY;

	/*
	 * Set alarm time in seconds and check for match on
	 * hours, minutes, and seconds.
	 */
	STM32_RTC_ALRMAR = sec_to_rtc_tr(alarm_sec) | 0xc0000000;

	/*
	 * Set alarm time in subseconds and check for match on subseconds.
	 * If the caller doesn't specify subsecond delay (e.g. host command),
	 * just align the alarm time to second.
	 */
	STM32_RTC_ALRMASSR = delay_us ?
			     (us_to_rtcss(alarm_us) | 0x0f000000) : 0;

#ifdef CONFIG_HOSTCMD_RTC
	/*
	 * If alarm is set by the host, preserve the wake time timestamp
	 * and alarm registers.
	 */
	if (save_alarm) {
		host_wake_time.ts.val = delay_s * SECOND + get_time().val;
		host_wake_time.rtc_alrmar = STM32_RTC_ALRMAR;
	}
#endif
	post_work_set_rtc_alarm();
}

uint32_t get_rtc_alarm(void)
{
	struct rtc_time_reg now;
	uint32_t now_sec;
	uint32_t alarm_sec;

	if (!(STM32_RTC_CR & STM32_RTC_CR_ALRAE))
		return 0;

	rtc_read(&now);

	now_sec = rtc_tr_to_sec(now.rtc_tr);
	alarm_sec = rtc_tr_to_sec(STM32_RTC_ALRMAR & 0x3fffff);

	return ((alarm_sec < now_sec) ? SECS_PER_DAY : 0) +
	       (alarm_sec - now_sec);
}

void reset_rtc_alarm(struct rtc_time_reg *rtc)
{
	rtc_unlock_regs();

	/* Disable alarm */
	STM32_RTC_CR &= ~STM32_RTC_CR_ALRAE;
	STM32_RTC_ISR &= ~STM32_RTC_ISR_ALRAF;

	/* Disable RTC alarm interrupt */
	STM32_EXTI_IMR &= ~BIT(18);
	STM32_EXTI_PR = BIT(18);

	/* Clear the pending RTC alarm IRQ in NVIC */
	task_clear_pending_irq(STM32_IRQ_RTC_ALARM);

	/* Read current time */
	rtc_read(rtc);

	rtc_lock_regs();
}

#ifdef CONFIG_HOSTCMD_RTC
static void set_rtc_host_event(void)
{
	host_set_single_event(EC_HOST_EVENT_RTC);
}
DECLARE_DEFERRED(set_rtc_host_event);
#endif

test_mockable_static
void __rtc_alarm_irq(void)
{
	struct rtc_time_reg rtc;

	reset_rtc_alarm(&rtc);

#ifdef CONFIG_HOSTCMD_RTC
	/* Wake up the host if there is a saved rtc wake alarm. */
	if (host_wake_time.ts.val) {
		host_wake_time.ts.val = 0;
		hook_call_deferred(&set_rtc_host_event_data, 0);
	}
#endif
}
DECLARE_IRQ(STM32_IRQ_RTC_ALARM, __rtc_alarm_irq, 1);


void print_system_rtc(enum console_channel ch)
{
	uint32_t sec;
	struct rtc_time_reg rtc;

	rtc_read(&rtc);
	sec = rtc_to_sec(&rtc);

	cprintf(ch, "RTC: 0x%08x (%d.00 s)\n", sec, sec);
}


#ifdef CONFIG_LOW_POWER_IDLE
/* Low power idle statistics */
static int idle_sleep_cnt;
static int idle_dsleep_cnt;
static uint64_t idle_dsleep_time_us;
static int dsleep_recovery_margin_us = 1000000;

/* STOP_MODE_LATENCY: delay to wake up from STOP mode with main regulator off */
#define STOP_MODE_LATENCY 50 /* us */
/* PLL_LOCK_LATENCY: delay to switch from HSI to PLL */
#define PLL_LOCK_LATENCY 150 /* us */
/*
 * SET_RTC_MATCH_DELAY: max time to set RTC match alarm. If we set the alarm
 * in the past, it will never wake up and cause a watchdog.
 */
#define SET_RTC_MATCH_DELAY 120 /* us */


void low_power_init(void)
{
	/* Enter stop1 mode */
	uint32_t val;

	val = STM32_PWR_CR1;
	val &= ~PWR_CR1_LPMS_MSK;
	val |= PWR_CR1_LPMS_STOP1;
	STM32_PWR_CR1 = val;
}

void clock_refresh_console_in_use(void)
{
}

void __idle(void)
{
	timestamp_t t0;
	uint32_t rtc_diff;
	int next_delay, margin_us;
	struct rtc_time_reg rtc0, rtc1;

	while (1) {
		asm volatile("cpsid i");

		t0 = get_time();
		next_delay = __hw_clock_event_get() - t0.le.lo;

		if (DEEP_SLEEP_ALLOWED &&
		    (next_delay > (STOP_MODE_LATENCY + PLL_LOCK_LATENCY +
				   SET_RTC_MATCH_DELAY))) {
			/* Deep-sleep in STOP mode */
			idle_dsleep_cnt++;

			uart_enable_wakeup(1);

			/* Set deep sleep bit */
			CPU_SCB_SYSCTRL |= 0x4;

			set_rtc_alarm(0, next_delay - STOP_MODE_LATENCY
						    - PLL_LOCK_LATENCY,
				      &rtc0, 0);


			/* ensure outstanding memory transactions complete */
			asm volatile("dsb");

			asm("wfi");

			CPU_SCB_SYSCTRL &= ~0x4;

			/* turn on PLL and wait until it's ready */
			STM32_RCC_APB1ENR1 |= STM32_RCC_APB1ENR1_PWREN;
			clock_wait_bus_cycles(BUS_APB, 2);

			stm32_configure_pll(OSC_HSI, STM32_PLLM,
						   STM32_PLLN, STM32_PLLR);

			/* Switch to PLL */
			clock_switch_osc(OSC_PLL);

			uart_enable_wakeup(0);

			/* Fast forward timer according to RTC counter */
			reset_rtc_alarm(&rtc1);
			rtc_diff = get_rtc_diff(&rtc0, &rtc1);
			t0.val = t0.val + rtc_diff;
			force_time(t0);

			/* Record time spent in deep sleep. */
			idle_dsleep_time_us += rtc_diff;

			/* Calculate how close we were to missing deadline */
			margin_us = next_delay - rtc_diff;
			if (margin_us < 0)
				/* Use CPUTS to save stack space */
				CPUTS("Idle overslept!\n");

			/* Record the closest to missing a deadline. */
			if (margin_us < dsleep_recovery_margin_us)
				dsleep_recovery_margin_us = margin_us;
		} else {
			idle_sleep_cnt++;

			/* Normal idle : only CPU clock stopped */
			asm("wfi");
		}
		asm volatile("cpsie i");
	}
}

/*****************************************************************************/
/* Console commands */
/* Print low power idle statistics. */
static int command_idle_stats(int argc, char **argv)
{
	timestamp_t ts = get_time();

	ccprintf("Num idle calls that sleep:           %d\n", idle_sleep_cnt);
	ccprintf("Num idle calls that deep-sleep:      %d\n", idle_dsleep_cnt);
	ccprintf("Time spent in deep-sleep:            %.6llus\n",
			idle_dsleep_time_us);
	ccprintf("Total time on:                       %.6llus\n", ts.val);
	ccprintf("Deep-sleep closest to wake deadline: %dus\n",
			dsleep_recovery_margin_us);

	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(idlestats, command_idle_stats,
			"",
			"Print last idle stats");
#endif /* CONFIG_LOW_POWER_IDLE */
