作者|赵庆尧随着Linux内核代码的逐步完善,GPIO口的操作界面也在不断完善。内核中有多种GPIOAPI接口。我们如何使用这些API接口呢?我们如何在设备树中配置GPIO?目前的内核提供了三个版本的API接口供我们使用,分别是Pinctrl子系统对应的API接口和GPIO子系统对应的API接口(GPIO子系统提供了两类API接口)。在这篇文章中,我将从内核中的GPIO架构的角度来说明这三类API接口;GPIO控制,除了需要相应的API接口外,还需要通过设备树对GPIO进行配置。虽然不同架构下设备树的配置方法不同,但本文也会在相应的代码中说明如何配置设备树以及如何使用。API接口;最后说明一下我们可以使用的调试方法。1.内核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函数较多,此处不再展示调用关系;内核使用结构体structpinctrl_dev来表示一个controller,所有的pinctrl_dev会组成一个链表,链表的头部是pinctrlldev_list。pinctrl_gpio_request函数内部会调用pinctrl_get_device_gpio_range函数根据GPIO编号pinctrldev_list遍历链表找到GPIO端口对应的pinctrl_dev。当然,这部分工作是由系统维护的。我们只需要知道整个框架以及如何使用GPIO的整个子系统即可。注:从我目前看到的代码来看,有些厂商在实现GPIO子系统时,并不是所有的功能都通过Pinctrl子系统,但是gpio_request会通过Pinctrl子系统,因为Pinctrl子系统会标记哪些GPIO被请求过,所以后续模块使用request继续申请该资源会失败;不过大家可以放心,即使它的部分功能没有经过Pinctrl子系统,它提供给驱动模块的API接口也不会受到影响。2、在用户驱动中控制GPIO在编写驱动时,可以采用以下两种方式进行设置:Pinctrl方式,最终使用Pinctrl子系统实现各种功能;GPIO子系统接口的使用方法,其实有两种方式,分别是legacy和gpiodescription。但在当前内核中,gpio描述会在legacy内部被调用。在接下来的内容中,我将以遗留方式来说明使用方法。Pinctrl控制pin方式下面我将以高通平台Pinctrl的方式讲解其代码和设备树配置。我们先来看看设备树:&tlmm{client1_state1:client1_state1{mux{pins="gpio0";function="gpio";};config{pins="gpio0";bias-disable;drive-strength=<2>;输入启用;};};client1_state2:client1_state2{mux{pins=“gpio0”;function=“gpio”;};config{pins=“gpio0”;偏置禁用;驱动强度=<2>;输出-高;};};};&soc{client1{pinctrl-names="state1","state2";pinctrl-0=<&client1_state1>;pinctrl-1=<&client1_state2>;};};上面是一个典型的设备树配置,包含两个节点,分别是tlmm中的pin配置和我们的设备client1。tlmm中定义了两个GPIO状态,即GPIO0作为输入功能,GPIO0作为输出功能。不同平台的设备树中的配置方法不同,但都需要像上面的设备树一样分两部分进行配置:引脚功能配置,是作为普通的GPIO口使用还是复用到其他功能中;管脚驱动配置,包括管脚内部的上拉或下拉,驱动能力等,接下来我们看看在代码中是如何操作的。操作需要按如下顺序进行:先调用devm_pinctrl_get或pinctrl_get函数获取对应的structpinctrl*;然后调用pinctrl_lookup_state(structpinctrl*p,constchar*name)name得到对应的配置;最后我们使用函数pinctrl_select_state(structpinctrl*p,structpinctrl_state*state)来选择状态。选择某个状态就是设置对应的pin,这个pin的request操作是在这个函数内部完成的,会调用pin_request申请,调用这个函数后pin的状态就是devicetree设备的状态在device中,如上图devicetree,client1_state1对应的pin使用了它的GPIO功能,配置为输入(在devicetree中通过input-enable配置),client1_state2对应的pin也使用了它的GPIO功能,并配置为输出高电平(通过设备树中的输出高电平配置)。使用GPIO子系统&soc{client1{qcom,gpio-client1=<&tlmm1000>;//100是GPIO编号}这个方法在设备树的配置上比较简单,代码操作如下:使用函数of_get_named_gpio(node,"qcom,gpio-client1",0)获取GPIO编号.接下来是最重要的一步,调用函数gpio_request进行GPIO请求操作。最后该函数会通过Pinctrl接口间接调用pin_request申请pin。有一次工作粗心,忘记了request操作,结果发现GPIO也可能正常工作,但是会出现工作不稳定的情况;然后可以调用函数gpio_direction_input或gpio_direction_output来配置GPIO的输入或输出模式,gpio_direction_output调用的同时可以设置输出高电平或输出低电平;如果配置位输入模式,可以使用函数gpio_get_value获取GPIO端口的状态。如果该引脚需要配置为中断功能,我们需要使用函数irq=gpio_to_irq(gpio)获取irq号,根据irq号进行相应的中断配置。通过以上两种方式,我们可以发现拼ctrl的设备树很复杂,但是API接口很简单。它只需要通过函数pinctrl_select_state在设备树中选择某个配置即可。GPIO子系统的方式就是设备树简单,但是代码复杂。设备树中只配置了GPIO编号,其他如GPIO的方向、输出信号等需要通过代码设置。这正是不同的API接口和它们的设备树之间的区别。的优点和缺点。3.GPIO调试不同平台的调试方法可能存在一些差异。比如MTK的不同平台会有差异。这里介绍一个常用的需要debugfs支持的方法。我们在编译内核的时候,需要配置相应的debugfs宏来开启这个功能。只有配置好相应的宏后,我们才能进入机器的/sys/class/gpio来操作GPIO口。下面是对应的操作顺序:cd/sys/class/gpio/echo99>export(这里的99代表pin号,准确的说,回显的时候应该使用对应pingpio_request获取的值)cdgpio99echoin/out>direction//设置GPIO输入或输出cat方向//获取GPIO输入输出状态echo0/1>value//下拉或上拉对应GPIO端口cat值//查看高低状态GPIO端口4.小结希望大家通过本文能够了解内核中GPIO的机制,掌握其操作方法,但需要说明的是,在内核之前的阶段也会进行管脚配置。比如我们使用串口打印bootloader阶段的log。在这种情况下,串口引脚必须在内核之前。阶段已配置。笔者介绍赵庆尧,51CTO社区编辑,从事驱动开发多年。他的研究兴趣包括安全操作系统和网络安全,并发表了与网络相关的专利。
