/* 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 "host_command.h"
#include "hwtimer.h"
#include "registers.h"
#include "rtc.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)

/* 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 & 0x300000) >> 20) * 10 +
	       ((rtc_tr & 0xf0000) >> 16)) * 3600;
	/* convert the minutes field */
	sec += (((rtc_tr & 0x7000) >> 12) * 10 + ((rtc_tr & 0xf00) >> 8)) * 60;
	/* convert the seconds field */
	sec += ((rtc_tr & 0x70) >> 4) * 10 + (rtc_tr & 0xf);
	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;
}

/* Register setup after RTC alarm is updated */
static void post_work_set_rtc_alarm(void)
{
	STM32_EXTI_PR = EXTI_RTC_ALR_EVENT;

	/* Enable alarm and alarm interrupt */
	STM32_EXTI_IMR |= EXTI_RTC_ALR_EVENT;
	STM32_RTC_CR |= STM32_RTC_CR_ALRAE;

	rtc_lock_regs();
}

#ifdef CONFIG_HOSTCMD_RTC
static struct wake_time host_wake_time;

int 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 & 0xF) * SECOND + rtcss_to_us(rtc0->rtc_ssr);
	rtc1_val = (rtc1->rtc_tr & 0xF) * 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 &= ~EXTI_RTC_ALR_EVENT;
	STM32_EXTI_PR = EXTI_RTC_ALR_EVENT;

	/* 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();
}

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;
		host_set_single_event(EC_HOST_EVENT_RTC);
	}
#endif
}
DECLARE_IRQ(STM32_IRQ_RTC_ALARM, __rtc_alarm_irq, 1);

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

void clock_init(void)
{
	/*
	 * The initial state :
	 *  SYSCLK from HSI (=8MHz), no divider on AHB, APB1, APB2
	 *  PLL unlocked, RTC enabled on LSE
	 */

	/*
	 * put 1 Wait-State for flash access to ensure proper reads at 48Mhz
	 * and enable prefetch buffer.
	 */
	STM32_FLASH_ACR = STM32_FLASH_ACR_LATENCY | STM32_FLASH_ACR_PRFTEN;

#ifdef CHIP_FAMILY_STM32F4
	/* Enable data and instruction cache. */
	STM32_FLASH_ACR |= STM32_FLASH_ACR_DCEN | STM32_FLASH_ACR_ICEN;
#endif

	config_hispeed_clock();

	rtc_init();
}

#ifdef CHIP_FAMILY_STM32F4
void reset_flash_cache(void)
{
	/* Disable data and instruction cache. */
	STM32_FLASH_ACR &= ~(STM32_FLASH_ACR_DCEN | STM32_FLASH_ACR_ICEN);

	/* Reset data and instruction cache */
	STM32_FLASH_ACR |= STM32_FLASH_ACR_DCRST | STM32_FLASH_ACR_ICRST;
}
DECLARE_HOOK(HOOK_SYSJUMP, reset_flash_cache, HOOK_PRIO_DEFAULT);
#endif

/*****************************************************************************/
/* Console commands */

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_CMD_RTC
static int command_system_rtc(int argc, char **argv)
{
	char *e;
	uint32_t t;

	if (argc == 3 && !strcasecmp(argv[1], "set")) {
		t = strtoi(argv[2], &e, 0);
		if (*e)
			return EC_ERROR_PARAM2;
		rtc_set(t);
	} else if (argc > 1)
		return EC_ERROR_INVAL;

	print_system_rtc(CC_COMMAND);

	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(rtc, command_system_rtc,
		"[set <seconds>]",
		"Get/set real-time clock");

#ifdef CONFIG_CMD_RTC_ALARM
static int command_rtc_alarm_test(int argc, char **argv)
{
	int s = 1, us = 0;
	struct rtc_time_reg rtc;
	char *e;

	ccprintf("Setting RTC alarm\n");

	if (argc > 1) {
		s = strtoi(argv[1], &e, 10);
		if (*e)
			return EC_ERROR_PARAM1;

	}
	if (argc > 2) {
		us = strtoi(argv[2], &e, 10);
		if (*e)
			return EC_ERROR_PARAM2;
	}

	set_rtc_alarm(s, us, &rtc, 0);
	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(rtc_alarm, command_rtc_alarm_test,
			"[seconds [microseconds]]",
			"Test alarm");
#endif /* CONFIG_CMD_RTC_ALARM */
#endif /* CONFIG_CMD_RTC */

/*****************************************************************************/
/* Host commands */

#ifdef CONFIG_HOSTCMD_RTC
static enum ec_status system_rtc_get_value(struct host_cmd_handler_args *args)
{
	struct ec_response_rtc *r = args->response;
	struct rtc_time_reg rtc;

	rtc_read(&rtc);
	r->time = rtc_to_sec(&rtc);
	args->response_size = sizeof(*r);

	return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_RTC_GET_VALUE,
		system_rtc_get_value,
		EC_VER_MASK(0));

static enum ec_status system_rtc_set_value(struct host_cmd_handler_args *args)
{
	const struct ec_params_rtc *p = args->params;

	rtc_set(p->time);
	return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_RTC_SET_VALUE,
		system_rtc_set_value,
		EC_VER_MASK(0));

static enum ec_status system_rtc_set_alarm(struct host_cmd_handler_args *args)
{
	struct rtc_time_reg rtc;
	const struct ec_params_rtc *p = args->params;

	/* Alarm timeout must be within 1 day (86400 seconds) */
	if (p->time >= SECS_PER_DAY)
		return EC_RES_INVALID_PARAM;

	set_rtc_alarm(p->time, 0, &rtc, 1);
	return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_RTC_SET_ALARM,
		system_rtc_set_alarm,
		EC_VER_MASK(0));

static enum ec_status system_rtc_get_alarm(struct host_cmd_handler_args *args)
{
	struct ec_response_rtc *r = args->response;

	r->time = get_rtc_alarm();
	args->response_size = sizeof(*r);

	return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_RTC_GET_ALARM,
		system_rtc_get_alarm,
		EC_VER_MASK(0));

#endif /* CONFIG_HOSTCMD_RTC */
