I.MX6ULL基于设备树使用pinctrl和gpio子系统驱动LED

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");



发布了62 篇原创文章 · 获赞 13 · 访问量 5572

猜你喜欢

转载自blog.csdn.net/qq_38113006/article/details/104495442