查看: 3359|回复: 2

[分享] 【经验分享】i.MX6ULL开发:驱动开发6——Pinctrl子系统与GPI...

[复制链接]
  • TA的每日心情
    开心
    2020-12-18 12:56
  • 签到天数: 55 天

    连续签到: 1 天

    [LV.5]常住居民I

    184

    主题

    425

    帖子

    0

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    2348
    最后登录
    2025-9-10
    发表于 2022-8-22 16:02:22 | 显示全部楼层 |阅读模式
    前面的两篇文章(寄存器配置点亮LED与设备树版的点亮LED),其本质都是通过寄存器配置,来控制LED的亮灭。
    • 使用直接操作寄存器的方式,是将与LED有关的寄存器信息,直接写到了LED的驱动代码中,这也是一种比较常规的控制方式。但当芯片的寄存器发了变动,就要对底层的驱动进行重写。
    • 使用设备树的方式,是将与LED有关的寄存器信息,写到了设备树文件中,这样,当设备的信息修改了,还可以通过设备树的接口函数,来获取设备信息,提高了驱动代码的复用能力。
    • 本篇介绍的Pinctrl子系统与GPIO子系统的方式,不需要再直接操作寄存器了,因为这两个子系统已经替我们实现了对寄存器的操作,我们只需要操作这两个子系统提供的API函数即可。

    1 Pinctrl子系统
    Pintrl子系统,顾名思义,就是管理pin引脚的一个系统,比如要点亮LED,即要控制LED对应引脚的高低电平,就要先通过Pintrl子系统将LED对应的引脚复用为GPIO功能(这一点是不是和之前寄存器配置时使用的MUX寄存器的功能有点像)。
    1.1 设备树中iomuxc节点
    如何使用Pintrl子系统呢?其实它也是要依赖设备树的,先来了解一下设备树里的iomuxc节点,这个节点是IOMUXC外设对应的节点,负责IO功能的复用。
    打开自己开发板对应的设备树文件(我的是imx6ull-myboard.dts),然后找到iomuxc节点,先来看一下其基本结构:
    1. <font face="Arial">&iomuxc {
    2. pinctrl-names = "default";
    3. pinctrl-0 = <&pinctrl_hog_1>;
    4. imx6ul-evk {
    5. pinctrl_hog_1: hoggrp-1 {
    6.   fsl,pins = <
    7.    MX6UL_PAD_UART1_RTS_B__GPIO1_IO19     0x17059 /* SD1 CD */
    8.    MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059 /* SD1 VSELECT */
    9.    MX6UL_PAD_GPIO1_IO09__GPIO1_IO09        0x17059 /* SD1 RESET */
    10.   >;
    11. };

    12. pinctrl_csi1: csi1grp {
    13.   fsl,pins = <
    14.    MX6UL_PAD_CSI_MCLK__CSI_MCLK  0x1b088
    15.    MX6UL_PAD_CSI_PIXCLK__CSI_PIXCLK 0x1b088
    16.    MX6UL_PAD_CSI_VSYNC__CSI_VSYNC  0x1b088
    17.    MX6UL_PAD_CSI_HSYNC__CSI_HSYNC  0x1b088
    18.    MX6UL_PAD_CSI_DATA00__CSI_DATA02 0x1b088
    19.    MX6UL_PAD_CSI_DATA01__CSI_DATA03 0x1b088
    20.    MX6UL_PAD_CSI_DATA02__CSI_DATA04 0x1b088
    21.    MX6UL_PAD_CSI_DATA03__CSI_DATA05 0x1b088
    22.    MX6UL_PAD_CSI_DATA04__CSI_DATA06 0x1b088
    23.    MX6UL_PAD_CSI_DATA05__CSI_DATA07 0x1b088
    24.    MX6UL_PAD_CSI_DATA06__CSI_DATA08 0x1b088
    25.    MX6UL_PAD_CSI_DATA07__CSI_DATA09 0x1b088
    26.   >;
    27. };
    28.        //省略...</font>
    复制代码
    这里以pinctrl_hog_1插拔子节点为例进行分析,它是和热插拔有关的Pin集合,比如USB OTG的ID引脚,pinctrl_csi1子节点是csi外设所使用的PIN,本篇需要控制LED的亮灭,就需要新建一个对应的节点,然后将这个自定义外设的所有Pin配置信息都放到这个子节点中。
    1.2 宏定义的含义解析
    对于pinctrl_hog_1这个字节点,注意其中的:
    1. <font face="Arial">MX6UL_PAD_UART1_RTS_B__GPIO1_IO19     0x17059 /* SD1 CD */</font>
    复制代码
    这就是对Pin引脚的配置,配置包括两方面:一是设置Pin的复用功能,二是设置Pin的电气特性。
    前面的MX6UL_PAD_UART1_RTS_B__GPIO1_IO19这个宏定义, 定义在arch/arm/boot/dts/imx6ul-pinfunc.h中(注意imx6ull.dtsi会引用imx6ull-pinfunc.h,而imx6ull-pinfunc.h又会引用imx6ul-pinfunc.h)
    图片 1.png
    这里一共有8 个以MX6UL_PAD_UART1_RTS_B开头的宏定义,分别代表这个IO的8种不同的功能。
    另外,这个宏定义的值,被分为了5段,每段的值都有具体的含义:
    • 0x0090 mux_reg寄存器偏移地址

    图片 2.png

    • 0x031C conf_reg寄存器偏移地址

    图片 3.png


    • 0x0000 input_reg寄存器偏移地址(这里无效)
    • 0x5 mux_reg寄存器的值

    图片 4.png


    • 0x0 input_reg寄存器值(这里无效)

    2 GPIO子系统
    GPIO子系统,顾名思义,就是管理GPIO功能的一个系统,其作用是初始化配置GPIO(这一点是不是和之前寄存器配置时使用的PAD寄存器的功能有点像),并提供对外的API接口。使用GPIO子系统后,就不需要自己操作寄存器,通过调用GPIO子系统提供的API函数即可实现对GPIO的控制。
    2.1 设备树中gpio信息
    仍以热插拔节点为例:
    图片 5.png
    UART1_RTS_B复用为GPIO1_IO19,通过读取其高低电平来判断SD卡有没有插入。
    那SD卡驱动程序怎么知道CD引脚连接的GPIO1_IO19呢?还是需要设备树告诉驱动,在设备树中SD卡节点下添加一个属性来描述SD卡的 CD 引脚就行了:
    图片 6.png
    属cd-gpios描述了SD卡的CD引脚使用的哪个IO,属性值一共有三个:
    • &gpio1 表示CD引脚所使用的IO属于GPIO1组
    • 19 表示GPIO1组的第19号IO
    • GPIO_ACTIVE_LOW 表示低电平有效

    根据上面这些信息,SD卡驱动程序就可以使用GPIO1_IO19来检测SD卡的CD信号了
    2.2 gpio子系统API函数2.2.1 gpio_request/free
    • gpio_request

    用于申请一个GPIO管脚
    1. <font face="Arial">/**
    2. * gpio: 要申请的gpio标号(使用of_get_named_gpio函数从设备树获取指定GPIO属性信息时返回的标号)
    3. * label: 给gpio设置个名字
    4. * return: 0-申请成功 其他值-申请失败
    5. */
    6. int gpio_request(unsigned gpio,  const char *label)</font>
    复制代码
    • gpio_free

    用于释放一个GPIO管脚
    1. <font face="Arial">/**
    2. * gpio: 要释放的gpio标号
    3. * return
    4. */
    5. void gpio_free(unsigned gpio)</font>
    复制代码
    2.2.2 gpio_direction_input/output
    • gpio_direction_input

    用于设置某个GPIO为输入
    1. <font face="Arial">/**
    2. * gpio: 要设置为输入的GPIO标号
    3. * return: 0-设置成功 负值-设置失败
    4. */
    5. int gpio_direction_input(unsigned gpio)</font>
    复制代码
    • gpio_direction_output

    此函数用于设置某个GPIO为输出,并且设置默认输出值
    1. <font face="Arial">/**
    2. * gpio: 要设置为输出的GPIO标号
    3. * value: GPIO默认输出值
    4. * return 0-设置成功 负值-设置失败
    5. */
    6. int gpio_direction_output(unsigned gpio, int value)</font>
    复制代码
    2.2.3 gpio_get_value/set_value
    • gpio_get_value

    此函数用于获取某个GPIO的值(0 或 1)
    1. <font face="Arial">#define gpio_get_value  __gpio_get_value
    2. /**
    3. * gpio: 要获取的gpio标号
    4. * return: 非负值-得到的gpio值 负值-获取失败
    5. */
    6. int __gpio_get_value(unsigned gpio)</font>
    复制代码
    • gpio_set_value

    用于设置某个GPIO的值
    1. <font face="Arial">#define gpio_set_value  __gpio_set_value
    2. /**
    3. * gpio: 要设置的gpio标号
    4. * value: 要设置的值
    5. * return
    6. */
    7. void __gpio_set_value(unsigned gpio, int value)</font>
    复制代码
    2.3 与gpio相关的OF函数
    2.3.1 of_gpio_named_count
    用于获取设备树某个属性里面定义了几个GPIO信息
    1. <font face="Arial">/**
    2. * np: 设备节点
    3. * propname: 要统计的gpio属性
    4. * return: 正值-统计到的gpio数量 负值-失败
    5. */
    6. int of_gpio_named_count(struct device_node *np, const char  *propname)</font>
    复制代码
    2.3.2 of_gpio_count
    统计“gpios”这个属性的gpio数量
    1. <font face="Arial">/**
    2. * np: 设备节点
    3. * return: 正值-统计到的gpio数量 负值-失败
    4. */
    5. int of_gpio_count(struct device_node *np)</font>
    复制代码
    2.3.3 of_get_named_gpio
    获取GPIO编号
    1. <font face="Arial">/**
    2. * np: 设备节点
    3. * propname: 包含要获取gpio信息的属性名
    4. * index: gpio索引(一个属性里面可能包含多个gpio)
    5. * return: 正值-获取到的gpio编号 负值-失败
    6. */
    7. int of_get_named_gpio(struct device_node *np,
    8.                              const char *propname,
    9.                                     int index)</font>
    复制代码
    3 Pinctr版LED驱动程序
    上面介绍了Pinctrl子系统与GPIO子系统的基本情况,下面就来使用它们来实现LED的亮灭控制。
    3.1 修改设备树文件
    修改imx6ull-myboard.dts,在iomuxc节点的imx6ull-evk字节点下创建一个名为pinctrl_led的子节点,节点内容如下:
    1. <font face="Arial">pinctrl_gpioled: ledgrp{
    2.    fsl,pins = <
    3.        MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03    0x10b0
    4.        >;
    5. };
    6. </font>
    复制代码
    • MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03 表示将该io复用为GPIO
    • 0x10b0 表示对PAD寄存器的配置值,具体含义为如下,之前的文章(驱动开发4--点亮LED(寄存器版))介绍过。

    1. <font face="Arial">/*寄存器SW_PAD_SNVS_TAMPER3设置IO属性
    2.     *bit 16:0 HYS关闭
    3.     *bit [15:14]: 00 默认下拉
    4.     *bit [13]: 0 kepper功能
    5.     *bit [12]: 1 pull/keeper使能
    6.     *bit [11]: 0 关闭开路输出
    7.     *bit [7:6]: 10 速度100Mhz
    8.     *bit [5:3]: 110 R0/6驱动能力
    9.     *bit [0]: 0 低转换率
    10.     */</font>
    复制代码
    图片 7.png



    在根节点下创建名为gpioled的LED节点,内容如下:
    1. <font face="Arial">/*pinctrl led*/
    2. gpioled {
    3.    compatible = "myboard,gpioled";
    4.    pinctrl-names = "default";
    5.    pinctrl-0 = <&pinctrl_gpioled>;
    6.    led-gpios = <&gpio5 3 GPIO_ACTIVE_LOW>;
    7.    status = "okay";
    8. };</font>
    复制代码
    • pinctrl-0 设置 LED所使用的PIN对应的pinctrl节点
    • led-gpio 指定了LED所使用的GPIO,这里是GPIO5的IO03,低电平有效

    图片 8.png


    3.2 检查引脚是否使用冲突
    因为我的开发板使用的设备树文件(imx6ull-myboard.dts)是从NXP官方提供的设备树文件(imx6ull-14x14-evk.dts)上修改而来的,可能某些引脚的配置与自己的开发板不一样,需要检查一下是否有使用冲突。
    本次添加的这个MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03与文件中的其它引脚没有出现冲突,因此无需修改。
    3.3 修改LED驱动文件
    在上一篇的设备树版的驱动文件上进行修改,主要修改内容如下。
    头文件需要添加一个:
    1. <font face="Arial">#include <linux/of_gpio.h></font>
    复制代码
    设备结构体改为gpio_led:
    1. <font face="Arial">/* gpioled设备结构体 */
    2. struct gpioled_dev{
    3.    dev_t         devid;    /* 设备号   */
    4.    struct cdev   cdev;     /* cdev     */
    5.    struct class  *class;   /* 类       */
    6.    struct device *device;  /* 设备     */
    7.    int           major;    /* 主设备号 */
    8.    int           minor;    /* 次设备号 */
    9.    struct device_node *nd; /* 设备节点 */
    10.    int           led_gpio; /* led使用的GPIO编号*/
    11. };

    12. struct gpioled_dev gpioled;    /* led设备 */</font>
    复制代码
    硬件初始化部分是主要修改的内容,这次就不需要从设备树读取寄存器操作了,也不需要自己再进行I/O内存映射了,而实使用gpio子系统的API函数来对LED的GPIO进行配置:
    1. <font face="Arial">static int gpioled_hardware_init(void)
    2. {
    3.     int ret;

    4.     /* 获取设备树中的属性数据 */
    5.     /* 1、获取设备节点:gpioled */
    6.     gpioled.nd = of_find_node_by_path("/gpioled");
    7.     if(gpioled.nd == NULL)
    8.     {
    9.         printk("gpioled node not find!\r\n");
    10.         return -EINVAL;
    11.     }
    12.     else
    13.     {
    14.         printk("gpioled node find!\r\n");
    15.     }

    16.     /* 2、获取gpio属性, 得到LED编号 */
    17.     gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
    18.     if(gpioled.led_gpio < 0)
    19.     {
    20.         printk("can't get led-gpio!\r\n");
    21.         return -EINVAL;
    22.     }
    23.     else
    24.     {
    25.         printk("led-gpio num = %d\r\n", gpioled.led_gpio);
    26.     }

    27.     /* 3、设置GPIO为输出, 并默认关闭LED */
    28.     ret = gpio_direction_output(gpioled.led_gpio, 1);
    29.     if(ret < 0)
    30.     {
    31.         printk("can't set led-gpio!\r\n");
    32.     }

    33.     return 0;
    34. }</font>
    复制代码
    开关LED时,也不需要再直接操作寄存器了,也是使用API函数来操作:
    1. <font face="Arial">static ssize_t gpioled_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
    2. {
    3.         //省略...

    4.     if(ledstat == LEDON)
    5.     {
    6.         gpio_set_value(dev->led_gpio, 0);         /* 打开LED灯 */
    7.         printk("led on!\n");
    8.     }
    9.     else if(ledstat == LEDOFF)
    10.     {
    11.         gpio_set_value(dev->led_gpio, 1);        /* 关闭LED灯 */
    12.         printk("led off!\n");
    13.     }

    14.     return 0;
    15. }</font>
    复制代码
    4 实验测试4.1 编译程序
    • 编译设备树文件(.dtb),和上篇设备树点亮LED的实验一样,先将设备树文件复制到nfs文件系统位置,再从网络启动开发板,串口中查看设备树中是否有添加的gpioled节点:

    图片 9.png


    • 编译LED驱动文件(.ko),复制到rootfs/lib/modules/4.1.15目录中:

    图片 10.png

    • LED应用程序不需要改,仍使用之前寄存器版点亮LED实验时使用的程序即可。

    4.2 测试
    测试方式与之前的一样,都是先加载驱动文件,然后调用应用程序来控制LED的亮灭:
    图片 11.png
    效果和之前的寄存器版点亮LED与设备树版点亮LED的效果一样
    图片 12.png
    5 总结
    本篇介绍了使用Pinctrl子系统与GPIO子系统的方式来点亮LED,与之前的寄存器版点亮LED与设备树版点亮LED的最大区别在于不需要直接操作寄存器了,而是使用API函数来配置GPIO,具体操作寄存器在过程在API函数内部实现,我们无需在进行繁琐的寄存器操作。
    本篇与上一篇的设备树版点亮LED的程序编写流程基本一致,因为都是要使用设备树,与上一篇的主要区别就在于,不需要将寄存器信息写入设备树,再从设备树获取出来手动配置寄存器了。


    签到
    回复

    使用道具 举报

  • TA的每日心情
    慵懒
    22 小时前
  • 签到天数: 1882 天

    连续签到: 7 天

    [LV.Master]伴坛终老

    203

    主题

    3万

    帖子

    64

    超级版主

    Rank: 8Rank: 8

    积分
    112745
    最后登录
    2025-9-10
    发表于 2022-8-22 16:59:53 | 显示全部楼层
    支持一下,666
    该会员没有填写今日想说内容.
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    慵懒
    11 小时前
  • 签到天数: 1643 天

    连续签到: 18 天

    [LV.Master]伴坛终老

    25

    主题

    1万

    帖子

    0

    版主

    Rank: 7Rank: 7Rank: 7

    积分
    11048

    活跃会员

    最后登录
    2025-9-10
    发表于 2022-8-22 20:23:39 | 显示全部楼层
    前排支持
    该会员没有填写今日想说内容.
    回复

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /3 下一条

    Archiver|手机版|小黑屋|恩智浦技术社区

    GMT+8, 2025-9-10 22:52 , Processed in 0.151965 second(s), 22 queries , MemCache On.

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.

    快速回复 返回顶部 返回列表