740 lines
18 KiB
C
740 lines
18 KiB
C
|
/* Copyright 2019 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.
|
||
|
*/
|
||
|
|
||
|
#include "aontaskfw/ish_aon_share.h"
|
||
|
#include "console.h"
|
||
|
#include "hwtimer.h"
|
||
|
#include "interrupts.h"
|
||
|
#include "ish_dma.h"
|
||
|
#include "ish_persistent_data.h"
|
||
|
#include "power_mgt.h"
|
||
|
#include "system.h"
|
||
|
#include "task.h"
|
||
|
#include "util.h"
|
||
|
#include "watchdog.h"
|
||
|
|
||
|
#define CPUTS(outstr) cputs(CC_SYSTEM, outstr)
|
||
|
#define CPRINTS(format, args...) cprints(CC_SYSTEM, format, ##args)
|
||
|
#define CPRINTF(format, args...) cprintf(CC_SYSTEM, format, ##args)
|
||
|
|
||
|
/* defined in link script: core/minute-ia/ec.lds.S */
|
||
|
extern uint32_t __aon_ro_start;
|
||
|
extern uint32_t __aon_ro_end;
|
||
|
extern uint32_t __aon_rw_start;
|
||
|
extern uint32_t __aon_rw_end;
|
||
|
|
||
|
/**
|
||
|
* on ISH, uart interrupt can only wakeup ISH from low power state via
|
||
|
* CTS pin, but most ISH platforms only have Rx and Tx pins, no CTS pin
|
||
|
* exposed, so, we need block ISH enter low power state for a while when
|
||
|
* console is in use.
|
||
|
* fixed amount of time to keep the console in use flag true after boot in
|
||
|
* order to give a permanent window in which the low speed clock is not used.
|
||
|
*/
|
||
|
#define CONSOLE_IN_USE_ON_BOOT_TIME (15*SECOND)
|
||
|
|
||
|
/* power management internal context data structure */
|
||
|
struct pm_context {
|
||
|
/* aontask image valid flag */
|
||
|
int aon_valid;
|
||
|
/* point to the aon shared data in aontask */
|
||
|
struct ish_aon_share *aon_share;
|
||
|
/* TSS segment selector for task switching */
|
||
|
int aon_tss_selector[2];
|
||
|
/* console expire time */
|
||
|
timestamp_t console_expire_time;
|
||
|
/* console in use timeout */
|
||
|
int console_in_use_timeout_sec;
|
||
|
} __packed;
|
||
|
|
||
|
static struct pm_context pm_ctx = {
|
||
|
.aon_valid = 0,
|
||
|
/* aon shared data located in the start of aon memory */
|
||
|
.aon_share = (struct ish_aon_share *)CONFIG_AON_RAM_BASE,
|
||
|
.console_in_use_timeout_sec = 60
|
||
|
};
|
||
|
|
||
|
/* D0ix statistics data, including each state's count and total stay time */
|
||
|
struct pm_stat {
|
||
|
uint64_t count;
|
||
|
uint64_t total_time_us;
|
||
|
};
|
||
|
|
||
|
struct pm_statistics {
|
||
|
struct pm_stat d0i0;
|
||
|
struct pm_stat d0i1;
|
||
|
struct pm_stat d0i2;
|
||
|
struct pm_stat d0i3;
|
||
|
};
|
||
|
|
||
|
static struct pm_statistics pm_stats;
|
||
|
|
||
|
/*
|
||
|
* Log a new statistic
|
||
|
*
|
||
|
* t0: start time, in us
|
||
|
* t1: end time, in us
|
||
|
*/
|
||
|
static void log_pm_stat(struct pm_stat *stat, uint32_t t0, uint32_t t1)
|
||
|
{
|
||
|
stat->total_time_us += t1 - t0;
|
||
|
stat->count++;
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_ISH_PM_AONTASK
|
||
|
|
||
|
/* The GDT which initialized in init.S */
|
||
|
extern struct gdt_entry __gdt[];
|
||
|
extern struct gdt_header __gdt_ptr[];
|
||
|
|
||
|
/* TSS desccriptor for saving main FW's cpu context during aontask switching */
|
||
|
static struct tss_entry main_tss;
|
||
|
|
||
|
/**
|
||
|
* add new entry in GDT
|
||
|
* if defined 'CONFIG_ISH_PM_AONTASK', the GDT which defined in init.S will
|
||
|
* have 3 more empty placeholder entries, this function is help to update
|
||
|
* these entries which needed by x86's HW task switching method
|
||
|
*
|
||
|
* @param desc_lo lower DWORD of the entry descriptor
|
||
|
* @param desc_up upper DWORD of the entry descriptor
|
||
|
*
|
||
|
* @return the descriptor selector index of the added entry
|
||
|
*/
|
||
|
static uint32_t add_gdt_entry(uint32_t desc_lo, uint32_t desc_up)
|
||
|
{
|
||
|
int index;
|
||
|
|
||
|
/**
|
||
|
* get the first empty entry of GDT which defined in init.S
|
||
|
* each entry has a fixed size of 8 bytes
|
||
|
*/
|
||
|
index = __gdt_ptr[0].limit >> 3;
|
||
|
|
||
|
/* add the new entry descriptor to the GDT */
|
||
|
__gdt[index].dword_lo = desc_lo;
|
||
|
__gdt[index].dword_up = desc_up;
|
||
|
|
||
|
/* update GDT's limit size */
|
||
|
__gdt_ptr[0].limit += sizeof(struct gdt_entry);
|
||
|
|
||
|
return __gdt_ptr[0].limit - sizeof(struct gdt_entry);
|
||
|
}
|
||
|
|
||
|
static void init_aon_task(void)
|
||
|
{
|
||
|
uint32_t desc_lo, desc_up;
|
||
|
struct ish_aon_share *aon_share = pm_ctx.aon_share;
|
||
|
struct tss_entry *aon_tss = aon_share->aon_tss;
|
||
|
|
||
|
if (aon_share->magic_id != AON_MAGIC_ID) {
|
||
|
pm_ctx.aon_valid = 0;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
pm_ctx.aon_valid = 1;
|
||
|
|
||
|
pm_ctx.aon_tss_selector[0] = 0;
|
||
|
|
||
|
/* fill in the 3 placeholder GDT entries */
|
||
|
|
||
|
/* TSS's limit specified as 0x67, to allow the task has permission to
|
||
|
* access I/O port using IN/OUT instructions,'iomap_base_addr' field
|
||
|
* must be greater than or equal to TSS' limit
|
||
|
* see 'I/O port permissions' on
|
||
|
* https://en.wikipedia.org/wiki/Task_state_segment
|
||
|
*/
|
||
|
main_tss.iomap_base_addr = GDT_DESC_TSS_LIMIT;
|
||
|
|
||
|
/* set GDT entry 3 for TSS descriptor of main FW
|
||
|
* limit: 0x67
|
||
|
* Present = 1, DPL = 0
|
||
|
*/
|
||
|
desc_lo = GEN_GDT_DESC_LO((uint32_t)&main_tss,
|
||
|
GDT_DESC_TSS_LIMIT, GDT_DESC_TSS_FLAGS);
|
||
|
desc_up = GEN_GDT_DESC_UP((uint32_t)&main_tss,
|
||
|
GDT_DESC_TSS_LIMIT, GDT_DESC_TSS_FLAGS);
|
||
|
add_gdt_entry(desc_lo, desc_up);
|
||
|
|
||
|
/* set GDT entry 4 for TSS descriptor of aontask
|
||
|
* limit: 0x67
|
||
|
* Present = 1, DPL = 0, Accessed = 1
|
||
|
*/
|
||
|
desc_lo = GEN_GDT_DESC_LO((uint32_t)aon_tss,
|
||
|
GDT_DESC_TSS_LIMIT, GDT_DESC_TSS_FLAGS);
|
||
|
desc_up = GEN_GDT_DESC_UP((uint32_t)aon_tss,
|
||
|
GDT_DESC_TSS_LIMIT, GDT_DESC_TSS_FLAGS);
|
||
|
pm_ctx.aon_tss_selector[1] = add_gdt_entry(desc_lo, desc_up);
|
||
|
|
||
|
/* set GDT entry 5 for LDT descriptor of aontask
|
||
|
* Present = 1, DPL = 0, Readable = 1
|
||
|
*/
|
||
|
desc_lo = GEN_GDT_DESC_LO((uint32_t)aon_share->aon_ldt,
|
||
|
aon_share->aon_ldt_size, GDT_DESC_LDT_FLAGS);
|
||
|
desc_up = GEN_GDT_DESC_UP((uint32_t)aon_share->aon_ldt,
|
||
|
aon_share->aon_ldt_size, GDT_DESC_LDT_FLAGS);
|
||
|
aon_tss->ldt_seg_selector = add_gdt_entry(desc_lo, desc_up);
|
||
|
|
||
|
/* update GDT register and set current TSS as main_tss (GDT entry 3) */
|
||
|
__asm__ volatile("lgdt __gdt_ptr;\n"
|
||
|
"push %eax;\n"
|
||
|
"movw $0x18, %ax;\n"
|
||
|
"ltr %ax;\n"
|
||
|
"pop %eax;");
|
||
|
|
||
|
aon_share->main_fw_ro_addr = (uint32_t)&__aon_ro_start;
|
||
|
aon_share->main_fw_ro_size = (uint32_t)&__aon_ro_end -
|
||
|
(uint32_t)&__aon_ro_start;
|
||
|
|
||
|
aon_share->main_fw_rw_addr = (uint32_t)&__aon_rw_start;
|
||
|
aon_share->main_fw_rw_size = (uint32_t)&__aon_rw_end -
|
||
|
(uint32_t)&__aon_rw_start;
|
||
|
|
||
|
ish_dma_init();
|
||
|
}
|
||
|
|
||
|
static inline void check_aon_task_status(void)
|
||
|
{
|
||
|
struct ish_aon_share *aon_share = pm_ctx.aon_share;
|
||
|
|
||
|
if (aon_share->last_error != AON_SUCCESS) {
|
||
|
CPRINTF("aontask has errors:\n");
|
||
|
CPRINTF(" last error: %d\n", aon_share->last_error);
|
||
|
CPRINTF(" error counts: %d\n", aon_share->error_count);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void switch_to_aontask(void)
|
||
|
{
|
||
|
interrupt_disable();
|
||
|
|
||
|
__sync_synchronize();
|
||
|
|
||
|
/* disable cache and flush cache */
|
||
|
__asm__ volatile("movl %%cr0, %%eax;\n"
|
||
|
"orl $0x60000000, %%eax;\n"
|
||
|
"movl %%eax, %%cr0;\n"
|
||
|
"wbinvd;"
|
||
|
:
|
||
|
:
|
||
|
: "eax");
|
||
|
|
||
|
/* switch to aontask through a far call with aontask's TSS selector */
|
||
|
__asm__ volatile("lcall *%0;" ::"m"(*pm_ctx.aon_tss_selector) :);
|
||
|
|
||
|
/* clear TS (Task Switched) flag and enable cache */
|
||
|
__asm__ volatile("clts;\n"
|
||
|
"movl %%cr0, %%eax;\n"
|
||
|
"andl $0x9FFFFFFF, %%eax;\n"
|
||
|
"movl %%eax, %%cr0;"
|
||
|
:
|
||
|
:
|
||
|
: "eax");
|
||
|
|
||
|
interrupt_enable();
|
||
|
}
|
||
|
|
||
|
__attribute__ ((noreturn))
|
||
|
static void handle_reset_in_aontask(enum ish_pm_state pm_state)
|
||
|
{
|
||
|
pm_ctx.aon_share->pm_state = pm_state;
|
||
|
|
||
|
/* only enable PMU wakeup interrupt */
|
||
|
disable_all_interrupts();
|
||
|
task_enable_irq(ISH_PMU_WAKEUP_IRQ);
|
||
|
|
||
|
if (IS_ENABLED(CONFIG_ISH_PM_RESET_PREP))
|
||
|
task_enable_irq(ISH_RESET_PREP_IRQ);
|
||
|
|
||
|
/* enable Trunk Clock Gating (TCG) of ISH */
|
||
|
CCU_TCG_EN = 1;
|
||
|
|
||
|
/* enable power gating of RF(Cache) and ROMs */
|
||
|
PMU_RF_ROM_PWR_CTRL = 1;
|
||
|
|
||
|
switch_to_aontask();
|
||
|
|
||
|
__builtin_unreachable();
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
static void enter_d0i0(void)
|
||
|
{
|
||
|
uint32_t t0, t1;
|
||
|
|
||
|
t0 = __hw_clock_source_read();
|
||
|
pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0I0;
|
||
|
|
||
|
/* halt ISH cpu, will wakeup from any interrupt */
|
||
|
ish_mia_halt();
|
||
|
|
||
|
t1 = __hw_clock_source_read();
|
||
|
pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0;
|
||
|
log_pm_stat(&pm_stats.d0i0, t0, t1);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ISH PMU does not support both-edge interrupt triggered gpio configuration.
|
||
|
* If both edges are configured, then the ISH can't stay in low poer mode
|
||
|
* because it will exit immediately.
|
||
|
*
|
||
|
* As a workaround, we scan all gpio pins which have been configured as
|
||
|
* both-edge triggered, and then temporarily set each gpio pin to the single
|
||
|
* edge trigger that is opposite of its value, then restore the both-edge
|
||
|
* trigger configuration immediately after exiting low power mode.
|
||
|
*/
|
||
|
static uint32_t convert_both_edge_gpio_to_single_edge(void)
|
||
|
{
|
||
|
uint32_t both_edge_pins = 0;
|
||
|
int i = 0;
|
||
|
|
||
|
/**
|
||
|
* scan GPIO GFER, GRER and GIMR registers to find the both edge
|
||
|
* interrupt trigger mode enabled pins.
|
||
|
*/
|
||
|
for (i = 0; i < 32; i++) {
|
||
|
if (ISH_GPIO_GIMR & BIT(i) &&
|
||
|
ISH_GPIO_GRER & BIT(i) &&
|
||
|
ISH_GPIO_GFER & BIT(i)) {
|
||
|
|
||
|
/* Record the pin so we can restore it later */
|
||
|
both_edge_pins |= BIT(i);
|
||
|
|
||
|
if (ISH_GPIO_GPLR & BIT(i)) {
|
||
|
/* pin is high, just keep falling edge mode */
|
||
|
ISH_GPIO_GRER &= ~BIT(i);
|
||
|
} else {
|
||
|
/* pin is low, just keep rising edge mode */
|
||
|
ISH_GPIO_GFER &= ~BIT(i);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return both_edge_pins;
|
||
|
}
|
||
|
|
||
|
static void restore_both_edge_gpio_config(uint32_t both_edge_pin_map)
|
||
|
{
|
||
|
ISH_GPIO_GRER |= both_edge_pin_map;
|
||
|
ISH_GPIO_GFER |= both_edge_pin_map;
|
||
|
}
|
||
|
|
||
|
static void enter_d0i1(void)
|
||
|
{
|
||
|
uint64_t current_irq_map;
|
||
|
uint32_t both_edge_gpio_pins;
|
||
|
uint32_t t0, t1;
|
||
|
|
||
|
/* only enable PMU wakeup interrupt */
|
||
|
current_irq_map = disable_all_interrupts();
|
||
|
task_enable_irq(ISH_PMU_WAKEUP_IRQ);
|
||
|
|
||
|
if (IS_ENABLED(CONFIG_ISH_PM_RESET_PREP))
|
||
|
task_enable_irq(ISH_RESET_PREP_IRQ);
|
||
|
|
||
|
t0 = __hw_clock_source_read();
|
||
|
pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0I1;
|
||
|
|
||
|
both_edge_gpio_pins = convert_both_edge_gpio_to_single_edge();
|
||
|
|
||
|
/* enable Trunk Clock Gating (TCG) of ISH */
|
||
|
CCU_TCG_EN = 1;
|
||
|
|
||
|
/* halt ISH cpu, will wakeup from PMU wakeup interrupt */
|
||
|
ish_mia_halt();
|
||
|
|
||
|
/* disable Trunk Clock Gating (TCG) of ISH */
|
||
|
CCU_TCG_EN = 0;
|
||
|
|
||
|
restore_both_edge_gpio_config(both_edge_gpio_pins);
|
||
|
|
||
|
pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0;
|
||
|
t1 = __hw_clock_source_read();
|
||
|
log_pm_stat(&pm_stats.d0i1, t0, t1);
|
||
|
|
||
|
/* Reload watchdog before enabling interrupts again */
|
||
|
watchdog_reload();
|
||
|
|
||
|
/* restore interrupts */
|
||
|
task_disable_irq(ISH_PMU_WAKEUP_IRQ);
|
||
|
restore_interrupts(current_irq_map);
|
||
|
}
|
||
|
|
||
|
static void enter_d0i2(void)
|
||
|
{
|
||
|
uint64_t current_irq_map;
|
||
|
uint32_t both_edge_gpio_pins;
|
||
|
uint32_t t0, t1;
|
||
|
|
||
|
/* only enable PMU wakeup interrupt */
|
||
|
current_irq_map = disable_all_interrupts();
|
||
|
task_enable_irq(ISH_PMU_WAKEUP_IRQ);
|
||
|
|
||
|
if (IS_ENABLED(CONFIG_ISH_PM_RESET_PREP))
|
||
|
task_enable_irq(ISH_RESET_PREP_IRQ);
|
||
|
|
||
|
t0 = __hw_clock_source_read();
|
||
|
pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0I2;
|
||
|
|
||
|
both_edge_gpio_pins = convert_both_edge_gpio_to_single_edge();
|
||
|
|
||
|
/* enable Trunk Clock Gating (TCG) of ISH */
|
||
|
CCU_TCG_EN = 1;
|
||
|
|
||
|
/* enable power gating of RF(Cache) and ROMs */
|
||
|
PMU_RF_ROM_PWR_CTRL = 1;
|
||
|
|
||
|
switch_to_aontask();
|
||
|
|
||
|
/* returned from aontask */
|
||
|
|
||
|
/* disable power gating of RF(Cache) and ROMs */
|
||
|
PMU_RF_ROM_PWR_CTRL = 0;
|
||
|
|
||
|
/* disable Trunk Clock Gating (TCG) of ISH */
|
||
|
CCU_TCG_EN = 0;
|
||
|
|
||
|
restore_both_edge_gpio_config(both_edge_gpio_pins);
|
||
|
|
||
|
t1 = __hw_clock_source_read();
|
||
|
pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0;
|
||
|
log_pm_stat(&pm_stats.d0i2, t0, t1);
|
||
|
|
||
|
/* Reload watchdog before enabling interrupts again */
|
||
|
watchdog_reload();
|
||
|
|
||
|
/* restore interrupts */
|
||
|
task_disable_irq(ISH_PMU_WAKEUP_IRQ);
|
||
|
restore_interrupts(current_irq_map);
|
||
|
}
|
||
|
|
||
|
static void enter_d0i3(void)
|
||
|
{
|
||
|
uint64_t current_irq_map;
|
||
|
uint32_t both_edge_gpio_pins;
|
||
|
uint32_t t0, t1;
|
||
|
|
||
|
/* only enable PMU wakeup interrupt */
|
||
|
current_irq_map = disable_all_interrupts();
|
||
|
task_enable_irq(ISH_PMU_WAKEUP_IRQ);
|
||
|
|
||
|
if (IS_ENABLED(CONFIG_ISH_PM_RESET_PREP))
|
||
|
task_enable_irq(ISH_RESET_PREP_IRQ);
|
||
|
|
||
|
t0 = __hw_clock_source_read();
|
||
|
pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0I3;
|
||
|
|
||
|
both_edge_gpio_pins = convert_both_edge_gpio_to_single_edge();
|
||
|
|
||
|
/* enable Trunk Clock Gating (TCG) of ISH */
|
||
|
CCU_TCG_EN = 1;
|
||
|
|
||
|
/* enable power gating of RF(Cache) and ROMs */
|
||
|
PMU_RF_ROM_PWR_CTRL = 1;
|
||
|
|
||
|
switch_to_aontask();
|
||
|
|
||
|
/* returned from aontask */
|
||
|
|
||
|
/* disable power gating of RF(Cache) and ROMs */
|
||
|
PMU_RF_ROM_PWR_CTRL = 0;
|
||
|
|
||
|
/* disable Trunk Clock Gating (TCG) of ISH */
|
||
|
CCU_TCG_EN = 0;
|
||
|
|
||
|
restore_both_edge_gpio_config(both_edge_gpio_pins);
|
||
|
|
||
|
t1 = __hw_clock_source_read();
|
||
|
pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0;
|
||
|
log_pm_stat(&pm_stats.d0i3, t0, t1);
|
||
|
|
||
|
/* Reload watchdog before enabling interrupts again */
|
||
|
watchdog_reload();
|
||
|
|
||
|
/* restore interrupts */
|
||
|
task_disable_irq(ISH_PMU_WAKEUP_IRQ);
|
||
|
restore_interrupts(current_irq_map);
|
||
|
}
|
||
|
|
||
|
static int d0ix_decide(timestamp_t cur_time, uint32_t idle_us)
|
||
|
{
|
||
|
int pm_state = ISH_PM_STATE_D0I0;
|
||
|
|
||
|
if (DEEP_SLEEP_ALLOWED) {
|
||
|
|
||
|
/* check if the console use has expired. */
|
||
|
if (sleep_mask & SLEEP_MASK_CONSOLE) {
|
||
|
if (cur_time.val > pm_ctx.console_expire_time.val) {
|
||
|
enable_sleep(SLEEP_MASK_CONSOLE);
|
||
|
ccprints("Disabling console in deep sleep");
|
||
|
} else {
|
||
|
return pm_state;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (IS_ENABLED(CONFIG_ISH_PM_D0I3) &&
|
||
|
idle_us >= CONFIG_ISH_D0I3_MIN_USEC &&
|
||
|
pm_ctx.aon_valid)
|
||
|
pm_state = ISH_PM_STATE_D0I3;
|
||
|
|
||
|
else if (IS_ENABLED(CONFIG_ISH_PM_D0I2) &&
|
||
|
idle_us >= CONFIG_ISH_D0I2_MIN_USEC &&
|
||
|
pm_ctx.aon_valid)
|
||
|
pm_state = ISH_PM_STATE_D0I2;
|
||
|
|
||
|
else if (IS_ENABLED(CONFIG_ISH_PM_D0I1))
|
||
|
pm_state = ISH_PM_STATE_D0I1;
|
||
|
}
|
||
|
|
||
|
return pm_state;
|
||
|
}
|
||
|
|
||
|
static void pm_process(timestamp_t cur_time, uint32_t idle_us)
|
||
|
{
|
||
|
int decide;
|
||
|
|
||
|
decide = d0ix_decide(cur_time, idle_us);
|
||
|
|
||
|
switch (decide) {
|
||
|
case ISH_PM_STATE_D0I1:
|
||
|
enter_d0i1();
|
||
|
break;
|
||
|
case ISH_PM_STATE_D0I2:
|
||
|
enter_d0i2();
|
||
|
check_aon_task_status();
|
||
|
break;
|
||
|
case ISH_PM_STATE_D0I3:
|
||
|
enter_d0i3();
|
||
|
check_aon_task_status();
|
||
|
break;
|
||
|
default:
|
||
|
enter_d0i0();
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ish_pm_init(void)
|
||
|
{
|
||
|
/* clear reset bit */
|
||
|
ISH_RST_REG = 0;
|
||
|
|
||
|
/* clear reset history register in CCU */
|
||
|
CCU_RST_HST = CCU_RST_HST;
|
||
|
|
||
|
/* disable TCG and disable BCG */
|
||
|
CCU_TCG_EN = 0;
|
||
|
CCU_BCG_EN = 0;
|
||
|
|
||
|
if (IS_ENABLED(CONFIG_ISH_PM_AONTASK))
|
||
|
init_aon_task();
|
||
|
|
||
|
/* unmask all wake up events */
|
||
|
PMU_MASK_EVENT = ~PMU_MASK_EVENT_BIT_ALL;
|
||
|
|
||
|
if (IS_ENABLED(CONFIG_ISH_PM_RESET_PREP)) {
|
||
|
/* unmask reset prep avail interrupt */
|
||
|
PMU_RST_PREP = 0;
|
||
|
|
||
|
task_enable_irq(ISH_RESET_PREP_IRQ);
|
||
|
}
|
||
|
|
||
|
if (IS_ENABLED(CONFIG_ISH_PM_D3)) {
|
||
|
/* unmask D3 and BME interrupts */
|
||
|
PMU_D3_STATUS &= (PMU_D3_BIT_SET | PMU_BME_BIT_SET);
|
||
|
|
||
|
if ((!(PMU_D3_STATUS & PMU_D3_BIT_SET)) &&
|
||
|
(PMU_D3_STATUS & PMU_BME_BIT_SET))
|
||
|
PMU_D3_STATUS = PMU_D3_STATUS;
|
||
|
|
||
|
task_enable_irq(ISH_D3_RISE_IRQ);
|
||
|
task_enable_irq(ISH_D3_FALL_IRQ);
|
||
|
task_enable_irq(ISH_BME_RISE_IRQ);
|
||
|
task_enable_irq(ISH_BME_FALL_IRQ);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
__attribute__ ((noreturn))
|
||
|
void ish_pm_reset(enum ish_pm_state pm_state)
|
||
|
{
|
||
|
if (IS_ENABLED(CONFIG_ISH_PM_AONTASK) &&
|
||
|
pm_ctx.aon_valid) {
|
||
|
handle_reset_in_aontask(pm_state);
|
||
|
} else {
|
||
|
ish_mia_reset();
|
||
|
}
|
||
|
|
||
|
__builtin_unreachable();
|
||
|
}
|
||
|
|
||
|
void __idle(void)
|
||
|
{
|
||
|
timestamp_t t0;
|
||
|
int next_delay = 0;
|
||
|
|
||
|
/**
|
||
|
* initialize console in use to true and specify the console expire
|
||
|
* time in order to give a fixed window on boot
|
||
|
*/
|
||
|
disable_sleep(SLEEP_MASK_CONSOLE);
|
||
|
pm_ctx.console_expire_time.val = get_time().val +
|
||
|
CONSOLE_IN_USE_ON_BOOT_TIME;
|
||
|
|
||
|
while (1) {
|
||
|
t0 = get_time();
|
||
|
next_delay = __hw_clock_event_get() - t0.le.lo;
|
||
|
|
||
|
/*
|
||
|
* Most of the time, 'next_delay' will be positive. But, due to
|
||
|
* interrupt latency, it's possible that get_time() returns
|
||
|
* the value bigger than the one from __hw_clock_event_get()
|
||
|
* which is supposed to be updated by ISR before control reaches
|
||
|
* to the get_time().
|
||
|
*
|
||
|
* Here, we handle the delayed update by changing negative to 0.
|
||
|
*/
|
||
|
pm_process(t0, MAX(0, next_delay));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* helper for command_idle_stats
|
||
|
*/
|
||
|
static void print_stats(const char *name, const struct pm_stat *stat)
|
||
|
{
|
||
|
if (stat->count)
|
||
|
ccprintf(" %s:\n"
|
||
|
" counts: %lu\n"
|
||
|
" time: %.6lus\n",
|
||
|
name, stat->count, stat->total_time_us);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Print low power idle statistics
|
||
|
*/
|
||
|
static int command_idle_stats(int argc, char **argv)
|
||
|
{
|
||
|
struct ish_aon_share *aon_share = pm_ctx.aon_share;
|
||
|
|
||
|
ccprintf("Aontask exists: %s\n", pm_ctx.aon_valid ? "Yes" : "No");
|
||
|
ccprintf("Total time on: %.6lus\n", get_time().val);
|
||
|
ccprintf("Idle sleep:\n");
|
||
|
print_stats("D0i0", &pm_stats.d0i0);
|
||
|
|
||
|
ccprintf("Deep sleep:\n");
|
||
|
print_stats("D0i1", &pm_stats.d0i1);
|
||
|
print_stats("D0i2", &pm_stats.d0i2);
|
||
|
print_stats("D0i3", &pm_stats.d0i3);
|
||
|
|
||
|
if (pm_ctx.aon_valid) {
|
||
|
ccprintf(" Aontask status:\n");
|
||
|
ccprintf(" last error: %lu\n", aon_share->last_error);
|
||
|
ccprintf(" error counts: %lu\n", aon_share->error_count);
|
||
|
}
|
||
|
|
||
|
return EC_SUCCESS;
|
||
|
}
|
||
|
|
||
|
DECLARE_CONSOLE_COMMAND(idlestats, command_idle_stats, "",
|
||
|
"Print power management statistics");
|
||
|
|
||
|
|
||
|
/**
|
||
|
* main FW only need handle PMU wakeup interrupt for D0i1 state, aontask will
|
||
|
* handle PMU wakeup interrupt for other low power states
|
||
|
*/
|
||
|
__maybe_unused
|
||
|
static void pmu_wakeup_isr(void)
|
||
|
{
|
||
|
/* at current nothing need to do */
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_ISH_PM_D0I1
|
||
|
DECLARE_IRQ(ISH_PMU_WAKEUP_IRQ, pmu_wakeup_isr);
|
||
|
#endif
|
||
|
|
||
|
/**
|
||
|
* from ISH5.0, when system doing S0->Sx transition, will receive reset prep
|
||
|
* interrupt, will switch to aontask for handling
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
__maybe_unused __attribute__ ((noreturn))
|
||
|
static void reset_prep_isr(void)
|
||
|
{
|
||
|
/* mask reset prep avail interrupt */
|
||
|
PMU_RST_PREP = PMU_RST_PREP_INT_MASK;
|
||
|
|
||
|
/*
|
||
|
* Indicate completion of servicing the interrupt to IOAPIC first
|
||
|
* then indicate completion of servicing the interrupt to LAPIC
|
||
|
*/
|
||
|
IOAPIC_EOI_REG = ISH_RESET_PREP_VEC;
|
||
|
LAPIC_EOI_REG = 0x0;
|
||
|
|
||
|
system_reset(0);
|
||
|
__builtin_unreachable();
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_ISH_PM_RESET_PREP
|
||
|
DECLARE_IRQ(ISH_RESET_PREP_IRQ, reset_prep_isr);
|
||
|
#endif
|
||
|
|
||
|
__maybe_unused
|
||
|
static void handle_d3(uint32_t irq_vec)
|
||
|
{
|
||
|
PMU_D3_STATUS = PMU_D3_STATUS;
|
||
|
|
||
|
if (PMU_D3_STATUS & (PMU_D3_BIT_RISING_EDGE_STATUS | PMU_D3_BIT_SET)) {
|
||
|
/*
|
||
|
* Indicate completion of servicing the interrupt to IOAPIC
|
||
|
* first then indicate completion of servicing the interrupt
|
||
|
* to LAPIC
|
||
|
*/
|
||
|
IOAPIC_EOI_REG = irq_vec;
|
||
|
LAPIC_EOI_REG = 0x0;
|
||
|
|
||
|
ish_persistent_data_commit();
|
||
|
ish_pm_reset(ISH_PM_STATE_D3);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void d3_rise_isr(void)
|
||
|
{
|
||
|
handle_d3(ISH_D3_RISE_VEC);
|
||
|
}
|
||
|
|
||
|
static void d3_fall_isr(void)
|
||
|
{
|
||
|
handle_d3(ISH_D3_FALL_VEC);
|
||
|
}
|
||
|
|
||
|
static void bme_rise_isr(void)
|
||
|
{
|
||
|
handle_d3(ISH_BME_RISE_VEC);
|
||
|
}
|
||
|
|
||
|
static void bme_fall_isr(void)
|
||
|
{
|
||
|
handle_d3(ISH_BME_FALL_VEC);
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_ISH_PM_D3
|
||
|
DECLARE_IRQ(ISH_D3_RISE_IRQ, d3_rise_isr);
|
||
|
DECLARE_IRQ(ISH_D3_FALL_IRQ, d3_fall_isr);
|
||
|
DECLARE_IRQ(ISH_BME_RISE_IRQ, bme_rise_isr);
|
||
|
DECLARE_IRQ(ISH_BME_FALL_IRQ, bme_fall_isr);
|
||
|
#endif
|
||
|
|
||
|
void ish_pm_refresh_console_in_use(void)
|
||
|
{
|
||
|
disable_sleep(SLEEP_MASK_CONSOLE);
|
||
|
|
||
|
/* Set console in use expire time. */
|
||
|
pm_ctx.console_expire_time = get_time();
|
||
|
pm_ctx.console_expire_time.val +=
|
||
|
pm_ctx.console_in_use_timeout_sec * SECOND;
|
||
|
}
|