diff options
author | Hu, Hebo <hebo.hu@intel.com> | 2019-03-08 15:34:21 +0800 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2019-04-08 02:51:29 -0700 |
commit | 6a184d5019f0b45fe692da09a14e9ce7c853d68c (patch) | |
tree | 541192356148342f09bacebb75bea95027e98e7d /chip | |
parent | d0a350e6691a9d93138051e2aa00e0d6c26151b5 (diff) | |
download | chrome-ec-6a184d5019f0b45fe692da09a14e9ce7c853d68c.tar.gz |
ish/ish5: implement AON low power management framework
AON PM framework including:
1: AON task skeleton
2: task switching between main FW and AON task
3: 'idlestats' console command for D0ix statistic information
4: D0ix entrance in idle task
BUG=b:122364080
BRANCH=none
TEST=tested on arcada
Change-Id: Iefa9e067892d5c42d9f0c795275fe88e5a36115b
Signed-off-by: Hu, Hebo <hebo.hu@intel.com>
Reviewed-on: https://chromium-review.googlesource.com/1510518
Commit-Ready: Rushikesh S Kadam <rushikesh.s.kadam@intel.com>
Commit-Ready: Hebo Hu <hebo.hu@intel.corp-partner.google.com>
Tested-by: Jett Rink <jettrink@chromium.org>
Reviewed-by: Jett Rink <jettrink@chromium.org>
Reviewed-by: Hebo Hu <hebo.hu@intel.corp-partner.google.com>
Diffstat (limited to 'chip')
-rw-r--r-- | chip/ish/aontaskfw/ish_aon_share.h | 42 | ||||
-rw-r--r-- | chip/ish/aontaskfw/ish_aontask.c | 383 | ||||
-rw-r--r-- | chip/ish/aontaskfw/ish_aontask.ld.in | 68 | ||||
-rw-r--r-- | chip/ish/build.mk | 7 | ||||
-rw-r--r-- | chip/ish/clock.c | 13 | ||||
-rw-r--r-- | chip/ish/config_chip.h | 22 | ||||
-rw-r--r-- | chip/ish/power_mgt.c | 478 | ||||
-rw-r--r-- | chip/ish/power_mgt.h | 43 | ||||
-rw-r--r-- | chip/ish/registers.h | 36 | ||||
-rw-r--r-- | chip/ish/system.c | 4 | ||||
-rw-r--r-- | chip/ish/watchdog.c | 10 |
11 files changed, 1106 insertions, 0 deletions
diff --git a/chip/ish/aontaskfw/ish_aon_share.h b/chip/ish/aontaskfw/ish_aon_share.h new file mode 100644 index 0000000000..3a5c1bd06b --- /dev/null +++ b/chip/ish/aontaskfw/ish_aon_share.h @@ -0,0 +1,42 @@ +/* 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. + */ + +#ifndef __CROS_EC_ISH_AON_SHARE_H +#define __CROS_EC_ISH_AON_SHARE_H + +#include "ia_structs.h" + +/* magic ID for valid aontask image sanity check */ +#define AON_MAGIC_ID 0x544E4F41 /*"AONT"*/ + +/* aontask error code */ +#define AON_SUCCESS 0 +#define AON_ERROR_NOT_SUPPORT_POWER_MODE 1 +#define AON_ERROR_DMA_FAILED 2 + + +/* shared data structure between main FW and aontask */ +struct ish_aon_share { + /* magic ID */ + uint32_t magic_id; + /* last error */ + /* error counter */ + uint32_t error_count; + /* last error */ + int last_error; + /* aontask's TSS segment entry */ + struct tss_entry *aon_tss; + /* aontask's LDT start address */ + ldt_entry *aon_ldt; + /* aontask's LDT's limit size */ + uint32_t aon_ldt_size; + /* current power state, see chip/ish/power_mgt.h */ + int pm_state; + /* for store/restore main FW's IDT */ + struct idt_header main_fw_idt_hdr; + +} __packed; + +#endif /* __CROS_EC_ISH_AON_SHARE_H */ diff --git a/chip/ish/aontaskfw/ish_aontask.c b/chip/ish/aontaskfw/ish_aontask.c new file mode 100644 index 0000000000..3c1095a157 --- /dev/null +++ b/chip/ish/aontaskfw/ish_aontask.c @@ -0,0 +1,383 @@ +/* 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. + */ + +/** + * ISH aontask is a seprated very small program from main FW, not like main FW + * resides in main SRAM, aontask resides in a small AON memory (ISH3 has no + * seprated AON memory, reserved last 4KB of main SRAM for AON use, from ISH4, + * there is seprated AON memory, 4KB for ISH4, and 8KB for ISH5). + * + * When ISH entered into low power states, aontask may get switched and run, + * aontask managments the main SRAM and is responsible for store and restore + * main FW's running context, for example, when entering D0i2 state, put main + * SRAM into retention mode, when exit D0i2 state and before switch back to + * main FW, put main SRAM into normal access mode, when entering D0i3 state, + * at first stores main FW's writeable data into IMR DDR (read only code and + * data already have copies in IMR), then power off the main SRAM completely, + * when exit D0i3 state, at first power on the main SRAM, and restore main FW's + * code and data from IMR to main SRAM, then switch back to main FW. + * + * On ISH, except the hpet timer, also have other wakeup sources, peripheral + * events, such as gpio interrupt, uart interrupt, ipc interrupt, I2C and SPI + * access are also can wakeup ISH. ISH's PMU (power management unit HW) will + * manage these wakeup sources and transfer to a PMU wakeup interrupt which + * can wakeup aontask, and aontask will handle it, when aontask got up, and + * swiched back to main FW, main FW will receive the original wakeup source + * interrupt which triggered the PMU wakeup interrupt in aontask, then main FW + * handle the original interrupt normally. + * + * In most of the time, aontask is in halt state, and waiting for PMU wakeup + * interrupt to wakeup (reset prep interrupt also can wakeup aontask + * if CONFIG_ISH_PM_RESET_PREP defined), after wakeup, aontask will handle the + * low power states exit process and finaly switch back to main FW. + * + * aontask is running in the 32bit protection mode with flat memory segment + * settings, paging and cache are disabled (cache will be power gated). + * + * We use x86's hardware context switching mechanism for the switching of + * main FW and aontask. + * see https://wiki.osdev.org/Context_Switching + * https://en.wikipedia.org/wiki/Task_state_segment + * + */ + +#include <common.h> +#include <ia_structs.h> +#include "power_mgt.h" +#include "ish_aon_share.h" + +/** + * ISH aontask only need handle PMU wakeup interrupt and reset prep interrupt + * (if CONFIG_ISH_PM_RESET_PREP defined), before switch to aontask, all other + * interrupts should be masked. Since aontask is a seprated program from + * main FW, and the main SRAM will be power offed or will be put in in + * retention mode, aontask need its own IDT to handle PMU wakeup interrupt and + * reset prep interrupt (if CONFIG_ISH_PM_RESET_PREP defined) + * + * Due to very limit AON memory size (typically total 4KB), we don't want to + * define and allocate whole 256 entries for aontask'IDT, that almost need 2KB + * (256 * 8), so we just defined the only needed IDT entries: + * AON_IDT_ENTRY_VEC_FIRST ~ AON_IDT_ENTRY_VEC_LAST + */ +#define AON_IDT_ENTRY_VEC_FIRST ISH_PMU_WAKEUP_VEC + +#ifdef CONFIG_ISH_PM_RESET_PREP +/** + * assume reset prep interrupt vector is greater than PMU wakeup interrupt + * vector, and also need handle reset prep interrupt + * (if CONFIG_ISH_PM_RESET_PREP defined) + */ +#define AON_IDT_ENTRY_VEC_LAST ISH_RESET_PREP_VEC +#else +/* only need handle single PMU wakeup interrupt */ +#define AON_IDT_ENTRY_VEC_LAST ISH_PMU_WAKEUP_VEC +#endif + +static void handle_reset(void); + +/* ISR for PMU wakeup interrupt */ +static void pmu_wakeup_isr(void) +{ + /** + * Indicate completion of servicing the interrupt to IOAPIC first + * then indicate completion of servicing the interrupt to LAPIC + */ + REG32(IOAPIC_EOI_REG) = ISH_PMU_WAKEUP_VEC; + REG32(LAPIC_EOI_REG) = 0x0; + + __asm__ volatile ("iret;"); + + __builtin_unreachable(); +} + +#ifdef CONFIG_ISH_PM_RESET_PREP + +/* ISR for reset prep interrupt */ +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 + */ + REG32(IOAPIC_EOI_REG) = ISH_RESET_PREP_VEC; + REG32(LAPIC_EOI_REG) = 0x0; + + handle_reset(); + + __builtin_unreachable(); +} + +#endif + + +/** + * Use a static data array for aon IDT, and setting IDT header for IDTR + * register + * + * Due to very limit AON memory size (typically total 4KB), we don't want to + * define and allocate whole 256 entries for aontask'IDT, that almost need 2KB + * (256 * 8), so we just defined the only needed IDT entries: + * AON_IDT_ENTRY_VEC_FIRST ~ AON_IDT_ENTRY_VEC_LAST + * + * Since on x86, the IDT entry index (count from 0) is also the interrupt + * vector number, for IDT header, the 'start' filed still need to point to + * the entry 0, and 'size' must count from entry 0. + * + * We only allocated memory for entry AON_IDT_ENTRY_VEC_FIRST to + * AON_IDT_ENTRY_VEC_LAST, a little trick, but works well on ISH + * + * ------>---------------------------<----- aon_idt_hdr.start + * | | entry 0 | + * | +-------------------------+ + * | | ... | + * | +-------------------------+<----- + * aon_idt_hdr.size | AON_IDT_ENTRY_VEC_FIRST | | + * | +-------------------------+ | + * | | ... | allocated memory in aon_idt + * | +-------------------------+ | + * | | AON_IDT_ENTRY_VEC_LAST | | + * ------>+-------------------------+<----- + * | ... | + * +-------------------------+ + * | entry 255 | + * --------------------------- + */ + +static struct idt_entry aon_idt[AON_IDT_ENTRY_VEC_LAST - + AON_IDT_ENTRY_VEC_FIRST + 1]; + +static struct idt_header aon_idt_hdr = { + + .limit = (sizeof(struct idt_entry) * (AON_IDT_ENTRY_VEC_LAST + 1)) - 1, + .entries = (struct idt_entry *)((uint32_t)&aon_idt - + (sizeof(struct idt_entry) * AON_IDT_ENTRY_VEC_FIRST)) +}; + +/* aontask entry point function */ +void ish_aon_main(void); + +/** + * 8 bytes reserved on stack, just for GDB to show the correct stack + * information when doing source code level debuging + */ +#define AON_SP_RESERVED (8) + +/* TSS segment for aon task */ +static struct tss_entry aon_tss = { + .prev_task_link = 0, + .reserved1 = 0, + .esp0 = (uint8_t *)(CONFIG_ISH_AON_SRAM_ROM_START - AON_SP_RESERVED), + /* entry 1 in LDT for data segment */ + .ss0 = 0xc, + .reserved2 = 0, + .esp1 = 0, + .ss1 = 0, + .reserved3 = 0, + .esp2 = 0, + .ss2 = 0, + .reserved4 = 0, + .cr3 = 0, + /* task excute entry point */ + .eip = (uint32_t)&ish_aon_main, + .eflags = 0, + .eax = 0, + .ecx = 0, + .edx = 0, + .ebx = 0, + /* set stack top pointer at the end of usable aon memory */ + .esp = CONFIG_ISH_AON_SRAM_ROM_START - AON_SP_RESERVED, + .ebp = AON_SP_RESERVED, + .esi = 0, + .edi = 0, + /* entry 1 in LDT for data segment */ + .es = 0xc, + .reserved5 = 0, + /* entry 0 in LDT for code segment */ + .cs = 0x4, + .reserved6 = 0, + /* entry 1 in LDT for data segment */ + .ss = 0xc, + .reserved7 = 0, + /* entry 1 in LDT for data segment */ + .ds = 0xc, + .reserved8 = 0, + /* entry 1 in LDT for data segment */ + .fs = 0xc, + .reserved9 = 0, + /* entry 1 in LDT for data segment */ + .gs = 0xc, + .reserved10 = 0, + .ldt_seg_selector = 0, + .reserved11 = 0, + .trap_debug = 0, + + /** + * 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 + */ + .iomap_base_addr = GDT_DESC_TSS_LIMIT +}; + +/** + * define code and data LDT segements for aontask + * code : base = 0x0, limit = 0xFFFFFFFF, Present = 1, DPL = 0 + * data : base = 0x0, limit = 0xFFFFFFFF, Present = 1, DPL = 0 + */ +static ldt_entry aon_ldt[2] = { + + /** + * entry 0 for code segment + * base: 0x0 + * limit: 0xFFFFFFFF + * flag: 0x9B, Present = 1, DPL = 0, code segment + */ + { + .dword_lo = GEN_GDT_DESC_LO(0x0, 0xFFFFFFFF, + GDT_DESC_CODE_FLAGS), + + .dword_up = GEN_GDT_DESC_UP(0x0, 0xFFFFFFFF, + GDT_DESC_CODE_FLAGS) + }, + + /** + * entry 1 for data segment + * base: 0x0 + * limit: 0xFFFFFFFF + * flag: 0x93, Present = 1, DPL = 0, data segment + */ + { + .dword_lo = GEN_GDT_DESC_LO(0x0, 0xFFFFFFFF, + GDT_DESC_DATA_FLAGS), + + .dword_up = GEN_GDT_DESC_UP(0x0, 0xFFFFFFFF, + GDT_DESC_DATA_FLAGS) + } +}; + + +/* shared data structure between main FW and aon task */ +struct ish_aon_share aon_share = { + .magic_id = AON_MAGIC_ID, + .error_count = 0, + .last_error = AON_SUCCESS, + .aon_tss = &aon_tss, + .aon_ldt = &aon_ldt[0], + .aon_ldt_size = sizeof(aon_ldt), +}; + +static void handle_d0i2(void) +{ + /* TODO set main SRAM into retention mode*/ + + /* ish_halt(); */ + /* wakeup from PMU interrupt */ + + /* TODO set main SRAM intto normal mode */ +} + +static void handle_d0i3(void) +{ + /* TODO store main FW 's context to IMR DDR from main SRAM */ + /* TODO power off main SRAM */ + + /* ish_halt(); */ + /* wakeup from PMU interrupt */ + + /* TODO power on main SRAM */ + /* TODO restore main FW 's context to main SRAM from IMR DDR */ +} + +static void handle_d3(void) +{ + /* TODO store main FW 's context to IMR DDR from main sram */ + /* TODO power off main SRAM */ + + /* TODO handle D3 */ +} + +static void handle_reset(void) +{ + /* TODO power off main SRAM */ + /* TODO handle reset */ +} + +static void handle_unknown_state(void) +{ + aon_share.last_error = AON_ERROR_NOT_SUPPORT_POWER_MODE; + aon_share.error_count++; + + /* switch back to main FW */ +} + +void ish_aon_main(void) +{ + + /* set PMU wakeup interrupt gate using LDT code segment selector(0x4) */ + aon_idt[0].dword_lo = GEN_IDT_DESC_LO(&pmu_wakeup_isr, 0x4, + IDT_DESC_FLAGS); + + aon_idt[0].dword_up = GEN_IDT_DESC_UP(&pmu_wakeup_isr, 0x4, + IDT_DESC_FLAGS); +#ifdef CONFIG_ISH_PM_RESET_PREP + + /* set reset prep interrupt gate using LDT code segment selector(0x4) */ + aon_idt[AON_IDT_ENTRY_VEC_LAST - AON_IDT_ENTRY_VEC_FIRST].dword_lo = + GEN_IDT_DESC_LO(&reset_prep_isr, 0x4, IDT_DESC_FLAGS); + + aon_idt[AON_IDT_ENTRY_VEC_LAST - AON_IDT_ENTRY_VEC_FIRST].dword_up = + GEN_IDT_DESC_UP(&reset_prep_isr, 0x4, IDT_DESC_FLAGS); +#endif + + while (1) { + + /** + * will start to run from here when switched to aontask from + * the second time + */ + + /* save main FW's IDT and load aontask's IDT */ + __asm__ volatile ( + "sidtl %0;\n" + "lidtl %1;\n" + : + : "m" (aon_share.main_fw_idt_hdr), + "m" (aon_idt_hdr) + ); + + aon_share.last_error = AON_SUCCESS; + + switch (aon_share.pm_state) { + case ISH_PM_STATE_D0I2: + handle_d0i2(); + break; + case ISH_PM_STATE_D0I3: + handle_d0i3(); + break; + case ISH_PM_STATE_D3: + handle_d3(); + break; + case ISH_PM_STATE_RESET_PREP: + handle_reset(); + break; + default: + handle_unknown_state(); + break; + } + + /* restore main FW's IDT and switch back to main FW */ + __asm__ volatile( + "lidtl %0;\n" + "iret;" + : + : "m" (aon_share.main_fw_idt_hdr) + ); + } +} diff --git a/chip/ish/aontaskfw/ish_aontask.ld.in b/chip/ish/aontaskfw/ish_aontask.ld.in new file mode 100644 index 0000000000..a45bbdb6a9 --- /dev/null +++ b/chip/ish/aontaskfw/ish_aontask.ld.in @@ -0,0 +1,68 @@ +/* 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 <config_chip.h> + +ENTRY(ish_aon_main); + +#define SRAM_START CONFIG_ISH_AON_SRAM_BASE_START +#define SRAM_RW_LEN (CONFIG_ISH_AON_SRAM_SIZE - CONFIG_ISH_AON_SRAM_ROM_SIZE) + +/* reserved stack size */ +#define STACK_SIZE (256) + +/** + * resered 8 bytes for GDB showing correct stack + * information during source code level debuging + */ +#define RESERVED_GDB_SIZE (8) + +#define RAM_LEN (SRAM_RW_LEN - STACK_SIZE - RESERVED_GDB_SIZE) + +/** + * AON memory layout + * --------------+--------------+-------------------+-------------------+ + * | RAM_LEN | STACK_SIZE | 8 Bytes for GDB | ROM(128 Bytes) | + * --------------+--------------+-------------------+-------------------+ + */ + +MEMORY +{ + /* leave STACK_SIZE bytes in the end of memory for stack */ + RAM : ORIGIN = SRAM_START, LENGTH = RAM_LEN } + +SECTIONS +{ + /* AON parts visible to FW are linked to the beginning of the AON area */ + .data.aon_share : AT(CONFIG_ISH_AON_SRAM_BASE_START) + { + KEEP(*(.data.aon_share)) + } > RAM + + .data : + { + *(.data) + *(.data*) + } > RAM + + .text : + { + *(.text) + *(.text*) + } > RAM + + .bss : + { + *(.bss) + *(.bss*) + } > RAM + + .stack_tag : + { + KEEP(*(.stack_tag)) + } > RAM + + +} diff --git a/chip/ish/build.mk b/chip/ish/build.mk index 31827497b4..2c242725d2 100644 --- a/chip/ish/build.mk +++ b/chip/ish/build.mk @@ -26,6 +26,13 @@ chip-$(CONFIG_HOSTCMD_HECI)+=heci.o system_state_subsys.o ipc_heci.o chip-$(CONFIG_HID_HECI)+=hid_subsys.o chip-$(CONFIG_HID_HECI)+=heci.o system_state_subsys.o ipc_heci.o chip-$(CONFIG_DMA_PAGING)+=dma.o +chip-$(CONFIG_LOW_POWER_IDLE)+=power_mgt.o + +ifeq ($(CONFIG_ISH_PM_AONTASK),y) +ish-aontask-fw=chip/ish/aontaskfw/ish_aontask +ish-aontask-fw-bin=$(out)/$(ish-aontask-fw).bin +PROJECT_EXTRA+=$(ish-aontask-fw-bin) +endif # location of the scripts and keys used to pack the SPI flash image SCRIPTDIR:=./chip/${CHIP}/util diff --git a/chip/ish/clock.c b/chip/ish/clock.c index ba0a750044..0be9e3c231 100644 --- a/chip/ish/clock.c +++ b/chip/ish/clock.c @@ -18,3 +18,16 @@ void clock_init(void) { /* No initialization for ISH clock since D0ix is not enabled yet */ } + +#ifdef CONFIG_LOW_POWER_IDLE + +void clock_refresh_console_in_use(void) +{ + /** + * TODO nothing need to do at current, on ISH, uart interrupt can + * wakeup ISH from low power state, will understand this function more + * to see if need anything to handle in ISH + */ +} + +#endif diff --git a/chip/ish/config_chip.h b/chip/ish/config_chip.h index d10ee650d6..93cc527f6b 100644 --- a/chip/ish/config_chip.h +++ b/chip/ish/config_chip.h @@ -36,6 +36,28 @@ #define CONFIG_ISH_SRAM_SIZE (CONFIG_ISH_SRAM_BASE_END - \ CONFIG_ISH_SRAM_BASE_START) +#if defined(CHIP_FAMILY_ISH3) +/* on ISH3, there is no seprated aon memory, using last 4KB of normal memory + * without poweroff + */ +#define CONFIG_ISH_AON_SRAM_BASE_START 0xFF09F000 +#define CONFIG_ISH_AON_SRAM_BASE_END 0xFF0A0000 +#elif defined(CHIP_FAMILY_ISH4) +#define CONFIG_ISH_AON_SRAM_BASE_START 0xFF800000 +#define CONFIG_ISH_AON_SRAM_BASE_END 0xFF801000 +#else +#define CONFIG_ISH_AON_SRAM_BASE_START 0xFF800000 +#define CONFIG_ISH_AON_SRAM_BASE_END 0xFF802000 +#endif + +#define CONFIG_ISH_AON_SRAM_SIZE (CONFIG_ISH_AON_SRAM_BASE_END - \ + CONFIG_ISH_AON_SRAM_BASE_START) + +/* reserve for readonly use in the last of AON memory */ +#define CONFIG_ISH_AON_SRAM_ROM_SIZE 0x80 +#define CONFIG_ISH_AON_SRAM_ROM_START (CONFIG_ISH_AON_SRAM_BASE_END - \ + CONFIG_ISH_AON_SRAM_ROM_SIZE) + /* Required for panic_output */ #define CONFIG_RAM_SIZE CONFIG_ISH_SRAM_SIZE #define CONFIG_RAM_BASE CONFIG_ISH_SRAM_BASE_START diff --git a/chip/ish/power_mgt.c b/chip/ish/power_mgt.c new file mode 100644 index 0000000000..da86bb7608 --- /dev/null +++ b/chip/ish/power_mgt.c @@ -0,0 +1,478 @@ +/* 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 <console.h> +#include <task.h> +#include <system.h> +#include <hwtimer.h> +#include <util.h> +#include "interrupts.h" +#include "aontaskfw/ish_aon_share.h" +#include "power_mgt.h" +#include "watchdog.h" + +#ifdef CONFIG_ISH_PM_DEBUG +#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) +#else +#define CPUTS(outstr) +#define CPRINTS(format, args...) +#define CPRINTF(format, args...) +#endif + +#ifdef CONFIG_WATCHDOG +extern void watchdog_enable(void); +extern void watchdog_disable(void); +#endif + +/* 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]; +} __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_ISH_AON_SRAM_BASE_START +}; + +/* D0ix statistics data, including each state's count and total stay time */ +struct pm_statistics { + uint64_t d0i0_cnt; + uint64_t d0i0_time_us; + +#ifdef CONFIG_ISH_PM_D0I1 + uint64_t d0i1_cnt; + uint64_t d0i1_time_us; +#endif + +#ifdef CONFIG_ISH_PM_D0I2 + uint64_t d0i2_cnt; + uint64_t d0i2_time_us; +#endif + +#ifdef CONFIG_ISH_PM_D0I3 + uint64_t d0i3_cnt; + uint64_t d0i3_time_us; +#endif + +} __packed; + +static struct pm_statistics pm_stats; + +#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;"); +} + +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(); +} + +#endif + +static void enter_d0i0(void) +{ + timestamp_t t0, t1; + + t0 = get_time(); + + pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0I0; + + /* halt ISH cpu, will wakeup from any interrupt */ + ish_halt(); + + t1 = get_time(); + + pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0; + + pm_stats.d0i0_time_us += t1.val - t0.val; + pm_stats.d0i0_cnt++; +} + +#ifdef CONFIG_ISH_PM_D0I1 + +static void enter_d0i1(void) +{ + timestamp_t t0, t1; + + t0 = get_time(); + + pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0I1; + + /* TODO: enable Trunk Clock Gating (TCG) of ISH */ + + /* halt ISH cpu, will wakeup from PMU wakeup interrupt */ + ish_halt(); + + /* TODO disable Trunk Clock Gating (TCG) of ISH */ + + pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0; + + t1 = get_time(); + pm_stats.d0i1_time_us += t1.val - t0.val; + pm_stats.d0i1_cnt++; +} + +#endif + +#ifdef CONFIG_ISH_PM_D0I2 + +static void enter_d0i2(void) +{ + timestamp_t t0, t1; + + t0 = get_time(); + + pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0I2; + + /* TODO: enable Trunk Clock Gating (TCG) of ISH */ + + switch_to_aontask(); + /* returned from aontask */ + + /* TODO just for test, will remove later */ + ish_halt(); + + /* TODO disable Trunk Clock Gating (TCG) of ISH */ + + t1 = get_time(); + + pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0; + + pm_stats.d0i2_time_us += t1.val - t0.val; + pm_stats.d0i2_cnt++; +} + +#endif + +#ifdef CONFIG_ISH_PM_D0I3 + +static void enter_d0i3(void) +{ + timestamp_t t0, t1; + + t0 = get_time(); + + pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0I3; + + /*TODO some preparing work for D0i3 */ + + switch_to_aontask(); + + /*TODO just for test, will remove later */ + ish_halt(); + + /*TODO some restore work for D0i3 */ + + t1 = get_time(); + + pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0; + + pm_stats.d0i3_time_us += t1.val - t0.val; + pm_stats.d0i3_cnt++; +} + +#endif + +static int d0ix_decide(uint32_t idle_us) +{ + int pm_state = ISH_PM_STATE_D0I0; + + if (DEEP_SLEEP_ALLOWED) { +#ifdef CONFIG_ISH_PM_D0I1 + pm_state = ISH_PM_STATE_D0I1; +#endif + +#ifdef CONFIG_ISH_PM_D0I2 + if (idle_us >= CONFIG_ISH_D0I2_MIN_USEC && pm_ctx.aon_valid) + pm_state = ISH_PM_STATE_D0I2; +#endif + +#ifdef CONFIG_ISH_PM_D0I3 + if (idle_us >= CONFIG_ISH_D0I3_MIN_USEC && pm_ctx.aon_valid) + pm_state = ISH_PM_STATE_D0I3; +#endif + } + + return pm_state; +} + +static void pm_process(uint32_t idle_us) +{ + int decide; + + decide = d0ix_decide(idle_us); + +#ifdef CONFIG_WATCHDOG + watchdog_disable(); +#endif + + switch (decide) { +#ifdef CONFIG_ISH_PM_D0I1 + case ISH_PM_STATE_D0I1: + enter_d0i1(); + break; +#endif +#ifdef CONFIG_ISH_PM_D0I2 + case ISH_PM_STATE_D0I2: + enter_d0i2(); + break; +#endif +#ifdef CONFIG_ISH_PM_D0I3 + case ISH_PM_STATE_D0I3: + enter_d0i3(); + break; +#endif + default: + enter_d0i0(); + break; + } + +#if defined(CONFIG_ISH_PM_D0I2) || defined(CONFIG_ISH_PM_D0I3) + if (decide == ISH_PM_STATE_D0I2 || decide == ISH_PM_STATE_D0I3) + check_aon_task_status(); +#endif + +#ifdef CONFIG_WATCHDOG + watchdog_enable(); + watchdog_reload(); +#endif + +} + +void ish_pm_init(void) +{ + +#ifdef CONFIG_ISH_PM_AONTASK + init_aon_task(); +#endif + + /* unmask all wake up events */ + PMU_MASK_EVENT = ~PMU_MASK_EVENT_BIT_ALL; +} + +void __idle(void) +{ + timestamp_t t0; + int next_delay = 0; + + while (1) { + t0 = get_time(); + next_delay = __hw_clock_event_get() - t0.le.lo; + + pm_process(next_delay); + } +} + +/** + * Print low power idle statistics + */ +static int command_idle_stats(int argc, char **argv) +{ +#if defined(CONFIG_ISH_PM_D0I2) || defined(CONFIG_ISH_PM_D0I3) + struct ish_aon_share *aon_share = pm_ctx.aon_share; +#endif + + ccprintf("Aontask exist: %s\n", pm_ctx.aon_valid ? "Yes" : "No"); + ccprintf("Idle sleep:\n"); + ccprintf(" D0i0:\n"); + ccprintf(" counts: %ld\n", pm_stats.d0i0_cnt); + ccprintf(" time: %.6lds\n", pm_stats.d0i0_time_us); + + ccprintf("Deep sleep:\n"); +#ifdef CONFIG_ISH_PM_D0I1 + ccprintf(" D0i1:\n"); + ccprintf(" counts: %ld\n", pm_stats.d0i1_cnt); + ccprintf(" time: %.6lds\n", pm_stats.d0i1_time_us); +#endif + +#ifdef CONFIG_ISH_PM_D0I2 + if (pm_ctx.aon_valid) { + ccprintf(" D0i2:\n"); + ccprintf(" counts: %ld\n", pm_stats.d0i2_cnt); + ccprintf(" time: %.6lds\n", pm_stats.d0i2_time_us); + } +#endif + +#ifdef CONFIG_ISH_PM_D0I3 + if (pm_ctx.aon_valid) { + ccprintf(" D0i3:\n"); + ccprintf(" counts: %ld\n", pm_stats.d0i3_cnt); + ccprintf(" time: %.6lds\n", pm_stats.d0i3_time_us); + } +#endif + +#if defined(CONFIG_ISH_PM_D0I2) || defined(CONFIG_ISH_PM_D0I3) + if (pm_ctx.aon_valid) { + ccprintf(" Aontask status:\n"); + ccprintf(" last error: %d\n", aon_share->last_error); + ccprintf(" error counts: %d\n", aon_share->error_count); + } +#endif + + ccprintf("Total time on: %.6lds\n", get_time().val); + + return EC_SUCCESS; +} + +DECLARE_CONSOLE_COMMAND(idlestats, command_idle_stats, "", + "Print last idle stats"); + + +#ifdef CONFIG_ISH_PM_D0I1 + +/** + * main FW only need handle PMU wakeup interrupt for D0i1 state, aontask will + * handle PMU wakeup interrupt for other low power states + */ +static void pmu_wakeup_isr(void) +{ + /* at current nothing need to do */ +} + +DECLARE_IRQ(ISH_PMU_WAKEUP_IRQ, pmu_wakeup_isr); + +#endif diff --git a/chip/ish/power_mgt.h b/chip/ish/power_mgt.h new file mode 100644 index 0000000000..fe0c1b4539 --- /dev/null +++ b/chip/ish/power_mgt.h @@ -0,0 +1,43 @@ +/* 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. + */ + +#ifndef __CROS_EC_POWER_MGT_H +#define __CROS_EC_POWER_MGT_H + +/* power states for ISH */ +enum { + /* D0 state: active mode */ + ISH_PM_STATE_D0 = 0, + /* sleep state: cpu halt */ + ISH_PM_STATE_D0I0, + /* deep sleep state 1: Trunk Clock Gating(TCG), cpu halt*/ + ISH_PM_STATE_D0I1, + /* deep sleep state 2: TCG, SRAM retention, cpu halt */ + ISH_PM_STATE_D0I2, + /* deep sleep state 3: TCG, SRAM power off, cpu halt*/ + ISH_PM_STATE_D0I3, + /* D3 state: power off state, on ISH5.0, can't do real power off, + * similar to D0I3, but will reset ISH + */ + ISH_PM_STATE_D3, + /* ISH received reset_prep interrupt during S0->Sx transition */ + ISH_PM_STATE_RESET_PREP, + ISH_PM_STATE_NUM +}; + +/* halt ISH cpu */ +static inline void ish_halt(void) +{ + /* make sure interrupts are enabled before halting */ + __asm__ volatile("sti;\n" + "hlt;"); +} + +/* ish low power management initialization, + * should be called at system init stage before RTOS task scheduling start + */ +void ish_pm_init(void); + +#endif /* __CROS_EC_POWER_MGT_H */ diff --git a/chip/ish/registers.h b/chip/ish/registers.h index 0f9e9d9a99..e8e9477e13 100644 --- a/chip/ish/registers.h +++ b/chip/ish/registers.h @@ -62,6 +62,7 @@ enum ish_i2c_port { #define ISH_UART0_IRQ 34 #define ISH_UART1_IRQ 35 #define ISH_RESET_PREP_IRQ 62 +#define ISH_PMU_WAKEUP_IRQ 18 /* Interrupt vectors 0-31 are architecture reserved. * Vectors 32-255 are user-defined. @@ -105,6 +106,7 @@ enum ish_i2c_port { #define ISH_UART1_VEC IRQ_TO_VEC(ISH_UART1_IRQ) #define ISH_IPC_VEC IRQ_TO_VEC(ISH_IPC_HOST2ISH_IRQ) #define ISH_RESET_PREP_VEC IRQ_TO_VEC(ISH_RESET_PREP_IRQ) +#define ISH_PMU_WAKEUP_VEC IRQ_TO_VEC(ISH_PMU_WAKEUP_IRQ) #ifdef CONFIG_ISH_UART_0 #define ISH_DEBUG_UART UART_PORT_0 @@ -198,6 +200,28 @@ enum ish_i2c_port { #define DEST_TR_WIDTH 2 #define DEST_BURST_SIZE 3 +#define PMU_MASK_EVENT REG32(ISH_PMU_BASE + 0x10) +#define PMU_MASK_EVENT_BIT_GPIO(pin) (0x1 << (pin)) +#define PMU_MASK_EVENT_BIT_HPET (0x1 << 16) +#define PMU_MASK_EVENT_BIT_IPC (0x1 << 17) +#define PMU_MASK_EVENT_BIT_D3 (0x1 << 18) +#define PMU_MASK_EVENT_BIT_DMA (0x1 << 19) +#define PMU_MASK_EVENT_BIT_I2C0 (0x1 << 20) +#define PMU_MASK_EVENT_BIT_I2C1 (0x1 << 21) +#define PMU_MASK_EVENT_BIT_SPI (0x1 << 22) +#define PMU_MASK_EVENT_BIT_UART (0x1 << 23) +#define PMU_MASK_EVENT_BIT_ALL (0xffffffff) + +#define PMU_RF_ROM_PWR_CTRL REG32(ISH_PMU_BASE + 0x30) + +#define PMU_LDO_CTRL REG32(ISH_PMU_BASE + 0x44) +#define PMU_LDO_BIT_ON (0x1 << 0) +#define PMU_LDO_BIT_OFF (0) +#define PMU_LDO_BIT_RETENTION_ON (0x1 << 1) +#define PMU_LDO_BIT_RETENTION_OFF (0) +#define PMU_LDO_BIT_CALIBRATION (0x1 << 2) +#define PMU_LDO_BIT_READY (0x1 << 3) + /* CCU Registers */ #define CCU_TCG_EN REG32(ISH_CCU_BASE + 0x0) #define CCU_BCG_EN REG32(ISH_CCU_BASE + 0x4) @@ -205,6 +229,18 @@ enum ish_i2c_port { #define CCU_RST_HST REG32(ISH_CCU_BASE + 0x34) /* Reset history */ #define CCU_TCG_ENABLE REG32(ISH_CCU_BASE + 0x38) #define CCU_BCG_ENABLE REG32(ISH_CCU_BASE + 0x3c) +#define CCU_BCG_BIT_MIA (0x1 << 0) +#define CCU_BCG_BIT_DMA (0x1 << 1) +#define CCU_BCG_BIT_I2C0 (0x1 << 2) +#define CCU_BCG_BIT_I2C1 (0x1 << 3) +#define CCU_BCG_BIT_SPI (0x1 << 4) +#define CCU_BCG_BIT_SRAM (0x1 << 5) +#define CCU_BCG_BIT_HPET (0x1 << 6) +#define CCU_BCG_BIT_UART (0x1 << 7) +#define CCU_BCG_BIT_GPIO (0x1 << 8) +#define CCU_BCG_BIT_I2C2 (0x1 << 9) +#define CCU_BCG_BIT_SPI2 (0x1 << 10) +#define CCU_BCG_BIT_ALL (0x7ff) /* Bitmasks for CCU_RST_HST */ #define CCU_SW_RST (1 << 0) /* Used to indicate SW reset */ diff --git a/chip/ish/system.c b/chip/ish/system.c index 6c8550b0b4..fad46aa5cc 100644 --- a/chip/ish/system.c +++ b/chip/ish/system.c @@ -19,6 +19,7 @@ #include "timer.h" #include "util.h" #include "spi.h" +#include "power_mgt.h" /* Indices for hibernate data registers (RAM backed by VBAT) */ enum hibdata_index { @@ -33,6 +34,9 @@ int system_is_reboot_warm(void) void system_pre_init(void) { +#ifdef CONFIG_LOW_POWER_IDLE + ish_pm_init(); +#endif } void chip_save_reset_flags(int flags) diff --git a/chip/ish/watchdog.c b/chip/ish/watchdog.c index fd0ebcf247..d9a9d84008 100644 --- a/chip/ish/watchdog.c +++ b/chip/ish/watchdog.c @@ -46,6 +46,16 @@ int watchdog_init(void) return EC_SUCCESS; } +void watchdog_enable(void) +{ + WDT_CONTROL |= WDT_CONTROL_ENABLE_BIT; +} + +void watchdog_disable(void) +{ + WDT_CONTROL &= ~WDT_CONTROL_ENABLE_BIT; +} + /* Parameters are pushed by hardware, we only care about %EIP */ __attribute__ ((noreturn)) void watchdog_warning(uint32_t errorcode, |