基于SMDK2440的电源管理系统
一. 何为电源管理
在不影响电子设备正常使用的前提下, 尽可能省电
1.1 简单设备的电源管理(8051单片机):
无电源管理:灭灯6.1MA 亮灯32.2MA
有电源管理:灭灯0.27uA 亮灯26.7MA
MCU全速运行时耗电6.1ma, led耗电26ma
1.2 复杂的电源管理(Linux):
a. 硬件更复杂:外接更多设备,这些设备也需要单独供电,休眠前可能需要单独设置
b. 软件更复杂:需要有统一的框架,唤醒后可能需要重新初始化
1.3 Linux系统省电模型:
1.3.1 系统睡眠模型:
a. on -> working //正常运行状态
b. standby -> CPU and RAM are powered but not executed //空闲状态
c. suspend to RAM (mem) -> RAM is powered and the running content is saved to RAM
d. suspend to disk (disk) -> all content is saved to disk and power down
1.3.1 runtime电源管理模型(on状态下如何省电):
a. 降低运行时钟
b. 关闭无用设备
二. UBOOT添加suspend命令
u-boot全速运行,耗电83ma
u-boot suspend:耗电33ma ==>外设备没有完全关闭,比如声卡、网卡
如何进入休眠状态(针对s3c2440):
1. 配置GPIO: 比如想维持LED亮或灭, 用于唤醒CPU的引脚要设为中断功能
2. 设置INTMSK屏蔽所有中断: 在sleep模式下,这些引脚只是用于唤醒系统,当CPU正常运行时可以重新设置INTMSK让这些引脚用于中断功能
3. 配置唤醒源
4. 设置MISCCR[13:12]=11b, 使得USB模块进入休眠
5. 在GSTATUS[4:3]保存某值, 它们可以在系统被唤醒时使用
6. 设置 MISCCR[1:0] 使能数据总线的上拉电阻
7. 清除 LCDCON1.ENVID 以停止LCD
8. 读这2个寄存器: rREFRESH and rCLKCON, 以便填充TLB(如果不使用MMU的话,这个目的可以忽略)
9. 设置 REFRESH[22]=1b,让SDRAM进入self-refresh mode
10. 等待SDRAM成功进入self-refresh mode
11. 设置 MISCCR[19:17]=111b以保护SDRAM信号(SCLK0,SCLK1 and SCKE)
12. 设置CLKCON的SLEEP位让系统进入sleep mode
唤醒系统:
1. 按下按键
2. 根据 GSTATUS2[1]判断是复位还是唤醒
3. 设置 MISCCR[19:17]=000b, 以释放SDRAM信号
4. 配置s3c2440的memory controller
5. 等待SDRAM退出self-refresh mode
6. 根据GSTATUS[3:4]的值来运行休眠前的函数
修改后的uboot:
u-boot-1.1.6_jz2440_suspend.patch
通过nfs下载:
nfs 30000000 192.168.1.124:/work/nfs_root/u-boot.bin;protect off all;erase 0 3ffff;cp.b 30000000 0 40000
nfs 30000000 192.168.1.124:/work/nfs_root/u-boot.bin;nand erase bootloader; nand write.jffs2 30000000 bootloader
三. Linux系统suspend的实现
1. suspend流程
启动 suspend to ram:
echo mem > /sys/power/state
------------------------------
state_store (kernel/power/main.c)
pm_suspend (kernel/power/suspend.c)
enter_state (kernel/power/suspend.c)
1. 准备
suspend_prepare (kernel/power/suspend.c)
pm_prepare_console (kernel/power/console.c)
pm_notifier_call_chain(PM_SUSPEND_PREPARE); (kernel/power/main.c) // 通知所有关心"休眠消息"的驱动程序
suspend_freeze_processes (kernel/power/power.h) // 冻结APP和内核线程
2. 休眠
suspend_devices_and_enter (kernel/power/suspend.c) // 让设备进入休眠状态
suspend_ops->begin // 如果平台相关的代码有begin函数就去调用它
suspend_console (kernel/power/suspend.c)
dpm_suspend_start(PMSG_SUSPEND); (drivers/base/power/main.c)
2.1 dmp_list链表中的各个设备, 准备休眠, 准备完成的设备, 入链dpm_prepared_list
dpm_prepare(state); (drivers/base/power/main.c)
对于dmp_list链表中的每一个设备,都调用device_prepare(dev, state);
对于该设备,调用它的dev->pm_domain->ops->prepare 或
dev->type->pm->prepare 或
dev->class->pm->prepare 或
dev->bus->pm->prepare 或
dev->driver->pm->prepare
2.2 对于dpm_prepared_list中已经就位的待休眠设备, 正式进入休眠状态, 并入链dpm_suspended_list
dpm_suspend(state); (drivers/base/power/main.c) // 让各类设备休眠
对于dpm_prepared_list链表中的每一个设备,都调用device_suspend(dev);
__device_suspend(dev, pm_transition, false);
对于该设备,调用它的dev->pm_domain->ops->suspend 或
dev->type->pm->suspend 或
dev->class->pm->suspend 或
dev->bus->pm->suspend 或
dev->driver->pm->suspend
2.3 CPU休眠前, 确认唤醒条件
suspend_enter(state, &wakeup) (kernel/power/suspend.c)
suspend_ops->prepare // 即s3c_pm_prepare
dpm_suspend_end(PMSG_SUSPEND); (drivers/base/power/main.c)
2.3.1 CPU休眠前, 处理dpm_suspended_list中休眠设备的收尾工作(若有), 并入链dpm_late_early_list
dpm_suspend_late(state); (drivers/base/power/main.c)
对于dpm_suspended_list链表中的每一个设备,都调用device_suspend_late(dev, state);
对于该设备,调用它的dev->pm_domain->ops->suspend_late 或
dev->type->pm->suspend_late 或
dev->class->pm->suspend_late 或
dev->bus->pm->suspend_late 或
dev->driver->pm->suspend_late
2.3.2 dpm_late_early_list中的设备, 屏蔽设备中断
对于dpm_late_early_list链表中的每一个设备,都调用device_suspend_noirq(dev, state);
对于该设备,调用它的dev->pm_domain->ops->suspend_noirq 或
dev->type->pm->suspend_noirq 或
dev->class->pm->suspend_noirq 或
dev->bus->pm->suspend_noirq 或
dev->driver->pm->suspend_noirq
2.4 CPU休眠前, 处理CPU休眠后的收尾工作
suspend_ops->prepare_late() //
disable_nonboot_cpus(); //针对多核CPU, 休眠主CPU外的其他CPU
arch_suspend_disable_irqs(); //屏蔽CPU中断
2.5 CPU正式休眠 syscore_suspend
suspend_ops->enter(state); // s3c_pm_enter (arch\arm\plat-samsung\pm.c)
......
pm_cpu_prep // s3c2410_pm_prepare (arch\arm\mach-s3c24xx\pm-s3c2410.c)
GSTATUS3 = s3c_cpu_resume
......
cpu_suspend(0, pm_cpu_sleep); // arch\arm\kernel\sleep.S
pm_cpu_sleep (arch\arm\mach-s3c24xx\pm-s3c2410.c) // s3c2410_cpu_suspend
s3c2410_cpu_suspend (arch\arm\mach-s3c24xx\sleep-s3c2410.S)
以上是休眠过程
===============================================================
下面开始唤醒过程
唤醒条件触发: 按键, 导致u-boot运行, 读取GSTATUS3, 执行s3c_cpu_resume
.....
1. CPU唤醒 ------->对应 syscore_suspend
s3c_pm_restore_core
syscore_resume
arch_suspend_enable_irqs //使能CPU中断
enable_nonboot_cpus //唤醒其他CPU
2. 设备唤醒
suspend_ops->wake
dpm_resume_start(PMSG_RESUME);
2.1 恢复设备中断
dpm_resume_noirq(state);
对于dpm_noirq_list链表中的每一个设备,调用device_resume_noirq(dev, state);
对于该设备,调用它的dev->pm_domain->ops->resume_noirq 或
dev->type->pm->resume_noirq 或
dev->class->pm->resume_noirq 或
dev->bus->pm->resume_noirq 或
dev->driver->pm->resume_noirq
2.2 设备唤醒
dpm_resume_early(state);
对于dpm_late_early_list链表中的每一个设备,调用device_resume_early(dev, state);
对于该设备,调用它的dev->pm_domain->ops->resume_early 或
dev->type->pm->resume_early 或
dev->class->pm->resume_early 或
dev->bus->pm->resume_early 或
dev->driver->pm->resume_early
suspend_ops->finish()
s3c_pm_finish
2.3 设备唤醒的收尾工作
dpm_resume_end(PMSG_RESUME);
resume_console();
3. CPU对唤醒操作整体收尾
suspend_finish();
suspend_thaw_processes();
pm_notifier_call_chain(PM_POST_SUSPEND);
pm_restore_console();
4. 返回用户空间
总结: 驱动程序里相关的电源管理函数的调用过程:
休眠: prepare —> suspend —> suspend_late —> suspend_noirq
唤醒: resume_noirq —> resume_early —> resume -> complete
四. 修改内核或驱动, 以支持suspend功能
4.1 实现理论
设置唤醒源: 配置GPIO引脚处于中断功能, 并设置触发方式
修改内核:
a. 用s3c_irq_wake 修改 s3c_irqwake_intmask、s3c_irqwake_eintmask(唤醒源)
b. 设置GPIO用于中断, 设置触发方式
int0,1,2,3
eint4,5,...15
在我们的按键驱动中: request_irq之后调用s3c_irq_wake或s3c_irqext_wake
4.2 两种思路:
1. 直接修改内核, s3c_irqwake_intmask、s3c_irqwake_eintmask, 并且配置GPIO (修改内核, 不可取)
2. 添加按键驱动, 在request_irq后, 调用用 s3c_irq_wake (驱动形式, 正途)
4.2.1 修改内核
a. 编译内核:
tar xjf linux-3.4.2.tar.bz2
cd linux-3.4.2
patch -p1 < ../linux-3.4.2_alsa_wm8976_uda1341_jz2440_mini2440_tq2440.patch
cp config_wm8976_jz2440 .config
make menuconfig
b. 解除irq屏蔽: 修改arch\arm\plat-samsung\pm.c
-- unsigned long s3c_irqwake_intmask = 0xffffffff;
-- unsigned long s3c_irqwake_eintmask = 0xffffffff;
++ unsigned long s3c_irqwake_intmask = 0xfffffffa; //EINT 0,2
++ unsigned long s3c_irqwake_eintmask = 0xfffff7ff; //EINT11
c. GPIO设置用于中断, 并设置触发方式: 修改arch\arm\plat-s3c24xx\pm.c /* 实验发现不修改这个文件也成功,原因在于UBOOT已经配置了GPIO,设置了中断触发类型 */
if (!irqstate) {
if (pinstate == S3C2410_GPIO_IRQ)
S3C_PMDBG("Leaving IRQ %d (pin %d) as is\n", irq, pin);
+ else{
+ s3c_gpio_cfgpin(pin, S3C2410_GPIO_IRQ);
+ }
/* 配置触发方式 */
} else {
d. 编译修改好的内核
make uImage && cp arch/arm/boot/uImage /work/nfs_root
sudo tar xjf fs_mini_mdev_new.tar.bz2 -C /work/nfs_root/
e. 新内核启动
set bootcmd 'nfs 30000000 192.168.1.124:/work/nfs_root/uImage; bootm 30000000'
set bootargs console=ttySAC0,115200 root=/dev/nfs nfsroot=192.168.1.124:/work/nfs_root/fs_mini_mdev_new ip=192.168.1.17
f. 测试
cat /sys/power/state
echo mem > /sys/power/state
按键换醒
4.2.2 添加按键驱动
a. 通知: notifier
在冻结APP之前,使用pm_notifier_call_chain(PM_SUSPEND_PREPARE)来通知驱动程序
在重启APP之后,使用pm_notifier_call_chain(PM_POST_SUSPEND)来通知驱动程序
如果驱动程序有事情在上述时机要处理,可以使用register_pm_notifier注册一个notifier
b. 添加suspend, resume函数
b.1 老方法:在platform_driver里实现suspend,resume成员
b.2 新方法:在platform_driver里的driver里的pm结构体, 实现suspend,resume成员
1) APP中指定唤醒系统的中断: buttons.c
static int buttons_init(void)
{
...
/* 指定这些中断可以用于唤醒系统 */
irq_set_irq_wake(IRQ_EINT0, 1);
irq_set_irq_wake(IRQ_EINT2, 1);
irq_set_irq_wake(IRQ_EINT11, 1);
...
}
static void buttons_exit(void)
{
...
irq_set_irq_wake(IRQ_EINT0, 0);
irq_set_irq_wake(IRQ_EINT2, 0);
irq_set_irq_wake(IRQ_EINT11, 0);
...
}
2) 驱动中实现休眠-唤醒: lcd_4.3.c
static struct dev_pm_ops lcd_pm = {
.suspend = lcd_suspend,
.resume = lcd_resume,
};
struct platform_driver lcd_drv = {
.probe = lcd_probe,
.remove = lcd_remove,
.driver = {
.name = "mylcd",
.pm = &lcd_pm,
}
};
static int lcd_suspend(struct device *dev)
{
int i;
unsigned long *dest = &lcd_regs_backup;
unsigned long *src = lcd_regs;
for (i = 0; i < sizeof(lcd_regs_backup)/sizeof(unsigned long); i++)
{
dest[i] = src[i];
}
lcd_regs->lcdcon1 &= ~(1<<0); /* 关闭LCD本身 */
*gpbdat &= ~1; /* 关闭背光 */
return 0;
}
static int lcd_resume(struct device *dev)
{
int i;
unsigned long *dest = lcd_regs;
unsigned long *src = &lcd_regs_backup;
struct clk *clk = clk_get(NULL, "lcd");
clk_enable(clk);
clk_put(clk);
#if 0
for (i = 0; i < sizeof(lcd_regs_backup)/sizeof(unsigned long); i++)
{
dest[i] = src[i];
}
#else
lcd_regs->lcdcon1 = lcd_regs_backup.lcdcon1 & ~1;
lcd_regs->lcdcon2 = lcd_regs_backup.lcdcon2;
lcd_regs->lcdcon3 = lcd_regs_backup.lcdcon3;
lcd_regs->lcdcon4 = lcd_regs_backup.lcdcon4;
lcd_regs->lcdcon5 = lcd_regs_backup.lcdcon5;
lcd_regs->lcdsaddr1 = lcd_regs_backup.lcdsaddr1;
lcd_regs->lcdsaddr2 = lcd_regs_backup.lcdsaddr2;
lcd_regs->lcdsaddr3 = lcd_regs_backup.lcdsaddr3;
#endif
lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD控制器 */
lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本身 */
*gpbdat |= 1; /* 输出高电平, 使能背光 */
return 0;
}
3) 驱动中实现休眠-唤醒notifier 提醒: lcd_4.3.c
static struct notifier_block lcd_pm_notif_block = {
.notifier_call = lcd_suspend_notifier,
};
static int lcd_suspend_notifier(struct notifier_block *nb,
unsigned long event,
void *dummy)
{
switch (event) {
case PM_SUSPEND_PREPARE:
printk("lcd suspend notifiler test: PM_SUSPEND_PREPARE\n");
return NOTIFY_OK;
case PM_POST_SUSPEND:
printk("lcd suspend notifiler test: PM_POST_SUSPEND\n");
return NOTIFY_OK;
default:
return NOTIFY_DONE;
}
}
static int lcd_init(void)
{
/* 电源管理 */
register_pm_notifier(&lcd_pm_notif_block);
...
}
static void lcd_exit(void)
{
unregister_framebuffer(s3c_lcd);
...
}
c. 改进
c.1 测试时, 发现唤醒后有瑕疵, 需要再次调用APP才能看到图像
对于LCD, 配置内核去掉 CONFIG_FRAMEBUFFER_CONSOLE, 可以在休眠-唤醒后保留LCD上的图像
c.2 可以在APP中, 调用某个函数, 禁止FreameBuffer用于console
d. 效果测试
系统处于运行状态并且LCD打开时, 耗电240mA
休眠时, 耗电50mA
五. runtime PM (Runtime Power Management)
1. runtime PM流程
怎样动态地打开或关闭设备的电源?
最简单的方法: 在驱动程序里,在open函数中打开电源,在close函数中关闭电源
上述方法有一个缺点: 多个APP使用该设备时可能造成干扰
解决方法:给驱动添加使用计数值: 当该计数大于0时打开电源, 等于0时关闭电源
内核驱动示例: drivers\input\misc\bma150.c
pm_runtime_enable / pm_runtime_disable : 使能/禁止runtime PM, 修改disable_depth变量
pm_runtime_get_sync / pm_runtime_put_sync : 增加/减小计数值, 并且让设备处于resume或suspend状态
在dev_pm_ops里提供3个回调函数: runtime_suspend, runtime_resume, runtime_idle
2. 流程分析
2.1 计数不为零, 则唤醒: pm_runtime_get_sync
__pm_runtime_resume(dev, RPM_GET_PUT);
atomic_inc(&dev->power.usage_count); // 增加使用计数
rpm_resume(dev, rpmflags); // resume设备
if (dev->power.disable_depth > 0) retval = -EACCES; // 该变量初值为1,要使用runtime PM, 要先pm_runtime_enable
if (!dev->power.timer_autosuspends) // 为防止设备频繁地开关,可以设置timer_autosuspends
pm_runtime_deactivate_timer(dev);
if (dev->power.runtime_status == RPM_ACTIVE) { // 如果设备已经是RPM_ACTIVE,就没必要再次resume,直接返回
// 如果设备处于RPM_RESUMING/RPM_SUSPENDING, 等待该操作完成
// Increment the parent's usage counter and resume it if necessary
// resume设备本身: 前面4个函数被称为 subsystem-level callback
callback = dev->pm_domain->ops.runtime_resume; 或
callback = dev->type->pm->runtime_resume; 或
callback = dev->class->pm->runtime_resume; 或
callback = dev->bus->pm->runtime_resume; 或
callback = dev->driver->pm->runtime_resume;
retval = rpm_callback(callback, dev);
// 成功时,给父亲的child_count加1
if (parent)
atomic_inc(&parent->power.child_count);
// 唤醒其他进程
wake_up_all(&dev->power.wait_queue);
// 如果resume失败, 让设备进入idle状态
if (!retval)
rpm_idle(dev, RPM_ASYNC);
2.2 计数减至0, 则休眠: pm_runtime_put_sync
__pm_runtime_idle(dev, RPM_GET_PUT);
atomic_dec_and_test(&dev->power.usage_count) // 减小使用计数
rpm_idle(dev, rpmflags); // 让设备进入idle状态
rpm_check_suspend_allowed(dev); // 检查是否允许设备进入suspend状态
if (dev->power.disable_depth > 0) //失败
if (atomic_read(&dev->power.usage_count) > 0) // 当前的使用计数不是0,失败
if (!pm_children_suspended(dev)) // 如果的孩子不全部处于suspended, 失败
if (dev->power.runtime_status != RPM_ACTIVE) // 如果设备本来就不处于RPM_ACTIVE,直接返回
// 调用idle回调函数: 前4个是subsystem-level callback
callback = dev->pm_domain->ops.runtime_idle; 或
callback = dev->type->pm->runtime_idle; 或
callback = dev->class->pm->runtime_idle; 或
callback = dev->bus->pm->runtime_idle; 或
callback = dev->driver->pm->runtime_idle;
__rpm_callback(callback, dev);
wake_up_all(&dev->power.wait_queue);
2.3 bma150.c : i2c_bus_type -> pm_generic_runtime_idle -> pm_runtime_suspend -> __pm_runtime_suspend(dev, 0);
-> rpm_suspend(dev, rpmflags);
如果设备不提供runtime_idle, 则最终会调用runtime_suspend
3. 使用runtime PM:
3.1 前提: 配置内核支持runtime PM
make menuconfig
Power management options --->
[*] Run-time PM core functionality
3.2 系统接口的使用
echo on > /sys/devices/platform/mylcd/power/control // 导致control_store -> pm_runtime_forbid(dev); :
atomic_inc(&dev->power.usage_count);
rpm_resume(dev, 0);
echo auto > /sys/devices/platform/mylcd/power/control // 导致control_store -> pm_runtime_allow(dev); :
atomic_dec_and_test(&dev->power.usage_count)
rpm_idle(dev, RPM_AUTO);
3.3 驱动程序lcd_4.3.c 提供接口, APP直接调用(参考内核驱动示例: drivers\input\misc\bma150.c)
a. dev_ops提供3个回调函数: runtime_suspend, runtime_resume, runtime_idle
struct platform_driver lcd_drv = {
.probe = lcd_probe,
.remove = lcd_remove,
.driver = {
.name = "mylcd",
.pm = &lcd_pm,
}
};
static struct dev_pm_ops lcd_pm = {
.suspend = lcd_suspend,
.resume = lcd_resume,
.runtime_suspend = lcd_suspend,
.runtime_resume = lcd_resume,
};
b. 对于runtime PM,默认状态下设备的状态是suspended, 如果硬件上它是运行状态,
需要调用pm_runtime_set_active()来修改它的状态, 然后调用pm_runtime_enable()来使能runtime PM
一般是在probe函数里调用上述函数
static int lcd_probe(struct platform_device *pdev)
{
pm_runtime_set_active(&pdev->dev);
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_enable(&pdev->dev);
return 0;
}
c. 在对应的系统调用接口里调用: pm_runtime_get_sync / pm_runtime_put_sync, 并且让设备处于resume或suspend状态
static struct fb_ops s3c_lcdfb_ops = {
.owner = THIS_MODULE,
.fb_setcolreg = s3c_lcdfb_setcolreg,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
.fb_open = mylcd_open,
.fb_release = mylcd_release,
};
static int mylcd_open(struct fb_info *info, int user)
{
pm_runtime_get_sync(&lcd_dev.dev);
return 0;
}
/* autosuspend: 如果不想让设备频繁地开、关,可以使用autosuspend功能
* 驱动里: 执行pm_runtime_use_autosuspend来设置启动autosuspend功能
*/
static int mylcd_release(struct fb_info *info, int user)
{
pm_runtime_mark_last_busy(&lcd_dev.dev);
pm_runtime_put_sync_autosuspend(&lcd_dev.dev);
return 0;
}
d. 在remove函数里调用pm_runtime_disable()
static int lcd_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
return 0;
}
六. regulator系统
6.1 概念
Regulator : 电源芯片, 比如电压转换芯片
Consumer : 消费者,使用电源的部件, Regulator是给Consumer供电的
machine : 单板,上面焊接有Regulator和Consumer
Constraints : 约束, 比如某个电源管理芯片输出的电压范围
Supply : 提供电源的部件, Regulator就是一个Supply; Regulator A可以给Regulator B供电, 那么Regulator B的Supply就是A
6.2 regulator_register流程分析:
// 分配regulator_dev
rdev = kzalloc(sizeof(struct regulator_dev), GFP_KERNEL);
/* set regulator constraints */
set_machine_constraints
add_regulator_attributes
/* add consumers devices */
set_consumer_device_supply
在regulator_map_list链表里生成一项regulator_map: 它里面有dev_name(consumer的名字),supply(cosumer的电源引脚名字)
// 把regulator_dev放入regulator_list
list_add(&rdev->list, ®ulator_list);
总结 : regulator_register执行后:
regulator_list -> regulator_dev
-> regulator_map_list
-> regulator_map ->
consumer : 供电设备名
suppy : 供电设备引脚
6.3 驱动程序
1. regulator: regulator.c
注册一个platform_driver: 在它的probe函数里分配、设置、注册一个regulator
"设置"里要做的事情: 实现regulator的操作, 比如enable, disable, set_voltage
static int myregulator_enable(struct regulator_dev *rdev)
{
*gpbdat |= 1; /* 输出高电平 */
return 0;
}
static int myregulator_disable(struct regulator_dev *rdev)
{
*gpbdat &= ~1; /* 输出低电平 */
return 0;
}
static int myregulator_is_enabled(struct regulator_dev *rdev)
{
if (*gpbdat & 1)
return 1;
else
return 0;
}
static struct regulator_ops myregulator_ops = {
.enable = myregulator_enable,
.disable = myregulator_disable,
.is_enabled = myregulator_is_enabled,
};
static struct regulator_desc myregulator_desc = {
.name = "myregulator",
.ops = &myregulator_ops,
.type = REGULATOR_VOLTAGE,
.id = 0,
.owner = THIS_MODULE,
.n_voltages = 1,
};
static struct regulator_dev *myregulator_dev;
static int myregulator_probe(struct platform_device *pdev)
{
struct regulator_init_data *init_data = dev_get_platdata(&pdev->dev);
gpbcon = ioremap(0x56000010, 8);
gpbdat = gpbcon+1;
*gpbcon &= ~(3); /* GPB0设置为输出引脚 */
*gpbcon |= 1;
/* 分配/设置/注册 regulator */
myregulator_dev = regulator_register(&myregulator_desc,
&pdev->dev,
init_data, NULL,
NULL);
if (IS_ERR(myregulator_dev)) {
printk("regulator_register error!\n");
return -EIO;
}
return 0;
}
2. machine: machine.c
注册一个platform_device: 在它的私有数据里指定regulator和consume的对应关系
(这个电源芯片给哪一个部件供电), 指定约束条件(比如电压范围)
#if 0
regulator_consumer_supply:
const char *dev_name; /* consumer的名字 */
const char *supply; /* consumer的电源引脚名称 */
#endif
static struct regulator_consumer_supply myregulator_supplies[] = {
REGULATOR_SUPPLY("VCC", "mylcd"),
};
static struct regulator_init_data myregulator_init_data = {
.constraints = {
.min_uV = 12000000,
.max_uV = 12000000,
.valid_modes_mask = REGULATOR_MODE_NORMAL,
.valid_ops_mask = REGULATOR_CHANGE_STATUS,
},
.num_consumer_supplies = 1,
.consumer_supplies = myregulator_supplies,
};
static struct platform_device myregulator_dev = {
.name = "myregulator",
.id = -1,
.dev = {
.release = myregulator_release,
.platform_data = &myregulator_init_data,
},
}
3. consumer: consumer_lcd_4.3.c
使用即可: regulator_get, regulator_enable, regulator_disable, regulator_set_voltage....
static struct regulator *myregulator;
static int lcd_probe(struct platform_device *pdev)
{
/* regulator */
myregulator = regulator_get(&pdev->dev, "VCC");
if (IS_ERR(myregulator)) {
printk("regulator_get error!\n");
return -EIO;
}
regulator_enable(myregulator);
...
}
static int lcd_remove(struct platform_device *pdev)
{
regulator_put(myregulator);
...
}
/* 当驱动处于休眠-唤醒状态, regulator无意义, 故在此屏蔽掉 */
static int lcd_suspend(struct device *dev)
{
...
regulator_disable(myregulator);
...
}
static int lcd_resume(struct device *dev)
{
...
regulator_enable(myregulator);
...
}
6.4 编译测试:
1. make menuconfig
Device Drivers --->
[*] Voltage and Current Regulator Support --->
修改 drivers/video/Makefile把原来的LCD驱动去掉
#obj-$(CONFIG_FB_S3C2410) += lcd_4.3.o
make uImage
2. 编译驱动
3. 使用新内核启动
4. 安装驱动:
insmod regulator.ko
insmod machine.ko
insmod consumer_lcd_4.3.ko
七. 修改数码相框项目, 自动关闭LCD(runtime)
1. 思路
关闭LCD : 在读取触摸屏的函数中,判断: 如果15S内无数据,执行:
echo auto > /sys/devices/platform/mylcd/power/control
打开LCD : 如果有触触摸屏动作发生, 执行:
echo on > /sys/devices/platform/mylcd/power/control
// alarm(second) : 若干秒后,内核会发出SIGALRM给APP, APP可以提供信号处理函数
a. 注册信号处理: signal(SIGALRM, function);
该函数用来关闭LCD
b. 在读取输入事件的进程里, 执行: alarm(15)
c. 如果一直没有读到触摸屏的数据, 定时器就会超时导致function被调用从而关闭LCD
d. 如果读到触摸屏数据, 再次执行alarm(15), 会更新超时时间为当前之后的15S
如果之前关闭过LCD, 还需要执行: 打开LCD
2. 修改
input_manager.c:
2.0 全局变量记录LCD当前状态
static int g_bLCDOn = 1; //运行
2.1 注册信号处理
int InputInit(void)
{
...
signal(SIGALRM, SigAlrmFn);
...
}
2.2 超时处理函数
static void SigAlrmFn(int Sigal)
{
/* 关闭LCD */
system("echo auto > /sys/devices/platform/mylcd/power/control");
g_bLCDOn = 0; //关闭
}
2.2 在读取输入事件的进程里, 执行: alarm(15)
static void *InputEventThreadFunction(void *pVoid)
{
T_InputEvent tInputEvent;
/* 定义函数指针 */
int (*GetInputEvent)(PT_InputEvent ptInputEvent);
GetInputEvent = (int (*)(PT_InputEvent))pVoid;
while (1)
{
alarm(15); /* 如果15秒内没有按压触摸屏,则超时 */
if(0 == GetInputEvent(&tInputEvent))
{
/* 唤醒主线程, 把tInputEvent的值赋给一个全局变量 */
/* 访问临界资源前,先获得互斥量 */
pthread_mutex_lock(&g_tMutex);
g_tInputEvent = tInputEvent;
/* 唤醒主线程 */
pthread_cond_signal(&g_tConVar);
/* 释放互斥量 */
pthread_mutex_unlock(&g_tMutex);
alarm(15); /* 如果读到触摸屏事件,修改超时时间 */
/* 如果LCD已经被关闭,则打开它 */
if (!g_bLCDOn) {
system("echo on > /sys/devices/platform/mylcd/power/control");
g_bLCDOn = 1;
}
}
}
return NULL;
}
3. 运行测试
配置内核添加驱动:
a. drivers/input/touchscreen/Makefile
#obj-$(CONFIG_TOUCHSCREEN_S3C2410) += s3c2410_ts.o
obj-$(CONFIG_TOUCHSCREEN_S3C2410) += s3c_ts.o
make menuconfig :
Device Drivers --->
Input device support --->
[*] Touchscreens --->
<*> Samsung S3C2410/generic touchscreen input driver
b. drivers/video/Makefile
obj-$(CONFIG_FB_S3C2410) += lcd_4.3.o
make uImage
echo auto > /sys/devices/platform/mylcd/power/control 无法使用,
因为dev->power.runtime_auto初始值是1,
成功使用echo auto > /sys/devices/platform/mylcd/power/control命令来关LCD的前提是dev->power.runtime_auto等于0
可以执行: echo on > /sys/devices/platform/mylcd/power/control 使得它等于0
但是也不适用于我们的情况,因为:
open(/dev/fb0)会让LCD的使用计数加1
再 echo on会让LCD的使用计数也加1
然后执行echo auto只能让使用计数减1变为
要再次修改驱动
4. 添加按键应用
4.1 buttons.c
在按键驱动中, 已经赋值 L, S, ENTER, 键
struct pin_desc pins_desc[4] = {
{IRQ_EINT0, "S2", S3C2410_GPF(0), KEY_L},
{IRQ_EINT2, "S3", S3C2410_GPF(2), KEY_S},
{IRQ_EINT11, "S4", S3C2410_GPG(3), KEY_ENTER},
{IRQ_EINT19, "S5", S3C2410_GPG(11), KEY_LEFTSHIFT},
};
并指定这些中断可以用于唤醒系统
irq_set_irq_wake(IRQ_EINT0, 1);
irq_set_irq_wake(IRQ_EINT2, 1);
irq_set_irq_wake(IRQ_EINT11, 1);
4.2 app: power.c
/* power <dev> */
int main(int argc, char **argv)
{
int fd;
int ret;
struct input_event event;
if (argc != 2)
{
printf("Usage:\n");
printf("%s <dev>\n", argv[0]);
return -1;
}
/* open */
fd = open(argv[1], O_RDONLY);
if (fd < 0)
{
printf("can't open %s\n", argv[1]);
return -1;
}
while (1)
{
/* read */
ret = read(fd, &event, sizeof(event));
if (ret != sizeof(event))
{
printf("can't read: ret = %d\n", ret);
return -1;
}
/* 按键类, L键, 松开 ->休眠 */
if (event.type == EV_KEY && event.code == KEY_L && event.value == 0)
{
system("echo mem > /sys/power/state");
}
}
return 0;
}
4.2 测试 : 实现按下L键(eint0),松开时, 系统suspend

浙公网安备 33010602011771号