【Linux】遇事不决,可先点灯,LED驱动的进化之路---1

这篇具有很好参考价值的文章主要介绍了【Linux】遇事不决,可先点灯,LED驱动的进化之路---1。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

【Linux】遇事不决,可先点灯,LED驱动的进化之路---1

前言:

一、最简单的LED驱动程序

1.1 字符设备驱动程序框架

1.2 程序实战

1.2.1 驱动程序(led_drive_simple.c)

1.2.2 应用程序(led_test_simple.c)

1.2.3 Makefile代码

1.3 运行测试

1.3.1 首先编译内核(如果没编译过)

1.3.2 设置交叉编译工具链(Ubuntu)

1.3.3 编译(Ubuntu)

1.3.4 上机测试(开发板)

二、LED驱动程序(分层)

2.1 分层设计思想

2.2 程序实战

2.2.1 头文件(led_opr.h)

2.2.2 驱动程序(board_imx6ull.c)

2.2.3 驱动程序(led_drive.c)

2.2.4 应用程序(led_test.c)

2.2.5 Makefile代码

2.3 运行测试

2.3.1 上机测试(开发板)

2.4 总结

三、LED驱动程序(分离)

3.1 分离设计思想

3.2 程序实战

3.2.1 头文件(led_resource.h)

3.2.2 驱动程序(board_A_led.c)

3.2.3 驱动程序(chip_imx6ull_gpio.c)

3.2.4 Makefile代码

3.3 运行测试

3.4 总结

四、LED驱动程序(总线设备驱动模型)

4.1 总线设备驱动模型

4.2 程序实战

4.2.1 头文件(led_drive.h)

4.2.2 驱动程序(led_drive.c)

4.2.3 驱动程序(board_A_led.c)

4.2.4 驱动程序(chip_imx6ull_gpio.c)

 4.2.5 Makefile代码

 4.3 测试运行

4.4 总结 

五、LED驱动程序(设备树)

5.1 设备树

5.1.1 设备树背景

5.1.2 设备树简述

5.2 程序实战

5.2.1 设备树dts文件(100ask_led.dts)

 5.2.2 驱动程序(chip_imx6ull_gpio.c)

 5.2.3 Makefile代码

5.3 总结


前言:

本文展示LED驱动进化升级化蝶的过程,并由浅入深的对驱动程序框架的理念作进一步阐述。遇到搞不明白的,就不妨先点个灯吧。

LED驱动进化之路:(层次递进)

  1. 最简单的LED驱动程序
  2. 加入分层思想的LED驱动程序
  3. 加入分离思想的LED驱动程序
  4. 总线设备驱动模型下的LED驱动程序
  5. 加入设备树的LED驱动程序

参考:韦老师课程,Linux笔记老师课程(设备树部分)

https://blog.csdn.net/qq_33487044/article/details/126325656

https://www.bilibili.com/video/BV14f4y1Q7ti?p=12&spm_id_from=pageDriver&vd_source=cf66c4035cd726f1d3cb6a42cfd6da5f

过一遍驱动框架后,有了大体的认知,但还需要进一步的实践感受。

https://blog.csdn.net/weixin_42373086/article/details/130521999

一、最简单的LED驱动程序

1.1 字符设备驱动程序框架

最简单的驱动程序,一个设备app调用open时就提供给驱动程序里的drv_open,read→drv_read,write→drv_write,ioctrl→drv_ioctl。

【Linux】遇事不决,可先点灯,LED驱动的进化之路---1

编写驱动程序的步骤:跟hello驱动程序框架完全一致

  1. 确定主设备号,(可以自己定义,设置为0时会让内核分配)
  2. 定义自己的file_operations结构体管理驱动程序drv_open/drv_write等,实现对应的drv_open/drv_write等函数。---注:file_operations为核心
  3.  把file_operations结构体告诉内核:register_chrdev(提供主设备号)。
  4. 实现入口函数,安装驱动程序时,入口函数调用register_chrdev,相应的就有出口函数,卸载驱动程序时,出口函数调用unregister_chrdev
  5. 辅助性功能:提供设备信息,自动创建设备节点:class_write,device_create

1.2 程序实战

1.2.1 驱动程序(led_drive_simple.c)

具体的注释和解析都在代码中:

/* 说明:
	*1,本代码是跟学韦老师课程所编写,增加了注释和分析
	*2,采用的是UTF-8编码格式
	*3,简单LED驱动程序 led_drive_simple.c
	*4,参照内核字符设备驱动程序cm4040_cs.c
*/


#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <asm/uaccess.h>
#include <linux/device.h>
#include <asm/io.h>



/*registers*/
//IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3:0x02290000 + 0x14
static volatile unsigned int* IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;

//GPIO5_GDIR:0x20AC004
static volatile unsigned int* GPIO5_GDIR;

//GPIO5_DR:0x020AC000
static volatile unsigned int* GPIO5_DR;




static struct class *led_class;
/*第一步:确定主设备号,这里由内核分配*/
static int major = 0;


/*第二步:定义自己的 file_operations 结构体,
实现对应的 drv_open/drv_write 等函数,填入 file_operations 结构体*/
/*

 *函数:		led_drv_open
 *功能:		enable gpio				 使能gpio
 			configure pin as gpio    设置引脚为GPIO
 			configure gpio as output 设置引脚为GPIO输出引脚
 *传入参数:
 			*flip:要打开的文件
*返回参数: 如果成功返回0
*/			

static int led_drv_open(struct inode *inode, struct file *filp)
{
	//imx6ull默认使能GPIO5.
	*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~0xf;
	*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |=  0x5;

	*GPIO5_GDIR |= (1<<3);
	return 0;
}

/*
 *函数:      led_drv_write
 *功能:		copy_from_user:get data form app  获取app的数据,并设置gpio的寄存器
 			set gpio register: out 1/0
 *传入参数:
 			*flip:要写的文件
 			*buf: 写的数据来自于buf
 			*size:写多大的数据
 			*offset:偏移值
*返回参数: 写的数据长度
*/
static ssize_t led_drv_write(struct file *filp, const char __user *buf,
			 size_t count, loff_t *ppos)
{
	char val;
	int err;
	err = copy_from_user(&val, buf, 1);
	if(val)
	{
		*GPIO5_DR &= ~(1<<3);
	}else
	{
		*GPIO5_DR |= (1<<3);	
	}
	return -1;
	
}

			 
static struct file_operations led_fops = {
	.owner		= THIS_MODULE,
	.write		= led_drv_write,
	.open		= led_drv_open,

};


/*第三步:定义一个入口函数, 调用register_chrdev(把 file_operations 结构体告诉内核)*/
/*
 *函数:       led_init
 *功能:		①注册主设备号	
 			②获取寄存器物理地址映射过来的虚拟地址
 			③辅助完善:提供设备信息,自动创建设备节点:class_create、device_create
*/

static int __init led_init(void)
{
	/*
	 *printk:判断一下是否调用了入口函数
	 *__FILE__ :表示文件
	 *__FUNCTION__ :当前函数名
	 *__LINE__ :在文件的哪一行
	*/	
	printk("%s %s %d\n",__FILE__,__FUNCTION__,__LINE__);

	//ioremap:物理地址映射到虚拟地址
	IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x02290000 + 0x14, 4);
	GPIO5_GDIR = ioremap(0x020AC004, 4);
	GPIO5_DR  = ioremap(0x020AC000, 4);

	
	
	//0:由内核分配主设备号,名称,指定函数结构体led_fops,返回值为主设备号
	major = register_chrdev(0,"xixiwuli_led", &led_fops);	
	led_class = class_create(THIS_MODULE, "led_2345");
	if (IS_ERR(led_class))
		return PTR_ERR(led_class);
	if (major < 0) {
		class_destroy(led_class);
		return major;
	}
	//创建/dev/myled的设备节点
	device_create(led_class, NULL, MKDEV(major, 0), NULL, "myled");
	return 0;
}


/*第四步:定义一个出口函数,出口函数调用unregister_chrdev*/
static void __exit led_exit(void)
{
	iounmap(IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3);
	iounmap(GPIO5_GDIR);
	iounmap(GPIO5_DR);
	
	device_destroy(led_class, MKDEV(major, 0));
	class_destroy(led_class);
	unregister_chrdev(major,"xixiwuli_led");
}


//把函数修饰成入口函数和出口函数
module_init(led_init);
module_exit(led_exit);
//内核遵循GPL协议,所以我们也要遵循GPL协议,才能够使用内核里的一些函数
MODULE_LICENSE("Dual BSD/GPL");

1.2.2 应用程序(led_test_simple.c)

具体的注释和解析都在代码中:

/* 说明:
	*1,本代码是跟学韦老师课程所编,增加了注释和理解
	*2,采用的是UTF-8编码格式
	*3,简单LED应用程序 led_test_simple.c
*/

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>


//led_test_simple /dev/myled on
//led_test_simple /dev/myled off

int main(int argc, char** argv)
{
	int fd;
	char status;
	
	if(argc != 3)
	{
		printf("usage: %s <dev> <on|off>\n", argv[0]);
		printf("eg: %s /dev/myled on\n", argv[0]);
		printf("eg: %s /dev/myled off\n", argv[0]);
	}

	//open
	fd = open(argv[1], O_RDWR);
	if(fd < 0)
	{
		printf("can not open%s\n",argv[1]);
		return -1;
	}
	
	//write
	if(strcmp(argv[2], "on") == 0)
	{
		status = 1;
		write(fd, &status, 1);
	}	
	if(strcmp(argv[2], "off") == 0)
	{
		status = 0;
		write(fd, &status, 1);
	}
	return 0;

}

1.2.3 Makefile代码

具体的注释和解析都在代码中:

# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o led_test_simple led_test_simple.c 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
	rm -f led_test_simple

obj-m	+= led_drive_simple.o

1.3 运行测试

1.3.1 首先编译内核(如果没编译过)

参照这篇文章https://blog.csdn.net/weixin_42373086/article/details/129796348?spm=1001.2014.3001.5501

1.3.2 设置交叉编译工具链(Ubuntu)

export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin

1.3.3 编译(Ubuntu)

make编译后,将.ko文件和 led_test_simple复制到nfs挂载的文件夹下。

make
cp *.ko led_test_simple ~/nfs_rootfs/

1.3.4 上机测试(开发板)

mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt
//复制到开发板上
cp /mnt/led_drive_simple.ko ./
cp /mnt/led_test_simple ./
//安装驱动模块
insmod led_drive_simple.ko
 
//查询是否有我们的hello程序
cat /proc/devices
lsmod
 
//查询是否有我们的设备节点
ls /dev/myled -l
//打开
./led_test_simple /dev/myled on
//关闭
./led_test_simple /dev/myled off

查询设备结果:

【Linux】遇事不决,可先点灯,LED驱动的进化之路---1

 点灯和关灯:

【Linux】遇事不决,可先点灯,LED驱动的进化之路---1

二、LED驱动程序(分层)

2.1 分层设计思想

如果对于多个板子,去驱动同一种设备(例如去点个灯),都要像上面的方式从头到尾写一个对应的驱动程序,是件非常麻烦的事情。

简而言之,这里LED驱动是否能支持多个板子?如何实现呢?

针对上述的情况,就要应用到分层的思想,我们先要将驱动拆为通用的框架(leddrv.c)、具体的硬件操作(board_X.c),如下图所示:

【Linux】遇事不决,可先点灯,LED驱动的进化之路---1

这里再进一步,以面向对象的思想,抽象出一个结构体,每个单板相关的boardX.c实现自己的led_operations结构体,供上层的leddrv.c调用。

struct led_operations
{
	int (*init) (int which); /*初始化LED,which---哪个LED*/
	int (*ctl) (int which, int status);/*控制LED,which-哪个LED,status:1/0亮灭*/
};

2.2 程序实战

 相较最简框架下的驱动程序,代码里注释差异点。

2.2.1 头文件(led_opr.h)

这个头文件是作为board_imx6ull.c和led_drive.c之间的桥梁。

//定义这个宏,防止二次调用
//例子LED调用LEDA,LEDA调用这个头文件,未加时,会二次调用这个头文件
//加了之后,第二次调用时,就不再其效用了。
#ifndef _LED_OPR_H
#define _LED_OPR_H

struct led_operations
{
	int num;
	int (*init) (int which); /*初始化LED,which---哪个LED*/
	int (*ctl) (int which, char status);/*控制LED,which-哪个LED,status:1/0亮灭*/
};

struct led_operations *get_board_led_opr(void);

#endif

2.2.2 驱动程序(board_imx6ull.c)

从这里我们可以看到,对应板子上的硬件操作都在这个程序(这层)里去实现了。

#include <linux/module.h>

#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 <asm/io.h>

#include "led_opr.h"


/*registers*/
//CCM_CCGR1:0x20C406C
static volatile unsigned int* CCM_CCGR1;
//IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3:0x02290000 + 0x14
static volatile unsigned int* IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;

//GPIO5_GDIR:0x20AC004
static volatile unsigned int* GPIO5_GDIR;

//GPIO5_DR:0x020AC000
static volatile unsigned int* GPIO5_DR;

/*
 *函数:    	board_demo_led_init
 *功能:		enable gpio				 使能gpio
 			configure pin as gpio    设置引脚为GPIO
 			configure gpio as output 设置引脚为GPIO输出引脚
 *传入参数:
 			*which:哪个LED
 *返回参数:如果成功返回0
*/


static int board_demo_led_init(int which)
{
	unsigned int val;
	//printk("%s %s line %d, led %d\n",__FILE__,__FUNCTION__,__LINE__,which);
	if(which == 0)
	{
		if(!CCM_CCGR1)
		{
			//ioremap:物理地址映射到虚拟地址
			CCM_CCGR1 = ioremap(0x20C406C, 4);
			IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x02290000 + 0x14, 4);
			GPIO5_GDIR = ioremap(0x020AC004, 4);
			GPIO5_DR  = ioremap(0x020AC000, 4);
		}

		
		*CCM_CCGR1 |= (3<<30);
		val = *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
		val &= ~0xf;
		val |=  0x5;
		*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = val;

		*GPIO5_GDIR |= (1<<3);
	}
	return 0;
}


/*
 *函数:    	board_demo_led_init
 *功能:		set gpio register: out 1/0

 *传入参数:
 			*which:哪个LED
 			*status: 状态1/0,亮灭
 *返回参数:如果成功返回0
*/
static int board_demo_led_ctl(int which, char status)
{
	if(which == 0)
	{
		if(status)
		{
			*GPIO5_DR &= ~(1<<3);
		}else
		{
			*GPIO5_DR |= (1<<3);
		}
	}
	return 0;
}


static struct led_operations board_demo_led_opr =
{
	.num  = 1,
	.init = board_demo_led_init,
	.ctl  = board_demo_led_ctl,
};

struct led_operations *get_board_led_opr(void)
{
	return &board_demo_led_opr;
}

2.2.3 驱动程序(led_drive.c)

之前的框架是在这里实现设备注册和硬件操作,现在只去实现注册设备驱动等通用功能。

/* 说明:
	*1,本代码是跟学韦老师课程所编写,增加了注释和分析
	*2,采用的是UTF-8编码格式
	*3,LED驱动程序(分层思想) led_drive.c
*/

#include <linux/module.h>
#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 "led_opr.h"



static struct class *led_class;
struct led_operations *p_ledopr;


/*第一步:确定主设备号,这里由内核分配*/
static int major = 0; 

/*第二步:定义自己的 file_operations 结构体,
实现对应的 drv_open/drv_write 等函数,填入 file_operations 结构体*/
/*

 *函数:		led_drv_open
 *差异点:      根据次设备号初始化LED
 *传入参数:
 			*flip:要打开的文件
 *返回参数:如果成功返回0
*/			

static int led_drv_open(struct inode *inode, struct file *filp)
{
	int minor = iminor(inode);/*次设备号*/

	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	p_ledopr->init(minor);
	return 0;
}

/*
 *函数:       led_drv_write
 *差异点:		根据次设备号和status控制LED
 *功能:		copy_from_user,从app中获取数据
 *传入参数:
 			*flip:要写的文件
 			*buf: 写的数据来自于buf
 			*size:写多大的数据
 			*offset:偏移值
*返回参数: 写的数据长度
*/
static ssize_t led_drv_write(struct file *filp, const char __user *buf,
			 size_t count, loff_t *ppos)
{
	char status;
	int err;

	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_from_user(&status, buf, 1);
	
	/*根据次设备号控制LED*/
	struct inode* inode = file_inode(filp);
	int minor = iminor(inode) & 0x0f;
	p_ledopr->ctl(minor, status);
	return -1;
	
}

			 
static struct file_operations led_fops = {
	.owner		= THIS_MODULE,
	.write		= led_drv_write,
	.open		= led_drv_open,

};


/*第三步:定义一个入口函数, 调用register_chrdev(把 file_operations 结构体告诉内核)*/
/*
 *函数:       led_init
 *差异点:      有多个次设备号
 *功能:		①注册主设备号	
 			②辅助完善:提供设备信息,自动创建设备节点:class_create、device_create
*/

static int __init led_init(void)
{
	/*
	 *printk:判断一下是否调用了入口函数
	 *__FILE__ :表示文件
	 *__FUNCTION__ :当前函数名
	 *__LINE__ :在文件的哪一行
	*/	
	printk("%s %s %d\n",__FILE__,__FUNCTION__,__LINE__);
	

		
	//0:由内核分配主设备号,名称,指定函数结构体led_fops,返回值为主设备号
	major = register_chrdev(0,"xixiwuli_led", &led_fops);	
	led_class = class_create(THIS_MODULE, "led_2345_class");
	int i;
	if (IS_ERR(led_class))
		return PTR_ERR(led_class);
	if (major < 0) {
		class_destroy(led_class);
		return major;
	}
	
	//通过p_ledopr,可以操作调用单板相关的代码
	p_ledopr = get_board_led_opr();
	
	//创建/dev/myled2的设备节点,多个次设备号控制多个LED
	for(i = 0;i < p_ledopr->num; i++)
	{
		device_create(led_class, NULL, MKDEV(major, i), NULL, "myled%d",i);
	}



	return 0;
}


/*第四步:定义一个出口函数,出口函数调用unregister_chrdev*/
static void __exit led_exit(void)
{

	int i;
	for(i = 0; i < p_ledopr->num; i++)
	{
		device_destroy(led_class, MKDEV(major, i));
	}
	class_destroy(led_class);
	unregister_chrdev(major,"xixiwuli_led");
}


//把函数修饰成入口函数和出口函数
module_init(led_init);
module_exit(led_exit);
//内核遵循GPL协议,所以我们也要遵循GPL协议,才能够使用内核里的一些函数
MODULE_LICENSE("Dual BSD/GPL");

2.2.4 应用程序(led_test.c)

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>


//led_test /dev/myled0 on
//led_test /dev/myled0 off

int main(int argc, char** argv)
{
	int fd;
	char status = 0;
	
	if(argc != 3)
	{
		printf("usage: %s <dev> <on|off>\n", argv[0]);
		printf("eg: %s /dev/myled on\n", argv[0]);
		printf("eg: %s /dev/myled off\n", argv[0]);
	}

	//open
	fd = open(argv[1], O_RDWR);
	if(fd < 0)
	{
		printf("can not open %s\n",argv[1]);
		return -1;
	}
	
	//write
	if (strcmp(argv[2], "on") == 0)
	{
		status = 1;
	}

	write(fd, &status, 1);
	close(fd);
	return 0;

}

2.2.5 Makefile代码

这里有小的变动,将board_imx6ull.c和led_drive.c编译在一起。

# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o led_test led_test.c 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
	rm -f led_test


#led_drive.c和board_demo.c编译成xixiwuli_led.ko
xixiwuli_led-y := led_drive.o board_imx6ull.o
obj-m	+= xixiwuli_led.o

2.3 运行测试

前三个步骤跟上述完全一致,就不再赘述。 

2.3.1 上机测试(开发板)

mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt
//复制到开发板上
cp /mnt/xixiwuli_led.ko ./
cp /mnt/led_test ./
//安装驱动模块
insmod xixiwuli_led.ko
 
//查询是否有我们的hello程序
cat /proc/devices
lsmod
 
//查询是否有我们的设备节点
ls /dev/myled0 -l
//打开
./led_test /dev/myled0 on
//关闭
./led_test /dev/myled0 off
//程序运行完,可以卸载相应的模块
rmmod xixiwuli_led.ko

查询设备结果:

【Linux】遇事不决,可先点灯,LED驱动的进化之路---1

点灯和关灯:

【Linux】遇事不决,可先点灯,LED驱动的进化之路---1

2.4 总结

从结果上来看,相较于简单的驱动框架下的驱动程序,也能够很好的实现点灯关灯。

但结构上,他很好的将通用功能和硬件操作部分做了分离,分别对应led_drive_stra.c和board_imx6ull.c,再加入一个设备时,也只需要创建一个board_X.c就可以了。

这样的驱动程序有了更好的拓展性,可以支持多个板子。也从这里我们能更好的理解了Linux驱动 = 驱动框架 + 硬件操作,相信大家也开始慢慢感受到驱动框架的魅力

三、LED驱动程序(分离)

3.1 分离设计思想

上述的方式,我们能够发现board_X.c里跟芯片硬件绑定的太死,如果我们要换个灯点亮,就需要修改代码,重新编译,那么如何解决呢?

简而言之,如何解决硬件操作不灵活的问题?

针对上述的问题, 就要应用到分离的设计思想。对于某一款芯片,引脚操作是类似的,可以写出一个通用的驱动程序,进行一个左右分离,一个定义资源(board_X.c),一个定义硬件的通用操作(chipY_gpio.c),如下图所示:

【Linux】遇事不决,可先点灯,LED驱动的进化之路---1

 这里就抽象出一个led_resource结构体,来表达具体资源是怎么样的。(沿用面向对象的思想)

3.2 程序实战

这里相较于第二节的程序,board_imx6ull.c文件要分成两个文件board_A_led.c和chip_imx6ull_gpio.c。所以这里主要阐述led_resource.h、board_A_led.c、chip_imx6ull_gpio.c以及Makefile代码。具体实现框架如下:

【Linux】遇事不决,可先点灯,LED驱动的进化之路---1

3.2.1 头文件(led_resource.h)

led_resource.h里的结构体led_resource定义资源。

#ifndef _LED_RE_H
#define _LED_RE_H

/*GPIO5*/
/*bit[31:16] = group*/
/*bit[15:0]  = pin*/

//可以用以下的宏,表示GPIO引脚
#define GROUP(x) 		(x>>16)
#define PIN(x)			(x&0xFFFF)
#define GROUP_PIN(g,p)	((g<<16) | (p))

struct led_resource
{
	int pin;
};

struct led_resource *get_led_resource(void);

#endif

3.2.2 驱动程序(board_A_led.c)

这里的资源:GPIO引脚,这里初始设置为GPIO5_3。 

#include "led_resource.h"

static struct led_resource board_A_led = {
	.pin = GROUP_PIN(5,3),	
};


struct led_resource * get_led_resource(void)
{
	return &board_A_led;
};

3.2.3 驱动程序(chip_imx6ull_gpio.c)

这里具体实现GPIO通用硬件操作,以GPIO5_3为例,先不涉及繁杂的硬件操作。

#include <linux/module.h>

#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 <asm/io.h>

#include "led_opr.h"
#include "led_resource.h"


static struct led_resource *led_rsc;


/*阐述说明:
 *现阶段仅以展示分离设计思想
 *以下的为GPIO5_3需要设置的寄存器绝对物理地址,后续可以按照基址表示GPIO组内多引脚。
 *定义好多个基址,可以实现表示多个GPIO组
*/

/*
//CCM_CCGR1:0x20C406C
static volatile unsigned int* CCM_CCGR1;
//IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3:0x02290000 + 0x14
static volatile unsigned int* IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;

//GPIO5_GDIR:0x20AC004
static volatile unsigned int* GPIO5_GDIR;

//GPIO5_DR:0x020AC000
static volatile unsigned int* GPIO5_DR;
*/


/*
 *函数:    	board_demo_led_init
 *功能:		enable gpio				 使能gpio
 			configure pin as gpio    设置引脚为GPIO
 			configure gpio as output 设置引脚为GPIO输出引脚
 *传入参数:
 			*which:哪个LED
 *返回参数:如果成功返回0
*/


static int board_demo_led_init(int which)
{
	unsigned int val;
	//printk("%s %s line %d, led %d\n",__FILE__,__FUNCTION__,__LINE__,which);

	if(!led_rsc)
	{
		led_rsc = get_led_resource();
	}
	printk("init gpio: group %d, pin %d\n", GROUP(led_rsc->pin), PIN(led_rsc->pin));
	switch(GROUP(led_rsc->pin))
	{
		case 0:
		{
			printk("init pin of group 0 ...\n");
			break;
		}
		case 1:
		{
			printk("init pin of group 1 ...\n");
			break;
		}
		case 2:
		{
			printk("init pin of group 2 ...\n");
			break;
		}
		case 3:
		{
			printk("init pin of group 3 ...\n");
			break;
		}
	}
/*
	if(which == 0)
	{
		if(!CCM_CCGR1)
		{
			//ioremap:物理地址映射到虚拟地址
			CCM_CCGR1 = ioremap(0x20C406C, 4);
			IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x02290000 + 0x14, 4);
			GPIO5_GDIR = ioremap(0x020AC004, 4);
			GPIO5_DR  = ioremap(0x020AC000, 4);
		}

		
		*CCM_CCGR1 |= (3<<30);
		val = *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
		val &= ~0xf;
		val |=  0x5;
		*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = val;

		*GPIO5_GDIR |= (1<<3);
	}
*/
	return 0;
}


/*
 *函数:    	board_demo_led_init
 *功能:		set gpio register: out 1/0
 *传入参数:
 			*which:哪个LED
 			*status: 状态1/0,亮灭
 *返回参数:如果成功返回0
*/
static int board_demo_led_ctl(int which, char status)
{
	printk("set led %s: group %d, pin %d\n", status ? "on" : "off", GROUP(led_rsc->pin), PIN(led_rsc->pin));
	switch(GROUP(led_rsc->pin))
	{
		case 0:
		{
			printk("set pin of group 0 ...\n");
			break;
		}
		case 1:
		{
			printk("set pin of group 1 ...\n");
			break;
		}
		case 2:
		{
			printk("set pin of group 2 ...\n");
			break;
		}
		case 3:
		{
			printk("set pin of group 3 ...\n");
			break;
		}
	}

/*
	if(which == 0)
	{
		if(status)
		{
			*GPIO5_DR &= ~(1<<3);
		}else
		{
			*GPIO5_DR |= (1<<3);
		}
	}
*/
	return 0;
}


static struct led_operations board_demo_led_opr =
{
	.num  = 1,
	.init = board_demo_led_init,
	.ctl  = board_demo_led_ctl,
};

struct led_operations *get_board_led_opr(void)
{
	return &board_demo_led_opr;
}

3.2.4 Makefile代码

这里有小的变动,将led_drive.c、board_A_led.c以及chip_imx6ull_gpio.c编译在一起。

# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o led_test led_test.c 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
	rm -f led_test


#led_drive.c、board_A_led.c、chip_imx6ull_gpio.c编译成xixiwuli.ko
xixiwuli_led-y := led_drive.o board_A_led.o chip_imx6ull_gpio.o
obj-m	+= xixiwuli_led.o

3.3 运行测试

跟上面所述基本一致。 

//打开
./led_test /dev/myled0 on
//关闭
./led_test /dev/myled0 off
dmesg

打印演示: 

【Linux】遇事不决,可先点灯,LED驱动的进化之路---1

3.4 总结

经过上述分离设计思想的实践,我们会发现在需要变更要控制的引脚时(打开另一个LED时),我们只需要改动board_A_led.c就可以了。

相较于第二节的部分,拓展性和灵活性又得到了进一步的提升。

四、LED驱动程序(总线设备驱动模型)

4.1 总线设备驱动模型

上一节的内容里,我们可以进一步发现如果我们处理多个不同的设备,例如LED、LCD等等,我们都要再定义一个相应的resource.h,这个是不现实的。 

基于上述的问题,提出了总线设备驱动模型,它是分离思想的进一步实现。

【Linux】遇事不决,可先点灯,LED驱动的进化之路---1后续进一步采用bus总线来管理platform_device  /platform_driver。如下图所示: 

【Linux】遇事不决,可先点灯,LED驱动的进化之路---1

4.2 程序实战

在第三节程序基础上进一步进阶到总线设备驱动框架,需要抽象出platform_device和platform_driver结构体,并实现它们之间的匹配配对。

【Linux】遇事不决,可先点灯,LED驱动的进化之路---1

具体的各个步骤如下: 

1.分配/设置/注册 platform_device结构体

  • 在里面定义所用资源,指定设备名字

2. 分配/设置/注册 platform_driver结构体

  • 在其中probe函数里,分配/设置/注册file_operations结构体
  • 并从platform_device中确定所用硬件资源,动态实现device_create
  • 指定platform_driver的名字

相较第三节内容,展示内容改动的部分。 

4.2.1 头文件(led_drive.h)

声明底层到上层的注册函数。

#ifndef _LEDDRV_H
#define _LEDDRV_H

#include "led_opr.h"

void led_device_create(int minor);
void led_device_destory(int minor);
void register_led_operations(struct led_operations *opr);


#endif /* _LEDDRV_H */

4.2.2 驱动程序(led_drive.c)

这里属于驱动程序的最上层,具体阐述和分析,详见代码。

/* 说明:
	*1,本代码是跟学韦老师课程所编写,增加了注释和分析
	*2,采用的是UTF-8编码格式
	*3,LED驱动程序(总线设备驱动模型) led_drive.c
*/

#include <linux/module.h>
#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 "led_opr.h"

/*第一步:确定主设备号,这里由内核分配*/
static int major = 0; 
static struct class *led_class;
struct led_operations *p_ledopr;


/*
 *阐述说明
 *定义给底层(chip_imx6ull_gpio.c)去调用的
*/
void led_device_create(int minor)
{

	device_create(led_class, NULL, MKDEV(major, minor), NULL, "myled%d", minor);
}

void led_device_destory(int minor)
{
	
	device_destroy(led_class, MKDEV(major, minor));
}


//定义底层向上层注册函数,避免交叉编译
void register_led_operations(struct led_operations *opr)
{
	p_ledopr = opr;
}
EXPORT_SYMBOL(led_device_create);
EXPORT_SYMBOL(led_device_destory);
EXPORT_SYMBOL(register_led_operations);


/*第二步:定义自己的 file_operations 结构体,
实现对应的 drv_open/drv_write 等函数,填入 file_operations 结构体*/
/*

 *函数:		led_drv_open
 *差异点:      根据次设备号初始化LED
 *传入参数:
 			*flip:要打开的文件
 *返回参数:如果成功返回0
*/			

static int led_drv_open(struct inode *inode, struct file *filp)
{
	int minor = iminor(inode);/*次设备号*/

	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	p_ledopr->init(minor);
	return 0;
}

/*
 *函数:       led_drv_write
 *差异点:		根据次设备号和status控制LED
 *功能:		copy_from_user,从app中获取数据
 *传入参数:
 			*flip:要写的文件
 			*buf: 写的数据来自于buf
 			*size:写多大的数据
 			*offset:偏移值
*返回参数: 写的数据长度
*/
static ssize_t led_drv_write(struct file *filp, const char __user *buf,
			 size_t count, loff_t *ppos)
{
	char status;
	int err;

	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_from_user(&status, buf, 1);
	
	/*根据次设备号控制LED*/
	struct inode* inode = file_inode(filp);
	int minor = iminor(inode) & 0x0f;
	p_ledopr->ctl(minor, status);
	return 1;
	
}

			 
static struct file_operations led_fops = {
	.owner		= THIS_MODULE,
	.write		= led_drv_write,
	.open		= led_drv_open,

};


/*第三步:定义一个入口函数, 调用register_chrdev(把 file_operations 结构体告诉内核)*/
/*
 *函数:       led_init
 *功能:		①注册主设备号	
 			②辅助完善:提供设备信息,自动创建设备节点:class_create、device_create
*/

static int __init led_init(void)
{
	/*
	 *printk:判断一下是否调用了入口函数
	 *__FILE__ :表示文件
	 *__FUNCTION__ :当前函数名
	 *__LINE__ :在文件的哪一行
	*/	
	printk("%s %s %d\n",__FILE__,__FUNCTION__,__LINE__);
	
		
	//0:由内核分配主设备号,名称,指定函数结构体led_fops,返回值为主设备号
	major = register_chrdev(0,"100ask_led", &led_fops);	
	led_class = class_create(THIS_MODULE, "led_2345_class");
	int i;
	if (IS_ERR(led_class))
		return PTR_ERR(led_class);
	if (major < 0) {
		class_destroy(led_class);
		return major;
	}
	
	return 0;
}


/*第四步:定义一个出口函数,出口函数调用unregister_chrdev*/
static void __exit led_exit(void)
{

	class_destroy(led_class);
	unregister_chrdev(major,"100ask_led");
}

//把函数修饰成入口函数和出口函数
module_init(led_init);
module_exit(led_exit);
//内核遵循GPL协议,所以我们也要遵循GPL协议,才能够使用内核里的一些函数
MODULE_LICENSE("Dual BSD/GPL");

4.2.3 驱动程序(board_A_led.c)

 本程序主要定义一些资源,编写实现platform_device结构体。

#include <linux/module.h>

#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/platform_device.h>


#include "led_resource.h"

static void led_dev_release(struct device *dev)
{
}

//定义一个资源数组
static struct resource resources[] = {
        {
                .start = GROUP_PIN(5,3),
                .flags = IORESOURCE_IRQ,
        },
        {
                .start = GROUP_PIN(3,1),
                .flags = IORESOURCE_IRQ,
        },

};

/*
 *name:平台名称
 *num_resources:资源个数
 *resource:引入资源数组
*/
static struct platform_device board_A_led_dev =
{
	.name = "100ask_led",
	.num_resources = ARRAY_SIZE(resources),
	.resource = resources,
	.dev = {
                .release = led_dev_release,
         },
		
	
};
/*入口函数,patform_device
 *功能:注册设备
*/
static int led_dev_init(void)
{
	int err;
	err = platform_device_register(&board_A_led_dev);
	return 0;
}

/*出口函数,platform_device*/
static void led_dev_exit(void)
{
	platform_device_unregister(&board_A_led_dev);
}


module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");

4.2.4 驱动程序(chip_imx6ull_gpio.c)

这里主体实现platform_driver结构体。

#include <linux/module.h>

#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/platform_device.h>


#include "led_drive.h"
#include "led_opr.h"
#include "led_resource.h"


static int g_ledpins[100];
static int g_ledcnt = 0;


/*阐述说明:
 *现阶段仅以示意总线设备驱动模型框架
*/

/*
 *函数:    	board_imx6ull_led_init
 *功能:		获取gpio引脚信息
 *输入参数:which---哪个引脚
 *返回参数:如果成功返回0
*/
static int board_imx6ull_led_init(int which)
{
	//printk("%s %s line %d, led %d\n",__FILE__,__FUNCTION__,__LINE__,which);
	printk("init gpio: group %d, pin %d\n", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));
	switch(GROUP(g_ledpins[which]))
	{
		case 0:
		{
			printk("init pin of group 0 ...\n");
			break;
		}
		case 1:
		{
			printk("init pin of group 1 ...\n");
			break;
		}
		case 2:
		{
			printk("init pin of group 2 ...\n");
			break;
		}
		case 3:
		{
			printk("init pin of group 3 ...\n");
			break;
		}
	}

	return 0;
}


/*
 *函数:    	board_imx6ull_led_init
 *功能:		打印某个gpio引脚设置信息
 *输入参数:which---哪一个引脚
 			status:LED状态,1/0亮灭
 *返回参数:如果成功返回0
*/
static int board_imx6ull_led_ctl(int which, char status)
{
	printk("set led %s: group %d, pin %d\n", status ? "on" : "off", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));
	switch(GROUP(g_ledpins[which]))
	{
		case 0:
		{
			printk("set pin of group 0 ...\n");
			break;
		}
		case 1:
		{
			printk("set pin of group 1 ...\n");
			break;
		}
		case 2:
		{
			printk("set pin of group 2 ...\n");
			break;
		}
		case 3:
		{
			printk("set pin of group 3 ...\n");
			break;
		}
	}

	return 0;
}


static struct led_operations board_imx6ull_led_opr =
{
	.init = board_imx6ull_led_init,
	.ctl  = board_imx6ull_led_ctl,
};

struct led_operations *get_board_led_opr(void)
{
    return &board_imx6ull_led_opr;
}


/*
 *函数:chip_imx6ull_gpio_led_probe
 *功能:记录引脚
 		创建设备,device_create
*/
static int chip_imx6ull_gpio_led_probe(struct platform_device *dev)
{
	int i = 0;
	struct resource *res;
	while(1)
	{
		//设备、哪一类资源、第几个资源
		res = platform_get_resource(dev,IORESOURCE_IRQ, i++);
		if(!res)
			break;
		g_ledpins[g_ledcnt] = res->start;

		/*创建设备*/
		led_device_create(g_ledcnt);
		g_ledcnt++;
		
	}
	return 0;
}

/*
 *函数:chip_imx6ull_gpio_led_remove
 *功能:注销设备,device_destory
*/
static int chip_imx6ull_gpio_led_remove(struct platform_device *dev)
{
	int i;
	for(i = 0; i < g_ledcnt; i++)
	{
		led_device_destory(i);
	}
	g_ledcnt = 0;
	return 0;
}


static struct platform_driver chip_imx6ull_gpio_driver = {
    .probe      = chip_imx6ull_gpio_led_probe,
    .remove     = chip_imx6ull_gpio_led_remove,
    .driver     = {
        .name   = "100ask_led",
    },
};


/*入口函数,platform_driver
 *功能:注册设备
 		底层到上层的注册
*/
static int chip_imx6ull_gpio_drv_init(void)
{
	int err;
	err = platform_driver_register(&chip_imx6ull_gpio_driver);
	register_led_operations(&board_imx6ull_led_opr);
	return 0;
}

/*出口函数,paltform_driver*/
static void chip_imx6ull_gpio_drv_exit(void)
{
	platform_driver_unregister(&chip_imx6ull_gpio_driver);
}


module_init(chip_imx6ull_gpio_drv_init);
module_exit(chip_imx6ull_gpio_drv_exit);
MODULE_LICENSE("Dual BSD/GPL");

 4.2.5 Makefile代码

上述程序的变动,Makefile也有一定的改动。 

# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o led_test led_test.c 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
	rm -f led_test
	
# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o

obj-m	+= led_drive.o chip_imx6ull_gpio.o board_A_led.o

 4.3 测试运行

//复制到开发板上
cp /mnt/board_A_led.ko ./
cp /mnt/chip_imx6ull_gpio.ko ./
cp /mnt/led_drive.ko ./
cp /mnt/led_test ./

//安装驱动模块
insmod board_A_led.ko
insmod led_drive.ko
insmod chip_imx6ull_gpio.ko
//GPIO5_3
./led_test  /dev/myled0 on
./led_test  /dev/myled0 off

//GPIO3_1
./led_test  /dev/myled1 on
./led_test  /dev/myled1 off
dmesg

打印结果: 

【Linux】遇事不决,可先点灯,LED驱动的进化之路---1

4.4 总结 

相较第三节上代码上的变化,可以感受到总线设备驱动模型框架的优点,在原有基础上进一步实现了对device和driver的分离。

在应对处理不同的设备时,这种方式就能更好的进行管理。

五、LED驱动程序(设备树)

5.1 设备树

5.1.1 设备树背景

我们从第四节的内容中发现,如果我们修改LED所用的GPIO引脚,我们需要修改board_A_led.c代码,之后重新编译加载驱动。

随着ARM芯片的流行,内核中针对不同厂商的开发保存里大量类似,没有技术含量的文件。

那么针对上述的问题,有没有好的解决方案呢?

上述问题的核心,就是在于是用.c文件来配置资源。这里就引入了专门的配置文件,这里就是用设备树来实现这一点。

采用设备树后,许多硬件的细节可以直接通过它传递给Linux,而不再需要在内核中进行大量的冗余编码,它通过bootloader将硬件资源传给内核,使得内核和文件资源描述相对独立。【Linux】遇事不决,可先点灯,LED驱动的进化之路---1

 最终的效果,设备在脚本里,驱动在c里。

 【Linux】遇事不决,可先点灯,LED驱动的进化之路---1

5.1.2 设备树简述

设备树包含DTC (device tree compiler),DTS (device tree source) 和DTB (device tree blob)。

【Linux】遇事不决,可先点灯,LED驱动的进化之路---1

dtc、dts/dtsi和dtb的关系:
dts和dtsi源文件会经过dtc编译器编译成dtb二进制文件,dtb文件最后会被放到系统中被内核解析。

【Linux】遇事不决,可先点灯,LED驱动的进化之路---1

具体使用的语法和设备树解析,详细参照:

https://blog.csdn.net/qq_33487044/article/details/126325656

 怎么使用设备树写驱动程序?

注意的地方: 

  • 1.设备树节点与platform_driver能匹配
  • 2.设备树节点指定资源,platform_driver获得资源

5.2 程序实战

设备树内容繁杂,不在这里详细阐述。这里的程序实现简单功能和走完整个流程。

后面主要展示改动的部分程序,设备树文件、驱动程序(chip_imx6ull_gpio.c),board_A_led.c文件则是不需要了。

5.2.1 设备树dts文件(100ask_led.dts)

修改设备树,添加设备节点100ask_led@0和100ask_led@1。

#define GROUP_PIN(g,p) ((g<<16) | (p))

/ {
	100ask_led@0 {
		compatible = "100as,leddrv";
		pin = <GROUP_PIN(3, 1)>;
	};

	100ask_led@1 {
		compatible = "100as,leddrv";
		pin = <GROUP_PIN(5, 8)>;
	};

};

imx6ull pro开发板里内核源码目录中arch/arm/boot/dts/100ask_imx6ull-14x14.dts,修改编译后得到arch/arm/boot/dts/100ask_imx6ull-14x14.dtb。 

【Linux】遇事不决,可先点灯,LED驱动的进化之路---1

详细步骤:(PC端) 

【Linux】遇事不决,可先点灯,LED驱动的进化之路---1

 5.2.2 驱动程序(chip_imx6ull_gpio.c)

这里主要设置of_match_table成员,用于设备树节点和platform_driver匹配之后。probe函数里获取资源的方式转变,通过读取设备树文件获取资源。

#include <linux/module.h>

#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/platform_device.h>


#include "led_drive.h"
#include "led_opr.h"
#include "led_resource.h"


static int g_ledpins[100];
static int g_ledcnt = 0;


/*阐述说明:
 *现阶段仅以示意总线设备驱动模型框架
*/

/*
 *函数:    	board_imx6ull_led_init
 *功能:		获取gpio引脚信息
 *输入参数:which---哪个引脚
 *返回参数:如果成功返回0
*/
static int board_imx6ull_led_init(int which)
{
	//printk("%s %s line %d, led %d\n",__FILE__,__FUNCTION__,__LINE__,which);
	printk("init gpio: group %d, pin %d\n", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));
	switch(GROUP(g_ledpins[which]))
	{
		case 0:
		{
			printk("init pin of group 0 ...\n");
			break;
		}
		case 1:
		{
			printk("init pin of group 1 ...\n");
			break;
		}
		case 2:
		{
			printk("init pin of group 2 ...\n");
			break;
		}
		case 3:
		{
			printk("init pin of group 3 ...\n");
			break;
		}
	}

	return 0;
}


/*
 *函数:    	board_imx6ull_led_init
 *功能:		打印某个gpio引脚设置信息
 *输入参数:which---哪一个引脚
 			status:LED状态,1/0亮灭
 *返回参数:如果成功返回0
*/
static int board_imx6ull_led_ctl(int which, char status)
{
	printk("set led %s: group %d, pin %d\n", status ? "on" : "off", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));
	switch(GROUP(g_ledpins[which]))
	{
		case 0:
		{
			printk("set pin of group 0 ...\n");
			break;
		}
		case 1:
		{
			printk("set pin of group 1 ...\n");
			break;
		}
		case 2:
		{
			printk("set pin of group 2 ...\n");
			break;
		}
		case 3:
		{
			printk("set pin of group 3 ...\n");
			break;
		}
	}

	return 0;
}


static struct led_operations board_imx6ull_led_opr =
{
	.init = board_imx6ull_led_init,
	.ctl  = board_imx6ull_led_ctl,
};

struct led_operations *get_board_led_opr(void)
{
    return &board_imx6ull_led_opr;
}


/*
 *函数:chip_imx6ull_gpio_led_probe
 *功能:记录引脚
 		创建设备,device_create
*/
static int chip_imx6ull_gpio_led_probe(struct platform_device *dev)
{
	int i = 0;
	struct resource *res;
	struct device_node *p;
	int led_pin;

	p = dev->dev.of_node;
	if(!p)
		return -1;

	//读取pin属性的值保存在led_pin变量里
	int err = of_property_read_u32(p, "pin", &led_pin);	
	g_ledpins[g_ledcnt] = led_pin;
	
	/*创建设备*/
	led_device_create(g_ledcnt);
	g_ledcnt++;
		
	return 0;
}

/*
 *函数:chip_imx6ull_gpio_led_remove
 *功能:注销设备,device_destory
*/
static int chip_imx6ull_gpio_led_remove(struct platform_device *dev)
{
	int i;
	for(i = 0; i < g_ledcnt; i++)
	{
		led_device_destory(i);
	}
	g_ledcnt = 0;
	return 0;
}

//of_match_table成员,用于设备树节点和platform_driver的匹配上
static const struct of_device_id ask100_leds[] = {
    { .compatible = "100as,leddrv" },
    { },
};

static struct platform_driver chip_imx6ull_gpio_driver = {
    .probe      = chip_imx6ull_gpio_led_probe,
    .remove     = chip_imx6ull_gpio_led_remove,
    .driver     = {
        .name   = "100ask_led",
		.of_match_table = ask100_leds,
    },
};


/*入口函数,platform_driver
 *功能:注册设备
 		底层到上层的注册
*/
static int chip_imx6ull_gpio_drv_init(void)
{
	int err;
	err = platform_driver_register(&chip_imx6ull_gpio_driver);
	register_led_operations(&board_imx6ull_led_opr);
	return 0;
}

/*出口函数,paltform_driver*/
static void chip_imx6ull_gpio_drv_exit(void)
{
	platform_driver_unregister(&chip_imx6ull_gpio_driver);
}


module_init(chip_imx6ull_gpio_drv_init);
module_exit(chip_imx6ull_gpio_drv_exit);
MODULE_LICENSE("Dual BSD/GPL");

 5.2.3 Makefile代码

相较上节的内容,这里不再需要board_A_led.c。 

# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o led_test led_test.c 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
	rm -f led_test
	
# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o

obj-m	+= led_drive.o chip_imx6ull_gpio.o

5.3 总结

测试程序:(设备树加载情况)

//加载设备树文件
cp /mnt/100ask_imx6ull-14x14.dtb /boot/
reboot
cd /sys/firmware/devicetree/base/
ls -ld *100ask*
cd 100ask_led@0
ls
cat compatible
hexdump pin

【Linux】遇事不决,可先点灯,LED驱动的进化之路---1

后续led_test测试程序的运行结果跟上一章节完全一致,这里是管理资源的方式发生了转变。

 相信大家在过完上述LED的驱动进化之路,会有一种酣畅淋漓的感觉。文章来源地址https://www.toymoban.com/news/detail-468074.html

到了这里,关于【Linux】遇事不决,可先点灯,LED驱动的进化之路---1的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • ARMday04(开发版简介、LED点灯)

    开发板为stm32MP157AAA,附加一个拓展版 PCB PCB( Printed Circuit Board),中文名称为印制电路板,又称印刷线路板,是重要的电子部件,是电子元器件的支撑体,是电子元器件电气相互连接的载体。由于它是采用电子印刷术制作的,故被称为“印刷”电路板。 电路板丝印  可以通过

    2024年02月02日
    浏览(39)
  • DAY3,ARM(LED点灯实验)

    结果:(我的板LD1坏了,所以不亮) 

    2024年02月12日
    浏览(40)
  • Linux下LED设备驱动开发(LED灯实现闪烁)

    前面我们介绍了Linux设备模型、平台设备驱动、设备树(device tree)、GPIO子系统以及pinctrl子系统等,大家看这篇文章之前需要提前知道的基础都在这篇文章中: Linux设备模型、平台设备驱动、设备树(device tree)、GPIO子系统以及pinctrl子系统介绍 有部分函数没有涉及到的最后会讲解

    2024年02月17日
    浏览(43)
  • Linux -- 字符设备驱动--LED的驱动开发(初级框架)

    看原理图确定引脚,确定引脚输出什么电平才能点亮 / 熄灭 LED 看主芯片手册,确定寄存器操作方法:哪些寄存器?哪些位?地址是? 编写驱动:先写框架,再写硬件操作的代码 注意 :在芯片手册中确定的寄存器地址被称为 物理地址 ,在 Linux 内核中无法直接使用。 需要使

    2024年04月28日
    浏览(37)
  • [Linux_IMX6ULL驱动开发]-LED驱动

    其实在本人的理解看来,在驱动上面操控LED,和使用STM32在操控LED是大同小异的,因为本质都是控制引脚的输出电平,来达到点亮或者熄灭LED的作用,在这里,我们想要操控LED,我们首先要先清除它的原理图是什么样的。 如上图所示可知,想要点亮LED,那么我们需要控制引脚

    2024年04月14日
    浏览(36)
  • Linux 驱动开发基础知识——认识LED驱动程序 (二)

     个人名片: 🦁作者简介:一名喜欢分享和记录学习的在校大学生 🐯个人主页:妄北y 🐧个人QQ:2061314755 🐻个人邮箱:2061314755@qq.com 🦉个人WeChat:Vir2021GKBS 🐼 本文由妄北y原创,首发CSDN 🎊🎊🎊 🐨座右铭:大多数人想要改造这个世界,但却罕有人想改造自己。 专栏导

    2024年01月21日
    浏览(40)
  • FPGA实战-----点灯大师(1)led灯闪烁流水跑马+按键

    FPGA实战 用verliog语言点亮FPGA开发板上的led灯是最最最最最基础的操作。 这里用的EP4CE6F17C8开发板,上边一共有四个led灯珠,可以实现简单的例如4个全亮,流水灯,跑马灯以及相比之下难了一点的呼吸灯等等等等效果。 本文最终目的就是实现 用按键切换led灯的运动模式 。做

    2024年02月04日
    浏览(49)
  • Linux驱动-基于QT控制LED灯

    平台 韦东山100ask imax6ull pro 大象嵌入式开发板 Build Root 使用Build root编译image,具体配置可参考《嵌入式Linux应用开发完全手册-IMX6ULL开发板(从零移植篇-预览版)-V0.1.pdf》,使用buildroot后仅需要配置menuconfig即可自动编译出完整的镜像,而且各种安装包也可以在 output/build 目录下拿

    2024年02月13日
    浏览(97)
  • Zynq(2):MIO,EMIO点灯之路

    由于个人原因,最近一直在对基础知识的复习,所以ZYNQ的后续学习记录,一直没有更新。 FLAG:新年新气象,争取2022年春节之前将所有关于ZYNQ中ARM裸机部分内容更新完毕,主要是ARM外设。 ZYNQ 分为 PS 和 PL 两部分,那么器件的引脚(Pin)资源同样也分成了两部分。ZYNQ PS 中的

    2024年02月13日
    浏览(41)
  • STM32——LED内容补充(寄存器点灯及反转的原理)

    本篇文章使用的是STM32F103xC系列的芯片,四个led灯在PE2,PE3,PE4,PE5上连接 1.开时钟 2.配置IO口 (1)清零指定寄存器位 (2)设置模式为推挽输出模式(led灯低电平有效) 3.设置开关灯 4.宏定义灯的反转 开时钟 1.先看时钟树,找到PE总线在APB2时钟 2.我们先来打开对应芯片的参考手

    2024年02月14日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包