1. 程式人生 > >一個Linux平臺PM功能初探

一個Linux平臺PM功能初探

-c padding start out att void ifd acer slab

WatchDog

用戶空間節點

/dev/watchdog

/sys/bus/platform/devices/zx29_ap_wdt.0

/sys/bus/platform/drivers/zx29_ap_wdt

時鐘資源

arch/arm/mach-zx297520v3/include/mach/iomap.h

#define ZX_LSP_CRPM_BASE (ZX_LSP_BASE)


drivers/clk/zte/clk-zx297520v3.c

/**************************************************************************
* ap wdt
***************************************************************************
*/
char* lsp_wdt_wclk_parents[2] =
{
NAME_CLK(main_clk_32k),
NAME_CLK(main_clk_26m)
};

static struct zx29_hwclk ap_wdt_apb =
{
.clk_en_reg ={ZX_LSP_CRPM_BASE+0x40, 0, 1},
.clk_div_reg ={ZX_LSP_CRPM_BASE+0x40, 0, 0},
.clk_gate_reg ={ZX_LSP_CRPM_BASE+0x40, 11, 1},
};
DEFINE_ZX29_CLK(ap_wdt_apb,peripheral_clk_ops,0,NULL);

static struct zx29_hwclk ap_wdt_work =
{
.clk_en_reg ={ZX_LSP_CRPM_BASE+0x40, 1, 1},
.clk_sel_reg ={ZX_LSP_CRPM_BASE+0x40, 4, 1},
.clk_div_reg ={ZX_LSP_CRPM_BASE+0x40, 12, 4},
.clk_gate_reg ={ZX_LSP_CRPM_BASE+0x40, 10, 1},
};
DEFINE_ZX29_CLK(ap_wdt_work,peripheral_clk_ops,2,lsp_wdt_wclk_parents);

struct clk_lookup periph_clocks_lookups[] = {
/* dev_id name clk struct */

CLK_ZX29_CONFIG("zx29_ap_wdt.0", "work_clk", &ap_wdt_work_clk), 表示設備名稱zx29_ap_wdt.0下的work_clk
CLK_ZX29_CONFIG("zx29_ap_wdt.0", "apb_clk", &ap_wdt_apb_clk),

};

platform設備代碼

中:

arch/arm/mach-zx297520v3/include/mach/iomap.h

#define ZX_AP_WDT_BASE (ZX_LSP_BASE + 0xE000)


arch/arm/mach-zx297520v3/zx297520v3-devices.c (這其中的資源會在probe中使用到)

static struct resource wdt_res[] = {
[0] = {
.start = (u32)ZX_AP_WDT_BASE,
.end = (u32)ZX_AP_WDT_BASE + SZ_4K - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = WDT_INT,
.end = WDT_INT,
.flags = IORESOURCE_IRQ,
},
};

static struct platform_device zx297520v2_wdt_device = {
.name = "zx29_ap_wdt",
.id = 0,
.resource = wdt_res,
.num_resources = ARRAY_SIZE(wdt_res),
};

struct platform_device *zx29_device_table[] __initdata={
/* --------------------------------------------------------------------
* ---------- for solution integration department --------- start
* -------------------------------------------------------------------- */

#ifdef CONFIG_ZX29_WATCHDOG
&zx297520v2_wdt_device,
#endif


}

platform驅動代碼

drivers/watchdog/zx29_wdt.c中:

時鐘相關

watchdog_init->platform_driver_register(&zx29_wdt_driver)-|->zx29_wdt_probe
-|->__devexit_p(zx29_wdt_remove)
-|->zx29_wdt_shutdown
-|->zx29_wdt_suspend
-|->zx29_wdt_resume
watchdog_exit->platform_driver_unregister(&zx29_wdt_driver)

cpuidle

menuconfig

CONFIG_CPU_IDLE=y
CONFIG_CPU_IDLE_GOV_LADDER=y
CONFIG_CPU_IDLE_GOV_MENU=y

cpuidle driver

在本平臺中cpuidle driver沒有作為單一的模塊,二是放在zx_pm_init中進行。

drivers/soc/zte/power/zx-cpuidle.c

zx_pm_init-|->pm_debug_init->idle_debug_init (/sys/zte_pm/cpuidle/)
|->zx_cpuidle_init->

drivers/soc/zte/power/zx297520v3-cpuidle.c中定義了cpuidle的狀態

#define WHOLE_CHIP_EXIT_LATENCY (4000) /* us */

#define LP2_DEFAULT_EXIT_LATENCY (500 + WHOLE_CHIP_EXIT_LATENCY) /* us */
#define LP2_MIN_POWER_OFF_TIME (500) /* us */

#define LP2_DELTA_EXIT_LATENCY (100) /* us -- for timer setting refresh time, should > 2us.

static struct cpuidle_state zx297520v3_cpuidle_set[] __initdata =
{
/* LP3 -- wfi */
[ZX_IDLE_CSTATE_LP3] =
{
.enter = zx_enter_idle,
.exit_latency = 2,
.target_residency = 5,
.flags = CPUIDLE_FLAG_TIME_VALID,
.name = "LP3",
.desc = "clock gating(WFI)",
},
/* LP2 -- POWEROFF */
[ZX_IDLE_CSTATE_LP2] =
{
.enter = zx_enter_idle,
.exit_latency = LP2_DEFAULT_EXIT_LATENCY, 4500us
.target_residency = LP2_DEFAULT_EXIT_LATENCY+LP2_MIN_POWER_OFF_TIME, 5000us
.flags = CPUIDLE_FLAG_TIME_VALID,
.name = "LP2",
.desc = "POWEROFF",
},
};

cpu_idle在start_kernel->rest_init->cpu_idle->while(1)中調用。cpu_idle調用cpuidle_idle_call:

cpu_idle->cpuidle_idle_call-|->trace_cpu_idle_rcuidle(next_state, dev->cpu);
|->trace_cpu_idle_rcuidle(PWR_EVENT_EXIT, dev->cpu);

cpuidle_curr_governor->select(drv, dev) 根據當前的governor,選擇合適的cpuidle狀態

entered_state = cpuidle_enter_state(dev, drv, next_state);

cpuidle_curr_governor->reflect(dev, entered_state)

cpuidle_curr_governor在cpuidle_switch_governor中進行設置,一個是在cpuidle_register_governor註冊時的時候,另一個是在store_current_governor通過sysfs節點設置cpuidle governor的時候。由於默認使用的是menu_governor,下面就來重點分析一下:

static struct cpuidle_governor menu_governor = {
.name = "menu",
.rating = 20,
.enable = menu_enable_device,
.select = menu_select,
.reflect = menu_reflect,
.owner = THIS_MODULE,
};

》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》

cpuidle_enter_state是執行根據governor確定的狀態執行,然後返回進入的狀態值。

cpuidle_enable_device設置cpuidle_enter_ops的值,cpuidle_enter_ops = drv->en_core_tk_irqen?cpuidle_enter_tk:cpuidle_enter;

cpuidle_enter調用target_state->enter(dev, drv, index);,指向zx_enter_idle。

zx_enter_idle-|->zx_pm_idle_prepare
|->zx_pm_idle_enter-|->zx_enter_deep_idle(ZX_IDLE_CSTATE_LP2)
               |->cpu_do_idle(ZX_IDLE_CSTATE_LP3)
|->dev->last_residency

cpuidle core

cpuidle governors

cpufreq

cpufreq driver

drivers/soc/zte/power/zx-cpufreq.c

drvers/soc/zte/power/zx297520v3-cpufreq.c

設置cpufreq的DVFS數據,在struct zx_dvfs_info中。

struct zx_dvfs_info {
unsigned int freq_cur_idx;
unsigned int pll_safe_idx;
unsigned int max_support_idx;
unsigned int min_support_idx;
struct clk *cpu_clk;
unsigned int *volt_table;
struct cpufreq_frequency_table *freq_table;
int (*set_freq)(unsigned int, unsigned int);
};

cpufreq driver想初始化DVFS通過調用zx29xx_cpufreq_init。

static int zx297520v3_cpufreq_init(struct zx_dvfs_info *info)
{
if(cpufreq_driver_inited)
return 0;

cpu_clk = clk_get(NULL, "cpu_clk");
if (IS_ERR(cpu_clk))
{
pr_info("[CPUFREQ] get cpu_clk error \n");
return PTR_ERR(cpu_clk);
}

info->freq_cur_idx = L1;
info->pll_safe_idx = L1;
info->max_support_idx = max_support_idx;
info->min_support_idx = min_support_idx;
info->cpu_clk = cpu_clk;
info->volt_table = zx297520v3_volt_table;
info->freq_table = zx297520v3_freq_table;
info->set_freq = zx297520v3_set_frequency;

cpufreq_driver_inited = 1;

INIT_DELAYED_WORK_DEFERRABLE(&pm_freq_work, pm_freq_func);
schedule_delayed_work(&pm_freq_work, PM_FREQ_DELAY);
pr_info("[CPUFREQ] zx297520v2_cpufreq_init ok \n");
return 0;
}


static int __init zx297520v3_freq_register(void)
{
zx29xx_cpufreq_init = zx297520v3_cpufreq_init;

return 0;
}

cpu_clk如下:

static struct zx29_hwclk cpu_work = {
/* reg_addr bit_offset bit_size */
.clk_en_reg ={0, 0, 0},
.clk_sel_reg={ZX_MATRIX_CRM_BASE+0x40, 0, 2},
.clk_div_reg={0, 0, 0},
};
DEFINE_ZX29_CLK(cpu_work,cpu_clk_ops,ARRAY_SIZE(cpu_clk_parents),cpu_clk_parents);

CLK_ZX29_CONFIG(NULL, "cpu_clk", &cpu_work_clk),

這裏通過zx297520v3_volt_table和zx297520v3_freq_table來達到OPP的概念,zx297520v3_set_frequency作為設置頻率的底層函數。

static unsigned int zx297520v3_volt_table[CPUFREQ_LEVEL_END] = {
1250000, 1150000, 1050000, /*975000, 950000,*/
};

static struct cpufreq_frequency_table zx297520v3_freq_table[] = { 實際的頻率只有兩種,而且不可以調壓
{L0, 624*1000},
{L1, 312*1000},
//{L2, 156*1000},
{0, CPUFREQ_TABLE_END},
};

cpufreq governors

cpufreq core

cpu hotplug

由於是單核CPU,所以不能使用hotplug功能。

wakelock

kernel/power/main.c

#ifdef CONFIG_PM_WAKELOCKS
static ssize_t wake_lock_show(struct kobject *kobj,
struct kobj_attribute *attr,
char *buf)
{
return pm_show_wakelocks(buf, true);
}

static ssize_t wake_lock_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t n)
{
int error = pm_wake_lock(buf);
return error ? error : n;
}

power_attr(wake_lock);

static ssize_t wake_unlock_show(struct kobject *kobj,
struct kobj_attribute *attr,
char *buf)
{
return pm_show_wakelocks(buf, false);
}

static ssize_t wake_unlock_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t n)
{
int error = pm_wake_unlock(buf);
return error ? error : n;
}

power_attr(wake_unlock);

#endif /* CONFIG_PM_WAKELOCKS */

static struct attribute * g[] = {

#ifdef CONFIG_PM_WAKELOCKS
&wake_lock_attr.attr,
&wake_unlock_attr.attr,
#endif

NULL,
};

kernel/power/wakelock.c

增加wakelock tracepoint

修改config.linux:

CONFIG_PM_DEBUG=y
CONFIG_PM_SLEEP_DEBUG=y
CONFIG_FTRACE=y
CONFIG_FUNCTION_TRACER=y
CONFIG_FUNCTION_GRAPH_TRACER=y
CONFIG_KPROBES=y
CONFIG_KPROBES_ON_FTRACE=y

修改include/trace/events/power.h,增加wakelock相關trace包括pm_wake_lock和pm_wake_unlock。

修改kernel/power/wakelock.c,在pm_wake_lock和pm_wake_unlock中添加trace。

Index: wakelock.c
===================================================================
--- wakelock.c (revision 1887)
+++ wakelock.c (working copy)
@@ -16,7 +16,9 @@
#include <linux/list.h>
#include <linux/rbtree.h>
#include <linux/slab.h>
+#include <trace/events/power.h>

+
static DEFINE_MUTEX(wakelocks_lock);

struct wakelock {
@@ -217,7 +219,7 @@
} else {
__pm_stay_awake(&wl->ws);
}
-
+ trace_pm_wake_lock(buf);
wakelocks_lru_most_recent(wl);

out:
@@ -249,6 +251,7 @@
goto out;
}
__pm_relax(&wl->ws);
+ trace_pm_wake_unlock(buf);

wakelocks_lru_most_recent(wl);
wakelocks_gc();

Runtime PM

Suspend and Resume

Clock、Regulator

一個Linux平臺PM功能初探