wakeup_in休眠唤醒

本文详细介绍了Linux驱动程序如何实现休眠唤醒功能,包括设备树配置、驱动注册、probe函数实现、驱动内容填充以及上层处理。通过配置wakeup_in管脚,利用中断和工作队列机制,实现低功耗模式(LPM)。在probe函数中,涉及pin、gpio、irq资源的申请和初始化,中断注册以及唤醒源的设置。上层处理部分,根据中断信号通过工作队列和wakelock机制控制系统的休眠与唤醒。
摘要由CSDN通过智能技术生成

问题背景:实现一个休眠唤醒的功能,并可控制的使单板进入休眠或者唤醒的状态(管脚拉低进入休眠状态,拉高恢复)。以此来达到LPM(低功耗模式)的目的。

文档以wakeup_in管脚的休眠唤醒功能为例,描述了一个平台驱动从注册,匹配以及Probe内容的填充的一整个驱动实现的流程。

Wakeup_in管脚休眠唤醒功能的调试,大致可以分为下面这几个步骤来实现:

目录

1. 配置设备树

2. 注册驱动

3. probe函数

4. 驱动内容填充

5. 上层处理


1. 配置设备树

Pinctrl.dtsi配置:

在驱动的注册过程设备树起的是信息传递的作用。所以不论是根据驱动找设备树,还是根据设备树找对应的驱动代码,都是可以通过compatible这个属性来查找。

 节点名称 {

compatiblecompatible属性是一个字符串的列表。(用于驱动和设备的匹配绑定)。wakeup_in_gpio:指向gpio控制器tlmm_pinmux(引用该节点),gpiox_82引脚,低电平有效。qcom,gpio-names:goio名称。

Pinctrl-names:定义了pin脚用到的state列表。

Pinctrl - x:pin脚的两种配置状态。(在驱动中获取pin脚状态时会根据名称来匹配)。

};

GPIO的信息在/sys/kernel/debug/gpio文件内可以查看

在驱动中获取gpio号时,获取到的不是82,而是一个与Linux通用GPIO API一起使用的GPIO编号,这里是924-1023,其中gpio82对应的GPIO编号是1006。

2. 注册驱动

platfprm_driver结构体如下:

平台驱动结构体内嵌继承了一个设备驱动结构体 。后续的注册过程,会完成这个结构体相关内容的填充。

平台驱动的设计主要是完成平台驱动结构体的填充和注册。

 以wakeup_in驱动为例,完成了:

平台驱动的probe函数实现:.probe = wakeup_in_probe,它是驱动的入口函数当总线完成了设备的 match 操作以后就会进入驱动中该函数的运行。

平台驱动的remove函数实现:.remove= wakeup_in_remove,

实现设备驱动的name、ownerpm、of_match_table变量

注册的过程

这里是实现对device_driver结构体内相关函数和变量的赋值。并将 driver的总线类型初始化为platform_bus_type赋值完成后,将device_driver结构体作为参数传递,继续注册:

device_driver结构体如下:

 继续跟代码:

driver_find传入两个参数,name和bus。通过驱动的name在bus上查找这个驱动是否已经注册过了,防止重复注册。(未注册则返回NULL)。

接下来的这个bus_add_driver接口,会根据总线类型把驱动添加到bus上,也是驱动正式开始注册的入口:

先通过bus_get接口获取总线(这里bus_type对应的是platform_bus_type)。

klist_init,kobject_init_and_add 和klist_add_tail,主要完成了Kobject的初始化和将初始化的kobject尾插到kset链表中。并给驱动创建目录(sys/bus/xxx/driver/xxx)。

到这块为止,其实已经将驱动注册到了内核中。接下里的操作,就是尝试给这个驱动匹配对应的设备。

driver_attach:尝试将这个驱动和设备进行绑定。

这里会获取设备链表里的每一个device,并调用fn(dev,data),也就是__driver_attach:

这里的match调用的是platform_match来进行匹配:

先将dev和drv转换为平台设备和驱动,然后会进行一个匹配方式的选择:这里采用OF类型(设备树类型)来进行匹配:

这里的drv->of_match_table,是在平台驱动结构体填充时赋值的:

name/type/compatible这三个匹配项,只要有一个不为空,就满足匹配条件,继续往下执行。

of_device_id结构体:

然后这里会进行一个匹配内容的选择,通过name/type/compatible。可以看出来,compatible是匹配优先级最高的项。这里使用compatible来进行匹配。

如果匹配成功了,则最后返回1。然后回到__driver_attach,执行device_driver_attach:

进入really_probe函数:将device结构中的driver指针指向这个驱动这里就完成了驱动和设备的匹配绑定

这里的probe调用的是platform_drv_probe来进行匹配: 

接下来会调用上面注册的drv->probe,也就是平台驱动结构体填充时赋值的probe。并且将与驱动匹配的struct platform_device 结构的 dev 作为参数传给drv的probe函数

至此,驱动的整个注册匹配过程就结束了,接下来就开始probe该驱动。

3. probe函数

 定义一个要用到的结构体:

wakeup_in管脚的休眠唤醒,在probe中主要体现在对pin、gpio、irq等资源的申请和初始化,以及相关功能函数的初始化

调用devm_pinctrl_get函数,向内核申请dev的pin脚资源。成功则会返回一个指向申请到的pin脚号的指针。(一个pinctrl的句柄,后面就可以通过该句柄,来对申请的pin脚进行操作)。

pinctrl_lookup_state这个函数,会根据传入的第二个参数state_name去遍历p->state链表的所有的状态,通过name来进行匹配,如果匹配成功,则返回指向这个状态的指针。

然后通过pinctrl_select_state这个函数,来将设备树中的pinctrl中的信息配置到该pin中。

通过of_get_named_gpio函数,根据第二个参数去设备树中查找对应的gpio,如果查询成功,则返回一个与Linux通用GPIO API一起使用的GPIO编号。

像pin/gpio这些在内核中都属于资源,所以使用之前先申请。通过pinctrl_gpio_request函数来向内核申请一个pin脚当作gpio来使用

申请到gpio之后,通过pinctrl_gpio_direction_input函数,将gpio配置为输入方向。

通过gpio_to_irq函数,来获取gpio对应的中断号,成功则返回该中断号。

通过gpio_to_irq获取到gpio对应的中断号后,再调用request_irq函数进行中断的注册:将这个中断号、中断的irq_handler、中断触发方式以及中断的名称注册到内核里。(注册的中断可以在/proc/interrupts文件内查看)。

通过wakeup_source_register函数来创建唤醒源设备,并将唤醒源设备加入全局的唤醒源设备列表,成功则返回指向这个唤醒源设备的指针。(注册的唤醒源设备可以在/sys/kernel/debug/wakeup_sources文件内查看)。

调用create_workqueue函数来创建一个工作队列,成功则返回指向该workqueue的指针。

调用INIT_WORK()函数来创建一个工作函数,并将该工作函数赋值给pdata->wk_work变量。

实现驱动LPM模式的suspend和resume,需要有一个中断唤醒源来将系统从sleep状态唤醒。通过调用enable_irq_wake(irq)函数,使该irq具有唤醒系统的功能 。(如果不设置该函数,则进休眠后就不会醒来)。

Probe主要的流程如图所示:

 注:中断注册成功之后,要确保该中断有唤醒系统的特性。调用enable_irq_wake()接口来实现该功能。 

Probe完成之后,接下来需要填充驱动所要实现的内容:在wakeup_in管脚拉低时,表示允许模组进入休眠状态,拉高时唤醒模组。

Linux pm core提供了wakelock及autoslepp内核休眠机制。autosleep 和 wakelock是并行存在,只有 wakelock 唤醒锁全部释放且 autosleep 为 enable 时,系统才能进入休眠。

autosleep节点路径为/sys/power/autosleep,该值为mem表示打开autoslepp功能,值为off表示关闭。

4. 驱动内容填充

外部触发中断(拉高拉低wakeup_in) --> irq_handler() --> timer_handler() --> work() -->odm_notify()。

拉高或者拉低wakeup_in管脚,触发中断,执行irq_handler:

在中断处理函数中调用mod_timer()来重新启动定时器。

定时时间超时后,执行timer_handle:

在定时器处理函数中,调用queue_work()来将pdata->work工作函数提交到pdata->wk_wq工作队列的工作链表中,并唤醒等待队列,然后执行该工作函数。

通过gpio_get_value()接口获取当前wakeup_in管脚的电平状态,来判断是要进休眠还是要唤醒

如果是要进休眠,则调用_pm_relax()来给wakelock释放锁

如果是要唤醒,则调用_pm_stay_awake()来给wakelock持锁。这里传入的参数都是probe中申请的指向唤醒源设备的指针。

串口发送awk '{ printf("%-32s %d\n", $1, $6) }' /sys/kernel/debug/wakeup_sources指令可以查看当前设备持锁情况

要使设备能进休眠或者唤醒,需要满足两个条件:持锁&&autosleep写mem;释放锁&&autosleep写off。

work函数中完成了持锁释放锁的操作,然后在进休眠时调用netlink_report_suspend,唤醒时调用netlink_report_resume函数:

休眠和唤醒对应不同的msg.id,然后调用odm_notify()函数,携带着对应的msg信息传递到上层去处理。

流程框图如图:

5. 上层处理

上层会根据传递上来的msg,根据不同的msg.id来执行对应的case:

如果是要进休眠,则调用frwk_system函数来给autosleep写mem;如果是要唤醒,则给autosleep写off。

至此,休眠和唤醒各自的两个条件都满足了,就可以实现休眠唤醒功能了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值