/* * Copyright (c) 2023 Intel Corporation * * SPDX-License-Identifier: BSD-3-Clause */ #include "aon/aon_share.h" #include "ish_dma.h" #include "ish_pm.h" #include "sedi_driver_core.h" #include "sedi_driver_uart.h" #include #include #include #include #include /* defined in link script: soc/x86/intel_ish/scripts/ish_linker.ld */ extern uint32_t __text_region_start; extern uint32_t __rodata_region_end; extern uint32_t _image_ram_start; extern uint32_t _image_ram_end; /* Disable debug print by default */ #define PM_DEBUG_PRINTS 1 #ifdef PM_DEBUG_PRINTS #define PM_LOG(...) printk(__VA_ARGS__) #else #define PM_LOG(...) #endif #define DLL 0x0 #define DLH 0x4 #define LCR 0xC #define DLF 0xC0 static sedi_uart_config_t uart0_cfg, uart1_cfg, uart2_cfg; static void uart_to_idle(void) { sedi_uart_get_config(SEDI_UART_0, &uart0_cfg); write32(SEDI_IREG_BASE(UART, 0) + LCR, 0x80); write32(SEDI_IREG_BASE(UART, 0) + DLL, 0x1); write32(SEDI_IREG_BASE(UART, 0) + DLH, 0x0); write32(SEDI_IREG_BASE(UART, 0) + LCR, 0); sedi_uart_get_config(SEDI_UART_0, &uart1_cfg); write32(SEDI_IREG_BASE(UART, 1) + LCR, 0x80); write32(SEDI_IREG_BASE(UART, 1) + DLL, 0x1); write32(SEDI_IREG_BASE(UART, 1) + DLH, 0x0); write32(SEDI_IREG_BASE(UART, 1) + LCR, 0); sedi_uart_get_config(SEDI_UART_0, &uart2_cfg); write32(SEDI_IREG_BASE(UART, 2) + LCR, 0x80); write32(SEDI_IREG_BASE(UART, 2) + DLL, 0x1); write32(SEDI_IREG_BASE(UART, 2) + DLH, 0x0); write32(SEDI_IREG_BASE(UART, 2) + LCR, 0); } static void uart_port_restore(void) { sedi_uart_set_config(SEDI_UART_0, &uart0_cfg); sedi_uart_set_config(SEDI_UART_1, &uart1_cfg); sedi_uart_set_config(SEDI_UART_2, &uart2_cfg); } static void pg_exit_restore_hw(void) { write32(LAPIC_SPUR, LAPIC_ENABLE | LAPIC_SPUR_RESET); write32(CCU_RST_HST, read32(CCU_RST_HST)); write32(CCU_TCG_ENABLE, 0); write32(CCU_BCG_ENABLE, 0); write32(CCU_BCG_MIA, 0); write32(CCU_BCG_DMA, 0); write32(CCU_BCG_I2C, 0); write32(CCU_BCG_SPI, 0); write32(CCU_BCG_UART, 0); write32(CCU_BCG_GPIO, 0); } /* 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]; } __attribute__((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, }; /* 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; struct pm_stat pg; }; 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, uint64_t t0, uint64_t t1) { stat->total_time_us += t1 - t0; stat->count++; } extern struct pseudo_descriptor _gdt; /* TSS descriptor for saving main FW's cpu context during aontask switching */ static struct tss_entry main_tss; /** * add new entry in GDT * * @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; struct gdt_header *gdt_ptr = (struct gdt_header *)&_gdt; struct gdt_entry *__gdt = (struct gdt_entry *)(gdt_ptr->entries); /** * get the first empty entry of GDT which defined in crt0.S * each entry has a fixed size of 8 bytes */ index = (gdt_ptr->limit + 1) >> 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->limit += sizeof(struct gdt_entry); return ((index + 1) << 3) - 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; main_tss.iomap_base_addr = GDT_DESC_TSS_LIMIT; 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); 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); 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); __asm__ volatile("lgdt _gdt;\n" "push %eax;\n" "movw $0x18, %ax;\n" "ltr %ax;\n" "pop %eax;"); aon_share->main_fw_ro_addr = (uint32_t)&__text_region_start; aon_share->main_fw_ro_size = (uint32_t)&__rodata_region_end - (uint32_t)&__text_region_start; aon_share->main_fw_rw_addr = (uint32_t)&_image_ram_start; aon_share->main_fw_rw_size = (uint32_t)&_image_ram_end - (uint32_t)&_image_ram_start; aon_share->uma_msb = read32(IPC_UMA_RANGE_LOWER_1); 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) { PM_LOG("aontask has errors:\n"); PM_LOG(" last error: %d\n", aon_share->last_error); PM_LOG(" error counts: %d\n", aon_share->error_count); } } static void pm_disable_irqs(uint64_t current_ioapic_state) { int i; for (i = 0; i < IOAPIC_NUM_RTES; i++) { if ((((uint64_t)1) << i & current_ioapic_state) && (i != SEDI_IRQ_PMU2IOAPIC) && (i != SEDI_IRQ_RESET_PREP)) sedi_core_irq_disable(i); } } static void pm_enable_irqs(uint64_t current_ioapic_state) { int i; for (i = 0; i < IOAPIC_NUM_RTES; i++) { if ((((uint64_t)1) << i & current_ioapic_state) && (i != SEDI_IRQ_PMU2IOAPIC) && (i != SEDI_IRQ_RESET_PREP)) sedi_core_irq_enable(i); } } static void switch_to_aontask(void) { unsigned int eflags = sedi_core_irq_lock(); __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"); sedi_core_irq_unlock(eflags); } static void handle_reset_in_aontask(enum ish_pm_state pm_state) { uint64_t ioapic_state; pm_ctx.aon_share->pm_state = pm_state; ioapic_state = sedi_core_get_irq_map(); pm_disable_irqs(ioapic_state); sedi_core_irq_enable(SEDI_IRQ_PMU2IOAPIC); sedi_core_irq_enable(SEDI_IRQ_RESET_PREP); /* enable Trunk Clock Gating (TCG) of ISH */ write32(CCU_TCG_EN, 1); /* enable power gating of RF(Cache) and ROMs */ write32(PMU_RF_ROM_PWR_CTRL, 1); switch_to_aontask(); __builtin_unreachable(); } static void enter_d0i0(void) { uint64_t t0, t1; t0 = sedi_rtc_get_us(); pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0I0; /* halt ISH cpu, will wakeup from any interrupt */ ish_mia_halt(); t1 = sedi_rtc_get_us(); pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0; log_pm_stat(&pm_stats.d0i0, t0, t1); } static void enter_d0i1(void) { #ifndef CONFIG_SOC_INTEL_ISH_5_6_0 uint64_t ioapic_state; #endif uint64_t t0, t1; #ifndef CONFIG_SOC_INTEL_ISH_5_6_0 ioapic_state = sedi_core_get_irq_map(); pm_disable_irqs(ioapic_state); #endif sedi_core_irq_enable(SEDI_IRQ_PMU2IOAPIC); sedi_core_irq_enable(SEDI_IRQ_RESET_PREP); t0 = sedi_rtc_get_us(); pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0I1; #ifndef CONFIG_SOC_INTEL_ISH_5_6_0 /* enable Trunk Clock Gating (TCG) of ISH */ write32(CCU_TCG_EN, 1); #else write32(CCU_BCG_MIA, read32(CCU_BCG_MIA) | CCU_BCG_BIT_MIA); #endif /* halt ISH cpu, will wakeup from PMU wakeup interrupt */ ish_mia_halt(); #ifndef CONFIG_SOC_INTEL_ISH_5_6_0 /* disable Trunk Clock Gating (TCG) of ISH */ write32(CCU_TCG_EN, 0); #else write32(CCU_BCG_MIA, read32(CCU_BCG_MIA) & (~CCU_BCG_BIT_MIA)); #endif pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0; t1 = sedi_rtc_get_us(); log_pm_stat(&pm_stats.d0i1, t0, t1); #ifndef CONFIG_SOC_INTEL_ISH_5_6_0 /* restore interrupts */ pm_enable_irqs(ioapic_state); #endif sedi_core_irq_disable(SEDI_IRQ_PMU2IOAPIC); } static void enter_d0i2(void) { uint64_t ioapic_state; uint64_t t0, t1; ioapic_state = sedi_core_get_irq_map(); pm_disable_irqs(ioapic_state); sedi_core_irq_enable(SEDI_IRQ_PMU2IOAPIC); sedi_core_irq_enable(SEDI_IRQ_RESET_PREP); t0 = sedi_rtc_get_us(); pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0I2; /* enable Trunk Clock Gating (TCG) of ISH */ write32(CCU_TCG_EN, 1); /* enable power gating of RF(Cache) and ROMs */ write32(PMU_RF_ROM_PWR_CTRL, 1); switch_to_aontask(); /* returned from aontask */ if (pm_ctx.aon_share->pg_exit) pg_exit_restore_hw(); /* disable power gating of RF(Cache) and ROMs */ write32(PMU_RF_ROM_PWR_CTRL, 0); /* disable Trunk Clock Gating (TCG) of ISH */ write32(CCU_TCG_EN, 0); t1 = sedi_rtc_get_us(); pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0; log_pm_stat(&pm_stats.d0i2, t0, t1); if (pm_ctx.aon_share->pg_exit) log_pm_stat(&pm_stats.pg, t0, t1); /* restore interrupts */ pm_enable_irqs(ioapic_state); sedi_core_irq_disable(SEDI_IRQ_PMU2IOAPIC); } static void enter_d0i3(void) { uint64_t ioapic_state; uint64_t t0, t1; ioapic_state = sedi_core_get_irq_map(); pm_disable_irqs(ioapic_state); sedi_core_irq_enable(SEDI_IRQ_PMU2IOAPIC); sedi_core_irq_enable(SEDI_IRQ_RESET_PREP); t0 = sedi_rtc_get_us(); pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0I3; /* enable Trunk Clock Gating (TCG) of ISH */ write32(CCU_TCG_EN, 1); /* enable power gating of RF(Cache) and ROMs */ write32(PMU_RF_ROM_PWR_CTRL, 1); switch_to_aontask(); /* returned from aontask */ if (pm_ctx.aon_share->pg_exit) pg_exit_restore_hw(); /* disable power gating of RF(Cache) and ROMs */ write32(PMU_RF_ROM_PWR_CTRL, 0); /* disable Trunk Clock Gating (TCG) of ISH */ write32(CCU_TCG_EN, 0); t1 = sedi_rtc_get_us(); pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0; log_pm_stat(&pm_stats.d0i3, t0, t1); if (pm_ctx.aon_share->pg_exit) log_pm_stat(&pm_stats.pg, t0, t1); /* restore interrupts */ pm_enable_irqs(ioapic_state); sedi_core_irq_disable(SEDI_IRQ_PMU2IOAPIC); } static void pre_setting_d0ix(void) { write32(PMU_VNN_REQ, read32(PMU_VNN_REQ)); uart_to_idle(); } static void post_setting_d0ix(void) { uart_port_restore(); } void sedi_pm_enter_power_state(int state) { switch (state) { case ISH_PM_STATE_D0I1: #ifndef CONFIG_SOC_INTEL_ISH_5_6_0 pre_setting_d0ix(); #endif enter_d0i1(); #ifndef CONFIG_SOC_INTEL_ISH_5_6_0 post_setting_d0ix(); #endif break; case ISH_PM_STATE_D0I2: pre_setting_d0ix(); enter_d0i2(); post_setting_d0ix(); check_aon_task_status(); break; case ISH_PM_STATE_D0I3: pre_setting_d0ix(); enter_d0i3(); post_setting_d0ix(); check_aon_task_status(); break; default: enter_d0i0(); break; } } static void reset_bcg(void) { write32(CCU_BCG_MIA, 0); write32(CCU_BCG_DMA, 0); write32(CCU_BCG_I2C, 0); write32(CCU_BCG_SPI, 0); write32(CCU_BCG_UART, 0); write32(CCU_BCG_GPIO, 0); } static void handle_d3(uint32_t irq_vec) { write32(PMU_D3_STATUS, read32(PMU_D3_STATUS)); if (read32(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 */ write32(SEDI_IOAPIC_EOI, irq_vec); write32(LAPIC_EOI, 0x0); if (!(pm_ctx.aon_share->host_in_suspend)) ish_pm_reset(ISH_PM_STATE_D3); } } static void pcie_dev_isr(void) { handle_d3(SEDI_VEC_PCIEDEV); } /** * 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 */ } static void reset_prep_isr(void) { /* mask reset prep avail interrupt */ write32(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 */ write32(SEDI_IOAPIC_EOI, SEDI_VEC_RESET_PREP); write32(LAPIC_EOI, 0x0); ish_mia_reset(); __builtin_unreachable(); } void sedi_pm_init(void) { /* clear reset bit */ write32(ISH_RST_REG, 0); /* clear reset history register in CCU */ write32(CCU_RST_HST, read32(CCU_RST_HST)); /* disable TCG and disable BCG */ write32(CCU_TCG_EN, 0); reset_bcg(); init_aon_task(); write32(PMU_GPIO_WAKE_MASK0, 0); write32(PMU_GPIO_WAKE_MASK1, 0); /* unmask all wake up events */ write32(PMU_MASK_EVENT, ~PMU_MASK_EVENT_BIT_ALL); write32(PMU_ISH_FABRIC_CNT, (read32(PMU_ISH_FABRIC_CNT) & 0xffff0000) | FABRIC_IDLE_COUNT); write32(PMU_PGCB_CLKGATE_CTRL, TRUNK_CLKGATE_COUNT); IRQ_CONNECT(SEDI_IRQ_RESET_PREP, 5, reset_prep_isr, 0, IOAPIC_LEVEL); IRQ_CONNECT(SEDI_IRQ_PMU2IOAPIC, 5, pmu_wakeup_isr, 0, IOAPIC_LEVEL); IRQ_CONNECT(SEDI_IRQ_PCIEDEV, 5, pcie_dev_isr, 0, IOAPIC_LEVEL); /* unmask reset prep avail interrupt */ write32(PMU_RST_PREP, 0); sedi_core_irq_enable(SEDI_IRQ_RESET_PREP); /* unmask D3 and BME interrupts */ write32(PMU_D3_STATUS, read32(PMU_D3_STATUS) & (PMU_D3_BIT_SET | PMU_BME_BIT_SET)); if ((!(read32(PMU_D3_STATUS) & PMU_D3_BIT_SET)) && (read32(PMU_D3_STATUS) & PMU_BME_BIT_SET)) write32(PMU_D3_STATUS, read32(PMU_D3_STATUS)); sedi_core_irq_enable(SEDI_IRQ_PCIEDEV); } void ish_pm_reset(enum ish_pm_state pm_state) { if (pm_ctx.aon_valid) { handle_reset_in_aontask(pm_state); } else { ish_mia_reset(); } __builtin_unreachable(); } void sedi_pm_reset(void) { ish_mia_reset(); } void sedi_pm_host_suspend(uint32_t suspend) { pm_ctx.aon_share->host_in_suspend = suspend; } /* * helper for print idle_stats */ static void print_stats(const char *name, const struct pm_stat *stat) { if (stat->count) PM_LOG(" %s:\n" " counts: %llu\n" " time: %.6llu ms\n", name, stat->count, (stat->total_time_us)/1000); } /** * Print low power idle statistics */ void command_idle_stats(void) { #ifdef PM_DEBUG_PRINTS struct ish_aon_share *aon_share = pm_ctx.aon_share; #endif uint64_t tall; tall = sedi_rtc_get_us(); PM_LOG("Aontask exists: %s\n", pm_ctx.aon_valid ? "Yes" : "No"); PM_LOG("Total time on: %.6llu ms\n", tall/1000); PM_LOG("Idle sleep:\n"); print_stats("D0i0", &pm_stats.d0i0); PM_LOG("Deep sleep:\n"); print_stats("D0i1", &pm_stats.d0i1); print_stats("D0i2", &pm_stats.d0i2); print_stats("D0i3", &pm_stats.d0i3); print_stats("IPAPG", &pm_stats.pg); if (pm_ctx.aon_valid) { PM_LOG(" Aontask status:\n"); PM_LOG(" last error: %u\n", aon_share->last_error); PM_LOG(" error counts: %u\n", aon_share->error_count); } }