ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

Linux GPIO子系统和PinCtrl子系统

2022-07-14 09:03:03  阅读:225  来源: 互联网

标签:引脚 pinctrl PinCtrl gpiod GPIO gpio 子系统


目录

基本概念

PinCtrl:Pin Controller,是一个虚拟概念,用于设置IOMUX,让某个引脚连接到指定模块,从而实现某个功能。不同于GPIO子系统,可用于GPIO功能、I2C功能等。
GPIO子系统:配置引脚输入、输出功能,设置方向等GPIO模块内功能。

不过,大多数的芯片并没有单独的IOMUX模块,引脚的复用、配置等,而是在GPIO模块内部实现的。


PinCtrl子系统

涉及2个对象:pin controller、client device。

  • pin controller 用它来复用引脚、配置引脚。

pin controller不存在于芯片手册,是一个软件上的概念,可认为它对应于IOMUX,用于复用引脚、配置引脚(如上下拉电阻等)。pin controller不同于GPIO Controller,可以先用pin controller将引脚配置为GPIO,再用GPIO Controller把引脚配置为输入或输出。

  • client device 声明要用哪些引脚的哪些功能,怎么配置。所谓“客户设备”,客户是指Pinctrl系统的客户,即使用Pinctrl系统的设备,使用引脚的设备。在设备树里会被定义为一个节点,在节点里声明要用哪些引脚。

下面这张图把几个重要概念综合到一起:

注意:并不是说dts中一定会存在pincontroller、device这2个node,这里只是作为例子表明概念,其名字可能是其他。

左边pin controller节点,右边client device节点。
1)pin state
对于一个"client device",如UART设备,它有多个“状态”:default、sleep等,那么对应的引脚也有这些状态。

比如,默认状态下,UART设备正常工作,那么所用的引脚就要复用为UART功能;
休眠状态下,为了省电,可以把这些引脚复用为GPIO功能;或者直接把它们配置输出高电平。

上图pinctrl-names定义2种状态:default,sleep。
第0种状态用到的引脚在pinctrl-0中定义,它是state_0_node_a,位于pincontroller节点中。
第1种状态用到的引脚在Pinctrl-1中定义,它是state_1_node_a,位于pincontroller节点中。

当UART设备处于default状态时,pinctrl子系统会自动根据上述信息将所用引脚复用为uart0功能。
当UART设备处于sleep状态时,pinctrl子系统会自动根据上述信息将所用引脚配置为高电平。

2)groups和function
一个设备会用到一个或多个引脚,这些引脚可以归纳为一组(group);
这些引脚可以复用为某个功能:function,如I2C功能,SPI功能,GPIO功能等。

一个设备可以用的多组引脚,如A1、A2两组引脚,A1组复用为F1功能,A2组复用为F2功能。

3)Generic pin multiplexing node和Generic pin configuration node
上图左边pin controller节点中,有子节点或孙节点,它们是给client device使用的。
可用来描述复用信息:哪组(group)引脚复用为哪个功能(function);
配置信息:哪组(group)引脚配置为哪个设置功能(setting),如上拉、下拉等;

注意:pin controller节点格式,没有统一格式,每家芯片都不一样。可能group、function关键字也不一样,但都有这样的概念。
client device节点格式都是类似的。

IMX6ULL pin controller

pinctrl_uart1: uart1grp {                /*!< Function assigned for  the core: Cortex-A7[ca7] */
    fsl,pins = <
        MX6UL_PAD_UART1_RX_DATA__UART1_DCE_RX      0x000010B0
        MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX      0x000010B0
    >;
};

对应client device

&uart1 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_uart1>;
    status = "okay";
};

代码中引用pinctrl

这部分是透明的,驱动通常不用管。当设备切换状态时,独有的pinctrl就会被调用。比如,在platform_device和platform_driver的枚举过程中,流程如下:

really_probe:
/* If using pinctrl, bind pins now before probing */
ret = pinctrl_bind_pins(dev); // 1 引脚被设置为某个状态, 不用我们手动调用代码
    dev->pins->default_state = pinctrl_lookup_state(dev->pins->p, PINCTRL_STATE_DEFAULT); /* 获得"default"状态的pinctrl */
    dev->pins->init_state = pinctrl_lookup_state(dev->pins->p, PINCTRL_STATE_INIT); /* 获得"init"状态的pinctrl */
ret = pinctrl_select_state(dev->pins->p, dev->pins->init_state); /* 优先设置"init"状态的引脚 */
ret = pinctrl_select_state(dev->pins->p, dev->pins->default_state); /* 如果没有init状态, 则设置"default"状态的引脚 */

...
ret = drv->probe(dev); // 2 调用我们的代码

也就是说,当系统休眠时,会自动其设置该设备sleep状态对应的引脚,不需要我们调用代码。

如果非要自己调用,可以用下面函数:

devm_pinctrl_get_select_default(struct device *dev);      // 使用"default"状态的引脚
pinctrl_get_select(struct device *dev, const char *name); // 根据name选择某种状态的引脚
pinctrl_put(struct pinctrl *p);   // 不再使用, 退出时调用

GPIO子系统

要操作GPIO引脚,得先把所用到的引脚配置为GPIO功能,这需要通过Pinctrl子系统实现。然后,就可以根据设置引脚方向(输入 or 输出)、读值(获取引脚电平状态)、写值(输出高or第电平)。

在裸机编程中,通过寄存器来操作GPIO引脚,不同的板子驱动代码不一样。
而BSP工程师实现了GPIO子系统后,我们可以:
在设备树里指定GPIO引脚;
在驱动代码中:
使用GPIO子系统的标准函数获得GPIO、设置GPIO方向、读取/设置GPIO值。
这样的驱动代码,跟板子无关。

在设备树中指定引脚

GPIO组号、组内编号 => 引脚
几乎所有ARM芯片中,GPIO都分为几组,每组都有若干引脚。所以在使用GPIO子系统前,先确定它是哪组的,然后是组内哪一个引脚。

在设备树中,“GPIO组”就是一个GPIO Controller,通常由芯片厂家设置好。驱动程序员要做的是找到它的名字,如"gpio1",然后指定用它里面那个引脚,比如<&gpio1 0>,表示使用GPIO1_0(GPIO第1组0号引脚)。

从imx6ull设备树文件(100ask_imx6ull-14x14.dts)的头文件imx6ul.dtsi(NXP原厂提供),截取下面关于gpio1和gpio2(2组gpio)的设备树描述。

// imx6ul.dtsi, 芯片级dts文件, 位于Linux 4.9.88 源码目录 arch/arm/boot/dts
...
gpio1: gpio@0209c000 {
    compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
    reg = <0x0209c000 0x4000>;
    interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
             <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
    gpio-controller;
    #gpio-cells = <2>;
    interrupt-controller;
    #interrupt-cells = <2>;
    gpio-ranges = <&iomuxc    0 23 10>, <&iomuxc 10 17 6>,
              <&iomuxc 16 33 16>;
};

gpio2: gpio@020a0000 {
    compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
    reg = <0x020a0000 0x4000>;
    interrupts = <GIC_SPI 68 IRQ_TYPE_LEVEL_HIGH>,
             <GIC_SPI 69 IRQ_TYPE_LEVEL_HIGH>;
    gpio-controller;
    #gpio-cells = <2>;
    interrupt-controller;
    #interrupt-cells = <2>;
    gpio-ranges = <&iomuxc 0 49 16>, <&iomuxc 16 111 6>;
};
...

我们暂时只需要关心这2个属性:

gpio-controller;
#gpio-cells = <2>;

"gpio-controller" 表示这个节点是一个GPIO Controller,它的下面有很多引脚。
"#gpio-cells = <2>" 表示这个控制器下每个引脚都要用2个32位的数(cell)来描述。
具体用几个数,是GPIO Controller自己决定的。通常是2个,当然,可以用更多cell表示其他特性。

通常用法:用2个数(cell),第一个cell表示哪个引脚,第二个cell表示是高电平还是低电平有效。
第2个cell含义:

GPIO_ACTIVE_HIGH : 高电平有效
GPIO_ACTIVE_LOW  :  低电平有效

芯片厂家提供的dts文件定义了GPIO Controller,那我们的驱动程序如何引用某个引脚呢?
可以在自己的设备节点中使用属性"[-]gpios",示例如下:

// 100ask_imx6ull-14x14.dts, 板级dts文件, 位于Linux 4.9.88 源码目录 arch/arm/boot/dts

led0: cpu {
    label = "cpu";
    gpios = <&gpio5 3 GPIO_ACTIVE_LOW>; // 属性gpios值为gpio5第3号引脚, 低电平有效
    default-state = "on";
    linux,default-trigger = "heartbeat";
};
gt9xx@5d {
        compatible = "goodix,gt9xx";
        reg = <0x5d>;
        status = "okay";
        interrupt-parent = <&gpio1>;
        interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_tsc_reset &pinctrl_touchscreen_int>;
        /*pinctrl-1 = <&pinctrl_tsc_irq>;*/
        /*pinctrl-names = "default", "int-output-low", "int-output-high", "int-input";
        pinctrl-0 = <&ts_int_default>;
        pinctrl-1 = <&ts_int_output_low>;
        pinctrl-2 = <&ts_int_output_high>;
        pinctrl-3 = <&ts_int_input>;
        */
        reset-gpios = <&gpio5 2 GPIO_ACTIVE_LOW>;
        irq-gpios = <&gpio1 5 IRQ_TYPE_EDGE_FALLING>;
        irq-flags = <2>;                /*1:rising 2: falling*/
...
};

通过gpios属性,name-gpios属性(如reset-gpios,irq-gpios)的值设为gpio-controller对应的node。

驱动代码中调用GPIO子系统

设备树中可以通过GPIO子系统指定GPIO的配置,那驱动代码中如何使用呢?

GPIO子系统有两套接口:
1)基于描述符(descriptor-based)的,函数前缀"gpiod_",使用gpio_desc结构体来表示一个引脚;

2)基于老(legacy)的,函数前缀"gpio_",使用一个整数来表示一个引脚。

操作一个GPIO引脚,要先get(获取)引脚,然后设置方向、读值、写值。

下面是2套接口简要说明:

  • descriptor-based
#include <linux/gpio/consumer.h> //descriptor-based

// 获得GPIO
gpiod_get
gpiod_get_index
gpiod_get_array
devm_gpiod_get
devm_gpiod_get_index
devm_gpiod_get_array

// 设置方向
gpiod_direction_input
gpiod_direction_output

// 读值、写值
gpiod_get_value
gpiod_set_value

// 释放GPIO
gpio_free
gpiod_put
gpiod_put_array
devm_gpiod_put
devm_gpiod_put_array
  • legacy
#include <linux/gpio.h>

// 获得GPIO
gpio_request
gpio_request_array

// 设置方向
gpio_direction_input
gpio_direction_output

// 读值、写值
gpio_get_value
gpio_set_value

// 释放GPIO
gpio_free
gpio_free_array

前缀"devm_" 含义是“设备资源管理(Managed Device Resource)”,这是一种自动释放资源的机制。其思想是“资源是属于设备的,设备不存在时资源就可以自动释放”。

比如,在Linux开发过程中,先申请了GPIO,再申请内存;如果内存申请失败,那么在返回前就需要先释放GPIO资源。如果内存申请失败时,可以直接返回,因为设备的销毁函数会自动地释放已经申请了的GPIO资源。

推荐使用"devm_"版本相关函数。

例如,假设设备树有如下自定义节点:

foo_device {
    compatible = "acme,foo";
    ...
    led-gpios = <&gpio 15 GPIO_ACTIVE_HIGH>, /* red */
                <&gpio 16 GPIO_ACTIVE_HIGH>, /* green */
                <&gpio 17 GPIO_ACTIVE_HIGH>, /* blue */
    
    power-gpios = <&gpio 1 GPIO_ACTIVE_LOW>;
};

那么可以用下面方式获取引脚:

struct gpio_desc *read, *green, *blue, *power;

red = gpiod_get_index(dev, "led", 0 GPIOD_OUT_HIGH); /* "led"是对应设备树文件中name-gpios中的name */
green = gpiod_get_index(dev, "led", 1 GPIOD_OUT_HIGH);
blue = gpiod_get_index(dev, "led", 2 GPIOD_OUT_HIGH);
power = gpiod_get(dev, "power", GPIOD_OUT_HIGH);

注意:gpiod_set_value设置的值是逻辑值,不一定等于物理值(电平高低)。

// 如果设备树里引脚指定为GPIO_ACTIVE_LOW, 那么gpiod_set_value 的逻辑值跟引脚的物理值相反
gpiod_set_value(dec, 0); // 输出高电平
gpiod_set_value(dec, 1); // 输出低电平

// 如果设备树里引脚没有指定GPIO_ACTIVE_LOW, 或者指定为GPIO_ACTIVE_HIGH
gpiod_set_value(dec, 0); // 输出低电平
gpiod_set_value(dec, 1); // 输出高电平

旧的"gpio_" 函数没办法根据设备树信息获得引脚,它需要先知道引脚号。

引脚号如何确定?
GPIO子系统中,每注册一个GPIO Controller时,会确定它的"base number",那么控制器里的第n号引脚的号码就是:base number + n。
但如果硬件有变化、设备树有变化,该base number并不能保证是固定的,应该查看sysfs来确定base number。

sysfs的访问方法

值sysfs中访问GPIO,实际上用到就是引脚号,老的方法。

a)先确定某个GPIO Controller的基准引脚号(base number),再计算出某个引脚的号码。

方法如下:
(1)先在开发板的/sys/class/gpio目录下,找到各个gpiochipXXX目录:

# cd /sys/class/gpio
# ls
export       gpiochip0    gpiochip128  gpiochip32   gpiochip64   gpiochip96   unexport

(2)然后进入某个gpiochip目录,查看文件label的内容

(3)根据label的内容对比设备树
label内容来自设备树,比如它的寄存器基地址。用来跟设备树(dtsi文件)比较,就可以指定这对应哪个GPIO Controller。

下面是100ask_imx6ull 运行结果,对比设备树可知gpiochip96对应gpio4:

# cd gpiochip96
# ls
base       device     label      ngpio      power      subsystem  uevent
# cat label
20a8000.gpio
// imx6ull.dtsi
gpio4: gpio@020a8000 { // gpio4地址就是20a8000
    compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
    reg = <0x020a8000 0x4000>;

因此,gpio4这组引脚的基准引脚号就是96,这也可以用"cat base"命令来确认:

# pwd
/sys/class/gpio/gpiochip96
# cat base
96

b)基于sysfs操作引脚

以100ask_imx6ull为例,它有一个按键,原理图:

那么GPIO4_14号码96+14=110,可以如下操作读取按键值(输入引脚):

# echo 110 > /sys/class/gpio/export          // 将gpio110配置为gpio
# echo in > /sys/class/gpio/gpio110/direction // 设置方向
# cat /sys/class/gpio/gpio110/value           // 读取gpio110输入电平
# echo 110 > /sys/class/gpio/unexport        // 取消gpio110配置

注:如果驱动程序已经使用了该引脚,那么将会export失败。

对于输出引脚,假设引脚号为N,可以用下面方法设置它的值为1:

# echo  N > /sys/class/gpio/export             // 将gpioN配置为gpio
# echo out > /sys/class/gpio/gpio110/direction // 设置方向
# echo 1 > /sys/class/gpio/gpioN/value         // 设置引脚输出值
# echo N > /sys/class/gpio/unexport            // 取消gpio110配置

基于GPIO子系统的LED驱动程序

思路:
1)通过Pinctrl子系统,在设备树中将引脚配置为GPIO功能;
2)设备树节点被内核自动转换为platform_device,需要node的compatibe属性与驱动程序的platform_driver的driver列表的某一项of_match_table的compatible相同;
3)也就是说,需要注册一个platform_driver;
4)在platform_driver的probe函数中,利用GPIO子系统的接口函数获取引脚(gpio_desc),注册文件操作接口file_operations;
5)在file_operations中,设置方向、读/写值;


小结

  1. GPIO子系统:设备树里指定GPIO引脚;提供一套C代码,用于驱动程序中设置GPIO模块内功能。
    PinCtrl子系统:通过设备树,用于引脚功能的选择,类似于IOMUX。PinCtrl子系统是系统根据设备树文件(配置)自动完成的,通常无需C驱动程序员参与(引用pinctrl)。
    另外,芯片厂家通常也会提供类似于NXP i.MX Pins Tool v6这样的工具,用于配置引脚功能,生成dts文件内容。

  2. PinCtrl可以为node定义多个state,每个state对应若干引脚。状态切换时,引脚的配置也是自动完成的。

  3. sysfs可用于在线调试GPIO功能,无需编写代码。


参考

http://www.100ask.org/

标签:引脚,pinctrl,PinCtrl,gpiod,GPIO,gpio,子系统
来源: https://www.cnblogs.com/fortunely/p/16476266.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有