I.MX6ULL基于设备树使用pinctrl和gpio子系统驱动LED
一、修改设备树文件
1、添加pinctrl节点
- 打开
imx6ull-alientk-emmc.dts
设备树源文件,在iomuxc
节点的imx6ul-evk
字节点下添加pinctrl_led
节点。(注意:节点前缀一定要为“pinctrl_
”) - 在
pinctrl_led
节点下添加“fsl,pins
”属性,对于I.MX系列的SOC而言,pinctrl驱动程序是通过读取“fsl,pins
”属性值来获取PIN的配置信息。 - 在“
fsl,pins
”属性中添加具体的PIN配置信息。将GPIO1_03这个引脚复用为GPIO1_IO03,电气属性为)0x10B0.
pinctrl_led: ledgrp{
fls,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED0 */
>;
};
2、添加LED设备节点
- 在根节点“
/
”下创建LED节点,节点名为gpio_led
。 - 将创建的
pinctrl_led
节点添加到gpio_led
设备节点中:
添加pinctrl-names
属性,此属性描述pinctrl名字为default
;
添加pinctrl-0
节点,引用创建的pinctrl_led
节点,表示gpio_led
设备所使用的PIN信息保存在pinctrl_led
节点中 - 在
gpio_led
设备节点中添加GPIO属性信息,表明gpio_led
所使用的GPIO1的IO03,低电平有效。
gpio_led {
#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、检查PIN是否被其他外设使用
- 检查
pinctrl
设置
在imx6ull-alientek-emmc.dts
中搜索MX6UL_PAD_GPIO1_IO03
,可以找到如下内容:
pinctrl_tsc
是TSC(电阻触摸屏接口)的pinctrl节点,在I.MX6U-ALPHA开发板上并没有用到TSC接口,所以可以将其使用/*
和*/
符号屏蔽掉。 - 检查这个GPIO有没有被别的外设使用
在imx6ull-alientek-emmc.dts
中搜索gpio1 3
,可以找到如下内容:
同样的,将其屏蔽掉。
然后我们就可使用make dtbs
命令重新编译设备树,使用新编译的设备树文件启动Linux 系统,启动后进入“/porc/device-tree
”目录中查看gpio_led
节点的存在:
二、LED驱动程序编写
驱动代码如下:
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/module.h>
#include <asm/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
/***************************************************************
文件名 : gpioled_drv.c
描述 : gpioled 驱动文件
***************************************************************/
#define DEV_NAME "gpioled_drv" /* 设备名 */
/* 1. 确定主设备号 */
static int major = 0; /* 主设备号 */
static struct class *gpioled_class; /*gpioled设备类*/
static struct device_node *gpioled_nd; /*gpioled设备节点*/
int led_gpio_num; /*led使用的gpio编号*/
/* 3. 实现对应的open/read/write等函数,填入file_operations结构体 */
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int gpioled_drv_open (struct inode *node, struct file *file)
{
unsigned int ret;
/* 设置LED所使用的GPIO*/
/* 1,获取设备节点:gpioled_nd*/
gpioled_nd = of_find_node_by_path("/gpio_led");
if(gpioled_nd == NULL)
{
printk("gpioled node cannot found!!!\r\n");
return -EINVAL;
}
/* 2,获取LED所使用的GPIO编号 */
led_gpio_num = of_get_named_gpio(gpioled_nd, "led-gpio", 0);
if(led_gpio_num < 0)
{
printk("cannot get led-gpio!!!\r\n");
return -EINVAL;
}
/* 3,设置GPIO_IO03为输出,并且输出高电平,默认关闭LED灯*/
ret = gpio_direction_output(led_gpio_num,1);
if(ret < 0)
{
printk("can't set gpio!!!\r\n");
}
return 0;
}
/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - size : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t gpioled_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
return 0;
}
/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - size : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t gpioled_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
int err;
unsigned char status_buf[1];
err = copy_from_user(status_buf, buf, size);
if(err < 0){
printk("kernel recevdata faigpioled!\r\n");
}
if(status_buf[0] == 1) //点亮gpioled
{
/* 设置GPIO1_DR输出低电平*/
gpio_set_value(led_gpio_num,0);
printk("set:gpioled ON \n");
}
else if(status_buf[0] == 0) //熄灭gpioled
{
/* 设置GPIO1_IO03输出高电平*/
gpio_set_value(led_gpio_num,1);
printk("set:gpioled OFF \n");
}
return 1;
}
/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int gpioled_drv_close (struct inode *node, struct file *file)
{
return 0;
}
/* 2. 定义自己的file_operations结构体 */
static struct file_operations gpioled_drv_fops = {
.owner = THIS_MODULE,
.open = gpioled_drv_open,
.read = gpioled_drv_read,
.write = gpioled_drv_write,
.release = gpioled_drv_close,
};
/* 4. 把file_operations结构体告诉内核:注册驱动程序 */
/* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
/* @description : 驱动入口函数
* @return : 0 成功;其他 失败 */
static int __init gpioled_init(void)
{
int err;
/* 注册字符设备驱动 参数1:主设备号(为0自动获得设备号);参数2:设备名;参数3:file_operations结构体*/
major = register_chrdev(0, DEV_NAME, &gpioled_drv_fops);
/*创建设备类*/
gpioled_class = class_create(THIS_MODULE, "gpioled_class");
err = PTR_ERR(gpioled_class);
if (IS_ERR(gpioled_class)) {
unregister_chrdev(major, DEV_NAME);
return -1;
}
/*创建设备节点 参数1:设备类结构体;参数2:NULL;参数3:设备号(主+次);参数4:NULL;参数5:设备名*/
device_create(gpioled_class, NULL, MKDEV(major, 0), NULL, DEV_NAME); /* /dev/gpioled */
printk("gpioled_drv insmod OK !\r\n");
return 0;
}
/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数 */
static void __exit gpioled_exit(void)
{
device_destroy(gpioled_class, MKDEV(major, 0));
class_destroy(gpioled_class);
unregister_chrdev(major, "gpioled");
printk("gpioled_drv exit!\r\n");
}
/* 7. 其他完善:提供设备信息,自动创建设备节点 */
/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(gpioled_init);
module_exit(gpioled_exit);
/* LICENSE和作者信息 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("william");