首页 > 其他分享 >【IMX6ULL学习笔记】十九、Pinctrl、GPIO驱动驱动框架

【IMX6ULL学习笔记】十九、Pinctrl、GPIO驱动驱动框架

时间:2023-02-18 23:33:24浏览次数:38  
标签:GPIO imx pinctrl Pinctrl 驱动 gpio reg struct

一、I.MX6ULL的pinctrl(IOMUXC)子系统

1、设备树中 PIN 配置信息详解

打开 imx6ull.dtsi 文件,找到一个叫做 iomuxc 的节点,如下所示:

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

iomuxc 节点就是 I.MX6ULL 的 IOMUXC 外设对应的节点,打开 imx6ull-alientek-emmc.dts,找到如下所示内容:

&iomuxc {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_hog_1>;
    imx6ul-evk {
        pinctrl_hog_1: hoggrp-1 {
            fsl,pins = <
                MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
                MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059
                MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059
                MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x13058
            >;
        };
......
        pinctrl_flexcan1: flexcan1grp{
            fsl,pins = <
                MX6UL_PAD_UART3_RTS_B__FLEXCAN1_RX 0x1b020
                MX6UL_PAD_UART3_CTS_B__FLEXCAN1_TX 0x1b020
            >;
        };
......
        pinctrl_wdog: wdoggrp {
            fsl,pins = <
                MX6UL_PAD_LCD_RESET__WDOG1_WDOG_ANY 0x30b0
            >;
        };
    };
};

示例代码是向 iomuxc 节点追加数据,不同的外设使用的 PIN 不同、其配置也不
同,如果需要在 iomuxc 中添加自定义外设的 PIN,那么需要新建一个子节点,然
后将这个自定义外设的所有 PIN 配置信息都放到这个子节点中。
完整的 iomuxc 节点,如下所示:

iomuxc: iomuxc@020e0000 {
    compatible = "fsl,imx6ul-iomuxc";
    reg = <0x020e0000 0x4000>;
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_hog_1>;
    imx6ul-evk {
        pinctrl_hog_1: hoggrp-1 {
            fsl,pins = <
                MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
                MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059
                MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059
                MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x13058
            >;
......
        };
    };
};

第 2 行:compatible 属性值为“fsl,imx6ul-iomuxc”,讲解设备树的时候说过,Linux 内核会根据 compatbile 属性值来查找对应的驱动文件
第 9~12 行:pinctrl_hog_1 子节点所使用的 PIN 配置信息

以第 9 行的 UART1_RTS_B 这个 PIN 为例,讲解一下如何添加 PIN 的配置信息,UART1_RTS_B 的配置信息如下:

MX6UL_PAD_UART1_RTS_B__GPIO1_IO19    0x17059

首先来看一下 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 这个头文件。从这里可以看出,可以在设备树中引用 C 语言中.h 文件中的内容。MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 的宏定义内容如下:

#define MX6UL_PAD_UART1_RTS_B__UART1_DCE_RTS   0x0090 0x031C 0x0620 0x0 0x3
#define MX6UL_PAD_UART1_RTS_B__UART1_DTE_CTS   0x0090 0x031C 0x0000 0x0 0x0
#define MX6UL_PAD_UART1_RTS_B__ENET1_TX_ER     0x0090 0x031C 0x0000 0x1 0x0
#define MX6UL_PAD_UART1_RTS_B__USDHC1_CD_B     0x0090 0x031C 0x0668 0x2 0x1
#define MX6UL_PAD_UART1_RTS_B__CSI_DATA05      0x0090 0x031C 0x04CC 0x3 0x1
#define MX6UL_PAD_UART1_RTS_B__ENET2_1588_EVENT1_OUT  0x0090 0x031C 0x0000 0x4 0x0
#define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19      0x0090 0x031C 0x0000 0x5 0x0
#define MX6UL_PAD_UART1_RTS_B__USDHC2_CD_B     0x0090 0x031C 0x0674 0x8 0x2

示例代码中一共有 8 个以“MX6UL_PAD_UART1_RTS_B”开头的宏定义,这 8 个宏定义分别对应 UART1_RTS_B 这个 PIN 的 8 个复用 IO。查阅《I.MX6ULL 参考手册》可以知 UART1_RTS_B 的可选复用 IO 如图下所示:
image
宏定义 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 表示将 UART1_RTS_B 这个 IO 复用为 GPIO1_IO19。此宏定义后面跟着 5 个数字,也就是这个宏定义的具体值,如下所示:

0x0090 0x031C 0x0000 0x5 0x0

这 5 个值的含义如下所示:

<mux_reg conf_reg input_reg mux_mode input_val>

综上所述可知:
0x0090:mux_reg 寄存器偏移地址,设备树中的 iomuxc 节点就是 IOMUXC 外设对应的节点 , 根据其 reg 属性知 IOMUXC 外设寄存器起始地址为 0x020e0000 ,而 0x020e0000 + mux_reg(0x0090)= 0x020e0090,查i.Mx6ull 手册可知IOMUXC_SW_MUX_CTL_PAD_UART1_RTS_B 寄存器的地址正好是 0x020e0090。

0x031C:conf_reg 寄存器偏移地址,和 mux_reg 一样,0x020e0000 + 0x031c = 0x020e031c,就是寄存器 IOMUXC_SW_PAD_CTL_PAD_UART1_RTS_B 的地址。

0x0000:input_reg 寄存器偏移地址,有些外设有 input_reg 寄存器,有 input_reg 寄存器的外设需要配置 input_reg 寄存器,没有的话就不需要设置。UART1_RTS_B 这个 PIN 在做 GPIO1_IO19 的时候是没有 input_reg 寄存器,因此这里 intput_reg 是无效的。

0x5: mux_reg 寄存器值,相当于设置IOMUXC_SW_MUX_CTL_PAD_UART1_RTS_B 寄存器为 0x5,也即是设置 UART1_RTS_B 这个 PIN 复用为 GPIO1_IO19。

0x0:input_reg 寄存器值,在这里无效。

这就是宏 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 的含义,但这个宏并没有 conf_reg 寄存器的值,config_reg 寄存器是设置一个 PIN 的电气特性的,这么回到示例代码中,内容如下所示:

MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059

MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 这个宏已经分析了,宏后面还跟着一个值 0x17059,这就是 conf_reg 寄存器值!此值由用户自行设置,通过此值来设置一个 IO 的上/下拉、驱动能力和速度等。

2、PIN 控制器驱动程序讲解

iomuxc 节点中 compatible 属性的值为“fsl,imx6ul-iomuxc”,在 Linux 内核中全局搜索“fsl,imx6ul-iomuxc”字符串就会找到对应的驱动文件。在文件drivers/pinctrl/freescale/pinctrl-imx6ul.c中有如下内容:

static struct of_device_id imx6ul_pinctrl_of_match[] = {
    { .compatible = "fsl,imx6ul-iomuxc", .data = &imx6ul_pinctrl_info, },
    { .compatible = "fsl,imx6ull-iomuxc-snvs", .data = &imx6ull_snvs_pinctrl_info, },
    { /* sentinel */ }
};

static int imx6ul_pinctrl_probe(struct platform_device *pdev)
{
    const struct of_device_id *match;
    struct imx_pinctrl_soc_info *pinctrl_info;

    match = of_match_device(imx6ul_pinctrl_of_match, &pdev->dev);

    if (!match)
        return -ENODEV;

    pinctrl_info = (struct imx_pinctrl_soc_info *) match->data;

    return imx_pinctrl_probe(pdev, pinctrl_info);
}

static struct platform_driver imx6ul_pinctrl_driver = {
    .driver = {
        .name = "imx6ul-pinctrl",
        .owner = THIS_MODULE,
        .of_match_table = of_match_ptr(imx6ul_pinctrl_of_match),
    },
    .probe = imx6ul_pinctrl_probe,
    .remove = imx_pinctrl_remove,
};

第 1~5 行:of_device_id 结构体数组,讲解设备树的时候说过,of_device_id
里面保存着这个驱动文件的兼容性值,设备树中的 compatible 属性值会和 of_device_id 中的所有兼容性字符串比较,查看是否可以使用此驱动。
imx6ul_pinctrl_of_match 结构体数组一共有两个兼容性字符串,分别为 “fsl,imx6ul-iomuxc”和“fsl,imx6ull-iomuxc-snvs”,因此 iomuxc 节点
与此驱动匹配,所以 pinctrl-imx6ul.c 会完成 I.MX6ULL 的 PIN 配置工作。

第 22~30 行:Pinctrl 控制器驱动是 platform 总线下的主机驱动(控制器驱动)。当设备和驱动匹配成功以后 platform_driver 的 probe 成员变量所代表的函数就会执行,即 imx6ul_pinctrl_probe 这函数就会执行,可以认为 imx6ul_pinctrl_probe 函数就是 I.MX6ULL 这个 SOC 的 PIN 配置入口函数。

函数调用路径如图所示:
image
①图中函数 imx_pinctrl_parse_groups 负责获取设备树中关于 PIN 的配置信息,也就是我们前面分析的宏定义所代表的那 6 个 u32 类型的值。处理过程如下所示:

#define FSL_PIN_SIZE 24
#define SHARE_FSL_PIN_SIZE 20

static int imx_pinctrl_parse_groups(struct device_node *np,
                                    struct imx_pin_group *grp,
                                    struct imx_pinctrl_soc_info *info,
                                    u32 index)
{
    int size, pin_size;
    const __be32 *list;
    int i;
    u32 config;
......

    for (i = 0; i < grp->npins; i++) {
        u32 mux_reg = be32_to_cpu(*list++);
        u32 conf_reg;
        unsigned int pin_id;
        struct imx_pin_reg *pin_reg;
        struct imx_pin *pin = &grp->pins[i];

......

        pin_id = (mux_reg != -1) ? mux_reg / 4 : conf_reg / 4;
        pin_reg = &info->pin_regs[pin_id];
        pin->pin = pin_id;
        grp->pin_ids[i] = pin_id;
        pin_reg->mux_reg = mux_reg;
        pin_reg->conf_reg = conf_reg;
        pin->input_reg = be32_to_cpu(*list++);
        pin->mux_mode = be32_to_cpu(*list++);
        pin->input_val = be32_to_cpu(*list++);

        /* SION bit is in mux register */
        config = be32_to_cpu(*list++);
        if (config & IMX_PAD_SION)
            pin->mux_mode |= IOMUXC_CONFIG_SION;
        pin->config = config & ~IMX_PAD_SION;
......
    }

    return 0;
}

第 5、6 行:设备树中的 mux_reg 和 conf_reg 值会保存在 info 参数中,input_reg、mux_mode、input_val 和 config 值会保存在 grp 参数中。
第 28~32 行:获取 mux_reg、conf_reg、input_reg、mux_mode 和 input_val 值。
第 38 行:获取 config 值。

②接下来看一下函数 pinctrl_register,此函数用于向 Linux 内核注册一个 PIN 控制器,此函数原型如下:

struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc, 
                                     struct device       *dev, 
                                     void                *driver_data)

参数 pctldesc 非常重要,因为此参数就是要注册的 PIN 控制器,PIN 控制器用于配置 SOC 的 PIN 复用功能和电气特性。参数 pctldesc 是 pinctrl_desc 结构体类型指针,pinctrl_desc 结构体如下所示:

struct pinctrl_desc {
    const char *name;
    struct pinctrl_pin_desc const *pins;
    unsigned int npins;
    const struct pinctrl_ops *pctlops;
    const struct pinmux_ops *pmxops;
    const struct pinconf_ops *confops;
    struct module *owner;
#ifdef CONFIG_GENERIC_PINCONF
    unsigned int num_custom_params;
    const struct pinconf_generic_params *custom_params;
    const struct pin_config_item *custom_conf_items;
#endif
};

第 5~7 行:这三个“_ops”结构体指针非常重要!因为这三个结构体就是 PIN 控制器的“工具”,这三个结构体里面包含了很多操作函数,通过这些操作函数就可以完成对某一个 PIN 的配置。pinctrl_desc 结构体和结构体里面的成员变量由半导体厂商编写,无需用户关心。

比如在 imx_pinctrl_probe 函数中可以找到如下所示代码:

int imx_pinctrl_probe(struct platform_device *pdev,
                      struct imx_pinctrl_soc_info *info)
{
    struct device_node *dev_np = pdev->dev.of_node;
    struct device_node *np;
    struct imx_pinctrl *ipctl;
    struct resource *res;
    struct pinctrl_desc *imx_pinctrl_desc;
......

    imx_pinctrl_desc = devm_kzalloc(&pdev->dev,
    sizeof(*imx_pinctrl_desc),
    GFP_KERNEL);
    if (!imx_pinctrl_desc)
        return -ENOMEM;
......

    imx_pinctrl_desc->name = dev_name(&pdev->dev);
    imx_pinctrl_desc->pins = info->pins;
    imx_pinctrl_desc->npins = info->npins;
    imx_pinctrl_desc->pctlops = &imx_pctrl_ops;
    imx_pinctrl_desc->pmxops = &imx_pmx_ops;
    imx_pinctrl_desc->confops = &imx_pinconf_ops;
    imx_pinctrl_desc->owner = THIS_MODULE;
......
    ipctl->pctl = pinctrl_register(imx_pinctrl_desc, &pdev->dev, ipctl);
......
}

第 8 行:定义结构体指针变量 imx_pinctrl_desc。
第 11 行:向指针变量 imx_pinctrl_desc 分配内存。
第 18~24 行:初始化 imx_pinctrl_desc 结构体指针变量,重点是 pctlops、pmxops 和 confops 这三个成员变量,分别对应 imx_pctrl_ops、imx_pmx_ops 和 imx_pinconf_ops 这三个结构体。
第 26 行:调用 pinctrl_register 向 Linux 内核注册imx_pinctrl_desc,注册以后 Linux 内核就有了对 I.MX6ULL 的 PIN 进行配置的工具。

imx_pctrl_ops、imx_pmx_ops 和 imx_pinconf_ops 这三个结构体定义如下:

static const struct pinctrl_ops imx_pctrl_ops = {
    .get_groups_count = imx_get_groups_count,
    .get_group_name = imx_get_group_name,
    .get_group_pins = imx_get_group_pins,
    .pin_dbg_show = imx_pin_dbg_show,
    .dt_node_to_map = imx_dt_node_to_map,
    .dt_free_map = imx_dt_free_map,

};
......
static const struct pinmux_ops imx_pmx_ops = {
    .get_functions_count = imx_pmx_get_funcs_count,
    .get_function_name = imx_pmx_get_func_name,
    .get_function_groups = imx_pmx_get_groups,
    .set_mux = imx_pmx_set,
    .gpio_request_enable = imx_pmx_gpio_request_enable,
    .gpio_set_direction = imx_pmx_gpio_set_direction,
};
......
static const struct pinconf_ops imx_pinconf_ops = {
    .pin_config_get = imx_pinconf_get,
    .pin_config_set = imx_pinconf_set,
    .pin_config_dbg_show = imx_pinconf_dbg_show,
    .pin_config_group_dbg_show = imx_pinconf_group_dbg_show,
};

③第一步解析设备树时系统将读取到的 mux_reg、conf_reg、input_reg、mux_mode、input_val 和 config 等值保存了起来,然后向系统注册了一系列操作函数,Linux 内核最终就是通过保存的参数值和操作函数去初始化各个引脚。

3、设备树中添加 pinctrl 节点模板

关于 I.MX 系列 SOC 的 pinctrl 设备树绑定信息可以参考文档
Documentation/devicetree/bindings/pinctrl/fsl,imx-pinctrl.txt。这里我们虚拟一个名为“test”的设备,test 使用了 GPIO1_IO00 这个 PIN 的 GPIO 功能,pinctrl 节点添加过程如下:

1、创建对应的节点

同一个外设的 PIN 都放到一个节点里,打开 imx6ull-alientek-emmc.dts,在 iomuxc 节点中的“imx6ul-evk”子节点下添加“pinctrl_test”节点,注意!节点前缀一定要为“pinctrl_”。添加完成以后如下所示:

pinctrl_test: testgrp {
    /* 具体的 PIN 信息 */
};

2、添加“fsl,pins”属性

设备树是通过属性来保存信息的,因此我们需要添加一个属性,属性名字一定要为“fsl,pins”,因为对于 I.MX 系列 SOC 而言,pinctrl 驱动程序是通过读取“fsl,pins”属性值来获取 PIN 的配置信息,完成以后如下所示:

pinctrl_test: testgrp {
    fsl,pins = <
        /* 设备所使用的 PIN 配置信息 */
    >;
};

3、在“fsl,pins”属性中添加 PIN 配置信息

最后在“fsl,pins”属性中添加具体的 PIN 配置信息,完成以后如下所示:

pinctrl_test: testgrp {
    fsl,pins = <
        MX6UL_PAD_GPIO1_IO00__GPIO1_IO00 config /*config 是具体设置值*/
    >;
};

至此,已经在 imx6ull-alientek-emmc.dts 文件中添加好了 test 设备所使用的 PIN 配置信息。

二、I.MX6ULL 的 gpio 子系统驱动

1、设备树中的 gpio 信息

UART1_RTS_B 做为 SD 卡的检测引脚,UART1_RTS_B 复用为 GPIO1_IO19,以此引脚为例。
打开imx6ull-alientek-emmc.dts,UART1_RTS_B 这个 PIN 的 pincrtl 设置如下:

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

第 3 行:设置 UART1_RTS_B 这个 PIN 为 GPIO1_IO19。

pinctrl 配置好以后就是设置 gpio 了,在设备树中 SD 卡节点下添加一个属性来描述 SD 卡的 CD 引脚就行了,SD 卡驱动直接读取这个属性值就知道 SD 卡的 CD 引脚使用的是哪个 GPIO 。SD 卡连接在 I.MX6ULL 的 usdhc1 接口上,在 imx6ull-alientek-emmc.dts 中找到名为“usdhc1”的节点,这个节点就是 SD 卡设备节点,如下所示:

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

第 6 行:此行原来没有,是添加的,usdhc1 节点作为 SD 卡设备总节点,usdhc1 节点需要描述 SD 卡所有的信息,因为驱动要使用。本行就是描述 SD 卡的 CD 引脚 pinctrl 信息所在的子节点,因为 SD 卡驱动需要根据 pincrtl 节点信息来设置 CD 引脚的复用功能等。

第 3~5 行:pinctrl-0~2 是 SD 卡其他 PIN 的 pincrtl 节点信息。
在 usdhc1 节点中实际“pinctrl-3 = <&pinctrl_hog_1>”这一行已经注释掉了,也就是说并没有指定 CD 引脚的 pinctrl 信息,原因是“iomuxc”节点引用了 pinctrl_hog_1 这个节点,所以 Linux 内核中的 Pinctrl(即iomuxc) 驱动就会自动初始化 pinctrl_hog_1 节点下的所有 PIN 引脚。

第 7 行:属性“cd-gpios”描述了 SD 卡的 CD 引脚使用的哪个 IO。属性值一共有三个,我们来看一下这三个属性值的含义,“&gpio1”表示 CD 引脚所使用的 IO 属于 GPIO1 组,“19”表示 GPIO1 组的第 19 号 IO,通过这两个值 SD 卡驱动程序就知道 CD 引脚使用了 GPIO1_IO19 这 GPIO。“GPIO_ACTIVE_LOW”表示低电平有效,如果改为“GPIO_ACTIVE_HIGH”就表示高电平有效(使用方式查看下面标红字段)。根据上面这些信息,SD 卡驱动程序就可以使用 GPIO1_IO19 来检测 SD 卡的 CD 信号了。

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

gpio1 节点信息描述了 GPIO1 控制器的所有信息,重点就是 GPIO1 外设寄存器基地址以及兼容属性 。 关于 I.MX 系列 SOC 的 GPIO 控制器绑定信息请查看文档
Documentation/devicetree/bindings/gpio/ fsl-imx-gpio.txt。
第 2 行:设置 gpio1 节点的 compatible 属性有两个,“fsl,imx6ul-gpio” 和 “fsl,imx35-gpio”,在 Linux 内核中搜索这两个字符串就可以找到 I.MX6UL 的 GPIO 驱动程序。
第 3 行:reg 属性设置GPIO1控制器的寄存器基地址为0X0209C000。
第 6 行:“gpio-controller”表示 gpio1 节点是个 GPIO 控制器。
第 7 行:“#gpio-cells”属性和“#address-cells”类似,#gpio-cells 应该为 2,表示一共有两个 cell,第一个 cell 为 GPIO 编号,比如“&gpio1 3”就表示 GPIO1_IO03。第二个 cell 表示 GPIO 极性,如果为 0(GPIO_ACTIVE_HIGH) 的话表示高电平有效,如果为 1(GPIO_ACTIVE_LOW)的话表示低电平有效。

2、GPIO 驱动程序简介

gpio1 节点的 compatible 属性描述了兼容性,搜索 “fsl,imx6ul-gpio” 和 “fsl,imx35-gpio” 这两个字符串,查找 GPIO 驱动文件。

drivers/gpio/gpio-mxc.c 就是 I.MX6ULL 的 GPIO 驱动文件,在此文件中有如下所示 of_device_id 匹配表:

static const struct of_device_id mxc_gpio_dt_ids[] = {
    { .compatible = "fsl,imx1-gpio", .data =
      &mxc_gpio_devtype[IMX1_GPIO], },
    { .compatible = "fsl,imx21-gpio", .data =
      &mxc_gpio_devtype[IMX21_GPIO], },
    { .compatible = "fsl,imx31-gpio", .data =
      &mxc_gpio_devtype[IMX31_GPIO], },
    { .compatible = "fsl,imx35-gpio", .data =
      &mxc_gpio_devtype[IMX35_GPIO], },
    { /* sentinel */ }
};

第 8 行:compatible 值为“fsl,imx35-gpio”,和 gpio1 的 compatible 属性匹配,因此 gpio-mxc.c 就是 I.MX6ULL 的 GPIO 控制器驱动文件。
gpio-mxc.c 所在的目录为 drivers/gpio,打开这个目录可以看到很多芯片的 gpio 驱动文件,“gpiolib”开始的文件是 gpio 驱动的核心文件,如图所示:
image
重点看 gpio-mxc.c 这个文件,在 gpio-mxc.c 文件中有如下所示内容:

static struct platform_driver mxc_gpio_driver = {
    .driver = {
        .name = "gpio-mxc",
        .of_match_table = mxc_gpio_dt_ids,
    },
    .probe = mxc_gpio_probe,
    .id_table = mxc_gpio_devtype,
};

可以看出 GPIO 驱动也是个平台设备驱动,因此当设备树中的设备节点与驱动的
of_device_id 匹配以后 probe 函数就会执行,即 mxc_gpio_probe 函数,这个函数就是 I.MX6ULL 的 GPIO 驱动入口函数。函数内容如下:

static int mxc_gpio_probe(struct platform_device *pdev)
{
    struct device_node *np = pdev->dev.of_node;
    struct mxc_gpio_port *port;
    struct resource *iores;
    int irq_base;
    int err;

    mxc_gpio_get_hw(pdev);

    port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL);
    if (!port)
        return -ENOMEM;

    iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    port->base = devm_ioremap_resource(&pdev->dev, iores);
    if (IS_ERR(port->base))
    return PTR_ERR(port->base);

    port->irq_high = platform_get_irq(pdev, 1);
    port->irq = platform_get_irq(pdev, 0);
    if (port->irq < 0)
        return port->irq;

    /* disable the interrupt and clear the status */
    writel(0, port->base + GPIO_IMR);
    writel(~0, port->base + GPIO_ISR);

    if (mxc_gpio_hwtype == IMX21_GPIO) {
        /*
         * Setup one handler for all GPIO interrupts. Actually
         * setting the handler is needed only once, but doing it for
         * every port is more robust and easier.
         */
        irq_set_chained_handler(port->irq, mx2_gpio_irq_handler);
    } else {
        /* setup one handler for each entry */
        irq_set_chained_handler(port->irq, mx3_gpio_irq_handler);
        irq_set_handler_data(port->irq, port);
        if (port->irq_high > 0) {
            /* setup handler for GPIO 16 to 31 */
            irq_set_chained_handler(port->irq_high,
            mx3_gpio_irq_handler);
            irq_set_handler_data(port->irq_high, port);
        }
    }

    err = bgpio_init(&port->bgc, &pdev->dev, 4,
                     port->base + GPIO_PSR,
                     port->base + GPIO_DR, NULL,
                     port->base + GPIO_GDIR, NULL, 0);
    if (err)
        goto out_bgio;

    port->bgc.gc.to_irq = mxc_gpio_to_irq;
    port->bgc.gc.base = (pdev->id < 0) ? of_alias_get_id(np, "gpio") * 32 : pdev->id * 32;

    err = gpiochip_add(&port->bgc.gc);
    if (err)
        goto out_bgpio_remove;

    irq_base = irq_alloc_descs(-1, 0, 32, numa_node_id());
    if (irq_base < 0) {
        err = irq_base;
        goto out_gpiochip_remove;
    }

    port->domain = irq_domain_add_legacy(np, 32, irq_base, 0,
                                         &irq_domain_simple_ops, NULL);
    if (!port->domain) {
        err = -ENODEV;
        goto out_irqdesc_free;
    }

    /* gpio-mxc can be a generic irq chip */
    mxc_gpio_init_gc(port, irq_base);

    list_add_tail(&port->node, &mxc_gpio_ports);

    return 0;
......
}

第 3 行:设备树节点指针。
第 4 行:定义一个结构体指针 port,结构体类型为 mxc_gpio_port。gpio-mxc.c 的重点工作就是维护 mxc_gpio_port。mxc_gpio_port 就是对 I.MX6ULL GPIO 的抽象,结构体定义如下:

struct mxc_gpio_port {
    struct list_head node;
    void __iomem *base;
    int irq;
    int irq_high;
    struct irq_domain *domain;
    struct bgpio_chip bgc;
    u32 both_edges;
};

mxc_gpio_port 的 bgc 成员变量很重要,因为稍后的重点就是初始化 bgc。
第 9 行:调用 mxc_gpio_get_hw 函数获取 gpio 的硬件相关数据,其实就是 gpio 的寄存器组,函数 mxc_gpio_get_hw 里面有如下代码:

static void mxc_gpio_get_hw(struct platform_device *pdev)
{
    const struct of_device_id *of_id =
            of_match_device(mxc_gpio_dt_ids, &pdev->dev);
    enum mxc_gpio_hwtype hwtype;
......

    if (hwtype == IMX35_GPIO)
        mxc_gpio_hwdata = &imx35_gpio_hwdata;
    else if (hwtype == IMX31_GPIO)
        mxc_gpio_hwdata = &imx31_gpio_hwdata;
    else
        mxc_gpio_hwdata = &imx1_imx21_gpio_hwdata;

    mxc_gpio_hwtype = hwtype;
}

mxc_gpio_hwdata 是个全局变量,如果硬件类型是 IMX35_GPIO 的话设置
mxc_gpio_hwdat 为 imx35_gpio_hwdata。对于 I.MX6ULL 而言,硬件类型就是 IMX35_GPIO,imx35_gpio_hwdata 是个结构体变量,描述了 GPIO 寄存器组,内容如下:

static struct mxc_gpio_hwdata imx35_gpio_hwdata = {
    .dr_reg = 0x00,
    .gdir_reg = 0x04,
    .psr_reg = 0x08,
    .icr1_reg = 0x0c,
    .icr2_reg = 0x10,
    .imr_reg = 0x14,
    .isr_reg = 0x18,
    .edge_sel_reg = 0x1c,
    .low_level = 0x00,
    .high_level = 0x01,
    .rise_edge = 0x02,
    .fall_edge = 0x03,
};

imx35_gpio_hwdata 结构体就是 GPIO 寄存器组,通过 mxc_gpio_hwdata 这个全局变量就可以访问 GPIO 的相应寄存器。
第 15 行:调用函数 platform_get_resource 获取设备树中内存资源信息,也就是 reg 属性值。前面说了 reg 属性指定了 GPIO1 控制器的寄存器基地址为 0X0209C000,在配合前面已经得到的 mxc_gpio_hwdata,这样 Linux 内核就可以访问 gpio1 的所有寄存器了。
第 16 行:调用 devm_ioremap_resource 函数进行内存映射,得到 0x0209C000 在 Linux 内核中的虚拟地址。
第 20、21 行:通过 platform_get_irq 函数获取中断号,第 20 行获取高 16 位 GPIO 的中断号,第 21 行获取底 16 位 GPIO 中断号。
第 26、27 行:操作 GPIO1 的 IMR 和 ISR 这两个寄存器,关闭 GPIO1 所有 IO 中断,并且清除状态寄存器。
第 36~46 行:设置对应 GPIO 的中断服务函数,不管是高 16 位还是低 16 位,中断服务函数都是 mx3_gpio_irq_handler。
第 48~51 行:bgpio_init 函数第一个参数为 bgc,是 bgpio_chip 结构体指针。bgpio_chip 结构体有个 gc 成员变量,gc 是个 gpio_chip 结构体类型的变量。gpio_chip 结构体是抽象出来的GPIO 控制器,结构体如下所示(有缩减):

struct gpio_chip {
    const char *label;
    struct device *dev;
    struct module *owner;
    struct list_head list;

    int (*request)(struct gpio_chip *chip, unsigned offset);
    void (*free)(struct gpio_chip *chip, unsigned offset);
    int (*get_direction)(struct gpio_chip *chip, unsigned offset);
    int (*direction_input)(struct gpio_chip *chip, unsigned offset);
    int (*direction_output)(struct gpio_chip *chip, unsigned offset, int value);
    int (*get)(struct gpio_chip *chip, unsigned offset);
    void (*set)(struct gpio_chip *chip, unsigned offset, int value);
......
};

gpio_chip 大量的成员都是函数,这些函数就是 GPIO 操作函数。bgpio_init 函数主要任务就是初始化 bgc->gc 。 bgpio_init 里面有三个 setup 函数: bgpio_setup_io 、bgpio_setup_accessors 和 bgpio_setup_direction。这三个函数就是初始化 bgc->gc 中的各种有关GPIO 的操作,比如输出,输入等等。第 49~51 行:GPIO_PSR、GPIO_DR 和 GPIO_GDIR 都是 I.MX6ULL 的 GPIO 寄存器。这些寄存器地址会赋值给 bgc 参数的 reg_dat、reg_set、reg_clr和 reg_dir 这些成员变量。至此,bgc 既有了对GPIO的操作函数,又有了 I.MX6ULL 有关 GPIO 的寄存器,只要得到 bgc 就可以对 I.MX6ULL 的 GPIO 进行操作。
第 58 行:调用函数gpiochip_add向Linux内核注册gpio_chip,也就是 port->bgc.gc。注册完成以后我们就可以在驱动中使用 gpiolib 提供的各个 API 函数。

3、gpio 子系统 API 函数

设置好设备树以后就可以使用 gpio 子系统提供的 API 函数来操作指定的GPIO,gpio 子系统向驱动开发人员屏蔽了具体的读写寄存器过程,提供响应的API操作函数。常用的 API 函数有下面几个:

1、gpio_request 函数

gpio_request 函数用于申请一个 GPIO 管脚,在使用一个 GPIO 之前一定要使用 gpio_request进行申请,函数原型如下:

int gpio_request(unsigned gpio, const char *label)

函数参数和返回值含义如下:

gpio:要申请的 gpio 标号,使用 of_get_named_gpio 函数从设备树获取指定 GPIO 属性信息,此函数会返回这个 GPIO 的标号。
label:给 gpio 设置个名字。
返回值:0,申请成功;其他值,申请失败。

2、gpio_free 函数

如果不使用某个 GPIO 了,那么就可以调用 gpio_free 函数进行释放。函数原型如下:

void gpio_free(unsigned gpio)

函数参数和返回值含义如下:

gpio:要释放的 gpio 标号。
返回值:无。

3、gpio_direction_input 函数

此函数用于设置某个 GPIO 为输入,函数原型如下所示:

int gpio_direction_input(unsigned gpio)

函数参数和返回值含义如下:

gpio:要设置为输入的 GPIO 标号。
返回值:0,设置成功;负值,设置失败。

4、gpio_direction_output 函数

此函数用于设置某个 GPIO 为输出,并且设置默认输出值,函数原型如下:

int gpio_direction_output(unsigned gpio, int value)

函数参数和返回值含义如下:

gpio:要设置为输出的 GPIO 标号。
value:GPIO 默认输出值。
返回值:0,设置成功;负值,设置失败。

5、gpio_get_value 函数

此函数用于获取某个 GPIO 的值(0 或 1),此函数是个宏,定义所示:

#define gpio_get_value __gpio_get_value
int __gpio_get_value(unsigned gpio)

函数参数和返回值含义如下:

gpio:要获取的 GPIO 标号。
返回值:非负值,得到的 GPIO 值;负值,获取失败。

6、gpio_set_value 函数

此函数用于设置某个 GPIO 的值,此函数是个宏,定义如下:

#define gpio_set_value __gpio_set_value
void __gpio_set_value(unsigned gpio, int value)

函数参数和返回值含义如下:

gpio:要设置的 GPIO 标号。
value:要设置的值。
返回值:无

4、设备树中添加 gpio 节点模板

1、创建 test 设备节点

在根节点“/”下创建 test 设备子节点,如下所示:

test {
    /* 节点内容 */
};

2、添加 pinctrl 信息

分析 Pinctrl 时创建了 pinctrl_test 节点,节点描述了 test 设备使用的 GPIO1_IO00 这个 PIN 的信息,要将这节点添加到 test 设备节点中,如下所示:

test {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_test>;
    /* 其他节点内容 */
};

第 2 行:添加 pinctrl-names 属性,此属性描述 pinctrl 名字为“default”。
第 3 行:添加 pinctrl-0 节点,此节点引用 45.1.3 中创建的 pinctrl_test 节点,表示 tset 设备的所使用的 PIN 信息保存在 pinctrl_test 节点中。

3、添加 GPIO 属性信息

最后需要在 test 节点中添加 GPIO 属性信息,表明 test 所使用的 GPIO 是哪个引脚,添加完成以后如下所示:

test {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_test>;
    gpio = <&gpio1 0 GPIO_ACTIVE_LOW>;
};

第 4 行:test 设备所使用的 gpio。

5、与 gpio 相关的 OF 函数

在上面分析了如何添加 GPIO 节点,定义了一个名为“gpio”的属性,gpio 属性描述了 test 这个设备所使用的 GPIO。在驱动程序中需要读取 gpio 属性内容,Linux 内核提供了几个与 GPIO 有关的 OF 函数,常用的几个 OF 函数如下所示:

1、of_gpio_named_count 函数

of_gpio_named_count 函数用于获取设备树某个属性里面定义了几个 GPIO 信息,要注意的是空的 GPIO 信息也会被统计到,比如:

gpios = <0
         &gpio1 1 2
         0
         &gpio2 3 4>;

上述代码的“gpios”节点一共定义了 4 个 GPIO,但是有 2 个是空的,没有实际的含义。通过 of_gpio_named_count 函数统计出来的 GPIO 数量就是 4 个,此函数原型如下:

int of_gpio_named_count(struct device_node *np, const char *propname)

函数参数和返回值含义如下:

np:设备节点。
propname:要统计的 GPIO 属性。
返回值:正值,统计到的 GPIO 数量;负值,失败。

2、of_gpio_count 函数

和 of_gpio_named_count 函数一样,但是不同的地方在于,此函数统计的是“gpios”这个属性的 GPIO 数量,而 of_gpio_named_count 函数可以统计任意属性的 GPIO 信息,函数原型如下所示:

int of_gpio_count(struct device_node *np)

函数参数和返回值含义如下:

np:设备节点。
返回值:正值,统计到的 GPIO 数量;负值,失败。

3、of_get_named_gpio 函数

此函数获取 GPIO 编号,因为 Linux 内核中关于 GPIO 的 API 函数都要使用 GPIO 编号,此函数会将设备树中类似<&gpio5 7 GPIO_ACTIVE_LOW>的属性信息转换为对应的 GPIO 编号,此函数在驱动中使用很频繁!函数原型如下:

int of_get_named_gpio(struct device_node *np,
                      const char         *propname,
                      int                index)

函数参数和返回值含义如下:

np:设备节点。
propname:包含要获取 GPIO 信息的属性名。
index:GPIO 索引,因为一个属性里面可能包含多个 GPIO,此参数指定要获取哪个 GPIO的编号,如果只有一个 GPIO 信息的话此参数为 0。
返回值:正值,获取到的 GPIO 编号;负值,失败。

标签:GPIO,imx,pinctrl,Pinctrl,驱动,gpio,reg,struct
From: https://www.cnblogs.com/KuDianWanJia/p/17133110.html

相关文章

  • 【IMX6ULL学习笔记】十八、Platform 驱动框架
    一、platform驱动Linux提出了platform这个虚拟总线,相应的就有platform_driver和platform_device。platform驱动使用platform_driver结构体表示,此结构体定义在......
  • 【IMX6ULL学习笔记】十七、总线驱动框架-Platform、IIC、SPI等
    一、总线Linux总线驱动模型主要可以分为三个部分:总线、设备、驱动。Linux中的总线(bus)、驱动(driver)和设备(device)模型,也就是常说的驱动分离。Linux内核在启动时会向......
  • Android增加USB Camera摄像头驱动支持
    一般情况下kernel需要添加以下宏=================================CONFIG_VIDEO_DEV=yCONFIG_VIDEOBUF2_CORE=yCONFIG_VIDEOBUF2_VMALLOC=yCONFIG_MEDIA_USB_SUPPORT=yC......
  • 【IMX6ULL学习笔记】十六、设备树下LED驱动
    一、修改设备树文件在根节点“/”下创建一个名为“alphaled”的子节点,打开imx6ull-alientek-emmc.dts文件,在根节点“/”最后面输入如下所示内容:alphaled{#address-ce......
  • mysq联表查询优化:小表驱动大表
     --todo   https://blog.csdn.net/zy_whynot/article/details/121608851?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Ede......
  • stm32 gpio外部中断
    使用过外部中断可知,中断模式可以选择上升沿触发或者下降沿触发,或者双边沿触发,例如下降沿触发,引脚由高电平变到低电平,进入外部中断,只要外部中断中的逻辑判断没有while循环导......
  • 字符设备驱动编写流程
    1什么是字符设备?字符(char)设备是能够像字节流(类似文件)一样被访问的设备,由字符设备驱动来实现这种特性。字符设备驱动程序通常至少要实现open、close、read和write系统......
  • 接口自动化测试思路和实战(5):【推荐】混合测试自动化框架(关键字+数据驱动)
    混合测试自动化框架(关键字+数据驱动)关键字驱动或表驱动的测试框架这个框架需要开发数据表和关键字。这些数据表和关键字独立于执行它们的测试自动化工具,并可以用来......
  • 接口自动化测试思路和实战(4):数据驱动测试框架
    数据驱动测试框架在这里测试的输入和输出数据是从数据文件中读取(数据池,ODBC源,CSV文件,EXCEL文件,Json文件,Yaml文件,ADO对象等)并且通过捕获工具生成或者手工生成的代码脚......
  • jmeter-sqlite-DDT数据驱动
    1、下载数据驱动sqlite的jar包,放到jmeter的lib文件夹,重启jmeter  2、创建连接pool:-线程池必填url:jdbc:sqlite:自定义一个dbfile文件名.dbclass:org.sqlit......