pinctl与gpio子系统(正点原子笔记)

L inux 内核针对 PIN 的配置推出了 pinctrl 子系统,对于 GPIO的配置推出了 gpio 子系统.

pinctrl子系统

作用就是设置 PIN(有的 SOC 叫做 PAD)的复用和电气属性。

大多数 SOC 的 pin 都是支持复用的,比如 I.MX6ULL 的 GPIO1_IO03 既可以作为普通的GPIO 使用,也可以作为 I2C1 的 SDA 等等。此外我们还需要配置 pin 的电气特性,比如上/下拉、速度、驱动能力等等。传统的配置 pin 的方式就是直接操作相应的寄存器,但是这种配置方式比较繁琐、而且容易出问题(比如 pin 功能冲突)。pinctrl 子系统就是为了解决这个问题而引入的,pinctrl 子系统主要工作内容如下:
①、获取设备树中 pin 信息。
②、根据获取到的 pin 信息来设置 pin 的复用功能
③、根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等。

PIN 配置信息详解

8 fsl,pins = <
9 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
10 MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059
11 MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059
12 MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x13058
13 >;

对于一个 PIN 的配置主要包括两方面,一个是设置这个 PIN 的复用功能,另一个就是设置这个 PIN 的电气特性。查看定义:

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

0x0090 0x031C 0x0000 0x5 0x0,这 5 个值的含义如下所示:

0x0090:mux_reg 寄存器偏移地址;dtsi中的 iomuxc 节点就是 IOMUXC 外设对应的节点 , 根 据 其 reg 属 性 可 知 IOMUXC 外 设 寄 存 器 起 始 地 址 为 0x020e0000 。 因 此
0x020e0000+0x0090=0x020e0090。

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

0x0000:input_reg 寄存器偏移地址,有 input_reg 寄存器的外设需要配置 input_reg 寄存器。没有的话就不需要设置。

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

注意上面没有config_reg的值,因此0x17059 就是他的值,此值由用户自行设置,通过此值来设置一个 IO 的上/下拉、驱动能力和速度等。

PIN 驱动程序讲解

pinctl与gpio子系统(正点原子笔记)_第1张图片

 pinctl与gpio子系统(正点原子笔记)_第2张图片

struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc,struct device *dev,
                                     void *driver_data)
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
};

设备树中添加 pinctrl 节点模板

1.创建对应的节点

在 iomuxc 节点中的“imx6ul-evk”子节点下添加“pinctrl_test”节点:

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

2.添加“fsl,pins”属性

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

pinctrl驱动程序是通过读取取“fsl,pins”属性值来获取 PIN 的配置信息,

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

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

gpio 子系统

如果 pinctrl 子系统将一个 PIN 复用为 GPIO 的话,那么接下来就要用到 gpio 子系统了。gpio 子系统顾名思义,就是用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO为输入输出,读取 GPIO 的值等。gpio 子系统的主要目的就是方便驱动开发者使用 gpio,驱动开发者在设备树中添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API函数来操作 GPIO。

1.设备树中的gpio信息

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

pinctrl 配置好以后就是设置 gpio 了,SD 卡驱动程序通过读取 GPIO1_IO19 的值来判断 SD卡有没有插入,但是 SD 卡驱动程序怎么知道 CD 引脚连接的 GPIO1_IO19 呢?肯定是需要设备树告诉驱动啊!在设备树中 SD 卡节点下添加一个属性来描述 SD 卡的 CD 引脚就行了,SD卡驱动直接读取这个属性值就知道 SD 卡的 CD 引脚使用的是哪个 GPIO 了。SD 卡连接在I.MX6ULL 的 usdhc1 接口上,在 imx6ull-alientek-emmc.dts 中找到名为“usdhc1”的节点,这个节点就是 SD 卡设备节点,如下所示:

760 &usdhc1 {
761 pinctrl-names = "default", "state_100mhz", "state_200mhz";
762 pinctrl-0 = <&pinctrl_usdhc1>;
763 pinctrl-1 = <&pinctrl_usdhc1_100mhz>;
764 pinctrl-2 = <&pinctrl_usdhc1_200mhz>;
765 /* pinctrl-3 = <&pinctrl_hog_1>; */
766 cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
767 keep-power-in-suspend;
768 enable-sdio-wakeup;
769 vmmc-supply = <®_sd1_vmmc>;
770 status = "okay";
771 };

第 766 行,属性“cd-gpios”描述了 SD 卡的 CD 引脚使用的哪个 IO。属性值一共有三个,“&gpio1”表示 CD 引脚所使用的 IO 属于 GPIO1 组,“19”表示 GPIO1 组的第 19 号 IO。

根据上面这些信息,SD 卡驱动程序就可以使用 GPIO1_IO19 来检测 SD 卡的 CD 信号了

504 gpio1: gpio@0209c000 {
505 compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
506 reg = <0x0209c000 0x4000>;
507 interrupts = ,
508 ;
509 gpio-controller;
510 #gpio-cells = <2>;
511 interrupt-controller;
512 #interrupt-cells = <2>;
513 };

#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 子系统 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 设置个名字。

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 默认输出值。

5、gpio_get_value 函数

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

#define gpio_get_value __gpio_get_value
int __gpio_get_value(unsigned 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:要设置的值。

设备树中添加 gpio 节点模板

1.创建 test 设备节点

在根节点“/”下创建 test 设备子节点:

1 test {
2 /* 节点内容 */
3 };

2.添加 pinctrl 信息

此节点描述了 test 设备所使用的 GPIO1_IO00 这个 PIN 的信息,我们要将这节点添加到 test 设备节点中:

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

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

3、添加 GPIO 属性信息

表明 test 所使用的 GPIO 是哪个引脚,添加完成以后如下图所示

1 test {
2 pinctrl-names = "default";
3 pinctrl-0 = <&pinctrl_test>;
4 gpio = <&gpio1 0 GPIO_ACTIVE_LOW>;
5 };

与 gpio 相关的 OF 函数

1、of_gpio_named_count 函数

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

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

#np:设备节点。
#propname:要统计的 GPIO 属性。

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

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

2、of_gpio_count 函数

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

int of_gpio_count(struct device_node *np)

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 编号;负值,失败。

实战练习

1、添加 pinctrl 节点

1 pinctrl_led: ledgrp {
2 fsl,pins = <
3 MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED0 */
4 >;
5 };

2、添加 LED 设备节点

在根节点“/”下创建 LED 灯节点,节点名为“gpioled”

1 gpioled {
2 #address-cells = <1>;
3 #size-cells = <1>;
4 compatible = "atkalpha-gpioled";
5 pinctrl-names = "default";
6 pinctrl-0 = <&pinctrl_led>;
7 led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
8 status = "okay";
9 };

第 6 行,pinctrl-0 属性设置 LED 灯所使用的 PIN 对应的 pinctrl 节点。
第 7 行,led-gpio 属性指定了 LED 灯所使用的 GPIO,在这里就是 GPIO1 的 IO03,低电平有效。

3、检查 PIN 是否被其他外设使用

①、检查 pinctrl 设置。

在实验中 LED 灯使用的 PIN 为 GPIO1_IO03,因此先检查 GPIO_IO03(MX6UL_PAD_GPIO1_IO03__GPIO1_IO03) 这个 PIN 有没有被其他的 pinctrl 节点使用,找到一个屏蔽一个。

②、如果这个 PIN 配置为 GPIO 的话,检查这个 GPIO 有没有被别的外设使用。

为实验将 GPIO1_IO03 这个 PIN 配置为了 GPIO,所以还需要查找一下有没有其他的外设使用了 GPIO1_IO03,在 imx6ull-alientek-emmc.dts 中搜索“gpio1 3”。找到一个屏蔽一个。

测试验证:

启动成功以后进入“/proc/device-tree”目录中,查看“gpioled”节点是否存在,如果存在的话就说明设备树基本修改成功

驱动编写

static int __init led_init(void)
{
 int ret = 0;

/* 设置 LED 所使用的 GPIO */
 /* 1、获取设备节点:gpioled */
 gpioled.nd = of_find_node_by_path("/gpioled");
 if(gpioled.nd == NULL) {
         printk("gpioled node cant not found!\r\n");
         return -EINVAL;
     } else {
         printk("gpioled node has been found!\r\n");
     }

    /* 2、 获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */
    gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
    if(gpioled.led_gpio < 0) {
        return -EINVAL;
    }
    printk("led-gpio num = %d\r\n", gpioled.led_gpio);

    /* 3、设置 GPIO1_IO03 为输出,并且输出高电平,默认关闭 LED 灯 */
    ret = gpio_direction_output(gpioled.led_gpio, 1);    
    if(ret < 0) {
        printk("can't set gpio!\r\n");
    }

/* 注册字符设备驱动 */
/* 1、创建设备号 */
if (gpioled.major) { /* 定义了设备号 */
    gpioled.devid = MKDEV(gpioled.major, 0);
    register_chrdev_region(gpioled.devid, GPIOLED_CNT,GPIOLED_NAME);
} else { /* 没有定义设备号 */
    alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME); /* 申请设备号 */
    gpioled.major = MAJOR(gpioled.devid); /* 获取分配号的主设备号 */
    gpioled.minor = MINOR(gpioled.devid); /* 获取分配号的次设备号 */
}
printk("gpioled major=%d,minor=%d\r\n",gpioled.major,gpioled.minor); 

/* 2、初始化 cdev */
 gpioled.cdev.owner = THIS_MODULE;
 cdev_init(&gpioled.cdev, &gpioled_fops);

/* 3、添加一个 cdev */
cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);

/* 4、创建类 */
 gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
if (IS_ERR(gpioled.class)) {
    return PTR_ERR(gpioled.class);
}

/* 5、创建设备 */
gpioled.device = device_create(gpioled.class, NULL,
gpioled.devid, NULL, GPIOLED_NAME);
if (IS_ERR(gpioled.device)) {
        return PTR_ERR(gpioled.device);
    }
    return 0;
 }

你可能感兴趣的:(linux)