Linux电源管理

【Android】电源管理,进入和退出 Suspend To RAM 电源管理 Suspend To RAM 进入和退出 STR 阅读详情

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

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

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);
#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之前对驱动的电源进行控制,
这就需要suspend和resume来实现。

  • 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_resume和runtime_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_suspend和runtime_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文件方式

#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 huangcheng.job@foxmail.com”);
MODULE_DESCRIPTION(“Tiny4412 machine driver.”);
MODULE_ALIAS(“Exynos4412_machine”);
MODULE_VERSION(“V1.0”);
{% endcodeblock %}

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 huangcheng.job@foxmail.com”);
MODULE_DESCRIPTION(“Tiny4412 regulator driver.”);
MODULE_ALIAS(“Exynos4412_regulator”);
MODULE_VERSION(“V1.0”);
{% endcodeblock %}

  • lcd_drv
    加载前面的machine.ko和regulator.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联系了起来。

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 huangcheng.job@foxmail.com”);
MODULE_DESCRIPTION(“Tiny4412 regulator driver.”);
MODULE_ALIAS(“Exynos4412_regulator”);
MODULE_VERSION(“V1.0”);
{% endcodeblock %}

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

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

Windows——Modern Standby(现代待机) S0改Suspend to RAM(待机到内存)S3睡眠解决方案(以机械革命F1 i5-11300H为例) 问题描述 睡眠合上盖子后两三分钟内可以正常唤醒,但稍微时间长一点,打开盖子就无法正常唤醒,只有电源键的灯在一直闪烁,按键盘和鼠标也没有反应,笔记本开合程度也已经开到最大,可就是没有反应。必须长按电源键关机重启才可以正常工作。 耗电高,合盖一晚上能吃掉50%电 问题分析 理论知识:电源选项中S1,S2,S3,S4,S5的含义 微软在新版Win10上强推的Modern Standby(现代待机),体验那是一言难尽。 与100%正常工作和可靠的S3(挂载到RAM)睡眠相比,它永远无法正常工作且永远无法可靠运行。 阅读详情

相关推荐

【电源管理】Linux电源管理之RuntimePM模型

本文简述了Runtime power manager (RPM)的概念,分析RPM的软件框架,介绍运行机制。最后讲解修改驱动程序以支持RPM的步骤,在应用层如何使用等。

记录嵌入式技能成长的博客 1479

Linux电源管理(11)_Runtime PM之功能描述

1. 前言 终于可以写Runtime PM(后面简称RPM)了,说实话,蜗蜗有点小激动。因为从个人的角度讲,我很推崇使用RPM进行日常的动态电源管理,而不是suspend机制。 软件工程的基本思想就是模块化:高内聚和低耦合。通俗地讲呢,就是“各人自扫门前雪”,尽量扫好自己的(高内聚),尽量不和别人交互(低耦合)。而RPM正体现了这一思想:每个设备(包括CPU)都处理好自身的电源管理工作,

蓝精灵的专栏 4295

LINUX电源管理

Linux的系统suspend和resume Linux内核 runtime_PM 框架 Linux内核电源管理综述

weixin_44991625的博客 1974

Linux休眠唤醒之Runtime PM功能

仅用作学习记录

qq_39575672的博客 2749

Linux电源管理子系统

Linux cpuidle framework(1)_概述和软件架构 Linux cpuidle framework(2)_cpuidle core Linux cpuidle framework(3)_ARM64 generic CPU idle driver Linux common clock framework(1)_概述 Linux common

kangtingting01的专栏 856

linux电源管理架构

linux电源管理非常复杂,牵扯到系统机的待机、频率电压变换、系统空闲时间的处理以及每个设备驱动对系统待机的支持和每个设备的运行时runtime电源管理,可以说它和系统中的每个设备驱动都息息相关。linux内核电源管理的整体架构, 1)CPU在运行时根据系统负载进行动态电压和频率变换的CPUFreq 2) CPU在系统空闲时根据空闲的情况进行低功耗模式的CPUIdle 3)多核系统内下

viewsky11的专栏 3358

Linux电源管理1

Linux电源管理1(基于Linux6.6)---整体架构

ListQ的博客 1647

用Python+OpenCV给斗地主做个‘外挂’:手把手教你写个桌面记牌器(附源码)

本文详细介绍了如何使用Python和OpenCV开发一个智能斗地主记牌器,包括环境搭建、图像采集与预处理、扑克牌识别技术及PyQt5界面设计。通过实战案例,帮助开发者掌握图像处理与界面开发的结合应用,提升游戏辅助工具的编写能力。

weixin_29079643的博客 543

Linux电源管理详解

1.概述   虽然Linux可以在任何一台386以上的PC上运行,目前大多数人使用的都是新型的,带有各种外设的桌面PC或者笔记本电脑,这样,电源管理功能 (PM)就逐渐变得越来越重要。在笔记本电脑上电源管理可以节能,延长电池寿命,而在桌面PC上它可以降低幅射,降温,延长外设使用寿命。现在的操作系统大都内置了电源管理支持,例如 Windows 和 Linux。 2.PC机实现电源管理的

myxmu的专栏 2885

Matlab使用GitHub的踩坑记录(持续更新)

Matlab使用git的踩坑记录前言Matlab设置Git的官方帮助文档系统配置新建Git两种代码管理方式典型源代码管理SVN分布式源代码管理 Git注意:SVN和Git都可以用GitHub创建新的库 前言 作为Git初学者,对Git的代码版本管理非常非常喜欢,给了我极大的安全感。特别是想到之前的几次Matlab、Latex和python的代码莫名其妙被折腾没了又没法找回的悲剧,决定慢慢的走入gi...

voluntry的博客 7296

《基于Yolo算法的火灾检测系统设计与实现》开题报告

在消防安全领域,火灾对生命财产安全构成严重威胁。传统的火灾检测手段存在诸多不足,如误报率高、检测范围有限、响应速度慢等。基于 Yolo 算法的火灾检测系统能够利用深度学习技术,实现对火灾的高效、精准检测,具有重要的现实意义。         从社会安全角度看,该系统可以广泛应用于各类公共场所、工业厂房和居民住宅,实时监测火灾隐患,及时发出警报,为人员疏散和消防救援争取宝贵时间,有效降低火灾造成的人员伤亡和财产损失,保障社会公共安全。

m0_69704149的博客 1383

Linux电源管理10

Linux电源管理10(基于Linux6.6)---驱动与PM架构

ListQ的博客 1384

YOLOv8中间特征层可视化

在通过观察这些特征,可以清楚地看到模型如何逐步将输入图像转化为有意义的表示。

weixin_45314480的博客 6888

linux 电源管理

  1.概述   虽然 linux 可以在任何一台 386 以上的 PC 上运行,目前大多数人使用的都是新型的,带有各种外设的桌面PC或者笔记本电脑,这样,电源管理功能(PM)就逐渐变得越来越重要。在笔记本电脑上电源管 理可以节能,延长电池寿命,而在桌面PC上它可以降低幅射,降温,延长外设使...

chongzhouqi9927的博客 194

TOD和PPS精确时间同步技术

授时

u010783226的专栏 1万+

IPD的分析工具与模型(十六)CBB公用基础模块管理实现资源共享,缩短开发周期

CBB流程的开发环节,基于CBB规划中所识别和设计的CBB,进行CBB的详细设计(包括参数设计和接口设计),并最终实现CBB的落地开发。作为特殊类型的技术构件,CBB的开发流程,可沿用技术开发中的构件开发流程,具体包括概念、计划、开发和迁移等步骤,具体可参见前文的相关介绍。有了蓝图与架构设计,落地实施是关键攻坚。具体工作内容包括:具体工作内容包括:a. 以相似性、聚类、相对独行性、可组合、可扩展性、可维护性等原则为指导,按照产品的功能特征和业务类型,聚焦零散的功能点,形成功能模块,实现产品功能模块的划分;

corgi2020的博客 2000

翻译:Linux的电源管理架构

设备电源管理 Copyright (c) 2010 Rafael J. Wysocki, Novell Inc. Copyright (c) 2010 Alan Sternstern@rowland.harvard.edu   ************************************************************* 本文由DroidPhone翻译于201

DroidPhone的专栏 2万+

三极管和MOS管工作原理详解

PN结的形成 PN结是三极管以及场效应管中最基本的组成部分,要想彻底搞明白三极管以及场效应管的工作原理,必须先搞清楚PN结形成的原理和工作特性。 本征半导体以及空穴对 本征半导体(intrinsic semiconductor))完全不含杂质且无晶格缺陷的纯净半导体称为本征半导体。主要常见代表有硅、锗这两种元素的单晶体结构。硅、锗都是4价原子,在半导体内部形成相对稳定的共价键结构,如图 1所

技术交流是便宜的的博客 10万+

Linux电源管理9

Linux电源管理9(基于Linux6.6)---Runtime PM介绍

ListQ的博客 1115

【知识分享】单端到混合S参数转换指南

信号完整性(SI)工程师几乎总是必须使用S参数,因为许多的高速接口和行业标准都正在转向串行链路架构,并使用基于S参数测量的频域限制指标,了解其工作过程将会很有意义。

一只豌豆象的博客 1575

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

初学Linux电源管理

故计算机需要对硬件的电源状态管理。当被唤醒时的过程正好相反,先恢复供电,然后恢复系统的运行,再恢复之前保存的信息,这样设备从电源键或其他外设唤醒时,就可以快速切换到进入运行状态了。指的是我们可以把某个CPU热移除,然后系统就不会再往这个CPU上派任务了,这个CPU就可以放心地完全关闭电源了,当把这个CPU再热插入之后,就对这个CPU恢复供电,这个CPU就可以正常执行任务了。指的是当某个CPU上没有进程可调度的时候可以暂时局部关掉这个CPU的电源,从而达到省电的目的,当再有进程需要执行的时候再恢复电源。

苏法迪的专栏 1437

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

Linux 电源管理子系统

Linux 在消费电子领域的应用已经相当普遍,而对于消费电子产品而言,省电是一个重要的议题。Linux 电源管理非常复杂,牵扯到系统级的待机、频率电压变换、系统空闲时的处理以及每个设备驱动对系统待机的支持和每个设备的运行时(Runtime)电源管理,可以说它和系统中的每个设备驱动都息息相关。对于消费电子产品来说,电源管理相当重要。因此,这部分工作往往在开发周期中占据相当大的比重,下图呈现了 Lin...

Peter的专栏 1474

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

Linux电源管理2

Linux电源管理2(基于Linux6.6)---Generic PM介绍

ListQ的博客 1214

Linux电源管理深度剖析

组件/子系统主要功能生活比喻关键接口/命令ACPI硬件-OS电源管理接口翻译官BIOS设置, ACPI表CPUFreq运行时动态调频调压汽车自动变速箱CPUIdle空闲状态管理智能睡眠系统PM QoS服务质量控制VIP优先服务Runtime PM设备运行时电源管理自动启停系统系统级睡眠房屋全面节能模式能量感知调度能耗智能计算器Regulator电源稳压器管理智能电闸PowerTOP功耗分析工具能耗审计师powertop命令acpid用户空间ACPI守护进程。

X 1382

Linux的电源管理架构

设备电源管理 Copyright (c) 2010 Rafael J. Wysocki, Novell Inc. Copyright (c) 2010 Alan Sternstern@rowland.harvard.edu   ************************************************************* 本文由DroidPhone翻译于201

曼云-->孤峰 2444
上一篇: 嵌入式Linux网络编程——TCP/UDP
下一篇: Linux摄像头驱动1——vivid
hceng_blog
博客等级 码龄10年 263粉丝 53原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值