473 lines
13 KiB
C
473 lines
13 KiB
C
/*
|
|
* standby driver for allwinnertech
|
|
*
|
|
* Copyright (C) 2015 allwinnertech Ltd.
|
|
* Author: Ming Li <liming@allwinnertech.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
#include "standby.h"
|
|
#include "../pm.h"
|
|
#include "main.h"
|
|
#include "dram/mctl_standby.h"
|
|
|
|
#define SUPER_FALG (0x10000)
|
|
|
|
static void cache_count_init(void);
|
|
static void cache_count_get(void);
|
|
static void cache_count_output(void);
|
|
static __u32 sp_backup;
|
|
static struct aw_pm_info pm_info;
|
|
static extended_standby_t extended_standby_para_info;
|
|
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;
|
|
|
|
static int dram_enter_selfresh(extended_standby_t *para);
|
|
static int dram_exit_selfresh(extended_standby_t *para);
|
|
static int cpu_enter_lowfreq(void);
|
|
static int cpu_freq_resume(void);
|
|
static int bus_enter_lowfreq(extended_standby_t *para);
|
|
static int bus_freq_resume(extended_standby_t *para);
|
|
static void query_wakeup_source(struct aw_pm_info *arg);
|
|
losc_enter_ss_func losc_enter_ss;
|
|
|
|
static u32 before_crc;
|
|
static u32 after_crc;
|
|
/* static unsigned int backup_actlr;*/
|
|
/* #define CHECK_CACHE_TLB_MISS */
|
|
#ifdef CHECK_CACHE_TLB_MISS
|
|
/* #define STATS_CACHE_TLB_MISS */
|
|
/* #define STATS_BUS_ACCESS */
|
|
#define STATS_CACHE_ACCESS
|
|
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
|
|
|
|
int standby_main(struct aw_pm_info *arg)
|
|
{
|
|
char *tmp_ptr = (char *)&__bss_start;
|
|
save_mem_status(STANDBY_START | 0X01);
|
|
if (!arg) {
|
|
/* standby parameter is invalid */
|
|
return -1;
|
|
}
|
|
|
|
/* clear bss segment */
|
|
do {
|
|
*tmp_ptr++ = 0;
|
|
} while (tmp_ptr <= (char *)&__bss_end);
|
|
/* save stack pointer registger, switch stack to sram */
|
|
sp_backup = save_sp();
|
|
|
|
/* 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();
|
|
/* backup_actlr = disable_prefetch();
|
|
* disable_cache();
|
|
*/
|
|
save_mem_status(STANDBY_START | 0X02);
|
|
|
|
/* copy standby parameter from dram */
|
|
standby_memcpy(&pm_info, arg, sizeof(pm_info));
|
|
|
|
/* copy extended standby info */
|
|
if (0 != pm_info.standby_para.pextended_standby) {
|
|
standby_memcpy(&extended_standby_para_info,
|
|
(void *)(pm_info.standby_para.pextended_standby),
|
|
sizeof(extended_standby_para_info));
|
|
}
|
|
|
|
/* preload tlb for standby */
|
|
mem_preload_tlb();
|
|
/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
|
|
/* init module before dram enter selfrefresh */
|
|
/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
|
|
/*init perf counter for timing: need double check */
|
|
init_perfcounters(1, 0);
|
|
standby_clk_init();
|
|
mem_clk_init(1);
|
|
save_mem_status(STANDBY_START | 0X03);
|
|
/* init uart for print */
|
|
if (unlikely
|
|
(pm_info.standby_para.debug_mask & PM_STANDBY_PRINT_STANDBY))
|
|
serial_init_manager();
|
|
|
|
/* calc dram checksum */
|
|
if (standby_dram_crc_enable(&(extended_standby_para_info.soc_dram_state))) {
|
|
before_crc = standby_dram_crc(&(extended_standby_para_info.soc_dram_state));
|
|
mem_preload_tlb();
|
|
}
|
|
|
|
save_mem_status(STANDBY_START | 0X04);
|
|
/* cpu reduce frequency */
|
|
cpu_enter_lowfreq();
|
|
|
|
standby_twi_init(pm_info.pmu_arg.twi_port);
|
|
|
|
/* process standby */
|
|
if (unlikely
|
|
(pm_info.standby_para.
|
|
debug_mask & PM_STANDBY_PRINT_CACHE_TLB_MISS)) {
|
|
cache_count_init();
|
|
}
|
|
/* enable dram enter into self-refresh */
|
|
dram_enter_selfresh(&extended_standby_para_info);
|
|
|
|
save_mem_status(STANDBY_START | 0X05);
|
|
#ifdef CONFIG_AW_AXP
|
|
if (SUPER_STANDBY_FLAG
|
|
== extended_standby_para_info.id) {
|
|
save_super_flags(SUPER_FALG);
|
|
save_super_addr(pm_info.resume_addr);
|
|
save_mem_status(before_crc);
|
|
power_enter_super_calc(&pm_info, &extended_standby_para_info,
|
|
&losc_enter_ss);
|
|
save_mem_status(STANDBY_START | 0X0a);
|
|
if (NULL != losc_enter_ss) {
|
|
save_mem_status(before_crc);
|
|
losc_enter_ss();
|
|
asm("b .");
|
|
}
|
|
/* when apb2 clk src is losc, twi clk divide ratio need reconfig */
|
|
standby_twi_init_losc(pm_info.pmu_arg.twi_port);
|
|
}
|
|
/* power domain suspend */
|
|
dm_suspend(&pm_info, &extended_standby_para_info);
|
|
save_mem_status(STANDBY_START | 0X0b);
|
|
#endif
|
|
|
|
save_mem_status(STANDBY_START | 0X07);
|
|
/* bus reduce frequency:
|
|
* dram need enter selfresh state
|
|
* the reason is: the hosc, ldo will be close.
|
|
*/
|
|
bus_enter_lowfreq(&extended_standby_para_info);
|
|
|
|
/* cpu enter sleep, wait wakeup by interrupt */
|
|
/*
|
|
*/
|
|
asm("WFI");
|
|
/* bus freq resume */
|
|
bus_freq_resume(&extended_standby_para_info);
|
|
|
|
#ifdef CONFIG_AW_AXP
|
|
save_mem_status(RESUME0_START | 0X02);
|
|
/* power domain resume */
|
|
dm_resume(&extended_standby_para_info);
|
|
#endif
|
|
|
|
save_mem_status(RESUME0_START | 0X03);
|
|
/* dram out self-refresh */
|
|
dram_exit_selfresh(&extended_standby_para_info);
|
|
|
|
if (unlikely
|
|
(pm_info.standby_para.
|
|
debug_mask & PM_STANDBY_PRINT_CACHE_TLB_MISS)) {
|
|
cache_count_get();
|
|
cache_count_output();
|
|
}
|
|
|
|
standby_twi_exit();
|
|
/* cpu freq resume */
|
|
cpu_freq_resume();
|
|
save_mem_status(RESUME0_START | 0X04);
|
|
|
|
|
|
if (unlikely
|
|
(pm_info.standby_para.debug_mask & PM_STANDBY_PRINT_STANDBY))
|
|
serial_exit_manager();
|
|
|
|
/* restore stack pointer register, switch stack back to dram */
|
|
restore_sp(sp_backup);
|
|
/* 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)) {
|
|
/* need double check..*/
|
|
init_perfcounters(1, 0);
|
|
change_runtime_env();
|
|
delay_ms(5);
|
|
}
|
|
|
|
/* enable_cache();
|
|
* restore_prefetch(backup_actlr);
|
|
*/
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dram_enter_selfresh(extended_standby_t *para)
|
|
{
|
|
s32 ret = -1;
|
|
dram_disable_all_master();
|
|
|
|
ret = dram_power_save_process();
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int dram_exit_selfresh(extended_standby_t *para)
|
|
{
|
|
s32 ret = -1;
|
|
|
|
ret = dram_power_up_process(&(pm_info.dram_para));
|
|
|
|
if (standby_dram_crc_enable(&(para->soc_dram_state))) {
|
|
after_crc = standby_dram_crc(&(para->soc_dram_state));
|
|
printk("before_crc = 0x%x, after_crc = 0x%x.\n", before_crc,
|
|
after_crc);
|
|
if (after_crc != before_crc) {
|
|
save_mem_status(RESUME0_START | 0xe);
|
|
printk("dram crc error...\n");
|
|
asm("b .");
|
|
}
|
|
}
|
|
dram_enable_all_master();
|
|
return ret;
|
|
}
|
|
|
|
static int cpu_enter_lowfreq(void)
|
|
{
|
|
/* 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);
|
|
|
|
delay_ms(10);
|
|
|
|
/* switch cpu clock to HOSC:
|
|
* after switch to HOSC, the axi, ahb freq will change also
|
|
* which will lead the jtag exception. */
|
|
standby_clk_core2hosc();
|
|
delay_us(1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cpu_freq_resume(void)
|
|
{
|
|
/* switch cpu clock to core pll */
|
|
standby_clk_core2pll();
|
|
change_runtime_env();
|
|
delay_ms(5);
|
|
|
|
/*restore freq from 408M to 1008M */
|
|
standby_clk_set_pll_factor(&orig_pll);
|
|
change_runtime_env();
|
|
delay_ms(10);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bus_enter_lowfreq(extended_standby_t *para)
|
|
{
|
|
/* change ahb src to axi */
|
|
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.apb2_div = 0;
|
|
tmp_clk_div.apb2_pre_div = 0;
|
|
standby_clk_setdiv(&tmp_clk_div);
|
|
|
|
/* swtich apb2 to losc from 24M */
|
|
standby_clk_apb2losc();
|
|
change_runtime_env();
|
|
/* delay_ms(1); */
|
|
standby_clk_plldisable();
|
|
|
|
standby_clk_set_keyfield();
|
|
/* switch cpu to 32k */
|
|
save_mem_status(STANDBY_START | 0x0d);
|
|
standby_clk_core2losc();
|
|
if (NULL != losc_enter_ss) {
|
|
save_mem_status((__u32) losc_enter_ss);
|
|
losc_enter_ss();
|
|
asm("b .");
|
|
}
|
|
save_mem_status(STANDBY_START | 0x0f);
|
|
if (1 == para->soc_dram_state.selfresh_flag) {
|
|
/* printk("disable HOSC, and disable LDO\n"); */
|
|
/* disable HOSC, and disable LDO */
|
|
if (0 == (para->cpux_clk_state.osc_en & BITMAP(OSC_HOSC_BIT)))
|
|
standby_clk_hoscdisable();
|
|
if (0 == (para->cpux_clk_state.osc_en & BITMAP(OSC_LDO1_BIT)))
|
|
standby_clk_ldodisable();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int bus_freq_resume(extended_standby_t *para)
|
|
{
|
|
if (1 == para->soc_dram_state.selfresh_flag) {
|
|
if (0 == (para->cpux_clk_state.osc_en & BITMAP(OSC_LDO1_BIT))) {
|
|
/* enable LDO, enable HOSC */
|
|
standby_clk_ldoenable();
|
|
/* delay 1ms for power be stable */
|
|
/* 3ms */
|
|
standby_delay_cycle(1);
|
|
}
|
|
if (0 == (para->cpux_clk_state.osc_en & BITMAP(OSC_HOSC_BIT))) {
|
|
standby_clk_hoscenable();
|
|
/* 3ms */
|
|
standby_delay_cycle(1);
|
|
}
|
|
}
|
|
/* switch clock to hosc */
|
|
standby_clk_core2hosc();
|
|
standby_clk_unset_keyfield();
|
|
|
|
/* swtich apb2 to hosc */
|
|
standby_clk_apb2hosc();
|
|
|
|
/* enable pll */
|
|
standby_clk_pllenable();
|
|
change_runtime_env();
|
|
delay_ms(10);
|
|
|
|
/* restore clock division */
|
|
standby_clk_setdiv(&clk_div);
|
|
|
|
standby_clk_bus_src_restore();
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CHECK_CACHE_TLB_MISS
|
|
|
|
static void cache_count_init(void)
|
|
{
|
|
#if defined(STATS_CACHE_TLB_MISS)
|
|
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);
|
|
#elif defined(STATS_BUS_ACCESS)
|
|
set_event_counter(BR_PRED);
|
|
set_event_counter(MEM_ACCESS);
|
|
set_event_counter(BUS_ACCESS);
|
|
set_event_counter(INST_SPEC);
|
|
init_event_counter(1, 0);
|
|
d_cache_miss_start = get_event_counter(BR_PRED);
|
|
d_tlb_miss_start = get_event_counter(MEM_ACCESS);
|
|
i_tlb_miss_start = get_event_counter(BUS_ACCESS);
|
|
i_cache_miss_start = get_event_counter(INST_SPEC);
|
|
#elif defined(STATS_CACHE_ACCESS)
|
|
set_event_counter(D_CACHE_ACCESS);
|
|
set_event_counter(D_CACHE_EVICT);
|
|
set_event_counter(L2_CACHE_REFILL);
|
|
set_event_counter(PREFETCH_LINEFILL);
|
|
init_event_counter(1, 0);
|
|
d_cache_miss_start = get_event_counter(D_CACHE_ACCESS);
|
|
d_tlb_miss_start = get_event_counter(D_CACHE_EVICT);
|
|
i_tlb_miss_start = get_event_counter(L2_CACHE_REFILL);
|
|
i_cache_miss_start = get_event_counter(PREFETCH_LINEFILL);
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
static void cache_count_get(void)
|
|
{
|
|
#if defined(STATS_CACHE_TLB_MISS)
|
|
d_cache_miss_end = get_event_counter(BR_PRED);
|
|
d_tlb_miss_end = get_event_counter(MEM_ACCESS);
|
|
i_tlb_miss_end = get_event_counter(BUS_ACCESS);
|
|
i_cache_miss_end = get_event_counter(INST_SPEC);
|
|
#elif defined(STATS_BUS_ACCESS)
|
|
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);
|
|
#elif defined(STATS_CACHE_ACCESS)
|
|
d_cache_miss_end = get_event_counter(D_CACHE_ACCESS);
|
|
d_tlb_miss_end = get_event_counter(D_CACHE_EVICT);
|
|
i_tlb_miss_end = get_event_counter(L2_CACHE_REFILL);
|
|
i_cache_miss_end = get_event_counter(PREFETCH_LINEFILL);
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
|
|
static void cache_count_output(void)
|
|
{
|
|
if (d_cache_miss_end || d_tlb_miss_end || i_tlb_miss_end
|
|
|| i_cache_miss_end) {
|
|
printk
|
|
("=============================NOTICE====================================. \n");
|
|
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);
|
|
asm("b .");
|
|
} else {
|
|
printk("no miss. \n");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
#else
|
|
static void cache_count_init(void)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static void cache_count_get(void)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static void cache_count_output(void)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|