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 个值的含义如下所示:
<mux_reg conf_reg input_reg mux_mode input_val>
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 驱动程序讲解
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 = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
508 <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
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;
}