本文主要是介绍【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驱动进化之路:(层次递进)
- 最简单的LED驱动程序
- 加入分层思想的LED驱动程序
- 加入分离思想的LED驱动程序
- 总线设备驱动模型下的LED驱动程序
- 加入设备树的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驱动程序框架完全一致
- 确定主设备号,(可以自己定义,设置为0时会让内核分配)
- 定义自己的file_operations结构体管理驱动程序drv_open/drv_write等,实现对应的drv_open/drv_write等函数。---注:file_operations为核心
- 把file_operations结构体告诉内核:register_chrdev(提供主设备号)。
- 实现入口函数,安装驱动程序时,入口函数调用register_chrdev,相应的就有出口函数,卸载驱动程序时,出口函数调用unregister_chrdev
- 辅助性功能:提供设备信息,自动创建设备节点: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的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!