前面我们使用设备树来驱动LED灯,其实就是将LED寄存器地址写入到设备树的属性reg中,通过OF函数 ,读取到LED灯的寄存器信息,从而操作寄存器来控制LED灯。在操作LED灯时候,我们使用到GPIO这个引脚,通过对这个GPIO的控制来实现LED灯的控制。
一、pinctrl子系统
pinctrl子系统,用来设置pin(引脚)的复用功能和配置这个pin的电气属性。主要体现:
- 获取设备树中pin信息。
- 根据获取到的pin信息来设备pin的复用功能。
- 根据获取到的pin信息来设备pin的电气属性,比如上/下拉、速度、驱动能力等。
对于我们来说,只需要在设备树里面设置好某个pin的相关属性,其他的初始化工作pinctrl子系统来完成,pinctrl子系统源码目录为drivers/pinctrl
pin配置信息详解
要使用 pinctrl 子系统,我们需要在设备树里面设置 PIN 的配置信息,毕竟 pinctrl 子系统要
根据你提供的信息来配置 PIN 功能,一般会在设备树里面创建一个节点来描述 PIN 的配置信
息。在imx6ull.dtsi中有者三个节点表示imx6ull的pin引脚:
iomuxc: iomuxc@020e0000 {
compatible = "fsl,imx6ul-iomuxc";
reg = <0x020e0000 0x4000>;
};
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的参考手册
在imx6ull-alientek-emmc.dts中,对pin进行追加信息比如:
不同的外设使用的 PIN 不同、其配置也不同,因此一个萝卜一个坑,将某个外设所使用的所有 PIN 都组织在一个子节点里面。其中,在iomux节点下有个imx6ul-evk的子结点,这个节点就是用来描述evk这个开发板外设所使用到的pin。简单来说就是在这个节点来表示出来evk开发板的各个功能使用到哪些引脚和引脚是哪一个。比如:hoggrp-1这个evk节点下面的子结点,表示热插拔功能所需要的引脚。
关于MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 含义:
这是一个宏定义,定义在文件arch/arm/boot/dts/imx6ul-pinfunc.h 中, imx6ull.dtsi 会引用 imx6ull-pinfunc.h 这个头文件,而imx6ull-pinfunc.h 又会引用 imx6ul-pinfunc.h 这个头文件,
在imx6ull参考手册中我们可以看出来MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 这个宏就是表示UART1_RTS_B这个引脚复用为GPIO1_IO19功能 。
<mux_reg conf_reg input_reg mux_mode input_val>
0x0090 0x031C 0x0000 0x5 0x0
0x0090:mux_reg寄存器偏移地址,复用功能寄存器地址。
0x031C: conf_reg 寄存器偏移地址,表示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 寄存器值,应为这个引脚没有input_reg寄存器,所以无法向里面写入值。
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059中的0x17059其实就是conf_reg寄存器的值,这个值由我们子设备,相当于配置这个引脚的电气属性。
设备树中添加pinctrl节点模板
关 于 I.MX 系 列 SOC 的 pinctrl 设 备 树 绑 定 信 息 可 以 参 考 文 档
Documentation/devicetree/bindings/pinctrl/fsl,imx-pinctrl.txt
二、gpio子系统
前面pinctrl子系统主要是设置引脚的复用功能和电气属性,如果 pinctrl 子系统将一个 PIN 复用为 GPIO 的话,那么接下来就要用到 gpio 子系统了。 gpio 子系统顾名思义,就是用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO为输入输出,读取 GPIO 的值等。 gpio 子系统的主要目的就是方便驱动开发者使用 gpio,驱动开发者在设备树中添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API函数来操作 GPIO, Linux 内核向驱动开发者屏蔽掉了 GPIO 的设置过程,极大的方便了驱动开发者使用 GPIO。
设备树中的gpio信息
比如我们在imx6ull-alientek-emmc.dts,有usdhc1这样一个节点:
pinctrl-name:用来表示设备的状态,这里有2个,分别为默认状态(default)和休眠状态(sleep)还可以自定义状态,比如state_100mhz:表示这个gpio运行为100mhz状态。
pinctrl-0:表示第0个状态对应于"default"状态,对应的引脚在pinctrl-0里面定义。
pinctrl-1:表示第1个状态对应于"state_100mhz"状态,对应的引脚在pinctrl-1里面定义。
cd-gpios:描述usdhc1使用到的IO引脚“&gpio1”表示 引脚所使用的 IO 属于 GPIO1 组,“19”表示 GPIO1 组的第 19 号 IO,“GPIO_ACTIVE_LOW”表示低电平有效,如果改为“GPIO_ACTIVE_HIGH”就表示高电平有效。
gpio子系统API函数
1、gpio_request函数
用于申请一个 GPIO 管脚,在使用一个 GPIO 之前一定要使用 gpio_request进行申请,定义在linux/gpio.h中函数原型如下:
static inline 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 函数进行释放。函数原型如下:
static inline void gpio_free(unsigned gpio)
gpio: 要释放的 gpio 标号。
3、gpio_direction_input、gpio_direction_output函数
设置某个 GPIO 为输入输出,函数原型如下所示:
static inline int gpio_direction_input(unsigned gpio)
static inline int gpio_direction_output(unsigned gpio, int value)
gpio: 要设置的 GPIO 标号。
value:设置为输出时,GPIO 默认输出值。
返回值:0,设置成功;负值,设置失败。
4、gpio_get_value、gpio_set_value函数
用于获取和设置某个 GPIO 的值(0 或 1),定义所示:
static inline int gpio_get_value(unsigned gpio)
static inline void gpio_set_value(unsigned gpio, int value)
gpio: 要设置的 GPIO 标号。
value: 要设置的值。
返回值:负值,获取失败,否者得到的GPIO值。
与gpio相关的OF函数
1、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 个,定义在linux/of_gpio.h里面,此函数原型如下:
static inline 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 信息,函数原型如下所示:
static inline 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 编
号,此函数在驱动中使用很频繁!函数原型如下:
static inline int of_get_named_gpio(struct device_node *np,
const char *propname,
int index)
np:设备节点。
propname:要统计的GPIO属性名字。
index:GPIO 索引,因为一个属性里面可能包含多个 GPIO,此参数指定要获取哪个 GPIO
的编号,如果只有一个 GPIO 信息的话此参数为 0。
返回值:正值,获取到的 GPIO 编号;负值,失败。
三、使用pinctrl子系统和gpio子系统
我们以正点原子阿尔法开发板为例,在设备树中添加LED灯的信息,通过pinctrl子系统和gpio子系统来配置LED的引脚,从而控制LED灯的开关。
1、修改设备树文件
①、添加pinctrl子结点
在原理图中,LED灯连接到了GPIO1_IO03这个引脚,所以我们的将这个引脚复用为GPIO1_IO03功能,并配置引脚的电气属性。
打开imx6ull-alientekemmc.dts,在 iomuxc 节点的 imx6ul-evk 子节点下创建一个名为“pinctrl_led”的子节点:
②、添加 LED 设备节点
在根节点“/”下创建 LED 灯节点,节点名为“gpioled”:
③、检查引脚是否被使用
我们所使用的设备树基本都是在半导体厂商提供的设备树文件基础上修改而来的,而半导体厂商提供的设备树是根据自己官方开发板编写的,很多 PIN 的配置和我们所使用的开发板不一样。检查 PIN 有没有被其他外设使用包括两个方面:
- 检查 pinctrl 设置。
- 如果这个 PIN 配置为 GPIO 的话,检查这个 GPIO 有没有被别的外设使用。
编译设备树,使用新编译的设备树文件,查看/proc/device-tree/是否存在我们添加的节点:
2、LED灯驱动程序编写
参考:五、(正点原子)设备树下的LED驱动-CSDN博客
当我们驱动框架搭建好以后,需要使用OF函数,获取设备树中LED节点的属性信息。
在LED控制函数中,我们就可以直接使用gpio子系统的API函数来对GPIO进行读写操作:
最终的gpioled.c驱动文件:
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/ide.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/err.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#define GPIOLED_CNT 1
#define GPIOLED_NAME "gpioled"
#define LED_ON 1
#define LED_OFF 0
/* gpioled设备结构体 */
typedef struct gpioled_dev{
dev_t devid; /* 设备号 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct cdev cdev; /* 字符设备 */
struct class *class; /* 类 */
struct device *device; /* 类的设备 */
struct device_node *nd; /* 节点 */
int led_gpio; /* LED的gpio编号 */
}gpioled_dev;
gpioled_dev gpioled;
/* LED等控制函数 */
static int Led_Switch(unsigned char Led_Status)
{
if(Led_Status == LED_ON){
gpio_set_value(gpioled.led_gpio,0);
}else if(Led_Status == LED_OFF){
gpio_set_value(gpioled.led_gpio,1);
}else{
return -1;
}
return 0;
}
/* 打开驱动文件 */
static int gpioled_open (struct inode *Inode, struct file *File)
{
File->private_data = &gpioled; /* 设置私有数据 */
return 0;
}
/* 关闭驱动文件 */
static int gpioled_release (struct inode *Inode, struct file *File)
{
return 0;
}
/* 对驱动文件进行写操作 */
static ssize_t gpioled_write (struct file *File, const char __user *buf,
size_t Count, loff_t *Loff)
{
int ret = 0;
unsigned char databuf[1];
ret = copy_from_user(databuf,buf,Count);
if(ret < 0){
return ret;
}
Led_Switch(databuf[0]);
return 0;
}
/* 字符设备的文件操作集合 */
static const struct file_operations gpioled_opts = {
.owner = THIS_MODULE,
.open = gpioled_open,
.release = gpioled_release,
.write = gpioled_write,
};
/* 模块入口 */
static int __init gpioled_init(void)
{
int ret = 0;
/* 获取设备节点:gpioled */
gpioled.nd = of_find_node_by_path("/gpioled");
if(gpioled.nd == NULL){
ret = -EINVAL;
goto fail_nd;
}
/* 获取设备树中的gpio属性,得到LED所使用的GPIO编号 */
gpioled.led_gpio = of_get_named_gpio(gpioled.nd,"led-gpio", 0);
if(gpioled.led_gpio < 0){
ret = -EINVAL;
goto fail_nd;
}
printk("led-gpio num = %d\r\n",gpioled.led_gpio);
/* 申请GPIO */
ret = gpio_request(gpioled.led_gpio, "led-gpio");
if(ret){
goto fail_nd;
}
/* 将LED的GPIO设置为输出,默认关闭LED灯(低电平点亮) */
ret = gpio_direction_output(gpioled.led_gpio, 0);
if(ret < 0){
goto fail_gpio;
}
/* 注册字符设备 */
/* 1、注册设备号 */
gpioled.major = 0;
if(gpioled.major){ /* 指定了设备号 */
gpioled.devid = MKDEV(gpioled.major,0);
ret = register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
}else{ /* 没有指定设备号 */
ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);
gpioled.major = MAJOR(gpioled.devid);
gpioled.minor = MINOR(gpioled.devid);
}
if(ret < 0){ /* 注册设备号失败 */
goto fail_devid;
}
printk("major=%d,minor=%d\r\n",gpioled.major,gpioled.minor);
/* 2、注册字符设备 */
gpioled.cdev.owner = THIS_MODULE;
cdev_init(&gpioled.cdev, &gpioled_opts); /* 初始化 */
ret = cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
if(ret < 0){
goto fail_cdev;
}
/* 自动添加节点 */
/* 1、添加类 */
gpioled.class = class_create(THIS_MODULE,GPIOLED_NAME);
if(IS_ERR(gpioled.class)){
ret = PTR_ERR(gpioled.class);
goto fail_class;
}
/* 2、添加类的设备 */
gpioled.device = device_create(gpioled.class, NULL,gpioled.devid, NULL, GPIOLED_NAME);
if(IS_ERR(gpioled.device)){
ret = PTR_ERR(gpioled.device);
goto fail_device;
}
return 0;
fail_device:
class_destroy(gpioled.class);
fail_class:
cdev_del(&gpioled.cdev);
fail_cdev:
unregister_chrdev_region(gpioled.devid,GPIOLED_CNT);
fail_devid:
fail_gpio:
gpio_free(gpioled.led_gpio);
fail_nd:
return ret;
}
/* 模块出口 */
static void __exit gpioled_exit(void)
{
/* 关灯 */
Led_Switch(LED_OFF);
/* gpio释放 */
gpio_free(gpioled.led_gpio);
/* 删除设备 */
cdev_del(&gpioled.cdev);
/* 注销设备号 */
unregister_chrdev_region(gpioled.devid,GPIOLED_CNT);
printk("gpioled_exit\r\n");
/* 销毁类的设备 */
device_destroy(gpioled.class,gpioled.devid);
/* 销毁类 */
class_destroy(gpioled.class);
}
/* 注册模块入口和出口函数 */
module_init(gpioled_init);
module_exit(gpioled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZhangXueGuo");