Linux驱动开发六.gpio和pinctl子系统1——基础知识

在前面两章我们通过设备树实现了最基础的点灯操作,但是实质上还是在设备树里写出来要操作的寄存器地址,然后在驱动中内核通过of函数获取到寄存器物理地址后经过映射后进行操作,整个过程其实和裸机开发的流程基本一样,而在Linux中,GPIO这种最基本的操作是有专门的驱动框架来供我们使用的,这就是pinctrl和gpio两个子系统。

pinctrl子系统

Linux驱动归根到底都是要用到最基础的GPIO,而GPIO到目前为止的使用无非就是PIN的复用功能和电气特性的设置,pinctrl子系统就可以根据我们在设备树中的设置来对pin进行相关的设置。pinctrl子系统的路径为drivers/pinctrl,在该目录下为各个半导体厂商维护的pin信息

 我们使用的imx6ull属于飞思卡尔的,所以主要要关注下面两个个文件

pinctrl子系统我们这里不展开讲了,首先要能用就可以!

设备树中的pinctrl配置信息

因为pinctrl属于LINUX最基础的配置,不管哪个开发板的后期驱动是在这个节点上延伸的,所以pinctrl的基础设置在imx6ull.dtsi这个设备树文件中。在里面搜索一下gpio的关键字iomuxc,可以找到下面几个节点

gpr: iomuxc-gpr@020e4000 {
    compatible = "fsl,imx6ul-iomuxc-gpr",
        "fsl,imx6q-iomuxc-gpr", "syscon";
    reg = <0x020e4000 0x4000>;
};
iomuxc_snvs: iomuxc-snvs@02290000 {
    compatible = "fsl,imx6ull-iomuxc-snvs";
    reg = <0x02290000 0x10000>;
};

这三个节点,就对应了IMX6ULL手册中三个GPIO的基础属性

 再看下各组寄存器的基地址

 

刚好就是设备树中定义的地址,然后这三组节点就构成了手册中GPIO的定义。但是在设备树文件中只有这几行描述IOMUXC,很显然这是不够的,一个IO接口具体功能是由具体的板子硬件决定的。所以我们要看继承了imx6ull.dtsi设备树文件的具体开发板的设备树文件。可以

iomuxc控制器节点

回顾一下,我们在写驱动时候最主要的就是IOMUXC这个节点(电气属性、复用功能都是在这个部分里),所以我们主要看下开发板设备树中的iomuxc这一部分(imx6ull-alientek-emmc.dts文件内搜索iomuxc)

&iomuxc {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_hog_1>;
    imx6ul-evk {
        pinctrl_enet1_reset: enet1resetgrp {
            fsl,pins = <
                MX6ULL_PAD_SNVS_TAMPER7__GPIO5_IO07 0x10B0
            >;
        };

        pinctrl_enet2_reset: enet2resetgrp {
            fsl,pins = <
                MX6ULL_PAD_SNVS_TAMPER8__GPIO5_IO08 0x10B0
            >;
        };

        pinctrl_hog_1: hoggrp-1 {
            fsl,pins = <
                MX6UL_PAD_UART1_RTS_B__GPIO1_IO19    0x17059 /* SD1 CD */
                MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT    0x17059 /* SD1 VSELECT */
                MX6UL_PAD_GPIO1_IO09__GPIO1_IO09        0x17059 /* SD1 RESET */
            >;
        };

    /*中间省略若干节点*/
    };
};
节点pinctrl_hog_1: hoggrp-1里那上面那个像宏一样的内容来讲一下
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19    0x17059 /* SD1 CD */

这个宏是在imx6ul-pinfunc.h(注意是imx6ul,不是6ull,6ull里内容很少,只有几十行)里定义的

#define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19                         0x0090 0x031C 0x0000 0x5 0x0

注意看一下这行宏前后的内容,前面的内容是一样的一组有8条宏(MX6UL_PAD_UART1_RTS_B)双下划线后面跟的就是复用的功能选择

  一共8个,对应手册里的复用模式(其中DCE和DTE两个功能都是复用的ALT0)

但是少了几个,就是把最常用的复用功能给拿出来了,如果没有我们要的功能驱动里没有定义的话只能自己写了。想把pin复用成哪个功能,把那个宏调用出来就可以了。

每个宏定义时候后面跟了4个参数,这个在imx6ul-pinfunc.h文件开头已经给我们说明了

/*
 * The pin function ID is a tuple of
 * <mux_reg conf_reg input_reg mux_mode input_val>
 */

结合前面的MX6UL_PAD_UART1_RTS_B__GPIO1_IO19来看,每个值如下

mux_reg     -------->0x0090
conf_reg    -------->0x031C
input_reg   -------->0x0000
mux_mode    -------->0x5
input_val   -------->0x0

而hoggrp-1节点是属于iomuxc的,基地址是0x020e0000,上面各个reg的地址就是在这个基地址进行便宜得到的地址

iomuxc: iomuxc@020e0000 {
    compatible = "fsl,imx6ul-iomuxc";
    reg = <0x020e0000 0x4000>;
};

mux_reg的地址就是0x020e0090,对应手册里的地址

 

 

对应后面的mux_mode=0x5就是该寄存器的值

 

复用为ALT5,就是复用为GPIO1_IO19。

conf_reg地址为0x020e031c,对应手册上的寄存器为

是电气属性设置地址。仔细看下这里并没有指定conf_val,是因为这个值在我们调用这个宏时后面传了个参数

MX6UL_PAD_UART1_RTS_B__GPIO1_IO19    0x17059 /* SD1 CD */

后面那个0x17509就是电气属性寄存器给定的值,把他分解到寄存器各个bit上就知道其电气属性了,这属于裸机开发里应该了解的内容。

input_reg输入寄存器值为0,即没有偏移量表示这个PIN没有input功能。

input_val写给input_reg寄存器的值。

有了上面的内容,我们就可以根据实习外设需要在设备树文件中添加新的信息。比方开发板上的蜂鸣器、LED,就可以根据IO接口进行设置。

GPIO子系统

上面我们大致知道了pinctrl子系统,说白了pinctrl子系统主要用来设置PIN的(),如果pinctrl子系统将PIN复用为GPIO的时候,那么接下来就要用到另外一个子系统——gpio子系统。gpio子系统主要用来初始化GPIO并且并提供相应的API接口,例如设置输入输出、读取输入值、设置输出值等等。gpio子系统的存在主要就是为了方便驱动开发人员能够更加便利店使用GPIO,在写驱动的时候只要在设备树中添加需要的gpio信息,就可以在驱动使用系统提供的API来操作GPIO,在开发流程上省略了GPIO的设置过程。

还是利用前面那个MX6UL_PAD_UART1_RTS_B__GPIO1_IO19对应的设备,因为在pinctrl中该PIN已经被复用为GPIO1的IO19,我们可以在设备树中搜索关键字gpio1 19,可以找到下面的节点

&usdhc1 {
    pinctrl-names = "default", "state_100mhz", "state_200mhz";
    pinctrl-0 = <&pinctrl_usdhc1>;
    pinctrl-1 = <&pinctrl_usdhc1_100mhz>;
    pinctrl-2 = <&pinctrl_usdhc1_200mhz>;
    /*pinctrl-3 = <&pinctrl_hog_1>;*/
    cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
    keep-power-in-suspend;
    enable-sdio-wakeup;
    vmmc-supply = <&reg_sd1_vmmc>;
    status = "okay";
};

注意一点,注释掉的那一行是根据教程上新加的,其中usdhc1是SD卡设备的总结点,表述了SD卡所有的信息,新加的那一行就是告诉设备树根据pinctrl_hog_1来找相应的pin设置(因为前面的讲pinctrl时是在该节点下

 

设备树需要根据这个节点进行相应的pinctrl设置,这里要注意的是,虽然在usdhc1节点下我们并没有指定通过引用pinctrl_hog_1节点,但是在iomuxc节点下引用了pinctrl_hog_1节点,内核中iomuxc驱动就会自动初始化该节点下所有pin

 

回到前面的gpio子系统,加粗的那一行代码,属性cd-gpios描述了SD卡的CD引脚使用了哪个IO——&gpio1 19表示GPIO1组的io19,GPIO_ACTIVE_LOW表示低电平有效,如果改成GPIO_ACTIVE_HIGH表示高电平有效。就根据这行代码,就可以功过GPIO1_IO19来判定SD卡的CD信号了。

gpio设备树分析

gpio1也是个设备树节点,在imx6ull.dtsi文件下,对其进行了描述

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>;
};

主要涉及到就是寄存器基地址:0x0209c000,对应手册里的寄存器是

 也就是GPIO1组的设置。后面的gpio-controller表示该gpio1节点是个GPIO控制器,gpio-cells和address-cells类似,表示一共有2个cell来对其进行描述,第一个cell为&gpio1 3就表示GPIO1_IO03,第二个cell表示GPIO的极性,如果为0(GPIO_ACTIVE_HIGH)表示高电平有效,反之为1的时候表示低电平有效(GPIO_ACTIVE_LOW)。gpio相关节点一共有五组,从gpio1一直到gpio5,对应5组GPIO。

gpio子系统API函数

gpio子系统的流程我们开始不用太关注,但是这里的API是我们在写驱动的时候要了解大致的使用方法的。下面几个函数需要我们了解(路径为/linux/include/linux/gpio.h)

gpio_request

用于申请一个GPIO管脚,在使用GPIO前应该先申请,如果不申请的话也许不报错,但是如果资源被占用就会报错!函数原型如下

static inline int gpio_request(unsigned gpio, const char *label)
{
    return -ENOSYS;
}

参数gpio为要申请的gpio标号,一般使用of_get_named_gpio函数从设备树中获取GPIO属性信息

参数label为gpio设置的名字

返回值如果为0表示申请成功,不为0是申请失败

gpio_free

和gpio_request为配对的函数,用来释放申请的GPIO资源,函数原型如下

static inline void gpio_free(unsigned gpio)
{
    might_sleep();

    /* GPIO can never have been requested */
    WARN_ON(1);
}

参数gpio为要释放掉的gpio标号

gpio_direction_input

用来设置gpio为输入,原型如下

static inline int gpio_direction_input(unsigned gpio)
{
    return -ENOSYS;
}

参数gpio为要设置的gpio标号

返回值为0时表示设置成功,否则设置失败

gpio_direction_output

用来设置GPIO为输出,并设置默认的输出状态

static inline int gpio_direction_output(unsigned gpio, int value)
{
    return -ENOSYS;
}

参数gpio为要设置的gpio标号

参数value为默认输出状态,可以为0或1

返回值为0时表示设置成功,否则设置失败

gpio_get_value

用于获取某个GPIO的输入状态,原型如下

static inline int gpio_get_value(unsigned gpio)
{
    /* GPIO can never have been requested or set as {in,out}put */
    WARN_ON(1);
    return 0;
}

这个函数使用方法有些疑问没搞懂,待定

gpio_set_value

用来设置指定GPIO的输出值

static inline void gpio_set_value(unsigned gpio, int value)
{
    /* GPIO can never have been requested or set as output */
    WARN_ON(1);
}

参数gpio为要设置的gpio标号

参数value为输出状态,可以为0或1

上面几个函数就是最常用的gpio子系统函数,另外还有很多,都在gpio.h文件中声明了。

 

posted @ 2022-07-01 12:42  银色的音色  阅读(826)  评论(0编辑  收藏  举报