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

2024-03-02 15:40

本文主要是介绍【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。

编写驱动程序的步骤:跟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				 使能gpioconfigure pin as gpio    设置引脚为GPIOconfigure 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 offint 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]);}//openfd = open(argv[1], O_RDWR);if(fd < 0){printf("can not open%s\n",argv[1]);return -1;}//writeif(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.88all: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 cleanrm -rf modules.orderrm -f led_test_simpleobj-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

查询设备结果:

 点灯和关灯:

二、LED驱动程序(分层)

2.1 分层设计思想

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

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

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

这里再进一步,以面向对象的思想,抽象出一个结构体,每个单板相关的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_Hstruct 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				 使能gpioconfigure pin as gpio    设置引脚为GPIOconfigure 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的设备节点,多个次设备号控制多个LEDfor(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 offint 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]);}//openfd = open(argv[1], O_RDWR);if(fd < 0){printf("can not open %s\n",argv[1]);return -1;}//writeif (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.88all:make -C $(KERN_DIR) M=`pwd` modules $(CROSS_COMPILE)gcc -o led_test led_test.c clean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderrm -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

查询设备结果:

点灯和关灯:

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),如下图所示:

 这里就抽象出一个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代码。具体实现框架如下:

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				 使能gpioconfigure pin as gpio    设置引脚为GPIOconfigure 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.88all:make -C $(KERN_DIR) M=`pwd` modules $(CROSS_COMPILE)gcc -o led_test led_test.c clean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderrm -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

打印演示: 

3.4 总结

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

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

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

4.1 总线设备驱动模型

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

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

后续进一步采用bus总线来管理platform_device  /platform_driver。如下图所示: 

4.2 程序实战

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

具体的各个步骤如下: 

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.88all:make -C $(KERN_DIR) M=`pwd` modules $(CROSS_COMPILE)gcc -o led_test led_test.c clean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderrm -f led_test# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.oobj-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

打印结果: 

4.4 总结 

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

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

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

5.1 设备树

5.1.1 设备树背景

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

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

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

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

采用设备树后,许多硬件的细节可以直接通过它传递给Linux,而不再需要在内核中进行大量的冗余编码,它通过bootloader将硬件资源传给内核,使得内核和文件资源描述相对独立。

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

 

5.1.2 设备树简述

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

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

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

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。 

详细步骤:(PC端) 

 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.88all:make -C $(KERN_DIR) M=`pwd` modules $(CROSS_COMPILE)gcc -o led_test led_test.c clean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderrm -f led_test# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.oobj-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

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

 相信大家在过完上述LED的驱动进化之路,会有一种酣畅淋漓的感觉。

这篇关于【Linux】遇事不决,可先点灯,LED驱动的进化之路---1的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/766618

相关文章

Linux使用nload监控网络流量的方法

《Linux使用nload监控网络流量的方法》Linux中的nload命令是一个用于实时监控网络流量的工具,它提供了传入和传出流量的可视化表示,帮助用户一目了然地了解网络活动,本文给大家介绍了Linu... 目录简介安装示例用法基础用法指定网络接口限制显示特定流量类型指定刷新率设置流量速率的显示单位监控多个

ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法

《ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法》本文介绍了Elasticsearch的基本概念,包括文档和字段、索引和映射,还详细描述了如何通过Docker... 目录1、ElasticSearch概念2、ElasticSearch、Kibana和IK分词器部署

Linux流媒体服务器部署流程

《Linux流媒体服务器部署流程》文章详细介绍了流媒体服务器的部署步骤,包括更新系统、安装依赖组件、编译安装Nginx和RTMP模块、配置Nginx和FFmpeg,以及测试流媒体服务器的搭建... 目录流媒体服务器部署部署安装1.更新系统2.安装依赖组件3.解压4.编译安装(添加RTMP和openssl模块

linux下多个硬盘划分到同一挂载点问题

《linux下多个硬盘划分到同一挂载点问题》在Linux系统中,将多个硬盘划分到同一挂载点需要通过逻辑卷管理(LVM)来实现,首先,需要将物理存储设备(如硬盘分区)创建为物理卷,然后,将这些物理卷组成... 目录linux下多个硬盘划分到同一挂载点需要明确的几个概念硬盘插上默认的是非lvm总结Linux下多

linux进程D状态的解决思路分享

《linux进程D状态的解决思路分享》在Linux系统中,进程在内核模式下等待I/O完成时会进入不间断睡眠状态(D状态),这种状态下,进程无法通过普通方式被杀死,本文通过实验模拟了这种状态,并分析了如... 目录1. 问题描述2. 问题分析3. 实验模拟3.1 使用losetup创建一个卷作为pv的磁盘3.

Linux环境变量&&进程地址空间详解

《Linux环境变量&&进程地址空间详解》本文介绍了Linux环境变量、命令行参数、进程地址空间以及Linux内核进程调度队列的相关知识,环境变量是系统运行环境的参数,命令行参数用于传递给程序的参数,... 目录一、初步认识环境变量1.1常见的环境变量1.2环境变量的基本概念二、命令行参数2.1通过命令编程

Linux之进程状态&&进程优先级详解

《Linux之进程状态&&进程优先级详解》文章介绍了操作系统中进程的状态,包括运行状态、阻塞状态和挂起状态,并详细解释了Linux下进程的具体状态及其管理,此外,文章还讨论了进程的优先级、查看和修改进... 目录一、操作系统的进程状态1.1运行状态1.2阻塞状态1.3挂起二、linux下具体的状态三、进程的

Linux编译器--gcc/g++使用方式

《Linux编译器--gcc/g++使用方式》文章主要介绍了C/C++程序的编译过程,包括预编译、编译、汇编和链接四个阶段,并详细解释了每个阶段的作用和具体操作,同时,还介绍了调试和发布版本的概念... 目录一、预编译指令1.1预处理功能1.2指令1.3问题扩展二、编译(生成汇编)三、汇编(生成二进制机器语

Rsnapshot怎么用? 基于Rsync的强大Linux备份工具使用指南

《Rsnapshot怎么用?基于Rsync的强大Linux备份工具使用指南》Rsnapshot不仅可以备份本地文件,还能通过SSH备份远程文件,接下来详细介绍如何安装、配置和使用Rsnaps... Rsnapshot 是一款开源的文件系统快照工具。它结合了 Rsync 和 SSH 的能力,可以帮助你在 li

Linux部署jar包过程

《Linux部署jar包过程》文章介绍了在Linux系统上部署Java(jar)包时需要注意的几个关键点,包括统一JDK版本、添加打包插件、修改数据库密码以及正确执行jar包的方法... 目录linux部署jar包1.统一jdk版本2.打包插件依赖3.修改密码4.执行jar包总结Linux部署jar包部署