您当前的位置: 首页 >  嵌入式

正点原子

暂无认证

  • 0浏览

    0关注

    382博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

【正点原子MP157连载】第二十五章 pinctrl和gpio子系统实验-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7

正点原子 发布时间:2022-02-12 10:00:55 ,浏览量:0

1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-318813-1-1.html 4)正点原子官方B站:https://space.bilibili.com/394620890 5)正点原子STM32MP157技术交流群:691905614 在这里插入图片描述

第二十五章 pinctrl和gpio子系统实验

上一章我们编写了基于设备树的LED驱动,但是驱动的本质还是没变,都是配置LED灯所使用的GPIO寄存器,驱动开发方式和裸机基本没啥区别。Linux是一个庞大而完善的系统,尤其是驱动框架,像GPIO这种最基本的驱动不可能采用“原始”的裸机驱动开发方式,否则就相当于你买了一辆车,结果每天推着车去上班。Linux内核提供了pinctrl和gpio子系统用于GPIO驱动,本章我们就来学习一下如何借助pinctrl和gpio子系统来简化GPIO驱动开发。

25.1 pinctrl子系统 25.1.1 pinctrl子系统简介 Linux驱动讲究驱动分离与分层,pinctrl和gpio子系统就是驱动分离与分层思想下的产物,驱动分离与分层其实就是按照面向对象编程的设计思想而设计的设备驱动框架,关于驱动的分离与分层我们后面会讲。本来pinctrl和gpio子系统应该放到驱动分离与分层章节后面讲解,但是不管什么外设驱动,GPIO驱动基本都是必须的,而pinctrl和gpio子系统又是GPIO驱动必须使用的,所以就将pintrcl和gpio子系统这一章节提前了。 我们先来回顾一下上一章是怎么初始化LED灯所使用的GPIO,步骤如下: ①、修改设备树,添加相应的节点,节点里面重点是设置reg属性,reg属性包括了GPIO相关寄存器。 ②、 获取reg属性中GPIOI_MODER、GPIOI_OTYPER、GPIOI_OSPEEDR、GPIOI_PUPDR和GPIOI_BSRR这些寄存器的地址,并且初始化它们,这些寄存器用于设置PI0这个PIN的复用功能、上下拉、速度等。 ③、 在②里面将PI0这个PIN设置为通用输出功能,因此需要设置PI0这个GPIO相关的寄存器,也就是设置GPIOI_MODER寄存器。 ④、在②里面将PI0这个PIN设置为高速、上拉和推挽模式,就要需要设置PI0的GPIOI_OTYPER、GPIOI_OSPEEDR和GPIOI_PUPDR这些寄存器。 总结一下,②中完成对PI0这个PIN的获取相关的寄存器地址,③和④设置这个PIN的复用功能、上下拉等,比如将GPIOI_0这个PIN设置为通用输出功能。如果使用过STM32单片机的话应该都记得,STM32单片机也是要先设置某个PIN的复用功能、速度、上下拉等,然后再设置PIN所对应的GPIO,STM32MP1是从STM32单片机发展而来的,设置GPIO是一样。其实对于大多数的32位SOC而言,引脚的设置基本都是这两方面,因此Linux内核针对PIN的复用配置推出了pinctrl子系统,对于GPIO的电气属性配置推出了gpio子系统。本小节我们来学习pinctrl子系统,下一小节再学习gpio子系统。 大多数SOC的pin都是支持复用的,比如STM32MP1的PI0既可以作为普通的GPIO使用,也可以作为SPI2的NSS引脚、TIM5的CH4引脚等等。此外我们还需要配置pin的电气特性,比如上/下拉、速度、驱动能力等等。传统的配置pin的方式就是直接操作相应的寄存器,但是这种配置方式比较繁琐、而且容易出问题(比如pin功能冲突)。pinctrl子系统就是为了解决这个问题而引入的,pinctrl子系统主要工作内容如下: ①、获取设备树中pin信息。 ②、根据获取到的pin信息来设置pin的复用功能 ③、根据获取到的pin信息来设置pin的电气特性,比如上/下拉、速度、驱动能力等。 对于我们使用者来讲,只需要在设备树里面设置好某个pin的相关属性即可,其他的初始化工作均由pinctrl子系统来完成,pinctrl子系统源码目录为drivers/pinctrl。 25.1.2 STM32MP1的pinctrl子系统驱动 1、PIN配置信息详解 要使用pinctrl子系统,我们需要在设备树里面设置PIN的配置信息,毕竟pinctrl子系统要根据你提供的信息来配置PIN功能,一般会在设备树里面创建一个节点来描述PIN的配置信息。打开stm32mp151.dtsi文件,找到一个叫做pinctrl的节点,如下所示:

示例代码25.1.2.1 pinctrl节点内容1

1814        pinctrl: pin-controller@50002000 {
1815            #address-cells = ;
1816            #size-cells = ;
1817            compatible = "st,stm32mp157-pinctrl";
1818            ranges = ;
1819            interrupt-parent = ;
1820            st,syscfg = ;
1821            hwlocks = ;
1822            pins-are-numbered;
......
1968        };

第1815~1816行,#address-cells属性值为1和#size-cells属性值为1,也就是说pinctrl下的所有子节点的reg第一位是起始地址,第二位为长度。 第1818行,ranges属性表示STM32MP1的GPIO相关寄存器起始地址,STM32MP1系列芯片最多拥有176个通用GPIO,分为12组,分别为:PA0PA15、PB0PB15、PC0PC15、PD0PD15、PE0PE15、PF0PF15、PG0PG15、PH0PH15、PJ0PJ15、PK0PK7、PZ0~PZ7。 其中PAPK这9组GPIO的寄存器都在一起,起始地址为0X50002000,终止地址为0X5000C3FF。这个可以在《STM32MP157参考手册》里面找到。PZ组寄存器起始地址为0X54004000,终止地址为0X540043FF,所以stm32mp151.dtsi文件里面还有个名为“pinctrl_z”的子节点来描述PZ组IO。pinctrl节点用来描述PAPK这11组IO,因此ranges属性中的0x50002000表示起始地址,0xa400表示寄存器地址范围。 第1819行,interrupt-parent属性值为“&exti”,父中断为exti。 后面的gpiox子节点先不分析,这些子节点都是gpio子系统的内容,到后面在去分析。这个节点看起来,根本没有PIN先关的配置,别急!先打开stm32mp15-pinctrl.dtsi文件,你们能找到如下内容:

    示例代码25.1.2.2 pinctrl节点内容2
1   &pinctrl {
......
534     m_can1_pins_a: m-can1-0 {
535         pins1 {
536             pinmux = ; 	/* CAN1_TX */
537             slew-rate = ;
538             drive-push-pull;
539             bias-disable;
540         };
541         pins2 {
542             pinmux = ; 	/* CAN1_RX */
543             bias-disable;
544         };
545     };
......
554     pwm1_pins_a: pwm1-0 {
555         pins {
556             pinmux = , /* TIM1_CH1 */
557                  , 		/* TIM1_CH2 */
558                  ; 		/* TIM1_CH4 */
559             bias-pull-down;
560             drive-push-pull;
561             slew-rate = ;
562         };
563     };
......
1411};

示例代码25.1.2.2就是向pinctrl节点追加数据,不同的外设使用的PIN不同、其配置也不同,因此一个萝卜一个坑,将某个外设所使用的所有PIN都组织在一个子节点里面。示例代码25.1.2.2中m_can1_pins_a子节点就是CAN1的所有相关IO的PIN集合,pwm1_pins_a子节点就是PWM1相关IO的PIN集合。绑定文档Documentation/devicetree/bindings/pinctrl/st,stm32-pinctrl.yaml描述了如何在设备树中设置STM32的PIN信息: 每个pincrtl节点必须至少包含一个子节点来存放pincrtl相关信息,也就是pinctrl集,这个集合里面存放当前外设用到哪些引脚(PIN)、这些引脚应该怎么配置、复用相关的配置、上下拉、默认输出高电平还是低电平。一般这个存放pincrtl集的子节点名字是“pins”,如果某个外设用到多种配置不同的引脚那么就需要多个pins子节点,比如示例代码25.1.2.2中第535行和541行的pins1和pins2这两个子节点分别描述PH13和PI9的配置方法,由于PH13和PI9这两个IO的配置不同,因此需要两个pins子节点来分别描述。第555~562行的pins子节点是描述PWM1的相关引脚,包括PE9、PE11、PE14,由于这三个引脚的配置是一模一样的,因此只需要一个pins子节点就可以了。 上面讲了,在pins子节点里面存放外设的引脚描述信息,这些信息包括: 1、pinmux属性 此属性用来存放外设所要使用的所有IO,示例代码25.1.2.2中第536行的pinmux属性内容如下: pinmux = ; 可以看出,这里使用STM32_PINMUX这宏来配置引脚和引脚的复用功能,此宏定义在include/dt-bindings/pinctrl/stm32-pinfunc.h文件里面,内容如下:

示例代码25.1.2.3 STM32_PINMUX宏定义
32 #define PIN_NO(port, line)   (((port) - 'A') * 0x10 + (line))
33 
34 #define STM32_PINMUX(port, line, mode) (((PIN_NO(port, line)) dev);
1532        pctl->pctl_desc.owner = THIS_MODULE;
1533        pctl->pctl_desc.pins = pins;
1534        pctl->pctl_desc.npins = pctl->npins;
1535        pctl->pctl_desc.link_consumers = true;
1536        pctl->pctl_desc.confops = &stm32_pconf_ops;
1537        pctl->pctl_desc.pctlops = &stm32_pctrl_ops;
1538        pctl->pctl_desc.pmxops = &stm32_pmx_ops;
1539        pctl->dev = &pdev->dev;
1540        pctl->pin_base_shift = pctl->match_data->pin_base_shift;
1541
1542        pctl->pctl_dev = devm_pinctrl_register(&pdev->dev, 
1543                         &pctl->pctl_desc, pctl);
......
1600};

第1458行,看它的结构体的名字就知道是ST官方自定义的一个结构体类型,用于存放STM32相关PIN属性集合。第22.5.1小节我们驱动代码也添加了自己的结构体,它们都有自己的结构体这样做有啥好处?可以实现一个驱动代码“通杀”多个设备。要想驱动能通用,就要用结构体来保存数据和驱动里面尽量不要使用全局变量(在pinctrl驱动里就没有一个全局变量,全部使用结构体来描述一个物体,物体的所有属性都作为结构体成员变量)。接着我们去看下stm32_pinctrl结构体是如何定义的,如示例代码25.1.2.9所示:

示例代码25.1.2.9 stm32_pinctrl结构体代码段
100 struct stm32_pinctrl {
......
103     struct pinctrl_desc pctl_desc;
......
107		struct stm32_gpio_bank *banks;
......
120 };

第103行,pinctrl_desc结构体用来描述PIN控制器,PIN控制器,PIN控制器用于配置SOC的PIN复用功能和电气特性。 第107行,这个stm32_gpio_bank结构体,是用来注册GPIO驱动。到后面GPIO子系统在说。 pinctrl_desc结构体内容如下所示:

示例代码25.1.2.10 pinctrl_desc结构体
130 struct pinctrl_desc {
131     const char *name;
132     const struct pinctrl_pin_desc *pins;
133     unsigned int npins;
134     const struct pinctrl_ops *pctlops;
135     const struct pinmux_ops *pmxops;
136     const struct pinconf_ops *confops;
137     struct module *owner;
138 #ifdef CONFIG_GENERIC_PINCONF
139     unsigned int num_custom_params;
140     const struct pinconf_generic_params *custom_params;
141     const struct pin_config_item *custom_conf_items;
142 #endif
143     bool link_consumers;
144 };

第134~136行,这三个“_ops”结构体指针非常重要!因为这三个结构体就是PIN控制器的“工具”,这三个结构体里面包含了很多操作函数,Linux内核初始化PIN最终使用的就是这些操作函数。因此编写一个SOC的PIN控制器驱动的核心就是实现pinctrl_desc里面的pctlops、pmxops和confops,pinctrl_desc结构体需要由用户提供,结构体里面的成员变量也是用户编写的。但是这个用户并不是我们这些使用芯片的程序员,而是半导体厂商,半导体厂商发布的Linux内核源码中已经把这些工作做完了。 示例代码25.1.2.8里,第1536~1538行,给这三个结构体赋值分别对应stm32_pconf_ops、stm32_prtrl_ops和stm32_pmx_ops,三个结构体如下:

示例代码25.1.2.11 stm32_pconf_ops、stm32_prtrl_ops和stm32_pmx_ops结构体
714     static const struct pinctrl_ops stm32_pctrl_ops = {
715         .dt_node_to_map     	= stm32_pctrl_dt_node_to_map,
716         .dt_free_map        	= pinctrl_utils_free_map,
717         .get_groups_count   	= stm32_pctrl_get_groups_count,
718         .get_group_name     	= stm32_pctrl_get_group_name,
719         .get_group_pins     	= stm32_pctrl_get_group_pins,
720     };
......
865     static const struct pinmux_ops stm32_pmx_ops = {
866         .get_functions_count 	= stm32_pmx_get_funcs_cnt,
867         .get_function_name  	= stm32_pmx_get_func_name,
868         .get_function_groups 	= stm32_pmx_get_func_groups,
869         .set_mux        		= stm32_pmx_set_mux,
870         .gpio_set_direction 	= stm32_pmx_gpio_set_direction,
871         .strict        	 		= true,
872     };
......
1238    static const struct pinconf_ops stm32_pconf_ops = {
1239        .pin_config_group_get	= stm32_pconf_group_get,
1240        .pin_config_group_set   	= stm32_pconf_group_set,
1241        .pin_config_dbg_show    	= stm32_pconf_dbg_show,
1242        .pin_config_set     		= stm32_pconf_set,
1243    };
pinctrl_desc结构体初始化完成以后,需要调用pinctrl_register或者devm_pinctrl_register函数就能够向Linux内核注册一个PIN控制器,示例代码25.1.2.8中的1542行就是向Linux内核注册PIN控制器。
总结一下,pinctrl驱动流程如下:

1、定义pinctrl_desc结构体。 2、初始化结构体,重点是pinconf_ops、pinmux_ops和pinctrl_ops这三个结构体成员变量,但是这部分半导体厂商帮我们搞定。 3、调用devm_pinctrl_register函数完成PIN控制器注册。 25.1.3 设备树中添加pinctrl节点模板 我们已经对pinctrl有了比较深入的了解,接下来我们学习一下如何在设备树中添加某个外设的PIN信息。比如我们需要将PG11这个PIN复用为UART4_TX引脚,pinctrl节点添加过程如下: 1、创建对应的节点 在pinctrl节点下添加一个“uart4_pins”节点:

	示例代码25.1.3.1 uart4_pins设备节点
1   &pinctrl {
2       uart4_pins: uart4-0 {
3       /* 具体的PIN信息 */
4       };
5   };

2、添加“pins”属性 添加一个“pins”子节点,这个子节点是真正用来描述PIN配置信,要注意,同一个pins子节点下的所有PIN电气属性要一样。如果某个外设所用的PIN可能有不同的配置,那么就需要多个pins子节点,例如UART4的TX和RX引脚配置不同,因此就有pins1和pins2两个子节点。这里我们只添加UART4的TX引脚,所以添加完pins1子节点以后如下所示:

示例代码25.1.3.2 uart4_pins设备节点下的pins1属性
1   &pinctrl {
2       uart4_pins: uart4-0 {
3           pins1{
4      			/* UART4 TX引脚的PIN配置信息 */
5           };
6       };
7   };

3、在“pins”节点中添加PIN配置信息 最后在“pins”节点中添加具体的PIN配置信息,完成以后如下所示:

示例代码25.1.3.3完整的uart4_pins设备pinctrl子节点
1   &pinctrl {
2       gpio_led: gpio-led-0 {
3           pins1{
4               pinmux = ; /* UART4_TX */ 
5               bias-disable;
6               drive-push-pull;
7           };
8       };
9   };
按道理来讲,当我们将一个IO用作GPIO功能的时候也需要创建对应的pinctrl节点,并且将所用的IO复用为GPIO功能,比如将PI0复用为GPIO的时候就需要设置pinmux属性为:,但是!对于STM32MP1而言,如果一个IO用作GPIO功能的时候不需要创建对应的pinctrl节点!

25.2 gpio子系统 25.2.1 gpio子系统简介 上一小节讲解了pinctrl子系统,pinctrl子系统重点是设置PIN(有的SOC叫做PAD)的复用和电气属性,如果pinctrl子系统将一个PIN复用为GPIO的话,那么接下来就要用到gpio子系统了。gpio子系统顾名思义,就是用于初始化GPIO并且提供相应的API函数,比如设置GPIO为输入输出,读取GPIO的值等。gpio子系统的主要目的就是方便驱动开发者使用gpio,驱动开发者在设备树中添加gpio相关信息,然后就可以在驱动程序中使用gpio子系统提供的API函数来操作GPIO,Linux内核向驱动开发者屏蔽掉了GPIO的设置过程,极大的方便了驱动开发者使用GPIO。 25.2.2 STM32MP1的gpio子系统驱动 1、设备树中的gpio信息 首先肯定是GPIO控制器的节点信息,以PI0这个引脚所在的GPIOI为例,打开stm32mp151.dtsi,在里面找到如下所示内容:

示例代码25.2.2.1 gpioi控制器节点
1814    pinctrl: pin-controller@50002000 {
1815        #address-cells = ;
1816        #size-cells = ;
1817        compatible = "st,stm32mp157-pinctrl";
......
1912        gpioi: gpio@5000a000 {
1913            gpio-controller;
1914            #gpio-cells = ;
1915            interrupt-controller;
1916            #interrupt-cells = ;
1917            reg = ;
1918            clocks = ;
1919            st,bank-name = "GPIOI";
1920            status = "disabled";
1921        };
1944    };
第1912~1921行就是GPIOI的控制器信息,属于pincrtl的子节点,因此对于STM32MP1而言,pinctrl和gpio这两个子系统的驱动文件是一样的,都为pinctrl-stm32mp157.c,所以在注册pinctrl驱动顺便会把gpio驱动程序一起注册。绑定文档Documentation/devicetree/bindings/gpio/gpio.txt详细描述了gpio控制器节点各个属性信息。
第1913行,“gpio-controller”表示gpioi节点是个GPIO控制器,每个GPIO控制器节点必须包含“gpio-controller”属性。
第1914行,“#gpio-cells”属性和“#address-cells”类似,#gpio-cells应该为2,表示一共有两个cell,第一个cell为GPIO编号,比如“&gpioi 0”就表示PI0。第二个cell表示GPIO极性,如果为0(GPIO_ACTIVE_HIGH)的话表示高电平有效,如果为1(GPIO_ACTIVE_LOW)的话表示低电平有效。
第1917行,reg属性设置了GPIOI控制器的寄存器基地址偏移为0X800,因此GPIOI寄存器地址为0X50002000+0X800=0X5000A000,大家可以打开《STM32MP157参考手册》,找到“Table 9. Register boundary addresses”章节的2.5.2 Memory map and register boundary addresses小节,如图25.2.2.1所示:

在这里插入图片描述

图25.2.2.1 GPIOI寄存器表 从图25.2.2.1可以看出,GPIOI控制器的基地址就是0X5000A000,这个地址是基于pinctrl的地址0X50002000+0x8000 = 0X5000A000。 第1918行,clocks属性指定这个GPIOI控制器的时钟。 示例代码25.2.2.1中的是GPIOI控制器节点,当某个具体的引脚作为GPIO使用的时候还需要进一步设置。ST官方EVK开发板将PG1用作SD卡的检测(CD)引脚,PG1复用为GPIO功能,通过读取这个GPIO的高低电平就可以知道SD卡有没有插入。但是,SD卡驱动程序怎么知道CD引脚连接的PG1呢?这里肯定需要设备树来告诉驱动,在设备树中的SD卡节点下添加一个属性来描述SD卡的CD引脚就行了,SD卡驱动直接读取这个属性值就知道SD卡的CD引脚使用的是哪个GPIO了。ST官方EVK开饭的SD卡连接在STM32MP157的sdmmc1接口上,在stm32mp15xx-edx.dtsi中找到名为“sdmmc1”的节点,这个节点就是SD卡设备节点,如下所示:

示例代码25.2.2.2 设备树中SD卡节点
333 &sdmmc1 {
334     pinctrl-names = "default", "opendrain", "sleep";
335     pinctrl-0 = ;
336     pinctrl-1 = ;
337     pinctrl-2 = ;
338     cd-gpios = ;
339     disable-wp;
......
351     status = "okay";
352 };
第338行,属性“cd-gpios”描述了SD卡的CD引脚使用的哪个IO。属性值一共有三个,我们来看一下这三个属性值的含义,“&gpiog”表示CD引脚所使用的IO属于GPIOG组,“1”表示GPIOG组的第1号IO,通过这两个值SD卡驱动程序就知道CD引脚使用了PG1这GPIO。最后一个是“GPIO_ACTIVE_LOW | GPIO_PULL_UP”,Linux内核定义在include/linux/gpio/machine.h文件中定义了枚举类型gpio_lookup_flags,内容如下:
示例代码25.2.2.3 gpio_lookup_flag枚举类型
8  enum gpio_lookup_flags {
9   	GPIO_ACTIVE_HIGH    	= (0             
关注
打赏
1665308814
查看更多评论
0.0728s