使用pinctrl和gpio子系统开发GPIO驱动正点原子IMX6ULL阿尔法板的LED灯
前言
在linux内核中,提供了pinctrl和gpio子系统,用于简化GPIO驱动开发。
pinctrl子系统
作用:根据设备树中的pin信息自动设置pin的复用功能和电气特性
模板:
/* 在设备树文件(如阿尔法板的imx6ull-alientek-emmc.dts文件)的iomuxc节点的imx6ull-evk子节点下添加一个"pinctrl_test"节点
并在该节点下添加fsl,pins属性,完整如下:
*/
&iomuxc {
...
imx6ull-evk {
...
pinctrl_test: testgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO00__GPIO1_IO00 config /* config为该IO具体的电气属性 */
>;
};
};
};
/*
注意:
1、对于IMX6ULL,pinctrl_test的test表示节点名字,testgrp表示test节点的集合,grp是group的缩写
2、对于IMX6ULL,pinctrl子系统是通过fsl,pins来获取pin信息,所以该属性名不能有误
3、MX6UL_PAD_GPIO1_IO00__GPIO1_IO00表示将GPIO1 IO0复用为普通IO,定义在imx6ul-pinfunc.h函数中
4、config表示设置pin的电气特性,比如上/下拉,速度,驱动能力等
*/
gpio子系统
作用:在设备树文件中添加gpio相关信息后,就可以在驱动程序中使用gpio的API函数来操作GPIO
模板:
/* 1、在设备树源文件(.dts后缀)的根节点下创建test设备子节点 */
/ {
...
test {
/* 2、向test设备子节点中添加pinctrl信息 */
pinctrl-name = "default";
pinctrl-0 = <&pinctrl-test>; /* 使用保存在pinctrl-test节点的PIN信息 */
/* 3、添加GPIO属性信息 */
gpio = <&gpio1 0 GPIO_ACTIVE_LOW>; /* gpio表示test设备所使用的GPIO,该GPIO为gpio1的io0,在低电平时有效 */
};
};
常用的gpio API函数:
/*
获取GPIO编号
将设备树中类似<&gpio1 0 GPIO_ACTIVE_LOW>的属性转换为对应的GPIO编号
*/
int of_get_named_gpio(struct device_node *np,
const char *propname,
int index)
参数:
np:设备节点
propname:包含GPIO信息的属性名
index:GPIO索引,如果只有一个GPIO信息,此参数为0
返回值:
正值:获取到的GPIO编号
负值:获取失败
例程
实现效果:点亮IMX6ULL阿尔法开发板的LED
1、添加pinctrl节点
/* 在imx6ull-alientek-emmc.dts的iomuxc节点中添加如下内容 */
&iomuxc {
...
imx6ull-evk {
...
pinctrl_led: ledgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED0 */
>;
};
};
};
2、添加LED设备节点
/* 在imx6ull-alientek-emmc.dts的根节点中添加如下内容 */
/ {
...
gpioled {
#address-cells = <1>;
#size-cells = <1>;
/* compatible = "atkalpha-gpioled"; */ /* 该属性现阶段可以不用,文章后面我会提到为什么 */
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led>;
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
status ="okay";
};
};
3、编写LED设备的driver文件
/*****************************************************************************
* 文件名:gpioled.c
* 作者:xxx
* 版本:v1.0
* 描述:LED驱动文件
* 其他:无
* 日志:v1.0 23/3/29 xxx创建
* 备注:该驱动使用了pinctrl和gpio子系统
* 要使用pinctrl子系统,需要在设备树中设置PIN的配置信息
*/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
//注册设备文件
#include <linux/cdev.h>
#include <linux/device.h>
//添加到设备树
#include <linux/of.h>
#include <linux/of_address.h>
//添加gpio子系统API函数
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define NEWCHRLED_CNT 1 /* 设备号个数 */
#define NEWCHRLED_NAME "gpioled" /* 名字 */
#define LED_OFF 0 /* 关灯 */
#define LED_ON 1 /* 开灯 */
/* gpioled 设备结构体 */
struct gpioled_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
int led_gpio; /* led所使用的GPIO编号 */
};
struct gpioled_dev gpioled; /* led设备 */
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,在open时将file结构体的private_data成员变量指向设备结构体
* @return : 0:成功;其他:失败
*/
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &gpioled; /* 设置私有数据 */
return 0;
}
/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,若为负,表示读取失败
*/
static ssize_t led_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt)
{
return 0;
}
/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 写入的字节数,若为负,表示写入失败
*/
static ssize_t led_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
{
int retvalue;
unsigned char databuf[1];
unsigned char ledstat;
struct gpioled_dev *dev = filp->private_data;
retvalue = copy_from_user(databuf, buf, cnt);
if (retvalue < 0){
printk("kernel write failed!\r\n");
return -EFAULT;
}
ledstat = databuf[0]; /* 获取状态值 */
if (ledstat == LED_ON){
gpio_set_value(dev->led_gpio, 0); /* 打开LED */
} else if (ledstat == LED_OFF){
gpio_set_value(dev->led_gpio, 1); /* 关闭LED */
}
return 0;
}
/*
* @description : 关闭或释放设备
* @param - inode : 设备的inode
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int led_release(struct inode *inode, struct file *filp)
{
return 0;
}
/*
* 设备操作函数结构体
*/
static struct file_operations gpioled_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
/*
* @description : 驱动入口函数
* @param : 无
* @return : 0 成功;其他 失败
*/
static int __init led_init(void)
{
unsigned int ret = 0;
/* 设置LED所使用的GPIO */
/* 1、获取设备节点:gpioled */
gpioled.nd = of_find_node_by_path("/gpioled");
if (gpioled.nd == NULL){
printk("gpioled node can 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){
printk("can not get led-gpio!\r\n");
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 not set gpio!\r\n");
/* 注册字符设备驱动 */
/* 1、创建设备号 */
if (gpioled.major){ /* 定义了设备号 */
gpioled.devid = MKDEV(gpioled.major, 0);
register_chrdev_region(gpioled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);
} else { /* 没有定义设备号 */
alloc_chrdev_region(&gpioled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_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, NEWCHRLED_CNT);
/* 4、创建类 */
gpioled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
if (IS_ERR(gpioled.class)){
return PTR_ERR(gpioled.class);
}
/* 5、创建设备 */
gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, NEWCHRLED_NAME);
if (IS_ERR(gpioled.device)){
return PTR_ERR(gpioled.device);
}
return 0;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit led_exit(void)
{
/* 注销字符设备驱动 */
cdev_del(&gpioled.cdev); /* 删除cdev */
unregister_chrdev_region(gpioled.devid, NEWCHRLED_CNT); /*删除设备号*/
device_destroy(gpioled.class, gpioled.devid); /*删除设备*/
class_destroy(gpioled.class); /*删除类*/
}
/*
* 将上面两个函数指定为驱动的入口和出口函数
*/
module_init(led_init);
module_exit(led_exit);
/*
* LICENSE和作者信息
*/
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("xxx");
4、编译设备树文件(编译完后需要把对应的.dtb文件烧录到开发板上)
/* 在内核源码中根目录下,使用如下命令编译所有设备树文件 */
make debs
5、编译LED设备模块(.ko后缀文件),并将编译出来的.ko文件复制到根文件系统的/lib/modules/4.1.15/下
6、重启开发板
7、重启后首次加载模块前,需要开发板的命令行中输入depmode
命令
8、加载模块,输入'modprobe xxx.ko'命令,示例如下:
问题
为什么在设备树的gpioled节点,没有使用compatible属性也可以驱动LED模块?
1、compatible是Linux用来绑定节点和设备驱动用的,在linux上电后,如果compatible匹配,就会调用对应驱动的probe函数,一般,在probe函数中,通常也包含了init函数。但,对于目前的学习,我们并没有学习到使用probe函数,并且当前采用的方式是手动加载模块。在模块加载时,会调用驱动里的init函数,因此当前阶段,有没有使用compatible都没有关系。
2、由于linux初始化时会初始化platform总线上的设备,会根据设备节点compatible属性和驱动中of_match_table对应的值,匹配就加载对应的驱动,所以compatible在使用platform框架的驱动中就比较重要