【IMX6ULL学习笔记】十九、Pinctrl、GPIO驱动驱动框架
一、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 如图下所示:
宏定义 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 配置入口函数。
函数调用路径如图所示:
①图中函数 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 = <®_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 驱动的核心文件,如图所示:
重点看 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 编号;负值,失败。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 提示词工程——AI应用必不可少的技术
· 地球OL攻略 —— 某应届生求职总结
· 字符编码:从基础到乱码解决
· SpringCloud带你走进微服务的世界