基于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, &regulator_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
posted @ 2020-11-06 22:39  chen_Enp  阅读(222)  评论(0)    收藏  举报