Linux Power Management分析一

最近研究marvell平台上的PMU管理机制,算是边学边写吧。 自己是个菜鸟,可能写起来就信马由缰了,毕竟懂得东西实在太少了。

marvell主要PMU管理在pxa3xx_pm.c中,而很多函数是建立在kernel/power目录下的main.c上的。下面先分析main.c文件吧

首先当然是注册了:
static int __init pm_init(void);
    这个函数使用了core_initcall(pm_init); 这个就涉及到linux driver启动顺序的问题。 core_initcall确保可以在比较靠前的顺序进行注册,防止了后面如果有用到这个驱动里的函数,但是此时此驱动还没有注册。(据说如果默认使用module_init,注册顺序是不固定的)。

在pm_init里注册了subsystem,
    subsystem_register(&power_subsys);
当然在进行注册前,先声明了变量power_subsys,使用方法是 decl_subsys(power,NULL,NULL);
可以对应源代码,看看decl_subsys到底干了啥事情,说白了就是初始化呗,声明并初始化了一个叫power_subsys的变量。
##在linux还是蛮常用的嘛。
#define decl_subsys(_name,_type,_uevent_ops) /
struct kset _name##_subsys = { /
        .kobj = { .name = __stringify(_name) }, /
        .ktype = _type, /
        .uevent_ops =_uevent_ops, /
}


Ok,子系统注册完了,该在sys下生成文件了。可以使用sysfs_create_group,在指定目录生成sys文件了,刚开始还很奇怪为啥使用sysfs_create_group,而不是使用sysfs_create_file,后来发现作者为了便于调试,通过宏定义,定义了一个trace的sysfs。这样,使用group函数可以在该目录同时生成两个,不用一步一步分别create了。代码如下:
#ifdef CONFIG_PM_TRACE
。。。。。。
static struct attribute * g[] = {
&state_attr.attr,
&pm_trace_attr.attr,
NULL,
};
#else
static struct attribute * g[] = {
&state_attr.attr,
NULL,
};
#endif /* CONFIG_PM_TRACE */
static struct attribute_group attr_group = {
.attrs = g,
};


Ok,到这里init就完成了。还算是比较简单。但是linux的作者们各种技巧确实给俺留下了深刻的印象。
例如sysfs的operation的声明,已经不止一次看到使用一个宏定义进行sysfs的operation声明了。
power_attr(pm_trace); //这样就声明了一个叫做pm_trace_attr的sysfs操作,attr为pm_trace。
欣赏一下它的原型吧:

#define power_attr(_name) /
static struct subsys_attribute _name##_attr = { /
.attr = { /
.name = __stringify(_name), /
.mode = 0644, /
}, /
.show = _name##_show, /
.store = _name##_store, /
}

很帅,至少我认为是这样。特别是有2个以上的sysfs文件是。 不过带来的坏处就是使用source insight时,经常不知所踪。唉。

pm_trace其实是使用sysfs来控制trace的显示,不过看代码,没有看到它控制哪里的trace啊,奇怪。

学学sysfs的show和store。嘿嘿,跑题了,不过谁让俺水平就那么低了,经过坎坷的找工作,刚开始入手linux。

static ssize_t pm_trace_show(struct subsystem * subsys, char * buf)
显示文件里的内容,直接使用sprintf就可以啦。 sprintf(buf, "%d/n", pm_trace_enabled);

static ssize_t pm_trace_store(struct subsystem * subsys, const char * buf, size_t n)
当往文件里写入时,执行该函数。 sscanf(buf, "%d", &val), 然后pm_trace_enabled = !!val;哈哈。结束了。多么简单。

其实proc文件系统也蛮简单的,以后阅读到的时候再分析吧。

还有有必要看看state的show和store吧,我们可以通过写state文件,来进入不同的PM状态,如standby, mem, lcdfresh, deepsleep等。下面看看咋实现的:
static ssize_t state_show(struct subsystem * subsys, char * buf)

s += sprintf(s,"%s ", pm_states[i]);
。。。。。
s += sprintf(s,"/n");

在state中,将这几种状态都打印出来。
static const char * const pm_states[PM_SUSPEND_MAX] = {
[PM_SUSPEND_STANDBY] = "standby",
[PM_SUSPEND_MEM] = "mem",
#ifdef CONFIG_SOFTWARE_SUSPEND
[PM_SUSPEND_DISK] = "disk",
#endif
[PM_SUSPEND_LCDREFRESH] = "lcdrefresh",
[PM_SUSPEND_DEEPSLEEP] = "deepsleep",
NULL,
};

#define PM_SUSPEND_ON ((__force suspend_state_t) 0)
注意__force的用法。:)

static ssize_t state_store(struct subsystem * subsys, const char * buf, size_t n)
如果向state写入standby,则会进入standby模式。
p = memchr(buf, '/n', n);
len = p ? p - buf : n;
剥离回车换行符。memchr函数是在指定的n字节内查找'/n'字符,并返回所在位置。

for (s = &pm_states[state]; state < PM_SUSPEND_MAX; s++, state++) {
if (*s && !strncmp(buf, *s, len))
break;
}
查找匹配的状态。
if (state < PM_SUSPEND_MAX && *s)
error = enter_state(state);
进入指定状态。 enter_state()。。。。。

唉,这才进入main.c文件的核心部分 enter_state。

        
<think> 我们正在讨论的是嵌入式系统中的时钟与电源管理(Clock & Power Management)。根据提供的引用材料,特别是引用[1][3][4]都提到了ARM架构(如S3C2440/S3C44B0X)的时钟与电源管理模块。 主要组成部分: 1. 时钟控制(Clock Control) 2. USB控制(USB Control) 3. 电源控制(Power Control) 我将从以下几个方面展开: 、时钟控制(Clock Control) 二、USB时钟控制(USB Clock Control) 三、电源管理(Power Management) 四、相关技术资料 注意:在描述中,我会使用LaTeX数学表达式来描述频率、时间等关系,并按照要求使用$...$或$$...$$格式。 --- ### 、时钟控制(Clock Control) 时钟控制逻辑产生系统所需的时钟信号,包括: - $FCLK$:CPU核心时钟(如ARM920T内核) - $HCLK$:高速总线时钟(用于AHB总线,连接内存控制器、中断控制器等高速外设) - $PCLK$:低速总线时钟(用于APB总线,连接UART、I2C等低速外设) 系统通常包含两个锁相环(PLL): - **MPLL**:为主系统提供时钟($FCLK$, $HCLK$, $PCLK$) - **UPLL**:专用于USB模块,产生48MHz时钟($UCLK$) **时钟分频**:通过设置分频系数(如HDIVN, PDIVN)可调整时钟频率: $$ HCLK = \frac{FCLK}{HDIVN} \quad , \quad PCLK = \frac{FCLK}{PDIVN} $$ 其中$HDIVN$和$PDIVN$为分频系数寄存器值[^1][^3]。 **时钟源选择**:系统启动时使用外部晶振(XTAL)或外部时钟(EXTCLK)。PLL在锁定后切换为系统时钟源。外部晶振频率$f_{\text{ext}}$通常较低(如12MHz),PLL通过倍频产生高频时钟: $$ FCLK = (M \times f_{\text{ext}}) \div (P \times 2^S) $$ 其中$M,P,S$为MPLL配置参数(参见硬件手册)[^4]。 --- ### 二、USB时钟控制(USB Clock Control) USB接口需要精确的48MHz时钟: - 由UPLL产生:$UCLK = 48\text{MHz}$ - 配置UPLL的参数需满足: $$ UCLK = \frac{(U_M \times f_{\text{ext}})}{(U_P \times 2^{U_S})} = 48\text{MHz} $$ - 在UPLL锁定前,USB模块不可用[^3][^4]。 --- ### 三、电源管理(Power Management) ARM处理器支持多种电源模式以降低功耗: | 模式 | 特点 | 唤醒方式 | |-------------|----------------------------------------------------------------------|------------------------------| | **NORMAL** | 全功能模式,所有模块运行 | - | | **SLOW** | 不使用PLL,直接使用外部时钟($f_{\text{ext}}$) | 外部中断 | | **IDLE** | 停止CPU时钟($FCLK$),但总线($HCLK$, $PCLK$)和外设仍运行 | 中断 | | **SLEEP** | 断开内部电源(仅保留唤醒逻辑),PLL停止 | 复位或特定唤醒信号(如EINT) | **功耗对比**: $$ P_{\text{SLEEP}} < P_{\text{IDLE}} < P_{\text{SLOW}} < P_{\text{NORMAL}} $$ **模式切换流程**: ```mermaid stateDiagram-v2 [*] --> NORMAL NORMAL --> SLOW : 关闭PLL NORMAL --> IDLE : 停止FCLK IDLE --> NORMAL : 中断触发 NORMAL --> SLEEP : SLEEP指令 SLEEP --> [*] : 复位/唤醒信号 ``` 关键点: - 进入SLEEP模式前需配置唤醒源(如GPIO中断)[^3] - 退出SLEEP模式相当于硬件复位,需重新初始化系统[^4] --- ### 四、相关技术资料 1. **芯片手册**:如S3C2440/S3C44B0X的“CLOCK & POWER MANAGEMENT”章节(引用[1][3][4]) 2. **Linux内核支持**: - Common Clock Framework(引用[2]):提供通用时钟API(`clk_enable()`, `clk_set_rate()`等) - 通过Device Tree配置时钟树,例如: ```dts /* 示例:S3C2440时钟节点 */ clocks { osc: oscillator { compatible = "fixed-clock"; clock-frequency = <12000000>; }; mpll: mpll { compatible = "samsung,s3c2440-mpll"; clocks = <&osc>; }; }; ``` 3. **调试工具**: - 通过`/sys/kernel/debug/clk`查看时钟频率 - 使用`pm_suspend`命令测试电源模式 --- §§ 相关问题 §§ 1. 如何配置S3C2440的MPLL以产生特定频率的$FCLK$? 2. Linux Common Clock Framework中如何实现个时钟驱动? 3. 在电源管理模式下,如何保留RAM数据? 4. USB主机控制器在SLEEP模式下的唤醒流程是什么? 5. ARM处理器中PLL锁定时间(Lock Time)对系统启动有何影响? </think> ### Clock Power Management 深度解析 Clock Power Management(时钟电源管理)是嵌入式系统的核心技术,通过动态控制时钟频率和电源状态来实现性能与功耗的平衡。基于ARM架构(如ARM920T)的系统通常包含**时钟控制**、**USB控制**和**电源控制**三大模块[^1][^3],其核心原理如下: --- #### 、时钟控制架构 1. **多级时钟生成** 系统通过锁相环(PLL)将外部低频晶振(如12MHz)转换为高频时钟: $$ \begin{cases} \text{MPLL} \rightarrow \text{FCLK (CPU核心时钟)} \\ \text{MPLL} \rightarrow \text{HCLK (AHB总线时钟)} \\ \text{MPLL} \rightarrow \text{PCLK (APB外设时钟)} \\ \text{UPLL} \rightarrow \text{UCLK (USB专用48MHz时钟)} \end{cases} $$ 其中 $FCLK$、$HCLK$、$PCLK$ 通常满足 $FCLK \geq HCLK \geq PCLK$[^3][^4]。 2. **动态分频控制** 通过寄存器调节分频系数,实现时钟频率的动态调整: ```c // 示例:S3C2440时钟分频寄存器配置 rCLKDIVN = (0x02 << 1) | (0x01); // HDIVN=2, PDIVN=1 // 结果:HCLK = FCLK/4, PCLK = HCLK/2 ``` 此举可将外设功耗降低30%-60%[^4]。 --- #### 二、电源管理状态机 ARM处理器支持四级功耗模式,其功耗与唤醒延迟关系为: | 模式 | 功耗比 | 唤醒延迟 | 状态描述 | |------------|--------|-------------|------------------------------| | **NORMAL** | 100% | - | 全功能运行模式 | | **IDLE** | ≈40% | <1μs | 停止CPU时钟(FCLK)[^3] | | **SLOW** | ≈15% | 10-100μs | 禁用PLL,直连外部晶振 | | **SLEEP** | <1% | 毫秒级 | 仅保留唤醒电路供电 | ```mermaid stateDiagram-v2 NORMAL --> IDLE: 停止FCLK NORMAL --> SLOW: 关闭PLL IDLE --> NORMAL: 中断触发 SLOW --> NORMAL: 重启PLL NORMAL --> SLEEP: SLEEP指令 SLEEP --> NORMAL: 复位/外部唤醒 ``` --- #### 三、关键技术实现 1. **Linux Common Clock Framework** - 提供标准化时钟API:`clk_enable()`, `clk_set_rate()` - 设备树(DT)配置示例: ```dts clock: clock-controller@4c004000 { compatible = "samsung,s3c2440-clock"; reg = <0x4c004000 0x1000>; clocks = <&xtal>; // 外部晶振节点 #clock-cells = <1>; }; ``` 2. **低功耗策略** - **动态电压频率调节(DVFS)**: $$ P_{\text{dynamic}} \propto C \cdot V^2 \cdot f $$ 同步降低电压($V$)与频率($f$)可指数级降耗 - **时钟门控**:通过寄存器禁用未使用外设的时钟 ```c rCLKCON &= ~(1<<14); // 禁用UART0时钟 ``` --- #### 四、实战调试技巧 1. **时钟测量方法** - 示波器:监控CLKOUT引脚波形 - 寄存器读取: ```c uint32_t get_hclk() { return (rMPLLCON * xtal_freq) / (rCLKDIVN & 0x0F); } ``` 2. **功耗异常排查** - 检查`IDLE`模式下总线活动(如DMA) - 验证SLEEP模式唤醒源配置(EINT/GPIO)[^3] ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值