首页 > 代码库 > 一个Linux平台PM功能初探

一个Linux平台PM功能初探

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=http://www.mamicode.com/{
/* --------------------------------------------------------------------
*    ----------  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 =http://www.mamicode.com/
{
    /* 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功能初探