1 修改LCD驱动支持Runtime电源管理
linux系统中分为两种电源管理模型:系统睡眠模型 和 Runtime电源管理模型
系统睡眠模型是让整个系统里面的所有设备全部处于运行(on)或睡眠(suspend)状态,要单独控制某个设备的休眠和唤醒要使用Runtime电源管理模型。
怎样动态地打开或关闭设备的电源?最简单的方法:在驱动程序里,在open函数中打开电源,在close函数中关闭电源
缺点: 多个APP使用该设备时可能造成干扰
解决方法:给驱动添加使用计数值: 当该计数大于0时打开电源, 等于0时关闭电源
参考内核的驱动程序drivers\input\misc\bma150.c学习电源管理
runtime PM只是提供辅助函数, 比如:
1 增加计数/减少计数
2. 使能runtime pm
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
修改LCD驱动程序具备runtime PM功能
1 在platform_driver lcd_drv 的.driver 的{.pm}成员dev_pm_ops结构体里提供3个回调函数: runtime_suspend, runtime_resume, runtime_idle,休眠/唤醒lcd(使能设置寄存器,使能背光灯)
2 对于runtime PM,默认状态下设备的状态是suspended, 如果硬件上它是运行状态,需要调用pm_runtime_set_active()来修改它的状态
然后调用pm_runtime_enable()来使能runtime PM , 一般是在probe函数里调用上述函数
3 在对应的系统调用接口里调用: pm_runtime_get_sync / pm_runtime_put_sync : 增加/减小计数值, 并且让设备处于resume或suspend状态
4 在remove函数里调用pm_runtime_disable()
前提: 配置内核支持runtime PM
make menuconfig
Power management options —>
[*] Run-time PM core functionality
使用
1.
echo on > /sys/devices/platform/mylcd/power/control
echo auto > /sys/devices/platform/mylcd/power/control
2. 在对应的系统调用接口里调用: pm_runtime_get_sync / pm_runtime_put_sync
3. autosuspend: 如果不想让设备频繁地开、关,可以使用autosuspend功能
驱动里: 执行pm_runtime_use_autosuspend来设置启动autosuspend功能,
put设备时, 执行这2个函数:
pm_runtime_mark_last_busy(&lcd_dev.dev);
pm_runtime_put_sync_autosuspend(&lcd_dev.dev);
用户空间, 执行以下命令设置时间值:
echo 2000 > /sys/devices/platform/mylcd/power/autosuspend_delay_ms
2 修改数码相框以支持自动关闭LCD
关闭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
修改input_manager.c,在InputInit中signal(SIGALRM, SigAlrmFn)注册信号处理函数,信号处理函数关闭LCD。
static void SigAlrmFn(int Sigal){
system(“echo auto > /sys/devices/platform/mylcd/power/control”);
g_bLCDOn = 0; //关闭状态
}
在InputEventThreadFunction函数中的读取循环里用闹钟函数alarm(15)如果15秒内没有按压触摸屏,则超时。定义一个g_bLCDOn变量判断开启关闭。如果读到数据且关闭g_bLCDOn=0,则修改超时时间alarm(15),system(“echo on > /sys/devices/platform/mylcd/power/control”)打开lcd,g_bLCDOn=1。
配置内核添加驱动
a. drivers/input/touchscreen/Makefile
obj-
(
C
O
N
F
I
G
T
O
U
C
H
S
C
R
E
E
N
S
3
C
2410
)
+
=
s
3
c
t
s
.
o
m
a
k
e
m
e
n
u
c
o
n
f
i
g
添
加
触
摸
屏
驱
动
b
.
d
r
i
v
e
r
s
/
v
i
d
e
o
/
M
a
k
e
f
i
l
e
o
b
j
−
(CONFIG_TOUCHSCREEN_S3C2410) += s3c_ts.o make menuconfig 添加触摸屏驱动 b. drivers/video/Makefile obj-
(CONFIGTOUCHSCREENS3C2410)+=s3cts.omakemenuconfig添加触摸屏驱动b.drivers/video/Makefileobj−(CONFIG_FB_S3C2410) += lcd_4.3.o
添加修改LCD驱动
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变为2,然后执行echo auto只能让使用计数减1变为1。
要再次修改lcd驱动,在mylcd_open中将pm_runtime_get_sync改为pm_runtime_forbid来增加计数,这会使得power.runtime_auto被设置为0,这样就可以使用echo auto来关闭lcd了。同样在mylcd_release中可以使用pm_runtime_allow来关闭。
static struct platform_device lcd_dev;
static int s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red,unsigned int green, unsigned int blue,unsigned int transp, struct fb_info *info);
struct lcd_regs { //寄存器
unsigned long lcdcon1;unsigned long lcdcon2;unsigned long lcdcon3;unsigned long lcdcon4;unsigned long lcdcon5;unsigned long lcdsaddr1;
unsigned long lcdsaddr2;unsigned long lcdsaddr3;unsigned long redlut;unsigned long greenlut;unsigned long bluelut;unsigned long reserved[9];
unsigned long dithmode;unsigned long tpal;unsigned long lcdintpnd;unsigned long lcdsrcpnd;unsigned long lcdintmsk;unsigned long lpcsel;
};
static int mylcd_open(struct fb_info *info, int user){
//pm_runtime_get_sync(&lcd_dev.dev);
pm_runtime_forbid(&lcd_dev.dev);
return 0;
}
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);
pm_runtime_allow(&lcd_dev.dev);
return 0;
}
static struct fb_ops s3c_lcdfb_ops = { //fb_info的操作函数
.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 struct fb_info *s3c_lcd;
static volatile unsigned long *gpbcon;
static volatile unsigned long *gpbdat;
static volatile unsigned long *gpccon;
static volatile unsigned long *gpdcon;
static volatile unsigned long *gpgcon;
static volatile struct lcd_regs* lcd_regs;
static u32 pseudo_palette[16];
static struct lcd_regs lcd_regs_backup;
/* from pxafb.c */
static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf){
chan &= 0xffff;
chan >>= 16 - bf->length;
return chan << bf->offset;
}
static int s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red,unsigned int green, unsigned int blue,unsigned int transp, struct fb_info *info){
unsigned int val;
if (regno > 16)
return 1;
/* 用red,green,blue三原色构造出val */
val = chan_to_field(red, &info->var.red);
val |= chan_to_field(green, &info->var.green);
val |= chan_to_field(blue, &info->var.blue);
pseudo_palette[regno] = val;
return 0;
}
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 struct notifier_block lcd_pm_notif_block = {
.notifier_call = lcd_suspend_notifier,
};
static void lcd_release(struct device * dev){}
static struct platform_device lcd_dev = { //平台dev
.name = "mylcd",
.id = -1,
.dev = {
.release = lcd_release,
},
};
static int lcd_probe(struct platform_device *pdev){ //平台drv的prob函数
pm_runtime_set_active(&pdev->dev); //电源管理
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_enable(&pdev->dev);
return 0;
}
static int lcd_remove(struct platform_device *pdev){
pm_runtime_disable(&pdev->dev);
return 0;
}
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);
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;
lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD控制器 */
lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本身 */
*gpbdat |= 1; /* 输出高电平, 使能背光 */
return 0;
}
static struct dev_pm_ops lcd_pm = {
.suspend = lcd_suspend,
.resume = lcd_resume,
.runtime_suspend = lcd_suspend,
.runtime_resume = lcd_resume,
};
struct platform_driver lcd_drv = { //平台drv
.probe = lcd_probe,
.remove = lcd_remove,
.driver = {
.name = "mylcd",
.pm = &lcd_pm,
}
};
static int lcd_init(void){
/* 1. 分配一个fb_info */
s3c_lcd = framebuffer_alloc(0, NULL);
/* 2. 设置 */
/* 2.1 设置固定的参数 */
strcpy(s3c_lcd->fix.id, "mylcd");
s3c_lcd->fix.smem_len = 480*272*16/8;
s3c_lcd->fix.type = FB_TYPE_PACKED_PIXELS;
s3c_lcd->fix.visual = FB_VISUAL_TRUECOLOR; /* TFT */
s3c_lcd->fix.line_length = 480*2;
/* 2.2 设置可变的参数 */
s3c_lcd->var.xres = 480;
s3c_lcd->var.yres = 272;
s3c_lcd->var.xres_virtual = 480;
s3c_lcd->var.yres_virtual = 272;
s3c_lcd->var.bits_per_pixel = 16;
/* RGB:565 */
s3c_lcd->var.red.offset = 11;
s3c_lcd->var.red.length = 5;
s3c_lcd->var.green.offset = 5;
s3c_lcd->var.green.length = 6;
s3c_lcd->var.blue.offset = 0;
s3c_lcd->var.blue.length = 5;
s3c_lcd->var.activate = FB_ACTIVATE_NOW;
/* 2.3 设置操作函数 */
s3c_lcd->fbops = &s3c_lcdfb_ops;
/* 2.4 其他的设置 */
s3c_lcd->pseudo_palette = pseudo_palette;
s3c_lcd->screen_size = 480*272*16/8;
/* 3. 硬件相关的操作 */
/* 3.1 配置GPIO用于LCD */
gpbcon = ioremap(0x56000010, 8);
gpbdat = gpbcon+1;
gpccon = ioremap(0x56000020, 4);
gpdcon = ioremap(0x56000030, 4);
gpgcon = ioremap(0x56000060, 4);
*gpccon = 0xaaaaaaaa; /* GPIO管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND */
*gpdcon = 0xaaaaaaaa; /* GPIO管脚用于VD[23:8] */
*gpbcon &= ~(3); /* GPB0设置为输出引脚 */
*gpbcon |= 1;
*gpbdat &= ~1; /* 输出低电平 */
*gpgcon |= (3<<8); /* GPG4用作LCD_PWREN */
/* 3.2 根据LCD手册设置LCD控制器, 比如VCLK的频率等 */
lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));
lcd_regs->lcdcon1 = (4<<8) | (3<<5) | (0x0c<<1);
// 垂直方向的时间参数
lcd_regs->lcdcon2 = (1<<24) | (271<<14) | (1<<6) | (9);
// 水平方向的时间参数
lcd_regs->lcdcon3 = (1<<19) | (479<<8) | (1);
// 水平方向的同步信号
lcd_regs->lcdcon4 = 40;
//信号的极性
lcd_regs->lcdcon5 = (1<<11) | (0<<10) | (1<<9) | (1<<8) | (1<<0);
/* 3.3 分配显存(framebuffer), 并把地址告诉LCD控制器 */
s3c_lcd->screen_base = dma_alloc_writecombine(NULL, s3c_lcd->fix.smem_len, &s3c_lcd->fix.smem_start, GFP_KERNEL);
lcd_regs->lcdsaddr1 = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30);
lcd_regs->lcdsaddr2 = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;
lcd_regs->lcdsaddr3 = (480*16/16); /* 一行的长度(单位: 2字节) */
/* 启动LCD */
lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD控制器 */
lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本身 */
*gpbdat |= 1; /* 输出高电平, 使能背光 */
/* 4. 注册 */
register_framebuffer(s3c_lcd);
/* 电源管理 */
register_pm_notifier(&lcd_pm_notif_block);
platform_device_register(&lcd_dev);
platform_driver_register(&lcd_drv);
return 0;
}
static void lcd_exit(void){
unregister_framebuffer(s3c_lcd);
lcd_regs->lcdcon1 &= ~(1<<0); /* 关闭LCD本身 */
*gpbdat &= ~1; /* 关闭背光 */
dma_free_writecombine(NULL, s3c_lcd->fix.smem_len, s3c_lcd->screen_base, s3c_lcd->fix.smem_start);
iounmap(lcd_regs);
iounmap(gpbcon);
iounmap(gpccon);
iounmap(gpdcon);
iounmap(gpgcon);
framebuffer_release(s3c_lcd);
unregister_pm_notifier(&lcd_pm_notif_block);
platform_device_unregister(&lcd_dev);
platform_driver_unregister(&lcd_drv);
}
module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");