探究Linux电源管理模型,并为Tiny4412的LCD驱动添加电源管理。
这是2018的第一篇博客,选了一个几乎没有接触过的角度作为开篇,希望2018年学习更多,记录更多。
1.电源管理的两种模型
以往接触的Linux驱动,没遇到使用电池供电的情况,因此几乎没关注电源的管理。
然而实际中,不少使用电池供电的硬件平台,例如手机、POS机等,就需要对电源进行管理,比如在不使用设备的时候,休眠屏幕省电。
Linux电源管理模型有两种:系统睡眠模型suspend和Runtime电源管理模型。
1.1系统睡眠模型Suspend
On (on) S0 - Working
Standby (standby) S1 - CPU and RAM are powered but not executed
Suspend to RAM (mem) S3 - RAM is powered and the running content is saved to RAM
Suspend to Disk, Hibernation (disk) S4 - All content is saved to Disk and power down
S3 aka STR(suspend to ram),挂起到内存,简称待机。计算机将目前的运行状态等数据存放在内存,关闭硬盘、外设等设备,进入等待状态。此时内存仍然需要电力维持其数据,但整机耗电很少。恢复时计算机从内存读出数据,回到挂起前的状态,恢复速度较快。对DDR的耗电情况进行优化是S3性能的关键,大多数手持设备都是用S3待机。
S4 aka STD(suspend to disk),挂起到硬盘,简称休眠。把运行状态等数据存放在硬盘上某个文件或者某个特定的区域,关闭硬盘、外设等设备,进入关机状态。此时计算机完全关闭,不耗电。恢复时计算机从休眠文件/分区中读出数据,回到休眠前的状态,恢复速度较慢。
系统休眠模型给我的感觉是以整机角度进行省电。
S3类似电脑的睡眠,在教长时间不使用电脑后,电脑黑屏,再次敲击键盘迅速显示桌面,原来的工作内容仍不变。
S4类似电脑的休眠,在长时间不使用电脑后,电脑黑屏,再次敲击键盘无反应,按下电源键,开机,原来的工作内容仍不变。
对于嵌入式设备,更多的是使用S3,将数据暂时放在内存里,以实现快速恢复,就像手机的电源键按下黑屏,再次按下迅速亮屏。
在Linux中,通过cat /sys/power/state
可以得知当前设备支持的节能模式,一般情况有如下选项:
- freeze:不涉及具体的Hardware或Driver,只是冻结所有的进程,包括用户空间进程及内核线程,能节省的能量较少,使用场景不多;
- standby:前面的S1状态,CPU处于浅睡眠模式,主要针对CPU功耗;
- mem:前面的S3状态,Suspend to RAM;
- disk:前面的S4状态,Suspend to Disk;
需要设置以上模式,只需echo mem > /sys/power/state
即可。
1.2 Runtime电源管理模型
Runtime电源管理模型给我的感觉是以模块角度进行省电。
某种程度上是“高内聚和低耦合”的体现。
每个设备(包括CPU)都处理好自身的电源管理工作,尽量以最低的能耗完成交代的任务,尽量在不需要工作的时候进入低功耗状态,尽量不和其它模块有过多耦合。每个设备都是最节省的话,整个系统一定是最节省的。
2. 系统睡眠模型suspend
2.1 Suspend流程分析
suspend的流程还是挺复杂的,向/sys/power/state
写入命令后再到唤醒,将进行以下流程:
- 对源码进行分析,其休眠过程如下:
驱动程序里休眠相关的电源管理函数的调用过程:prepare—>suspend—>suspend_late—>suspend_noirq
- 对源码进行分析,其唤醒过程如下:
驱动程序里唤醒相关的电源管理函数的调用过程:resume_noirq—>resume_early—>resume->complete
对于驱动程序,我们主要关心Device PM
(针对每一个驱动)和少量Platform dependent PM
(针对CPU芯片相关)的内容。
2.2 使用Suspend功能
首先将suspend功能加入内核:
这里默认是勾选上了的,就不管了。
进入Tiny4412内核,尝试休眠echo mem > /sys/power/state
,系统提示No wake-up sources!
。
可见,要进入休眠,必须要有唤醒源,没有唤醒源,休眠也没有意义。
2.2.1 设置唤醒源
唤醒源最常见的就是按键中断,就如同手机进入锁屏状态下,按下电源键唤醒一样,因此先写一个按键驱动。
原理图:
底板上有四个按键,分别连在GPX3_2、GPX3_3、GPX3_4、GPX3_5,引脚状态常高,按键按下变低电平。设备树:
1234567button_interrupt: button_interrupt {compatible = "tiny4412,button_interrupt";tiny4412,gpx3_2 = <&gpx3 2 GPIO_ACTIVE_HIGH>;tiny4412,gpx3_3 = <&gpx3 3 GPIO_ACTIVE_HIGH>;tiny4412,gpx3_4 = <&gpx3 4 GPIO_ACTIVE_HIGH>;tiny4412,gpx3_5 = <&gpx3 5 GPIO_ACTIVE_HIGH>;};按键驱动:
12//设置为中断唤醒源irq_set_irq_wake(irq, 1);usb4604驱动:
前面的Exynos4412——网卡移植和NFS启动里面,移植USB4604驱动时,删除了电源管理的代码,实际测试中唤醒时USB设备会报错,添加上电源管理相关代码即可:123456789101112131415161718192021222324#ifdef CONFIG_PM_SLEEPstatic int usb4604_i2c_suspend(struct device *dev){struct i2c_client *client = to_i2c_client(dev);struct usb4604 *hub = i2c_get_clientdata(client);usb4604_switch_mode(hub, USB4604_MODE_STANDBY);return 0;}static int usb4604_i2c_resume(struct device *dev){struct i2c_client *client = to_i2c_client(dev);struct usb4604 *hub = i2c_get_clientdata(client);usb4604_switch_mode(hub, hub->mode);return 0;}#endifstatic SIMPLE_DEV_PM_OPS(usb4604_i2c_pm_ops, usb4604_i2c_suspend,usb4604_i2c_resume);
2.2.2 休眠唤醒(未成功)
加入中断源后,休眠过程不再提示No wake-up sources!
,看样子休眠应该成功了。
此时,按下按键,板子并未唤醒,琢磨了一阵,初步怀疑有以下原因:
- 唤醒的时候,应该需要uboot配合,uboot读取某个寄存器来判断是正常启动还是唤醒;
- Exynos4412的PMU特性没摸透,可能需要其它额外的操作;
- Exynos4412启动时的BL1和BL2,可能也有影响;
这里先卡住,继续后面。
2.3 使驱动支持Suspend
2.3.1 通知Notifier
前面的suspend流程分析里面,
在冻结APP之前,使用pm_notifier_call_chain(PM_SUSPEND_PREPARE)
来通知驱动程序;
在重启APP之后,使用pm_notifier_call_chain(PM_POST_SUSPEND)
来通知驱动程序;
因此,如果驱动程序有事情在上述时机要处理,可以使用notifier
机制。
使用步骤:
a.定义notifier_block结构体
b.notifier操作函数
c.注册notifier
在驱动init()或probe()里注册:
在前面LCD驱动上修改lcd_drv.c
,测试如下:
2.3.2 Suspend和Resume
前面的notifier
只是通知,在冻结APP之前和重启APP之后通知,
而电源管理应该刚好相反,是在冻结APP之后和重启APP之前对驱动的电源进行控制,
这就需要suspend
和resume
来实现。
a.在
platform_driver
里的driver
里添加pm
结构体:12345678910static struct platform_driver lcd_driver ={.driver = {.name = "lcd_s702",.pm = &lcd_pm,.of_match_table = of_match_ptr(lcd_dt_ids),},.probe = lcd_probe,.remove = lcd_remove,};b.设置
pm
成员函数:1234static struct dev_pm_ops lcd_pm = {.suspend = s702_lcd_suspend,.resume = s702_lcd_resume,};c.编写成员函数:
12345678910111213141516171819202122232425262728static int s702_lcd_suspend(struct device *dev){//lcd休眠操作//Direct Off: ENVID and ENVID_F are set to "0" simultaneously.unsigned int temp;printk("enter %s\n", __func__);temp = readl(lcd_regs_base + VIDCON0);temp &= ~(0x01 << 1 | 0x01 << 0);writel(temp, lcd_regs_base + VIDCON0);return 0;}static int s702_lcd_resume(struct device *dev){//lcd唤醒操作//Display On: ENVID and ENVID_F are set to "1".unsigned int temp;printk("enter %s\n", __func__);temp = readl(lcd_regs_base + VIDCON0);writel(temp | (0x01 << 1) | (0x01 << 0), lcd_regs_base + VIDCON0);return 0;}
这里只是简单的关闭/打开显示,理论上的操作应该是:
休眠时先备份所有LCD相关寄存器,恢复时再恢复所有寄存器,以及其它可能操作,比如重新开启时钟等。
同理,因为LCD显示和backlight是分开的,因此需要在backlight里也进行类似操作。
3.Runtime电源管理模型
前面的suspend系统睡眠模型是将整个系统进行休眠,但如果需要在系统运行时,单独对某个模块进行休眠,就需要Runtime电源管理模型,这两个模型互相协作,才能最大的发挥电源管理的效果。
Runtime电源管理模型的原理比较简单,就是计数,
当该设备驱动被使用时就加1,放弃使用时就减1,
计数大于1时,就打开该设备的电源,等于0时就关闭电源。
Runtime PM相关的函数:
a. 使能/禁止 Runtime PM:pm_runtime_enable
/ pm_runtime_disable
(修改disable_depth变量)
b. 增加计数/减少计数:pm_runtime_get_sync
/ pm_runtime_put_sync
(修改usage_count变量)
c. 回调函数 暂停/恢复/空闲:runtime_suspend
/ runtime_resume
/ runtime_idle
3.1 Runtime流程分析
调用
pm_runtime_get_sync
去增加使用次数以及恢复的流程如下:调用
pm_runtime_put_sync
去减少使用次数以及暂停的流程如下:
前面的两个流程,只看到了runtime_resume
和runtime_idle
的调用,没有看到runtime_suspend
,
实际上,如果设备不提供runtime_idle
, 则最终会调用runtime_suspend
。
3.2 使用Runtime功能
首先将Runtime功能加入内核,但本内核4.13.9里没找到相关选项,应该默认已经加入到内核里面了。
调用方式一:
驱动程序提供接口, APP来调用。
在驱动函数的open()
、close()
里,增加和减少引用计数。
APP调用驱动的时候就能相应的恢复、暂停设备。调用方式二:
直接操作应用层文件:
恢复:1echo on > /sys/devices/.../power/control
流程:control_store(drivers\base\power\sysfs.c) -> pm_runtime_forbid -> atomic_inc -> rpm_resume
暂停:
流程:control_store(drivers\base\power\sysfs.c) -> pm_runtime_allow -> atomic_dec_and_test -> rpm_idle
3.3 使驱动支持Runtime
a.在
platform_driver
里的driver
里添加pm
结构体:(和前面的一样,这里就无需操作)12345678910static struct platform_driver lcd_driver ={.driver = {.name = "lcd_s702",.pm = &lcd_pm,.of_match_table = of_match_ptr(lcd_dt_ids),},.probe = lcd_probe,.remove = lcd_remove,};b.设置
pm
成员函数:1234567static struct dev_pm_ops lcd_pm ={.suspend = s702_lcd_suspend,.resume = s702_lcd_resume,.runtime_suspend = s702_lcd_suspend,.runtime_resume = s702_lcd_resume,};
添加runtime_suspend
和runtime_resume
,runtime和suspend的暂停配置是一样的,直接使用前面的。
c.编写成员函数:(和前面的一样,这里就无需操作)
12345678910111213141516171819202122232425262728static int s702_lcd_suspend(struct device *dev){//lcd休眠操作//Direct Off: ENVID and ENVID_F are set to "0" simultaneously.unsigned int temp;printk("enter %s\n", __func__);temp = readl(lcd_regs_base + VIDCON0);temp &= ~(0x01 << 1 | 0x01 << 0);writel(temp, lcd_regs_base + VIDCON0);return 0;}static int s702_lcd_resume(struct device *dev){//lcd唤醒操作//Display On: ENVID and ENVID_F are set to "1".unsigned int temp;printk("enter %s\n", __func__);temp = readl(lcd_regs_base + VIDCON0);writel(temp | (0x01 << 1) | (0x01 << 0), lcd_regs_base + VIDCON0);return 0;}d.使能Runtime:
对于Runtime PM,默认状态下设备的状态是Suspended,
如果硬件上它是运行状态,需要调用pm_runtime_set_active()
来修改它的状态,
然后调用pm_runtime_enable()
来使能Runtime PM。
在probe()
函数的后面添加:
反之,还要在remove()
里禁止:
e.修改计数:
一般在open()
和release()
里面增加和减少引用计数:1234567891011121314151617181920212223242526272829303132333435363738static int s702_lcd_open(struct fb_info *info, int user){struct device *dev = info->dev;int ret;printk("enter %s\n", __func__);ret = pm_runtime_get_sync(dev);if (ret < 0 && ret != -EACCES){pm_runtime_put_sync(dev);return ret;}return 0;}static int s702_lcd_release(struct fb_info *info, int user){struct device *dev = info->dev;printk("enter %s\n", __func__);pm_runtime_put_sync(dev);return 0;}static struct fb_ops tiny4412_lcdfb_ops ={.owner = THIS_MODULE,.fb_setcolreg = cfb_setcolreg, //设置调色板,实现伪颜色表.fb_fillrect = cfb_fillrect, //填充矩形.fb_copyarea = cfb_copyarea, //数据复制.fb_imageblit = cfb_imageblit, //图形填充.fb_open = s702_lcd_open,.fb_release = s702_lcd_release};f.优化——加入延时机制:
现在的程序基本完成,测试的时候,先加载backlight驱动insmod backlight_drv.ko
,运行背光应用程序设置亮度./app 200
;
然后加载LCD驱动insmod lcd_drv.ko
,运行图片显示应用程序jpg_rgb显示图像./jpg_rgb cq.jpg
,结果并没有显示图像,
手动的echo on > /sys/devices/platform/11c00000.lcd_s702/power/control
才正常显示图像。
上述流程中,运行图片显示应用程序时,先open()
了一次,引用计数加1,程序调用完又马上close
,引用计数减1,导致看不到显示,
重新操作/sys/devices/platform/11c00000.lcd_s702/power/control
就立即显示了图像。
对于正常的使用情景是,运行应用程序,立即图片显示,然后维持显示一段时间,如果有操作继续显示,没有的话再自己熄灭。
因此,想要实现上面的功能,还需要加入自动休眠。
在之前的probe()
中加入pm_runtime_use_autosuspend()
:
同时,release()
也要修改:
此时,加载驱动后,运行应用程序,屏幕显示,5s后,屏幕自动熄灭,再次运行程序或者修改control
来重新显示。
通过函数pm_runtime_set_autosuspend_delay()
或修改echo xx > /sys/devices/.../power/autosuspend_delay_ms
来修改自动休眠时间。
完整代码见Github。
4.regulator系统
前面的两个电源管理模型偏“软”,regulator系统偏“硬”,
在复杂的单板中,有专门的电源管理芯片控制各个模块电源,regulator系统就是为这个电源芯片编写驱动,实现电源管理。
4.1 regulator框架
①Regulator(稳定器):指可以自动维持恒定电压(voltage)或电流(current)的装置,一般指电源芯片。在嵌入式设备中,基本上每一种电压,都是经过regulator输出的;
②③Consumer(使用者):使用电源的装置,Regulator是给Consumer供电的;
④Machine(单板):使用软件语言(struct regulator_init_data),静态的描述regulator在板级的物理现状,包含:
a.级联关系:Regulator A的输出是Regulator B的输入,Regulator A就是Supply regulator,B是Consumer regulator;
b.约束限制:Regulator Constraints,比如电压/电流最大值/最小值、允许的操作等;
从设备驱动的角度看,regulator系统比较简单,
Machine提供Supply与Consumer的对应关系、单板相关的约束条件(device);
Regulator提供电源芯片的控制函数,如使能/去能、设置电压/电流等(driver);
Consumer调用Regulator相关函数控制电源的开关、调节(use);
即一个描述关系,一个提供相关函数,一个调用相关函数。
4.2 regulator流程
4.3 regulator驱动
regulator系统仍然是采用总线设备驱动模型。
device采用c文件或设备树的形式,提供硬件相关信息;
driver加载后,一但和device名字匹配,就调用probe()
函数注册register
,并绑定操作函数;
后面将使用两种实现regulator驱动。
这两种方式的核心都是一样的,device
先提供Supply与Consumer的对应关系、单板相关的约束条件;driver
提供电源芯片的控制函数,如使能/去能、设置电压/电流等。
4.3.1 C文件方式
device:
在一个单板C文件里,提供级联关系regulator_consumer_supply
,约束条件regulator_init_data
:[machine.c]link 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778static struct regulator_consumer_supply tiny4412_regulator_supplies[] ={REGULATOR_SUPPLY("VCC_LCD", "11c00000.lcd_s702"),//consumer的电源引脚名称;consumer的名字};static struct regulator_consumer_supply tiny4412_regulator_supplies[] ={REGULATOR_SUPPLY("VCC_LCD", "lcd_s702"),};static struct regulator_init_data tiny4412_regulator_init_data ={.constraints = {//.name = "tiny4412_regulator",.min_uV = 1000000,.max_uV = 1000000,.valid_modes_mask = REGULATOR_MODE_NORMAL,.valid_ops_mask = REGULATOR_CHANGE_STATUS,.boot_on = 0,.always_on = 0,},.num_consumer_supplies = 1,.consumer_supplies = tiny4412_regulator_supplies,};static void tiny4412_regulator_release(struct device *dev){}static struct platform_device tiny4412_regulator_dev ={.name = "tiny4412_regulator",.id = -1,.dev = {.release = tiny4412_regulator_release,.platform_data = &tiny4412_regulator_init_data,},};static int tiny4412_regulator_machine_init(void){printk("enter %s\n", __func__);platform_device_register(&tiny4412_regulator_dev);return 0;}static void tiny4412_regulator_machine_exit(void){printk("enter %s\n", __func__);platform_device_unregister(&tiny4412_regulator_dev);}module_init(tiny4412_regulator_machine_init);module_exit(tiny4412_regulator_machine_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("hceng <huangcheng.job@foxmail.com>");MODULE_DESCRIPTION("Tiny4412 machine driver.");MODULE_ALIAS("Exynos4412_machine");MODULE_VERSION("V1.0");driver
提供操作函数并注册regulator
:[regulator.c]link 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120static int regulator_states = 0;static int tiny4412_regulator_enable(struct regulator_dev *rdev){printk("enter %s\n", __func__);printk("------LCD Power Open.------\n");regulator_states = 1;return 0;}static int tiny4412_regulator_disable(struct regulator_dev *rdev){printk("enter %s\n", __func__);printk("------LCD Power Close.------\n");regulator_states = 0;return 0;}static int tiny4412_regulator_is_enabled(struct regulator_dev *rdev){printk("enter %s\n", __func__);printk("------LCD Power Test.------\n");if (regulator_states)return 1;elsereturn 0;}static struct regulator_ops tiny4412_regulator_ops ={.enable = tiny4412_regulator_enable,.disable = tiny4412_regulator_disable,.is_enabled = tiny4412_regulator_is_enabled,};static struct regulator_desc tiny4412_regulator_desc ={.name = "tiny4412_regulator",.ops = &tiny4412_regulator_ops,.type = REGULATOR_VOLTAGE,//电压源.id = 0,.owner = THIS_MODULE,.n_voltages = 1,//能提供的电压数量};static struct regulator_dev *tiny4412_regulator_dev;static int tiny4412_regulator_probe(struct platform_device *pdev){struct regulator_config config = { };config.dev = &pdev->dev;config.init_data = dev_get_platdata(&pdev->dev);printk("enter %s\n", __func__);tiny4412_regulator_dev = devm_regulator_register(&pdev->dev, &tiny4412_regulator_desc, &config);if (IS_ERR(tiny4412_regulator_dev)){printk("devm_regulator_register error!\n");return PTR_ERR(tiny4412_regulator_dev);}return 0;}static int tiny4412_regulator_remove(struct platform_device *pdev){printk("enter %s\n", __func__);devm_regulator_unregister(&pdev->dev, tiny4412_regulator_dev);return 0;}struct platform_driver tiny4412_regulator_drv ={.probe = tiny4412_regulator_probe,.remove = tiny4412_regulator_remove,.driver = {.name = "tiny4412_regulator",}};static int tiny4412_regulator_init(void){printk("enter %s\n", __func__);platform_driver_register(&tiny4412_regulator_drv);return 0;}static void tiny4412_regulator_exit(void){printk("enter %s\n", __func__);platform_driver_unregister(&tiny4412_regulator_drv);}module_init(tiny4412_regulator_init);module_exit(tiny4412_regulator_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("hceng <huangcheng.job@foxmail.com>");MODULE_DESCRIPTION("Tiny4412 regulator driver.");MODULE_ALIAS("Exynos4412_regulator");MODULE_VERSION("V1.0");lcd_drv
加载前面的machine.ko
和regulator.ko
,名字匹配后调用probe()
注册regulator
。
在LCD驱动中,若想使用regulator系统,需要先在LCD驱动的probe()
根据名字获取对应regulator
:1234567//regulatortiny4412_regulator = regulator_get(&pdev->dev, "VCC_LCD");if (IS_ERR(tiny4412_regulator)){printk("regulator_get error!\n");return -EIO;}
再在suspend()
和resume()
封面便调用regulator_disable()
和regulator_enable()
。
此时,如果使用 系统睡眠模型 或 Runtime电源模型 进行休眠操作,就会调用到regulator系统的操作函数,实现电源管理芯片的关闭。
使用完后,释放regulator
:
4.3.2 设备树方式
与前面的操作几乎一样,只不过是在dts实现device
。
- dts
在regulators
节点下添加新的regulator
,设置约束条件:1234567891011121314151617181920regulators {compatible = "simple-bus";#address-cells = <1>;#size-cells = <0>;mmc_reg: regulator@0{compatible = "regulator-fixed";reg = <0>;regulator-name = "VMEM_VDD_2.8V";regulator-min-microvolt = <2800000>;regulator-max-microvolt = <2800000>;};lcd_reg: regulator@1{compatible = "tiny4412,lcd_regulator";regulator-name = "VCC_LCD";regulator-min-microvolt = <1200000>;regulator-max-microvolt = <1200000>;};};
在lcd节点下,添加级联关系:
其中vlcd-supply
与前面的regulator
联系了起来。
driver
提供操作函数及注册:[regulator.c]link 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129static int regulator_states = 0;static int tiny4412_regulator_enable(struct regulator_dev *rdev){printk("enter %s\n", __func__);printk("------LCD Power Open.------\n");regulator_states = 1;return 0;}static int tiny4412_regulator_disable(struct regulator_dev *rdev){printk("enter %s\n", __func__);printk("------LCD Power Close.------\n");regulator_states = 0;return 0;}static int tiny4412_regulator_is_enabled(struct regulator_dev *rdev){printk("enter %s\n", __func__);printk("------LCD Power Test.------\n");if (regulator_states)return 1;elsereturn 0;}static struct regulator_ops tiny4412_regulator_ops ={.enable = tiny4412_regulator_enable,.disable = tiny4412_regulator_disable,.is_enabled = tiny4412_regulator_is_enabled,};static struct regulator_desc tiny4412_regulator_desc ={.name = "tiny4412_regulator_dev",.ops = &tiny4412_regulator_ops,.type = REGULATOR_VOLTAGE,//电压源.id = 0,.owner = THIS_MODULE,.n_voltages = 1,//能提供的电压数量};static struct regulator_dev *tiny4412_regulator_dev;static int tiny4412_regulator_probe(struct platform_device *pdev){struct regulator_config config = { };config.dev = &pdev->dev;config.init_data = dev_get_platdata(&pdev->dev);printk("enter %s\n", __func__);tiny4412_regulator_dev = devm_regulator_register(&pdev->dev, &tiny4412_regulator_desc, &config);if (IS_ERR(tiny4412_regulator_dev)){printk("devm_regulator_register error!\n");return PTR_ERR(tiny4412_regulator_dev);}return 0;}static int tiny4412_regulator_remove(struct platform_device *pdev){printk("enter %s\n", __func__);devm_regulator_unregister(&pdev->dev, tiny4412_regulator_dev);return 0;}static const struct of_device_id regulators_of_match[] ={{ .compatible = "tiny4412,lcd_regulator" },{ },};MODULE_DEVICE_TABLE(of, regulators_of_match);struct platform_driver tiny4412_regulator_drv ={.probe = tiny4412_regulator_probe,.remove = tiny4412_regulator_remove,.driver = {.name = "tiny4412_regulator_drv",.of_match_table = of_match_ptr(regulators_of_match),}};static int tiny4412_regulator_init(void){printk("enter %s\n", __func__);platform_driver_register(&tiny4412_regulator_drv);return 0;}static void tiny4412_regulator_exit(void){printk("enter %s\n", __func__);platform_driver_unregister(&tiny4412_regulator_drv);}module_init(tiny4412_regulator_init);module_exit(tiny4412_regulator_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("hceng <huangcheng.job@foxmail.com>");MODULE_DESCRIPTION("Tiny4412 regulator driver.");MODULE_ALIAS("Exynos4412_regulator");MODULE_VERSION("V1.0");lcd_drv
和前面的使用完全一致。
参考资料:
韦东山第三期项目视频_电源管理
蜗窝科技