数码相框的电源管理1

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.omakemenuconfigb.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");
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值