/* * Copyright (c) 2007-2017 Allwinnertech Co., Ltd. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ /* ********************************************************************************************************* * LINUX-KERNEL * AllWinner Linux Platform Develop Kits * Kernel Module * * (c) Copyright 2006-2011, kevin.z China * All Rights Reserved * * File : standby.c * By : kevin.z * Version : v1.0 * Date : 2011-5-30 18:34 * Descript: platform standby fucntion. * Update : date auther ver notes ********************************************************************************************************* */ #include "standby_i.h" static void restore_ccu(void); static void backup_ccu(void); static void destory_mmu(void); static void restore_mmu(void); static void cache_count_init(void); static void cache_count_get(void); static void cache_count_output(void); extern char *__bss_start; extern char *__bss_end; extern char *__standby_start; extern char *__standby_end; static __u32 sp_backup; static __u32 ttb_0r_backup; #define MMU_START (0xc0004000) #define MMU_END (0xc0007ffc) /* reserve 0xffff0000 range. */ static __u32 mmu_backup[((MMU_END - MMU_START) >> 2) + 1]; static void standby(void); #ifdef CHECK_CACHE_TLB_MISS int d_cache_miss_start; int d_tlb_miss_start; int i_tlb_miss_start; int i_cache_miss_start; int d_cache_miss_end; int d_tlb_miss_end; int i_tlb_miss_end; int i_cache_miss_end; #endif static void standby(void); #ifdef CONFIG_SUNXI_ARISC static void arisc_standby(void); #else unsigned int dram_suspend_flag; static __u8 dram_traning_area_back[DRAM_TRANING_SIZE]; static void cpux_standby(void); static struct pll_factor_t orig_pll; static struct pll_factor_t local_pll; static struct standby_clk_div_t clk_div; static struct standby_clk_div_t tmp_clk_div; #endif /* parameter for standby, it will be transfered from sys_pwm module */ struct aw_pm_info pm_info; unsigned int power_regu_tree[VCC_MAX_INDEX]; struct normal_standby_para normal_standby_para_info; extended_standby_t extended_standby_para_info; /* ********************************************************************************************************* * STANDBY MAIN PROCESS ENTRY * * Description: standby main process entry. * * Arguments : arg pointer to the parameter that transfered from sys_pwm module. * * Returns : none * * Note : the code&data may resident in cache. ********************************************************************************************************* */ int standby_main(struct aw_pm_info *arg) { char *tmpPtr = (char *)&__bss_start; if (!arg) { /* standby parameter is invalid */ return -1; } /* flush data and instruction tlb, there is 32 items of data tlb and 32 items of instruction tlb, The TLB is normally allocated on a rotating basis. The oldest entry is always the next allocated */ mem_flush_tlb(); /* clear bss segment */ do { *tmpPtr++ = 0; } while (tmpPtr <= (char *)&__bss_end); /* save stack pointer registger, switch stack to sram */ sp_backup = save_sp(); save_mem_status(RESUME0_START | 0X02); /* copy standby parameter from dram */ standby_memcpy(&pm_info, arg, sizeof(pm_info)); standby_memcpy(&power_regu_tree, arg->pmu_arg.soc_power_tree, sizeof(power_regu_tree)); /* preload tlb for standby */ mem_preload_tlb(); /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ /* init module before dram enter selfrefresh */ /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ /*init perf counter for timing. */ init_perfcounters(1, 0); /* need double check.. */ standby_clk_init(); mem_clk_init(1); #ifdef CONFIG_SUNXI_ARISC standby_arisc_init(); #endif save_mem_status(RESUME0_START | 0X03); if (unlikely (pm_info.standby_para.debug_mask & PM_STANDBY_PRINT_STANDBY)) { /* don't need init serial ,depend kernel? */ serial_init_manager(); printk("normal standby wakeup src config = 0x%x. \n", pm_info.standby_para.event); save_mem_status(RESUME0_START | 0X05); } /* copy extended standby info */ if (0 != pm_info.standby_para.pextended_standby) { printk("use extended_standby cfg.\n"); standby_memcpy(&extended_standby_para_info, (void *)(DRAM_EXTENDED_STANDBY_INFO_VA), sizeof(extended_standby_para_info)); save_mem_status(RESUME0_START | 0X06); } save_mem_status(RESUME0_START | 0X07); /* init some system wake source */ /* process standby */ if (unlikely (pm_info.standby_para. debug_mask & PM_STANDBY_PRINT_CACHE_TLB_MISS)) { cache_count_init(); } save_mem_status(RESUME0_START | 0X08); standby(); /* restore stack pointer register, switch stack back to dram */ restore_sp(sp_backup); if (unlikely (pm_info.standby_para.debug_mask & PM_STANDBY_PRINT_STANDBY)) { /* restore serial clk & gpio config. */ serial_exit_manager(); } #ifdef CONFIG_SUNXI_ARISC standby_arisc_exit(); #endif /* report which wake source wakeup system */ arg->standby_para.event = pm_info.standby_para.event; arg->standby_para.axp_event = pm_info.standby_para.axp_event; /* enable_cache(); */ save_mem_status(RESUME0_START | 0x0c); /* FIXME: seems the dram para have some err. * 1. the dram crc, in normal standby case, may have crc err. * such as the region: 0x40000000 -> 0x40010000 * 2. need delay, such as: 5ms, before return to kernel. * 3. the bug is inexplicable, the summary as follow: * 3.1 rtc err code: may be 5004, or 5005, or 8000. * mean, the reason for cpu die is not sure. * 3.2 add delay here, bring good effect for cpu running. * but if we have an condition expresstion before delay, * it may have no effect. * so, memory attribute or bus behavior may have * contribute to this bug. to locate the real reason, * not use compile optiomize is better option. * 3.3 dram crc, in normal standby case, may have crc err. * 4. the right flow to correct this bug is: * 4.1 make sure dram crc is right. (right now, crc err occur.) * 4.2 make sure the sramA1 memory attribute is correct. * (right now, strongly-order is in use? conflict with trm.) */ if (likely(pm_info.standby_para.debug_mask & PM_STANDBY_TEST)) { init_perfcounters(1, 0); /* need double check.. */ change_runtime_env(); delay_ms(5); } return 0; } #ifndef CONFIG_SUNXI_ARISC /* ********************************************************************************************************* * SYSTEM PWM ENTER STANDBY MODE * * Description: cpux enter standby mode. * * Arguments : none * * Returns : none; ********************************************************************************************************* */ static void cpux_standby(void) { unsigned int dram_crc_bef = 0; unsigned int dram_crc_aft = 0; if ((NULL != pm_info.standby_para.pextended_standby)) { standby_set_dram_crc_paras(extended_standby_para_info. soc_dram_state.crc_en, extended_standby_para_info. soc_dram_state.crc_start, extended_standby_para_info. soc_dram_state.crc_len); dram_crc_bef = standby_dram_crc(); } if ((NULL != pm_info.standby_para.pextended_standby) && (0 == extended_standby_para_info.soc_dram_state.selfresh_flag)) { /* not enter selfresh according user define. */ printk("selfresh flag = 0. \n"); } else { /*dram crc */ /* backup dram traning area */ standby_memcpy((char *)dram_traning_area_back, (char *)DRAM_BASE_ADDR, DRAM_TRANING_SIZE); /* dram enter self-refresh flag */ dram_suspend_flag = 1; dram_power_save_process(); /* mctl_self_refresh_entry(); */ /* gating off dram clock */ /* standby_clk_dramgating(0); */ } /* backup cpu freq */ standby_clk_get_pll_factor(&orig_pll); /* backup bus src */ standby_clk_bus_src_backup(); /*lower freq from 1008M to 408M */ local_pll.FactorN = 16; local_pll.FactorK = 0; local_pll.FactorM = 0; local_pll.FactorP = 0; standby_clk_set_pll_factor(&local_pll); change_runtime_env(); delay_ms(10); /* switch cpu clock to HOSC, and disable pll */ standby_clk_core2hosc(); change_runtime_env(); delay_us(1); if (extended_standby_para_info.pmu_id) { int i = 0; standby_twi_init(pm_info.pmu_arg.twi_port); for (i = 0; i < VCC_MAX_INDEX; i++) { if ((0 < extended_standby_para_info.soc_pwr_dm_state. volt[i]) && (4200 > extended_standby_para_info.soc_pwr_dm_state. volt[i])) { standby_set_power(extended_standby_para_info. pmu_id, i, &power_regu_tree, extended_standby_para_info. soc_pwr_dm_state.volt[i]); } } } /* change ahb src to axi? losc? */ standby_clk_bus_src_set(); standby_clk_getdiv(&clk_div); /* set clock division cpu:axi:ahb:apb = 2:2:2:1 */ tmp_clk_div.axi_div = 0; tmp_clk_div.ahb_div = 0; tmp_clk_div.ahb_pre_div = 0; tmp_clk_div.apb_div = 0; tmp_clk_div.apb_pre_div = 0; standby_clk_setdiv(&tmp_clk_div); /* swtich apb2 to losc */ standby_clk_apb2losc(); change_runtime_env(); /* delay_ms(1); */ standby_clk_plldisable(); /* switch cpu to 32k */ standby_clk_core2losc(); #if (ALLOW_DISABLE_HOSC) if (1 == dram_suspend_flag) { /* disable HOSC, and disable LDO */ standby_clk_hoscdisable(); standby_clk_ldodisable(); } #endif /* cpu enter sleep, wait wakeup by interrupt */ asm("WFI"); #if (ALLOW_DISABLE_HOSC) if (1 == dram_suspend_flag) { /* enable LDO, enable HOSC */ standby_clk_ldoenable(); /* delay 1ms for power be stable */ /* 3ms */ standby_delay_cycle(1); standby_clk_hoscenable(); /* 3ms */ standby_delay_cycle(1); } #endif /* switch clock to hosc */ standby_clk_core2hosc(); /* swtich apb2 to hosc */ standby_clk_apb2hosc(); /* restore clock division */ standby_clk_setdiv(&clk_div); /* check system wakeup event */ pm_info.standby_para.event = 0; /* check system wakeup event */ pm_info.standby_para.event |= mem_query_int(INT_SOURCE_EXTNMI) ? 0 : CPU0_WAKEUP_EXINT; pm_info.standby_para.event |= mem_query_int(INT_SOURCE_USBOTG) ? 0 : CPU0_WAKEUP_USB; pm_info.standby_para.event |= mem_query_int(INT_SOURCE_USBEHCI0) ? 0 : CPU0_WAKEUP_USB; pm_info.standby_para.event |= mem_query_int(INT_SOURCE_USBEHCI1) ? 0 : CPU0_WAKEUP_USB; pm_info.standby_para.event |= mem_query_int(INT_SOURCE_USBEHCI2) ? 0 : CPU0_WAKEUP_USB; pm_info.standby_para.event |= mem_query_int(INT_SOURCE_USBOHCI0) ? 0 : CPU0_WAKEUP_USB; pm_info.standby_para.event |= mem_query_int(INT_SOURCE_USBOHCI1) ? 0 : CPU0_WAKEUP_USB; pm_info.standby_para.event |= mem_query_int(INT_SOURCE_USBOHCI2) ? 0 : CPU0_WAKEUP_USB; pm_info.standby_para.event |= mem_query_int(INT_SOURCE_LRADC) ? 0 : CPU0_WAKEUP_KEY; pm_info.standby_para.event |= mem_query_int(INT_SOURCE_IR0) ? 0 : CPU0_WAKEUP_IR; pm_info.standby_para.event |= mem_query_int(INT_SOURCE_ALARM) ? 0 : CPU0_WAKEUP_ALARM; pm_info.standby_para.event |= mem_query_int(INT_SOURCE_TIMER0) ? 0 : CPU0_WAKEUP_TIMEOUT; /* enable pll */ standby_clk_pllenable(); change_runtime_env(); delay_ms(10); if (extended_standby_para_info.pmu_id) { /* restore voltage for exit standby */ standby_recovery_power(extended_standby_para_info.pmu_id); standby_twi_exit(); } standby_clk_bus_src_restore(); /* switch cpu clock to core pll */ standby_clk_core2pll(); change_runtime_env(); delay_ms(10); /*restore freq from 384 to 1008M */ standby_clk_set_pll_factor(&orig_pll); change_runtime_env(); delay_ms(5); if (1 == dram_suspend_flag) { /* gating on dram clock */ /* standby_clk_dramgating(1); */ /* enable watch-dog to preserve dram training failed */ /* standby_tmr_enable_watchdog(); */ /* restore dram */ dram_power_up_process(0); /* mctl_self_refresh_exit(); */ /* init_DRAM(&pm_info.dram_para); */ /* disable watch-dog */ /* standby_tmr_disable_watchdog(); */ dram_suspend_flag = 0; /* restore dram traning area */ standby_memcpy((char *)DRAM_BASE_ADDR, (char *)dram_traning_area_back, DRAM_TRANING_SIZE); } if ((NULL != pm_info.standby_para.pextended_standby)) { dram_crc_aft = standby_dram_crc(); if (dram_crc_aft != dram_crc_bef) { save_mem_status(RESUME0_START | 0X0b); printk ("ERR: (dram_crc_bef = 0x%x) != (dram_crc_aft = 0x%x) \n", dram_crc_bef, dram_crc_aft); while (1) { }; } else { printk ("OK: (dram_crc_bef = 0x%x) == (dram_crc_aft = 0x%x) \n", dram_crc_bef, dram_crc_aft); } } return; } #endif #ifdef CONFIG_SUNXI_ARISC /* ********************************************************************************************************* * SYSTEM PWM ENTER STANDBY MODE * * Description: enter standby mode. * * Arguments : none * * Returns : none; ********************************************************************************************************* */ static void arisc_standby(void) { /*backup clk freq and voltage */ backup_ccu(); /*notify arisc enter normal standby */ normal_standby_para_info.event = pm_info.standby_para.axp_event; normal_standby_para_info.timeout = pm_info.standby_para.timeout; normal_standby_para_info.gpio_enable_bitmap = pm_info.standby_para.gpio_enable_bitmap; standby_arisc_standby_normal((&normal_standby_para_info)); /* cpu enter sleep, wait wakeup by interrupt */ asm("WFI"); /*restore cpu0 ccu: enable hosc and change to 24M. */ restore_ccu(); /*query wakeup src */ standby_arisc_query_wakeup_src((unsigned long *) &(pm_info.standby_para.axp_event)); save_mem_status(RESUME1_START | 0x01); /* enable watch-dog to prevent in case dram training failed */ mem_tmr_enable_watchdog(); save_mem_status(RESUME1_START | 0x02); /* notify for cpus to: restore cpus freq and volt, restore dram */ standby_arisc_notify_restore(STANDBY_ARISC_ASYNC); save_mem_status(RESUME1_START | 0x03); /* check system wakeup event */ pm_info.standby_para.event = 0; /* actually, msg_box int will be clear by arisc-driver. */ pm_info.standby_para.event |= mem_query_int(INT_SOURCE_MSG_BOX) ? 0 : CPU0_WAKEUP_MSGBOX; pm_info.standby_para.event |= mem_query_int(INT_SOURCE_LRADC) ? 0 : CPU0_WAKEUP_KEY; /*check completion status: only after restore completion, access dram is allowed. */ save_mem_status(RESUME1_START | 0x04); while (standby_arisc_check_restore_status()) { if (unlikely (pm_info.standby_para. debug_mask & PM_STANDBY_PRINT_STANDBY)) { printk("0xf1c20050 value: 0x%x. \n", *((volatile unsigned int *)0xf1c20050)); printk("0xf1c20000 value: 0x%x. \n", *((volatile unsigned int *)0xf1c20000)); }; } if (unlikely (pm_info.standby_para. debug_mask & PM_STANDBY_PRINT_CACHE_TLB_MISS)) { cache_count_get(); if (d_cache_miss_end || d_tlb_miss_end || i_tlb_miss_end || i_cache_miss_end) { printk ("=============================NOTICE====================================. \n"); cache_count_output(); } else { printk("no miss. \n"); /* cache_count_output(); */ } } save_mem_status(RESUME1_START | 0x05); /* disable watch-dog */ mem_tmr_disable_watchdog(); if (unlikely (pm_info.standby_para.debug_mask & PM_STANDBY_PRINT_STANDBY)) { printk("after mem_tmr_disable_watchdog. \n"); } return; } static void backup_ccu(void) { return; } /*change clk src to hosc*/ static void restore_ccu(void) { #if (ALLOW_DISABLE_HOSC) /* enable LDO, ldo1, enable HOSC */ standby_clk_ldoenable(); standby_clk_pll1enable(); /* delay 10ms for power be stable */ standby_delay_cycle(1); /* ?ms */ /* switch to 24M src */ standby_clk_core2hosc(); #endif return; } #endif /* ********************************************************************************************************* * destory_mmu * * Description: to destory the mmu mapping, so, the tlb miss will result in an data/cache abort * while not accessing dram. * Arguments : none * * Returns : none; ********************************************************************************************************* */ static void destory_mmu(void) { __u32 ttb_1r = 0; int i = 0; volatile __u32 *p_mmu = (volatile __u32 *)MMU_START; for (p_mmu = (volatile __u32 *)MMU_START; p_mmu < (volatile __u32 *)MMU_END; p_mmu++, i++) { mmu_backup[i] = *p_mmu; *p_mmu = 0; } flush_dcache(); /* u need to set ttbr0 to 0xc0004000? */ /* backup */ asm volatile ("mrc p15, 0, %0, c2, c0, 0" : "=r" (ttb_0r_backup)); /* get ttbr1 */ asm volatile ("mrc p15, 0, %0, c2, c0, 1" : "=r" (ttb_1r)); /* use ttbr1 to set ttbr0 */ asm volatile ("mcr p15, 0, %0, c2, c0, 0" : : "r" (ttb_1r)); asm volatile ("dsb"); asm volatile ("isb"); return; } static void restore_mmu(void) { volatile __u32 *p_mmu = (volatile __u32 *)MMU_START; int i = 0; /* restore ttbr0 */ asm volatile ("mcr p15, 0, %0, c2, c0, 0" : : "r" (ttb_0r_backup)); asm volatile ("dsb"); asm volatile ("isb"); for (p_mmu = (volatile __u32 *)MMU_START; p_mmu < (volatile __u32 *)MMU_END; p_mmu++, i++) { *p_mmu = mmu_backup[i]; } flush_dcache(); return; } #ifdef CHECK_CACHE_TLB_MISS static void cache_count_init(void) { set_event_counter(D_CACHE_MISS); set_event_counter(D_TLB_MISS); set_event_counter(I_CACHE_MISS); set_event_counter(I_TLB_MISS); init_event_counter(1, 0); d_cache_miss_start = get_event_counter(D_CACHE_MISS); d_tlb_miss_start = get_event_counter(D_TLB_MISS); i_tlb_miss_start = get_event_counter(I_TLB_MISS); i_cache_miss_start = get_event_counter(I_CACHE_MISS); return; } static void cache_count_get(void) { d_cache_miss_end = get_event_counter(D_CACHE_MISS); d_tlb_miss_end = get_event_counter(D_TLB_MISS); i_tlb_miss_end = get_event_counter(I_TLB_MISS); i_cache_miss_end = get_event_counter(I_CACHE_MISS); return; } static void cache_count_output(void) { printk("d_cache_miss_start = %d, d_cache_miss_end= %d. \n", d_cache_miss_start, d_cache_miss_end); printk("d_tlb_miss_start = %d, d_tlb_miss_end= %d. \n", d_tlb_miss_start, d_tlb_miss_end); printk("i_cache_miss_start = %d, i_cache_miss_end= %d. \n", i_cache_miss_start, i_cache_miss_end); printk("i_tlb_miss_start = %d, i_tlb_miss_end= %d. \n", i_tlb_miss_start, i_tlb_miss_end); return; } #else static void cache_count_init(void) { return; } static void cache_count_get(void) { return; } static void cache_count_output(void) { return; } #endif /* ********************************************************************************************************* * SYSTEM PWM ENTER STANDBY MODE * * Description: cpux enter standby mode. * * Arguments : none * * Returns : none; ********************************************************************************************************* */ static void standby(void) { #ifdef CONFIG_SUNXI_ARISC arisc_standby(); #else cpux_standby(); #endif }