Boot Linux faster!

Check our new training course

Boot Linux faster!

Check our new training course
and Creative Commons CC-BY-SA
lecture and lab materials

Bootlin logo

Elixir Cross Referencer

/*
 * Copyright (c) 2019 Microchip Technology Inc.
 * Copyright (c) 2016 Intel Corporation.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr.h>
#include <sys/sys_io.h>
#include <sys/__assert.h>
#include <power/power.h>
#include <soc.h>

/*
 * CPU will spin up to DEEP_SLEEP_WAIT_SPIN_CLK_REQ times
 * waiting for PCR CLK_REQ bits to clear except for the
 * CPU bit itself. This is not necessary as the sleep hardware
 * will wait for all CLK_REQ to clear once WFI has executed.
 * Once all CLK_REQ signals are clear the hardware will transition
 * to the low power state.
 */
/* #define DEEP_SLEEP_WAIT_ON_CLK_REQ_ENABLE */
#define DEEP_SLEEP_WAIT_SPIN_CLK_REQ		1000


/*
 * Some peripherals if enabled always assert their CLK_REQ bits.
 * For example, any peripheral with a clock generator such as
 * timers, counters, UART, etc. We save the enables for these
 * peripherals, disable them, and restore the enabled state upon
 * wake.
 */
#define DEEP_SLEEP_PERIPH_SAVE_RESTORE


/*
 * Light sleep: PLL remains on. Fastest wake latency.
 */
void soc_lite_sleep_enable(void)
{
	SCB->SCR &= ~(1ul << 2);
	PCR_REGS->SYS_SLP_CTRL = MCHP_PCR_SYS_SLP_LIGHT;
}

/*
 * Deep sleep: PLL is turned off. Wake is fast. PLL requires
 * a minimum of 3ms to lock. During this time the main clock
 * will be ramping up from ~16 to 24 MHz.
 */

#if defined(CONFIG_SYS_POWER_DEEP_SLEEP_STATES)

void soc_deep_sleep_enable(void)
{
	SCB->SCR = (1ul << 2); /* Cortex-M4 SLEEPDEEP */
	PCR_REGS->SYS_SLP_CTRL = MCHP_PCR_SYS_SLP_HEAVY;
}

void soc_deep_sleep_disable(void)
{
	SCB->SCR &= ~(1ul << 2); /* disable Cortex-M4 SLEEPDEEP */
}


void soc_deep_sleep_wait_clk_idle(void)
{
#ifdef DEEP_SLEEP_WAIT_ON_CLK_REQ_ENABLE
	u32_t clkreq, cnt;

	cnt = DEEP_SLEEP_WAIT_CLK_REQ;
	do {
		clkreq = PCR_REGS->CLK_REQ0 | PCR_REGS->CLK_REQ1
			 | PCR_REGS->CLK_REQ2 | PCR_REGS->CLK_REQ3
			 | PCR_REGS->CLK_REQ4;
	} while ((clkreq != (1ul << MCHP_PCR1_CPU_POS)) && (cnt-- != 0));
#endif
}


/*
 * Allow peripherals connected to external masters to wake the PLL but not
 * the EC. Once the peripheral has serviced the external master the PLL
 * will be turned back off. For example, if the eSPI master requests eSPI
 * configuration information or state of virtual wires the EC doesn't need
 * to be involved. The hardware can power on the PLL long enough to service
 * the request and then turn the PLL back off.  The SMBus and I2C peripherals
 * in slave mode can also make use of this feature.
 */
void soc_deep_sleep_non_wake_en(void)
{
#ifdef CONFIG_ESPI_XEC
	GIRQ22_REGS->SRC = 0xfffffffful;
	GIRQ22_REGS->EN_SET = (1ul << 9);
#endif
}

void soc_deep_sleep_non_wake_dis(void)
{
#ifdef CONFIG_ESPI_XEC
	GIRQ22_REGS->EN_CLR = 0xfffffffful;
	GIRQ22_REGS->SRC = 0xfffffffful;
#endif
}

/* Variables used to save various HW state */
#ifdef DEEP_SLEEP_PERIPH_SAVE_RESTORE

static u32_t ecs[1];

static void deep_sleep_save_ecs(void)
{
	ecs[0] = ECS_REGS->ETM_CTRL;
	ECS_REGS->ETM_CTRL = 0;
}

struct ds_timer_info {
	uintptr_t addr;
	u32_t restore_mask;
};

const struct ds_timer_info ds_timer_tbl[] = {
	{
		(uintptr_t)&B16TMR0_REGS->CTRL, 0
	},
	{
		(uintptr_t)&B16TMR1_REGS->CTRL, 0
	},
	{
		(uintptr_t)&B32TMR0_REGS->CTRL, 0
	},
	{
		(uintptr_t)&B32TMR1_REGS->CTRL, 0
	},
	{
		(uintptr_t)&CCT_REGS->CTRL,
		(MCHP_CCT_CTRL_COMP1_SET | MCHP_CCT_CTRL_COMP0_SET),
	},
};
#define NUM_DS_TIMER_ENTRIES \
	(sizeof(ds_timer_tbl) / sizeof(struct ds_timer_info))


static u32_t timers[NUM_DS_TIMER_ENTRIES];
static u8_t uart_activate[3];

static void deep_sleep_save_uarts(void)
{
	uart_activate[0] = UART0_REGS->ACTV;
	if (uart_activate[0]) {
		while ((UART0_REGS->LSR & MCHP_UART_LSR_TEMT) == 0) {
		};
	}
	UART0_REGS->ACTV = 0;
	uart_activate[1] = UART1_REGS->ACTV;
	if (uart_activate[1]) {
		while ((UART1_REGS->LSR & MCHP_UART_LSR_TEMT) == 0) {
		};
	}
	UART1_REGS->ACTV = 0;
	uart_activate[2] = UART2_REGS->ACTV;
	if (uart_activate[2]) {
		while ((UART2_REGS->LSR & MCHP_UART_LSR_TEMT) == 0) {
		};
	}
	UART2_REGS->ACTV = 0;
}

static void deep_sleep_save_timers(void)
{
	const struct ds_timer_info *p;
	u32_t i;

	p = &ds_timer_tbl[0];
	for (i = 0; i < NUM_DS_TIMER_ENTRIES; i++) {
		timers[i] = REG32(p->addr);
		REG32(p->addr) = 0;
		p++;
	}
}

static void deep_sleep_restore_ecs(void)
{
	ECS_REGS->ETM_CTRL = ecs[0];
}

static void deep_sleep_restore_uarts(void)
{
	UART0_REGS->ACTV = uart_activate[0];
	UART1_REGS->ACTV = uart_activate[1];
	UART2_REGS->ACTV = uart_activate[2];
}

static void deep_sleep_restore_timers(void)
{
	const struct ds_timer_info *p;
	u32_t i;

	p = &ds_timer_tbl[0];
	for (i = 0; i < NUM_DS_TIMER_ENTRIES; i++) {
		REG32(p->addr) = timers[i] & ~p->restore_mask;
		p++;
	}
}

void soc_deep_sleep_periph_save(void)
{
	deep_sleep_save_uarts();
	deep_sleep_save_ecs();
	deep_sleep_save_timers();
}

void soc_deep_sleep_periph_restore(void)
{
	deep_sleep_restore_ecs();
	deep_sleep_restore_uarts();
	deep_sleep_restore_timers();
}

#else

void soc_deep_sleep_periph_save(void)
{
}

void soc_deep_sleep_periph_restore(void)
{
}

#endif /* DEEP_SLEEP_PERIPH_SAVE_RESTORE */

#endif /* CONFIG_SYS_POWER_DEEP_SLEEP_STATES */