GPIO到底该如何控制

作者 | 赵青窕
随着Linux内核代码的逐步完善,其GPIO口的操作接口也在不断完善。内核中存在多种GPIO API接口,我们该如何使用这些API接口呢?我们又该如何在设备树中配置GPIO呢?
目前的内核中提供了三种版本的API接口供我们使用,分别是Pinctrl子系统对应的API接口和GPIO子系统对应的API接口(GPIO子系统提供了两种类型的API接口),在本文中我将会通过内核中GPIO架构的角度来说明这三类API接口;我们对GPIO的控制,除了合适的API接口外,还需要通过设备树对GPIO进行配置,虽然不同架构中设备树的配置方式不同,但本文中也会举例说明设备树如何配置,对应的代码中如何使用API接口;最后说明我们可以用到的调试手段。
内核GPIO架构
下图是内核中,GPIO架构中的核心部分框架图(暂时不考虑GPIO架构对应的sysfs和debugfs):

图 1:GPIO架构
从上图中的上半部分可以看出以下关键信息:
内核提供了两种控制引脚的方式,一种是采用Pinctrl子系统,一种是采用GPIO子系统(该系统又有两种方式,后面小节中会进行说明),用户编写驱动时可以调用这两个子系统提供的API接口来达到控制GPIO口的目的;
GPIO子系统的功能是通过Pinctrl子系统来实现的。虽然从图中看出,Pinctrl子系统和GPIO子系统并存,但内核需要对所有的GPIO口进行管理,因此就需要一个统一的管理接口或者模块,Pinctrl就完成这种统一管理的目标,比如我们采用GPIO子系统的API接口gpio_request来申请GPIO口时,内核需要记录哪些GPIO口已经申请过了,若Pinctrl子系统和GPIO子系统各维护一套GPIO管理策略,那就可能导致Pinctrl子系统和GPIO子系统同时操作同一个GPIO口的情况,这种显然是不可行的,且从高通平台内核代码中可以看出gpio_request有如下的调用关系:
gpio_request--->gpiod_request----> gpiod_request_commit---->chip->request(系统启动时设置为gpopchip_generic_request) ---->pinctrl_gpio_request
该调用关系从GPIO子系统的API函数gpio_request最后调用了Pinctrl子系统的函数pinctrl_gpio_request,这种调用关系,也证实了Pinctrl和GPIO子系统的关系。对于其他接口,如gpio_direction_input,gpio_set_value等函数的调用,同gpio_request相似,其最后均是调用到对应的chip->direction_input,chip->set,进而调用pinctrl_gpio_direction_input,等函数,由于这类API函数比较多,在此就不展示其调用关系了;
内核采用结构体struct pinctrl_dev来表示一个控制器,所有的pinctrl_dev会形成链表,链表头就是pinctrldev_list,在函数pinctrl_gpio_request内部,会调用函数pinctrl_get_device_gpio_range来根据GPIO号,遍历链表pinctrldev_list来查找该GPIO口对应的pinctrl_dev,当然这部分工作均是由系统来维护,我们只需知道整个框架,并如何使用GPIO的整个子系统即可。
注意:目前从我看到的代码中发现,有些厂家在实现GPIO子系统时,并非所有的功能均通过Pinctrl子系统,但gpio_request是会通过Pinctrl子系统,因为Pinctrl子系统中会标记哪些GPIO已经request了,这样后续模块采用request继续申请该资源时就会失败;不过大家放心,即使其部分功能不通过Pinctrl子系统,但其对驱动模块提供的API接口不受其影响。
用户驱动中控制GPIO
在编写驱动时,可以采用以下两种方式来设置:
Pinctrl方式,该方式最终是采用Pinctrl子系统来实现各项功能的;
采用GPIO子系统接口的方式,该方式其实有两种,分别是legacy和gpio description的方式。但目前的内核中,legacy内部会调用gpio description的方式,后面内容中,我会以legacy的方式来说明使用方法。
&tlmm{client1_state1: client1_state1 {mux {pins = "gpio0";function = "gpio";};config {pins = "gpio0";bias-disable;drive-strength =<2> ;input-enable;};};client1_state2: client1_state2 {mux {pins = "gpio0";function = "gpio";};config {pins = "gpio0";bias-disable;drive-strength = <2>;output-high;};};};&soc {client1 {pinctrl-names = "state1", "state2";pinctrl-0 = <&client1_state1 >;pinctrl-1 = <&client1_state2>;};};
引脚功能配置,是作为普通的GPIO口,还是复用为其他的功能;
引脚驱动配置,包括引脚内部的上拉或者下拉,驱动能力等。
先调用devm_pinctrl_get或者pinctrl_get函数获取对应的struct pinctrl*;
紧接着调用pinctrl_lookup_state(struct pinctrl *p, const char *name)来根据name获取对应的配置;
最后我们采用函数pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *state)来进行状态的选择。选择某一状态,就是设置了对应的引脚,且引脚的request操作是在该函数内部完成的,该函数内部会调用pin_request来进行申请,且调用该函数后,引脚的状态就是设备树中设备的状态,如上面设备树中,client1_state1对应的引脚使用了其GPIO功能,且配置为输入(设备树中是通过input-enable进行配置的),client1_state2对应的引脚也使用了其GPIO的功能,且配置为输出高(设备树中是通过out-high进行配置的)。
&soc {client1 {qcom,gpio-client1 = <&tlmm 100 0>; //100就是GPIO号}
使用函数of_get_named_gpio(node, " qcom,gpio-client1", 0)获取GPIO号。
接下来是最重要的一步,调用函数gpio_request来进行GPIO的request操作。该函数最后会通过Pinctrl接口间接调用pin_request来进行引脚的申请操作。之前有一次工作中粗心,忘记request操作,但发现该GPIO可能也会正常操作,但会有工作不稳定的情况发生;
接下来可以调用函数gpio_direction_input或者gpio_direction_output来进行GPIO输入或者输出模式的配置,gpio_direction_output调用的同时,可以设置输出高或者输出低;
如果配置位输入模式,则可以使用函数gpio_get_value来获取GPIO口的状态。
如果需要把引脚配置为中断功能,则我们需要使用函数irq = gpio_to_irq(gpio)来获得irq号,根据irq号来进行适当的中断配置。
GPIO调试
cd /sys/class/gpio/
echo 99 > export(此处99代表引脚号,确切地说echo时,应采用对应引脚gpio_request获得到的数值)
cd gpio99
echo in/out > direction //设置GPIO输入或输出
cat direction //获取GPIO输入输出状态
echo 0/1 > value //拉低或者拉高对应的GPIO口
cat value //查看GPIO口的高低状态
总结

关注公众号:拾黑(shiheibook)了解更多
[广告]赞助链接:
四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/
关注网络尖刀微信公众号随时掌握互联网精彩
- 1 习近平同马克龙交流互动的经典瞬间 7903996
- 2 日本飞机多次抵近滋扰中国海军训练 7808251
- 3 中国队今晚将再次对阵日本队 7713616
- 4 三项世界级成就见证中国实力 7616458
- 5 男童坠冰窟 小姑娘喊“我下去救” 7523587
- 6 烈犬撕咬路人致死 主人需承担刑责吗 7426905
- 7 全网寻找的用围巾擦地女乘客找到了 7327690
- 8 轰-6甲投下我国第一枚氢弹 7237048
- 9 男子装电表箱触电身亡 家属索赔142万 7142214
- 10 今日大雪 要做这些事 7044062







51CTO技术栈
