summaryrefslogtreecommitdiff
path: root/chip
diff options
context:
space:
mode:
authorHu, Hebo <hebo.hu@intel.com>2019-03-08 15:34:21 +0800
committerchrome-bot <chrome-bot@chromium.org>2019-04-08 02:51:29 -0700
commit6a184d5019f0b45fe692da09a14e9ce7c853d68c (patch)
tree541192356148342f09bacebb75bea95027e98e7d /chip
parentd0a350e6691a9d93138051e2aa00e0d6c26151b5 (diff)
downloadchrome-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.h42
-rw-r--r--chip/ish/aontaskfw/ish_aontask.c383
-rw-r--r--chip/ish/aontaskfw/ish_aontask.ld.in68
-rw-r--r--chip/ish/build.mk7
-rw-r--r--chip/ish/clock.c13
-rw-r--r--chip/ish/config_chip.h22
-rw-r--r--chip/ish/power_mgt.c478
-rw-r--r--chip/ish/power_mgt.h43
-rw-r--r--chip/ish/registers.h36
-rw-r--r--chip/ish/system.c4
-rw-r--r--chip/ish/watchdog.c10
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,