Linux电源管理

CSDN仅用于增加百度收录权重,排版未优化,日常不维护。请访问:www.hceng.cn 查看、评论。
本博文对应地址: https://hceng.cn/2018/01/18/Linux电源管理/#more

探究Linux电源管理模型,并为Tiny4412的LCD驱动添加电源管理。

这是2018的第一篇博客,选了一个几乎没有接触过的角度作为开篇,希望2018年学习更多,记录更多。

1.电源管理的两种模型

以往接触的Linux驱动,没遇到使用电池供电的情况,因此几乎没关注电源的管理。
然而实际中,不少使用电池供电的硬件平台,例如手机、POS机等,就需要对电源进行管理,比如在不使用设备的时候,休眠屏幕省电。

Linux电源管理模型有两种:系统睡眠模型suspendRuntime电源管理模型

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功能加入内核:

Power management options  --->
    [*] Suspend to RAM and standby

这里默认是勾选上了的,就不管了。

进入Tiny4412内核,尝试休眠echo mem > /sys/power/state,系统提示**No wake-up sources!**。

可见,要进入休眠,必须要有唤醒源,没有唤醒源,休眠也没有意义。

2.2.1 设置唤醒源

唤醒源最常见的就是按键中断,就如同手机进入锁屏状态下,按下电源键唤醒一样,因此先写一个按键驱动。

  • 原理图:

    底板上有四个按键,分别连在GPX3_2、GPX3_3、GPX3_4、GPX3_5,引脚状态常高,按键按下变低电平。

  • 设备树:

    button_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>;
    };      
  • 按键驱动:
        //设置为中断唤醒源
        irq_set_irq_wake(irq, 1);
  • usb4604驱动:
    前面的Exynos4412——网卡移植和NFS启动里面,移植USB4604驱动时,删除了电源管理的代码,实际测试中唤醒时USB设备会报错,添加上电源管理相关代码即可:
#ifdef CONFIG_PM_SLEEP
static 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;
}
#endif

static 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结构体

static struct notifier_block lcd_pm_notif_block = {
	.notifier_call = lcd_suspend_notifier,
};

b.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;
	}
}

c.注册notifier
在驱动init()或probe()里注册:

	ret = register_pm_notifier(&lcd_pm_notif_block);
    if(ret) {
        printk("failed to register pm notifier.\n");
        return  -EINVAL;
    }

在前面LCD驱动上修改lcd_drv.c,测试如下:

2.3.2 Suspend和Resume

前面的notifier只是通知,在冻结APP之前重启APP之后通知,
而电源管理应该刚好相反,是在冻结APP之后重启APP之前对驱动的电源进行控制,
这就需要suspendresume来实现。

  • a.在platform_driver里的driver里添加pm结构体:
static 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成员函数:
static struct dev_pm_ops lcd_pm = {
	.suspend = s702_lcd_suspend,
	.resume  = s702_lcd_resume,	
};
  • c.编写成员函数:
    {% codeblock lang:c %}
    static 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;

}
{% endcodeblock %}

这里只是简单的关闭/打开显示,理论上的操作应该是:
休眠时先备份所有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_resumeruntime_idle的调用,没有看到runtime_suspend
实际上,如果设备不提供runtime_idle, 则最终会调用runtime_suspend

3.2 使用Runtime功能

首先将Runtime功能加入内核,但本内核4.13.9里没找到相关选项,应该默认已经加入到内核里面了。

  • 调用方式一
    驱动程序提供接口, APP来调用。
    在驱动函数的open()close()里,增加和减少引用计数。
    APP调用驱动的时候就能相应的恢复、暂停设备。

  • 调用方式二
    直接操作应用层文件:
    恢复:

echo on >  /sys/devices/.../power/control

流程:control_store(drivers\base\power\sysfs.c) -> pm_runtime_forbid -> atomic_inc -> rpm_resume

暂停:

echo auto >  /sys/devices/.../power/control

流程:control_store(drivers\base\power\sysfs.c) -> pm_runtime_allow -> atomic_dec_and_test -> rpm_idle

3.3 使驱动支持Runtime

  • a.在platform_driver里的driver里添加pm结构体:(和前面的一样,这里就无需操作)
static 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成员函数:
static 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_suspendruntime_resume,runtime和suspend的暂停配置是一样的,直接使用前面的。

  • c.编写成员函数:(和前面的一样,这里就无需操作)
    {% codeblock lang:c %}
    static 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;

}
{% endcodeblock %}

  • d.使能Runtime:
    对于Runtime PM,默认状态下设备的状态是Suspended,
    如果硬件上它是运行状态,需要调用pm_runtime_set_active()来修改它的状态,
    然后调用pm_runtime_enable()来使能Runtime PM。

probe()函数的后面添加:

    pm_runtime_set_active(&pdev->dev);
    pm_runtime_enable(&pdev->dev);

反之,还要在remove()里禁止:

pm_runtime_disable(&pdev->dev);
  • e.修改计数:
    一般在open()release()里面增加和减少引用计数:
    {% codeblock lang:c %}
    static 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

};
{% endcodeblock %}

  • 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():
{% codeblock lang:c %}
//Runtime
pm_runtime_use_autosuspend(&pdev->dev);//add autosleep
pm_runtime_set_autosuspend_delay(&pdev->dev, 5000);

pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);

{% endcodeblock %}

同时,release()也要修改:
{% codeblock lang:c %}
static 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);
pm_runtime_mark_last_busy(dev);
pm_runtime_put_sync_autosuspend(dev);

return 0;

}
{% endcodeblock %}

此时,加载驱动后,运行应用程序,屏幕显示,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:
    {% codeblock lang:c [machine.c] https://github.com/hceng/learn/blob/master/tiny4412/04_power_management/regulator/use_machine/machine.c %}
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include

#if 1

static struct regulator_consumer_supply tiny4412_regulator_supplies[] =
{
REGULATOR_SUPPLY(“VCC_LCD”, “11c00000.lcd_s702”),//consumer的电源引脚名称;consumer的名字
};

#else

static struct regulator_consumer_supply tiny4412_regulator_supplies[] =
{
REGULATOR_SUPPLY(“VCC_LCD”, “lcd_s702”),
};

#endif

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 [email protected]”);
MODULE_DESCRIPTION(“Tiny4412 machine driver.”);
MODULE_ALIAS(“Exynos4412_machine”);
MODULE_VERSION(“V1.0”);
{% endcodeblock %}

  • driver
    提供操作函数并注册regulator:
    {% codeblock lang:c [regulator.c] https://github.com/hceng/learn/blob/master/tiny4412/04_power_management/regulator/use_machine/regulator.c %}
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include

static 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;
else
    return 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 [email protected]”);
MODULE_DESCRIPTION(“Tiny4412 regulator driver.”);
MODULE_ALIAS(“Exynos4412_regulator”);
MODULE_VERSION(“V1.0”);
{% endcodeblock %}

  • lcd_drv
    加载前面的machine.koregulator.ko,名字匹配后调用probe()注册regulator
    在LCD驱动中,若想使用regulator系统,需要先在LCD驱动的probe()根据名字获取对应regulator
    //regulator
    tiny4412_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

static int lcd_remove(struct platform_device *pdev)
{
    //Direct Off: ENVID and ENVID_F are set to "0" simultaneously.
    unsigned int temp;
    
    temp = readl(lcd_regs_base + VIDCON0);
    temp &= ~(0x01 << 1 | 0x01 << 0);
    writel(temp, lcd_regs_base + VIDCON0);

    regulator_put(tiny4412_regulator);

    pm_runtime_disable(&pdev->dev);

    unregister_framebuffer(tiny4412_lcd);
    dma_free_writecombine(NULL, tiny4412_lcd->fix.smem_len, tiny4412_lcd->screen_base, tiny4412_lcd->fix.smem_start);
    framebuffer_release(tiny4412_lcd);

    return 0;
}

4.3.2 设备树方式

与前面的操作几乎一样,只不过是在dts实现device

  • dts
    regulators节点下添加新的regulator,设置约束条件
	regulators {
    		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节点下,添加级联关系

    lcd_s702@11C00000 {
        compatible = "tiny4412, lcd_s702";
        reg = <0x11C00000  0x20c0 0x10010210 0x08 0x10023c80 0x04 0x1003c000 0x1000>;
        pinctrl-names = "default";
        pinctrl-0 = <&lcd_s702>;
        clocks = <&clock CLK_FIMD0 &clock CLK_ACLK160>;
        clock-names = "fimd0","aclk160";
        vlcd-supply = <&lcd_reg>;
        status = "okay";
    }; 

其中vlcd-supply与前面的regulator联系了起来。

  • driver
    提供操作函数及注册:
    {% codeblock lang:c [regulator.c] https://github.com/hceng/learn/blob/master/tiny4412/04_power_management/regulator/use_dts/regulator.c %}
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include

static 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;
else
    return 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 [email protected]”);
MODULE_DESCRIPTION(“Tiny4412 regulator driver.”);
MODULE_ALIAS(“Exynos4412_regulator”);
MODULE_VERSION(“V1.0”);
{% endcodeblock %}

  • lcd_drv
    和前面的使用完全一致。

参考资料:
韦东山第三期项目视频_电源管理
蜗窝科技

你可能感兴趣的:(嵌入式基础,Linux驱动)