/* 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-f.h"
#include "common.h"
#include "console.h"
#include "cpu.h"
#include "hooks.h"
#include "hwtimer.h"
#include "registers.h"
#include "system.h"
#include "task.h"
#include "timer.h"
#include "util.h"

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

enum clock_osc {
	OSC_HSI = 0,	/* High-speed internal oscillator */
	OSC_HSE,	/* High-speed external oscillator */
	OSC_PLL,	/* PLL */
};

/*
 * NOTE: Sweetberry requires MCO2 <- HSE @ 24MHz
 * MCO outputs are selected here but are not changeable later.
 * A CONFIG may be needed if other boards have different MCO
 * requirements.
 */
#define RCC_CFGR_MCO_CONFIG ((2 << 30) | /* MCO2 <- HSE  */ \
			     (0 << 27) | /* MCO2 div / 4 */ \
			     (6 << 24) | /* MCO1 div / 4 */ \
			     (3 << 21))  /* MCO1 <- PLL  */

#ifdef CONFIG_STM32_CLOCK_HSE_HZ
/* RTC clock must 1 Mhz when derived from HSE */
#define RTC_DIV DIV_ROUND_NEAREST(CONFIG_STM32_CLOCK_HSE_HZ, STM32F4_RTC_REQ)
#else /* !CONFIG_STM32_CLOCK_HSE_HZ */
/* RTC clock not derived from HSE, turn it off */
#define RTC_DIV 0
#endif /* CONFIG_STM32_CLOCK_HSE_HZ */


/* Bus clocks dividers depending on the configuration */
/*
 * max speed configuration with the PLL ON
 * as defined in the registers file.
 * For STM32F446: max 45 MHz
 * For STM32F412: max AHB 100 MHz / APB2 100 Mhz / APB1 50 Mhz
 */
#define RCC_CFGR_DIVIDERS_WITH_PLL (RCC_CFGR_MCO_CONFIG  | \
				    CFGR_RTCPRE(RTC_DIV) | \
				    CFGR_PPRE2(STM32F4_APB2_PRE) | \
				    CFGR_PPRE1(STM32F4_APB1_PRE) | \
				    CFGR_HPRE(STM32F4_AHB_PRE))
/*
 * lower power configuration without the PLL
 * the frequency will be low (8-24Mhz), we don't want dividers to the
 * peripheral clocks, put /1 everywhere.
 */
#define RCC_CFGR_DIVIDERS_NO_PLL (RCC_CFGR_MCO_CONFIG | CFGR_RTCPRE(0) | \
				  CFGR_PPRE2(0) | CFGR_PPRE1(0) | CFGR_HPRE(0))

/* PLL output frequency */
#define STM32F4_PLL_CLOCK (STM32F4_VCO_CLOCK / STM32F4_PLLP_DIV)

/* current clock settings (PLL is initialized at startup) */
static int current_osc = OSC_PLL;
static int current_io_freq = STM32F4_IO_CLOCK;
static int current_timer_freq = STM32F4_TIMER_CLOCK;

/* the EC code expects to get the USART/I2C clock frequency here (APB clock) */
int clock_get_freq(void)
{
	return current_io_freq;
}

int clock_get_timer_freq(void)
{
	return current_timer_freq;
}

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

	switch (osc) {
	case OSC_HSI:
		ready = STM32_RCC_CR_HSIRDY;
		on = STM32_RCC_CR_HSION;
		break;
	case OSC_HSE:
		ready = STM32_RCC_CR_HSERDY;
		on = STM32_RCC_CR_HSEON;
		break;
	case OSC_PLL:
		ready = STM32_RCC_CR_PLLRDY;
		on = STM32_RCC_CR_PLLON;
		break;
	default:
		ASSERT(0);
		return;
	}

	/* Turn off the oscillator, but don't wait for shutdown */
	if (!enabled) {
		STM32_RCC_CR &= ~on;
		return;
	}

	/* Turn on the oscillator if not already on */
	wait_for_ready(&STM32_RCC_CR, on, ready);
}

static void clock_switch_osc(enum clock_osc osc)
{
	uint32_t sw;
	uint32_t sws;

	switch (osc) {
	case OSC_HSI:
		sw = STM32_RCC_CFGR_SW_HSI | RCC_CFGR_DIVIDERS_NO_PLL;
		sws = STM32_RCC_CFGR_SWS_HSI;
		break;
	case OSC_HSE:
		sw = STM32_RCC_CFGR_SW_HSE | RCC_CFGR_DIVIDERS_NO_PLL;
		sws = STM32_RCC_CFGR_SWS_HSE;
		break;
	case OSC_PLL:
		sw = STM32_RCC_CFGR_SW_PLL | RCC_CFGR_DIVIDERS_WITH_PLL;
		sws = STM32_RCC_CFGR_SWS_PLL;
		break;
	default:
		return;
	}

	STM32_RCC_CFGR = sw;
	while ((STM32_RCC_CFGR & STM32_RCC_CFGR_SWS_MASK) != sws)
		;
}

void clock_set_osc(enum clock_osc osc)
{
	volatile uint32_t unused __attribute__((unused));

	if (osc == current_osc)
		return;

	hook_notify(HOOK_PRE_FREQ_CHANGE);

	switch (osc) {
	default:
	case OSC_HSI:
		/* new clock settings: no dividers */
		current_io_freq = STM32F4_HSI_CLOCK;
		current_timer_freq = STM32F4_HSI_CLOCK;
		/* Switch to HSI */
		clock_switch_osc(OSC_HSI);
		/* optimized flash latency settings for <30Mhz clock (0-WS) */
		STM32_FLASH_ACR = (STM32_FLASH_ACR & ~STM32_FLASH_ACR_LAT_MASK)
				| STM32_FLASH_ACR_LATENCY_SLOW;
		/* read-back the latency as advised by the Reference Manual */
		unused = STM32_FLASH_ACR;
		/* Turn off the PLL1 to save power */
		clock_enable_osc(OSC_PLL, false);
		break;

#ifdef CONFIG_STM32_CLOCK_HSE_HZ
	case OSC_HSE:
		/* new clock settings: no dividers */
		current_io_freq = CONFIG_STM32_CLOCK_HSE_HZ;
		current_timer_freq = CONFIG_STM32_CLOCK_HSE_HZ;
		/* Switch to HSE */
		clock_switch_osc(OSC_HSE);
		/* optimized flash latency settings for <30Mhz clock (0-WS) */
		STM32_FLASH_ACR = (STM32_FLASH_ACR & ~STM32_FLASH_ACR_LAT_MASK)
				| STM32_FLASH_ACR_LATENCY_SLOW;
		/* read-back the latency as advised by the Reference Manual */
		unused = STM32_FLASH_ACR;
		/* Turn off the PLL1 to save power */
		clock_enable_osc(OSC_PLL, false);
		break;
#endif /* CONFIG_STM32_CLOCK_HSE_HZ */

	case OSC_PLL:
		/* new clock settings */
		current_io_freq = STM32F4_IO_CLOCK;
		current_timer_freq = STM32F4_TIMER_CLOCK;
		/* turn on PLL and wait until it's ready */
		clock_enable_osc(OSC_PLL, true);
		/*
		 * Increase flash latency before transition the clock
		 * Use the minimum Wait States value optimized for the platform.
		 */
		STM32_FLASH_ACR = (STM32_FLASH_ACR & ~STM32_FLASH_ACR_LAT_MASK)
				| STM32_FLASH_ACR_LATENCY;
		/* read-back the latency as advised by the Reference Manual */
		unused = STM32_FLASH_ACR;
		/* Switch to PLL */
		clock_switch_osc(OSC_PLL);

		break;
	}

	current_osc = osc;
	hook_notify(HOOK_FREQ_CHANGE);
}

static void clock_pll_configure(void)
{
#ifdef CONFIG_STM32_CLOCK_HSE_HZ
	int srcclock = CONFIG_STM32_CLOCK_HSE_HZ;
#else
	int srcclock = STM32F4_HSI_CLOCK;
#endif
	int plldiv, pllinputclock;
	int pllmult, vcoclock;
	int systemclock;
	int usbdiv;
	int i2sdiv;

	/* PLL input must be between 1-2MHz, near 2 */
	/* Valid values 2-63 */
	plldiv = (srcclock + STM32F4_PLL_REQ - 1) / STM32F4_PLL_REQ;
	pllinputclock = srcclock / plldiv;

	/* PLL output clock: Must be 100-432MHz */
	pllmult = (STM32F4_VCO_CLOCK + (pllinputclock / 2)) / pllinputclock;
	vcoclock = pllinputclock * pllmult;

	/* CPU/System clock */
	systemclock = vcoclock / STM32F4_PLLP_DIV;
	/* USB clock = 48MHz exactly */
	usbdiv = (vcoclock + (STM32F4_USB_REQ / 2)) / STM32F4_USB_REQ;
	assert(vcoclock / usbdiv == STM32F4_USB_REQ);

	/* SYSTEM/I2S: same system clock */
	i2sdiv = (vcoclock + (systemclock / 2)) / systemclock;

	/* Set up PLL */
	STM32_RCC_PLLCFGR =
		PLLCFGR_PLLM(plldiv) |
		PLLCFGR_PLLN(pllmult) |
		PLLCFGR_PLLP(STM32F4_PLLP_DIV / 2 - 1) |
#if defined(CONFIG_STM32_CLOCK_HSE_HZ)
		PLLCFGR_PLLSRC_HSE |
#else
		PLLCFGR_PLLSRC_HSI |
#endif
		PLLCFGR_PLLQ(usbdiv) |
		PLLCFGR_PLLR(i2sdiv);
}

void low_power_init(void);

void config_hispeed_clock(void)
{
#ifdef CONFIG_STM32_CLOCK_HSE_HZ
	/* Ensure that HSE is ON */
	clock_enable_osc(OSC_HSE, true);
#endif

	/* Put the PLL settings, they are never changing */
	clock_pll_configure();
	clock_enable_osc(OSC_PLL, true);

	/* Switch SYSCLK to PLL, setup bus prescalers. */
	clock_switch_osc(OSC_PLL);

#ifdef CONFIG_LOW_POWER_IDLE
	low_power_init();
#endif
}

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_DMA_GET_ISR(0);
	} else { /* APB */
		while (cycles--)
			unused = STM32_USART_BRR(STM32_USART1_BASE);
	}
}

void clock_enable_module(enum module_id module, int enable)
{
	if (module == MODULE_USB) {
		if (enable) {
			STM32_RCC_AHB2ENR |= STM32_RCC_AHB2ENR_OTGFSEN;
			STM32_RCC_AHB1ENR |= STM32_RCC_AHB1ENR_OTGHSEN |
				STM32_RCC_AHB1ENR_OTGHSULPIEN;
		} else {
			STM32_RCC_AHB2ENR &= ~STM32_RCC_AHB2ENR_OTGFSEN;
			STM32_RCC_AHB1ENR &= ~STM32_RCC_AHB1ENR_OTGHSEN &
				     ~STM32_RCC_AHB1ENR_OTGHSULPIEN;
		}
		return;
	} else if (module == MODULE_I2C) {
		if (enable) {
			/* Enable clocks to I2C modules if necessary */
			STM32_RCC_APB1ENR |=
				STM32_RCC_I2C1EN | STM32_RCC_I2C2EN
				| STM32_RCC_I2C3EN | STM32_RCC_FMPI2C4EN;
			STM32_RCC_DCKCFGR2 =
				(STM32_RCC_DCKCFGR2 & ~DCKCFGR2_FMPI2C1SEL_MASK)
				| DCKCFGR2_FMPI2C1SEL(FMPI2C1SEL_APB);
		} else {
			STM32_RCC_APB1ENR &=
				~(STM32_RCC_I2C1EN | STM32_RCC_I2C2EN |
				  STM32_RCC_I2C3EN | STM32_RCC_FMPI2C4EN);
		}
		return;
	} else if (module == MODULE_ADC) {
		if (enable)
			STM32_RCC_APB2ENR |= STM32_RCC_APB2ENR_ADC1EN;
		else
			STM32_RCC_APB2ENR &= ~STM32_RCC_APB2ENR_ADC1EN;
		return;
	}
}

/* Real Time Clock (RTC) */

#ifdef CONFIG_STM32_CLOCK_HSE_HZ
#define RTC_PREDIV_A 39
#define RTC_FREQ ((STM32F4_RTC_REQ) / (RTC_PREDIV_A + 1)) /* Hz */
#else /* from LSI clock */
#define RTC_PREDIV_A 1
#define RTC_FREQ (STM32F4_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

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

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

void rtc_init(void)
{
	/* Setup RTC Clock input */
#ifdef CONFIG_STM32_CLOCK_HSE_HZ
	/* RTC clocked from the HSE */
	STM32_RCC_BDCR = STM32_RCC_BDCR_RTCEN | BDCR_RTCSEL(BDCR_SRC_HSE);
#else
	/* RTC clocked from the LSI, ensure first it is ON */
	wait_for_ready(&(STM32_RCC_CSR),
		STM32_RCC_CSR_LSION, STM32_RCC_CSR_LSIRDY);

	STM32_RCC_BDCR = STM32_RCC_BDCR_RTCEN | BDCR_RTCSEL(BDCR_SRC_LSI);
#endif

	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: Needs two separate writes. */
	STM32_RTC_PRER =
		(STM32_RTC_PRER & ~STM32_RTC_PRER_S_MASK) | RTC_PREDIV_S;
	STM32_RTC_PRER =
		(STM32_RTC_PRER & ~STM32_RTC_PRER_A_MASK)
		| (RTC_PREDIV_A << 16);

	/* 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;
	STM32_EXTI_RTSR |= EXTI_RTC_ALR_EVENT;
	task_enable_irq(STM32_IRQ_RTC_ALARM);

	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

#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)
{
	/* Turn off the main regulator during stop mode */
	STM32_PWR_CR |= STM32_PWR_CR_LPSDSR /* aka LPDS */;
}

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++;

			/*
			 * TODO(b/174337385) no support for wake-up on USART
			 * 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);

			/* Switch to HSI */
			clock_switch_osc(OSC_HSI);
			/* Turn off the PLL1 to save power */
			clock_enable_osc(OSC_PLL, false);

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

			asm("wfi");

			CPU_SCB_SYSCTRL &= ~0x4;

			/* turn on PLL and wait until it's ready */
			clock_enable_osc(OSC_PLL, true);
			/* 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");
	}
}

/* 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:            %.6llds\n",
			idle_dsleep_time_us);
	ccprintf("Total time on:                       %.6llds\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 */
