精华内容
参与话题
问答
  • Linux电源管理

    千次阅读 2019-05-06 09:17:56
    CSDN仅用于增加百度收录权重,排版未...探究Linux电源管理模型,并为Tiny4412的LCD驱动添加电源管理。 这是2018的第一篇博客,选了一个几乎没有接触过的角度作为开篇,希望2018年学习更多,记录更多。 1.电源管理...

    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);
    
    #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文件方式

    #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.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联系了起来。

    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
      和前面的使用完全一致。

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

    展开全文
  • linux 电源管理

    千次阅读 2012-10-24 15:58:08
    ACPI共有六种状态,分别是S0到S5,它们代表的含义分别是:  S0--实际上这就是我们平常的工作状态,所有设备全开,功耗一般会超过80W;  S1--也称为POS(Power on Suspend),这时除了通过CPU时钟控制器将CPU关闭...

    ACPI共有六种状态,分别是S0到S5,它们代表的含义分别是:
      S0--实际上这就是我们平常的工作状态,所有设备全开,功耗一般会超过80W;
      S1--也称为POS(Power on Suspend),这时除了通过CPU时钟控制器将CPU关闭之外,其他的部件仍然正常工作,这时的功耗一般在30W以下。In this state, no system context is lost (CPU or chip set) and hardware maintains all system context.
      S2--这时CPU处于停止运作状态,总线时钟也被关闭,但其余的设备仍然运转。This state is similar to the S1 sleeping state except that the CPU and system cache context is lost
      S3--这就是我们熟悉的STR(Suspend to RAM),这时的功耗不超过10W。all system context is lost except system memory. CPU, cache, and chip set context are lost in this state.
      S4--也称为STD(Suspend to Disk),这时系统主电源关闭,硬盘存储S4前数据信息,所以S4是比S3更省电状态。It is assumed that the hardware platform has powered off all devices. Platform context is maintained.
      S5--这种状态是最干脆的,就是连电源在内的所有设备全部关闭,即关机(shutdown),功耗为0。

     

    linux支持3种ACPI的节电模式:

    1. S1 - POS standby, power on standby 显示屏断电,主机通电

    2. S3 - STR standby, (supend to ram) 挂起到内存,键盘不能唤醒,需要从电源键唤醒

    3. S4 -hibernate (supend to disk)挂起到硬盘,需要从硬盘恢复

     

    可以简化为 sleep 分为standby 和mem, hibernate即disk分为shutdown和platform(shutdown将系统状态保存到磁盘,BIOS关闭计算机;platform同时点亮挂起指示灯)

    For device driver, linux takes S1/S3 exactly the same. The difference is in linux kernel, but NOT the driver. The kernel does different things for S1 and S3.

     

    cat /sys/power/state   检查内核支持哪些节电模式,有standby,mem,disk等

     

    linux 设备模型中一条就是为了电源管理和系统管理,能让系统以特定的顺便遍历硬件。

    linux中休眠主要做三件事情:冻结用户进程和内核任务;调用注册的设备的suspend回调函数;休眠核心设备和CPU

     

    # echo standby > /sys/power/state

    用户对于/sys/power/state 的读写会调用到main.c中的state_store(),

    1. 同步文件系统

    2. 关掉用户态的helper进程,并调用suspend_freeze_processes()冻结所有的进程

    3. 调用suspend_devices_and_enter()休眠所有的外设。在这个函数中, 先对于dpm_list(device_add中调用device_pm_add时添加)中的每个设备调用dpm_prepare;然后在dpm_suspend()中对于dpm_list中所有的设备调用device_suspend()。device_suspend依次调用class->pm, type->pm, bus->pm。譬如pci总线,bus->pm是pci_bus_type的pci_pm_suspend。这个函数判定如果legacy pci的话(即在pci_driver中指定了suspend、suspend_late、resume、resume_early等)则调用pci_legacy_suspend,会由dev找到pci_dev,然后找到pci_drvier,调用其suspend(即在register_pci_driver中的参数);如果非legacy&(!pm),则pci_disable_enabled_device(写入非master);如果非legacy并且driver->pm不为NULL,则调用driver->pm接口(并不调用driver->suspend)。

    4. 调用dpm_suspend_enter。禁止所有的irq,对于dmp_list中的设备依照反序调用bus->pm->suspend_noirq,禁用没启用的CPU,sysdev_suspend,suspend_ops->enter(state)(要么acpi_suspend_ops_old,要么acpi_suspend_ops)。

     

    resume 的过程刚好是反向过程。

     

    因为所做的项目用到了多处kernel thread,在处理driver的时候要特别注意。默认情况下,所有的kernel thread并不能freeze(用户态的都可以),只有显示调用了set_freezable的才可以(去掉PF_NOFREEZE标志,但是不能重新设置PF_NOFREEZE标志)。kernel thread通过调用try_to_freeze进入睡眠(或者通过调用wait_event_freezable来调用try_to_freeze),如果try_to_freeze返回error,则整个hibernation过程失败。

     

    展开全文
  • linux电源管理

    2017-05-08 23:05:50
    当前流行的计算机系统都支持电源管理。特备是在笔记本电脑中电源管理有非常重要的作用。  目前比较流行的电源管理技术主要有两种: 高级电源管理(APM) , 高级配置与能源接口(ACPI).其中 ACPI 在两者中比较先进...

        当前流行的计算机系统都支持电源管理。特备是在笔记本电脑中电源管理有非常重要的作用。


        目前比较流行的电源管理技术主要有两种: 高级电源管理(APM) , 高级配置与能源接口(ACPI).其中 ACPI 在两者中比较先进, 它将电源管理交由操作系统负责, 可以有更灵活的管理方式。 有实例表明采用ACPI 管理方式的计算机在能耗方面比采用APM 管理方式的计算机明显比较节能。


        能否使用电源管理, 以及是否可以启用ACPI, 不仅仅要求操作系统进行支持,同时也依赖于硬件环境。 例如计算机的主板不支持ACPI的话, 操作系统也不可能利用ACPI进行电源管理。

       

       在Linux操作系统比较新的内核中, 缺省的配置均是使用ACPI进行电源管理。 但是要注意一点, 就是ACPI 与APM两种管理方式只能有一种进行实际的控制管理。 如果在编译系统内核时两种管理方式都选择了,那么系统内核会自动判断该计算机的硬件配置适合两种方式中的哪一种。如果该计算机的硬件对这两种管理方式都支持,那么操作系统将会自动选择ACPI方式进行管理。


       在设备的驱动程序中,应该对电源管理有足够的重视。哪怕只有一设备没有按照规范进行设备的电源管理,都会引起整个系统的电源管理失效,通常会使系统不能正常挂起。

       电源管理的框架如下 :

         1)在设备初始化时采用pm_register 函数进行注册。

         2) 在每次对硬件的操作之前调用pm_access函数,确认设血是否处理准备好的状态。

         3)在进入和退出挂起姿状态时,调用pm_callback函数。

         4)在设备不再使用时,调用pm_dev_idle以利于设备的空闲检测。

          5)当卸载设备驱动时,调用pm_umregister.


    数据结构:

       

    typedef int (*pm_callback)(struct pm_dev *dev, pm_request_t rqst, void *data);
    

    此处定义了pm_callback 函数的接口信息。

    /*
     * Dynamic device information
     */
    struct pm_dev
    {
    	pm_dev_t	 type;
    	unsigned long	 id;
    	pm_callback	 callback;
    	void		*data;
    
    	unsigned long	 flags;
    	unsigned long	 state;
    	unsigned long	 prev_state;
    
    	struct list_head entry;
    };
    


    pm_dev{ } 是内核进行电源管理的结构。其中包括设备的类型, id号,内部数据存储空间,标志flags, 状态state, 上一个状态以及进行链表管理的list_head{}变量。



    设备注册时调用pm_register()进行注册。 其中参数如下:

          type  :设备的类型。

          id :设备的序号

         callback: 该设备的能源管理函数。


         函数pm_register() 的主要功能是注册一个需要进行能源管理的设备,获得pm_dev{}. 这样当系统发生一些能源管理的事件时(如硬盘待机时),将会通知该设备,来进行一些准备工作。如果成功的话,将会返回pm_dev{ }的指针;如果失败了,则返回NULL 指针。


    /**
     *	pm_register - register a device with power management
     *	@type: device type 
     *	@id: device ID
     *	@callback: callback function
     *
     *	Add a device to the list of devices that wish to be notified about
     *	power management events. A &pm_dev structure is returned on success,
     *	on failure the return is %NULL.
     *
     *      The callback function will be called in process context and
     *      it may sleep.
     */
     
    struct pm_dev *pm_register(pm_dev_t type,
    			   unsigned long id,
    			   pm_callback callback)
    {
    	struct pm_dev *dev = kzalloc(sizeof(struct pm_dev), GFP_KERNEL);  // 获取内存空间保存pm_dev{} 结构
    	if (dev) {
    		dev->type = type;
    		dev->id = id;
    		dev->callback = callback;
    
    		down(&pm_devs_lock);
    		list_add(&dev->entry, &pm_devs);   // 将dev加入pm_devs所连接起来的链表中。
    		up(&pm_devs_lock);
    	}
    	return dev;
    }



    设备卸载时,调用pm_unregister() 删除电源管理中的相关记录。当今后发生能源管理事件时,该设备将不会再接收到通知。它只需要提供注册时获得的pm_dev{ } 结构的指针。


    /**
     *	pm_unregister -  unregister a device with power management
     *	@dev: device to unregister
     *
     *	Remove a device from the power management notification lists. The
     *	dev passed must be a handle previously returned by pm_register.
     */
     
    void pm_unregister(struct pm_dev *dev)
    {
    	if (dev) {
    		down(&pm_devs_lock);
    		list_del(&dev->entry); // 对pm_devs 管理的链表进行删除操作
    		up(&pm_devs_lock);
    
    		kfree(dev);   // 释放dev 指向的pm_dev{}所占用的内存空间
    	}
    }




    展开全文
  • Linux电源管理非常复杂,牵扯到系统级的待机、频率电压变换、系统空闲时的处理以及每个设备驱动对于系统待机的支持和每个设备的运行时电源管理,可以说和系统中的每个设备驱动都息息相关。 对于消费电子产品来说,...

    更多精华文章请扫描下方二维码关注Linux阅码场

    1.   Linux电源管理全局架构

    Linux电源管理非常复杂,牵扯到系统级的待机、频率电压变换、系统空闲时的处理以及每个设备驱动对于系统待机的支持和每个设备的运行时电源管理,可以说和系统中的每个设备驱动都息息相关。

    对于消费电子产品来说,电源管理相当重要。因此,这部分工作往往在开发周期中占据相当大的比重,图1呈现了Linux内核电源管理的整体架构。大体可以归纳为如下几类:

    1.      CPU在运行时根据系统负载进行动态电压和频率变换的CPUFreq

    2.      CPU在系统空闲时根据空闲的情况进行低功耗模式的CPUIdle

    3.      多核系统下CPU的热插拔支持

    4.      系统和设备对于延迟的特别需求而提出申请的PM QoS,它会作用于CPUIdle的具体策略

    5.      设备驱动针对系统Suspend to RAM/Disk的一系列入口函数

    6.      SoC进入suspend状态、SDRAM自刷新的入口

    7.      设备的runtime(运行时)动态电源管理,根据使用情况动态开关设备

    8.      底层的时钟、稳压器、频率/电压表(OPP模块完成)支撑,各驱动子系统都可能用到

     

    图1 Linux电源管理系统架构

    展开全文
  • Linux电源管理详解.docx

    2019-09-12 18:51:50
    Linux电源管理详解docx,虽然Linux可以在任何一台386以上的PC上运行,目前大多数人使用的都是新型的,带有各种外设的桌面PC或者笔记本电脑,这样,电源管理功能 (PM)就逐渐变得越来越重要。在笔记本电脑上电源管理...
  • linux电源管理非常好的个ppt.比较新的一个presentation,里面的流程图非常的清晰,比较适合linux开发人员
  • 主要介绍了详解linux电源管理驱动编写,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • 1、Linux电源管理的总体架构2、CPUFreq、CPUIdle、CPU热插拔以及底层的基础设施Regulator(稳压电源管理)、OPP(某个domain所支持的&lt;频率,电压&gt;对的集合)以及电源管理的调试工具PowerTop3、系统挂...
  • Linux电源管理--PM QoS

    2019-11-29 11:33:33
    什么是PM QoS ...PM QoS framework针对两种对象分别提供了电源管理的基础框架和接口。 那么在电源管理的范畴内要如何理解服务质量呢?实际上在这里,服务的定义是请求方对于某一个性能的需求,比如...

空空如也

1 2 3 4 5 ... 20
收藏数 2,218
精华内容 887
关键字:

linux电源管理

linux 订阅