韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型)

这篇具有很好参考价值的文章主要介绍了韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


本人学习完韦老师的视频,因此来复习巩固,写以笔记记之。
韦老师的课比较难,第一遍不知道在说什么,但是坚持看完一遍,再来复习,基本上就水到渠成了。
看完视频复习的同学观看最佳!
基于 IMX6ULL-PRO
参考视频 Linux快速入门到精通视频
参考资料:01_嵌入式Linux应用开发完全手册V5.1_IMX6ULL_Pro开发板.pdf

2024.4.18更新 利用图解方式探索内核函数调用
代码上传到git网站驱动代码

一、Hello 驱动编程

1-1 APP 打开的文件在内核中如何表示

APP打开文件时,得到一个整数fd,被称为文件句柄。对于APP的每一个文件句柄,在内核里面都有一个struct file结构体(include/linux/fs.h)与之对应。
韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型),Linux嵌入式,驱动开发,ubuntu,linux,嵌入式,c语言
open打开文件时,传入的flags、mode等参数会被记录在内核中对应的struct file 结构体里f_flagsf _mode
去读写文件时,文件的当前偏移地址也会保存在f _pos 成员里。

int open(const char *pathname, int flags, mode_t mode);
1-2 打开字符设备节点时,内核中也有对应的 struct file

struct file_operations *f_op,由驱动程序提供。struct file_operations结构体是Linux内核中用于描述文件操作函数集合的结构体。它包含了许多函数指针成员,每个成员对应于不同的文件操作。
韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型),Linux嵌入式,驱动开发,ubuntu,linux,嵌入式,c语言

1-3 Hello 驱动程序

如何编写驱动程序步骤
1、确定主设备号,也可让内核分配
2、定义自己file_operations结构体
3、实现drv_open()、drv_read()、drv_write()等函数,写入file_operations结体
4、file_operations结构体告诉内核:register_chrdev()
5、谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
6、有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用unregister_chrdev
7、其他完善:提供设备信息,自动创建设备节点:class_create, device_create

分析Hello 驱动程序代码

/*头文件省略,记得找到源码添加即可*/
#define MIN(a,b) ((a < b) ? a: b)

/*驱动程序*/
/*1、确定主设备号,也可以让内核分配*/
static int major = 0;   //设备号为0 系统自动分配
static char kernel_buf[1024];
static struct class *hello_class;

/*3、实现对应的 drv_open/drv_read/drv_write等函数,填入 file_operations结构体*/
static ssize_t hello_drv_read(struct file *file, char __user * buf, size_t size, loff_t * offset)
{
	int err;
	/*驱动调试 屏幕打印文件名,函数名,行号*/
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_to_user(buf, kernel_buf, MIN(1024, size));  //内核存储的数据读到用户buf中
	return MIN(1024, size);
}

static ssize_t hello_drv_write(struct file *file, const char __user * buf, size_t size, loff_t * offset)
{
	int err;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_from_user(kernel_buf,  buf, MIN(1024, size));  //用户buf的数据写到内核中
	return MIN(1024, size);
}

static int hello_drv_open(struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

static int hello_drv_close(struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

/*2、定义自己的 file_operations结构体*/
static struct file_operations hell_drv = {
	 .owner = THIS_MODULE,
	 .open = hello_drv_open,
	 .read = hello_drv_read,
	 .write = hello_drv_write,
	 .release = hello_drv_close,
};

/*4、把 file_operations结构体告诉内核: register_chrdev 谁来注册驱动程序啊?*/
/*5、得有一个入口函数:安装驱动程序时,就会去调用这个入口函数*/
static int __init hello_init(void)
{
	int err;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	
	/*注册驱动程序*/
	major = register_chrdev(0, "hello", &hell_drv);  
	/*创建设备节点*/
	hello_class = class_create(THIS_MODULE, "hello_class");
	
	err = PTR_ERR(hello_class);  /*错误处理函数*/
	if (IS_ERR(hello_class)){
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(0, "hello");
		return -1;
	}
	//device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...)
	device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello");  /* /dev/hello */

	return 0;
}

/*6、有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用 unregister_chrdev */
static void __exit hello_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	device_destroy(hello_class, MKDEV(major, 0)); //MKDEV(major, 0)主设备号 次设备号
	class_destroy(hello_class);
	
	unregister_chrdev(0, "hello");
}

/*7、其他完善:提供设备信息,自动创建设备节点: class_create, device_create*/
module_init(hello_init);   	//修饰为入口函数
module_exit(hello_exit);
MODULE_LICENSE("GPL");	//遵守GPL协议

register_chrdev()是Linux内核中用于注册字符设备驱动程序的函数。在Linux系统中,字符设备是一种提供面向字符的I/O操作的设备,例如终端、打印机、鼠标、键盘、LED、I2C、SPI等。

int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
major = register_chrdev(0, "hello", &hell_drv);  

其中参数含义如下:
major: 主设备号,用于唯一标识该字符设备驱动程序。
name: 设备名称,用于在/proc/devices中显示。
fops: 指向struct file_operations结构体的指针,包含了该字符设备支持的操作函数。
当调用register_chrdev()函数时,内核会为指定的字符设备注册一个主设备号,并将其与对应的操作函数关联起来。这样,在用户空间通过设备文件和系统调用就能够访问和操作这个字符设备了。

class_create()是Linux内核中用于创建一个新的设备类的函数。在Linux系统中,设备类是一种将相关设备实例进行组织和分类的机制。

struct class *class_create(struct module *owner, const char *name);
hello_class = class_create(THIS_MODULE, "hello_class");

其中参数含义如下:
owner: 指向struct module类型的指针,表示拥有该设备类的模块。
name: 设备类的名称,用于在/sys/class/目录下创建相应的子目录。
成功调用class_create()函数后,会在/sys/class/目录下创建一个与指定名称对应的子目录,用于存放属于该设备类的设备实例。

device_create()是Linux内核中用于创建一个设备实例的函数。在Linux系统中,设备实例是设备类中的具体设备对象。通过device_create()函数可以创建一个新的设备实例,并将其与指定的设备类进行关联。

struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *format, ...);
device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello");  /* /dev/hello */

其中参数含义如下:
class: 指向struct class类型的指针,表示要创建设备实例所属的设备类。
parent: 指向struct device类型的指针,表示要创建设备实例的父设备。通常为NULL,表示没有父设备。
devt: 设备号,用于标识设备实例的唯一性。
drvdata: 指向设备驱动程序特定的数据结构的指针,可以传递给设备实例。
format: 设备名称的格式化字符串,用于在/sys/class/<class_name>/目录下创建相应的设备实例目录。
成功调用device_create()函数后,会在/sys/class/<class_name>/目录下创建一个与指定格式化字符串对应的设备实例目录,通过在/dev目录下创建相应的设备节点。

1-4 测试

(1) 修改makefile文件,内核路径对应于自己系统上的

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

(2) 打印内核信息,在串口处设置快捷方式
韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型),Linux嵌入式,驱动开发,ubuntu,linux,嵌入式,c语言

echo "7 4 1 7" > /proc/sys/kernel/printk

控制内核打印信息级别,>7的才输出。即不输出内核打印信息

/include/linux/kern_levels.h 
#define KERN_SOH    "\001"      /* ASCII Start Of Header */
 
#define KERN_EMERG  KERN_SOH "0"    /* system is unusable 系统崩溃前的信息*/
#define KERN_ALERT  KERN_SOH "1"    /* action must be taken immediately 需要立即处理的消息 */
#define KERN_CRIT   KERN_SOH "2"    /* critical conditions 严重情况*/
#define KERN_ERR    KERN_SOH "3"    /* error conditions 错误*/
#define KERN_WARNING    KERN_SOH "4"    /* warning conditions 警告*/
#define KERN_NOTICE KERN_SOH "5"    /* normal but significant condition 注意 */
#define KERN_INFO   KERN_SOH "6"    /* informational 普通信息*/
#define KERN_DEBUG  KERN_SOH "7"    /* debug-level messages 调试*/

(3) 装载驱动insmod
(4)显示的当前内核已经加载的模块和驱动lsmod
韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型),Linux嵌入式,驱动开发,ubuntu,linux,嵌入式,c语言
(5)卸载驱动rmmod
(6)显示主设备号 register_chrdev()

cat /proc/devices

韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型),Linux嵌入式,驱动开发,ubuntu,linux,嵌入式,c语言
(7)创建设备节点class_create()和device_create()
韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型),Linux嵌入式,驱动开发,ubuntu,linux,嵌入式,c语言

1-5 补充知识
(1) module_init/module_exit 的实现

一个驱动程序有入口函数、出口函数。驱动程序可以被编进内核里,也可以被编译为ko文件后手工加载。

module_init(hello_init)
module_exit(hello_exit);

当编译为ko文件时,使用insmod 命令加载驱动时,内核调用 init_module 函数,实际上就是调用hello_init函数;使用rmmod命令卸载驱动时,内核都是调用cleanup_module 函数,实际上就是调用hello_ exit 函数。

(2) register_chrdev 的内部实现

chrdevs[i]数组项是一个链表头,链表里每一个元素都是一个char_device_struct 结构体,每个元素表示一个驱动程序。

struct char_device_struct {
	struct char_device_struct *next;
	unsigned int major;
	unsigned int basem inor;
	int minorct;
	char name[64];
	struct cdev *cdev; /* will die */
}*chrdevs[CHRDEV_MAJOR_HASH_SIZE];

它指定了主设备号major 、次设备号 baseminor 、个数 minorct ,在 cdev中含有 file_operations 结构体。因此,内核通过主、次设备号,找到对应的file_operations 结构体”。
APP 打开某个字符设备节点时,进入内核。在内核里根据字符设备节点的主、次设备号,通过cdev_add()函数调用在cdev_map链表中快速得到cdev,再从cdev中得到file_operations结构体。
韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型),Linux嵌入式,驱动开发,ubuntu,linux,嵌入式,c语言

(3) class_destroy/device_create 浅析

在/sys目录下创建一些目录、文件,这样 Linux 系统中的 APP就可以根据这些目录或文件来创建设备节点。
韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型),Linux嵌入式,驱动开发,ubuntu,linux,嵌入式,c语言

二、GPIO基础知识

GPIO: General purpose input/output ,通用的输入输出口

2-1 GPIO结构

(1) 有多组GPIO,每组有多个GPIO。例如:GPIO2_IO15。
(2) 使能:电源/时钟
(3) 模式(Mode):引脚可用于GPIO或其他功能
(4) 方向:引脚Mode设置为GPIO时,可以继续设置它是输出引脚,还是输入引脚
(5) 数值:
◼ 对于输出引脚,可以设置寄存器让它输出高、低电平
◼ 对于输入引脚,可以读取寄存器得到引脚的当前电平

将某一位置1,采用操作

val = val |(1<<n)
val |= (1<<n)

将某一位清0,用与且取反操作

val = val & ~(1<<n)
val &= ~(1<<n)

GPIO控制涉及CCM、IOMUXC、GPIO模块本身等三个部分。
CCM:Clock Controller Module (时钟控制模块),用于设置是否向GPIO模块提供时钟。参考资料:芯片手册《Chapter 18: Clock Controller Module (CCM) 》

IOMUXC:IOMUX Controller,IO复用控制器。引脚的模式和功能,参考资料:芯片手册《Chapter 32: IOMUX Controller (IOMUXC) 》。
韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型),Linux嵌入式,驱动开发,ubuntu,linux,嵌入式,c语言
韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型),Linux嵌入式,驱动开发,ubuntu,linux,嵌入式,c语言

2-2 GPIO模块内部

韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型),Linux嵌入式,驱动开发,ubuntu,linux,嵌入式,c语言
① GPIOx_GDIR:设置引脚方向,每位对应一个引脚 1-output,0-input
② GPIOx_DR:设置输出引脚的电平,每位对应一个引脚 ,1-高电平, 0-低电平
③ GPIOx_PSR:读取引脚的电平,每位对应一个引脚 1-高电平, 0-低电平
GPIO设置步骤
1、使能时钟/电源
2、选择GPIO模式
3、 设置IO方向
4、设置高/低电平

三、LED驱动

3-1 最简单的LED驱动程序

韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型),Linux嵌入式,驱动开发,ubuntu,linux,嵌入式,c语言
SVC用于生成系统函数调用.例如,用户程序不允许直接访问硬件,操作系统可以通过SVC提供对硬件的访问。 因此,当用户程序想要使用某些硬件时,可以使用SVC指令,然后执行操作系统中的软件异常处理程序,并提供用户应用程序请求的服务。
(1) 驱动怎么操作硬件?
通过ioremap映射寄存器的物理地址得到虚拟地址,读写虚拟地址。
(2) 驱动怎么和APP传输数据?
通过 copy to_user()、copy_from_user() 这2个函数。

volatile关键字,防止程序被优化,如某一变量的点灯、灭灯(写操作:*p=0;*p=1读操作亦是如此)

ioremap()可以将物理地址映射为虚拟地址

 virt_addr = ioremap(phys_addr, size);

把物理地址phys_addr 开始的一段空间 ,映射为虚拟地址返回值是该段虚拟地址的首地址。实际上,它是按页4096字节进行映射的,即整页整页地映射的。

/*头文件参考源码即可*/
static int major;
static struct class *led_class;

/*register*/
//IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3地址:0x02290000 + 0x14
static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;

//GPIO5_GDIR地址:0x020AC004
static volatile unsigned int *GPIO5_GDIR;

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

static ssize_t led_write(struct file *file, const char __user *buf,
			 size_t count, loff_t *ppos)
{
	char val;
	int ret;
	/*copy_from_user : get data from app 用户空间的数据拷贝到内核空间
	*status中的数据在buf中
	*/
	ret = copy_from_user(&val, buf, 1);/*buf里的值是用户空间的state buf=&state*/

	/*to set GPIO register:out 1 or 0*/
	if(val){
		/*set gpio to let led on*/
		*GPIO5_DR &= ~(1<<3);	//输出0
	}
	else{
		/*set gpio to let led off 熄灭*/
		*GPIO5_DR |=(1<<3);	//输出1
	}

	return 1;
}

static int led_open(struct inode *inode, struct file *filp)
{
	/*enable gpio
	*configure gpio5_io3 as gpio
	*configure gpio5_io3 as output
	*/
	/*配置为GPIO引脚功能*/
	*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~0xf; //清零
	*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= 0x05;

	/*GPIO引脚配置为输出方式*/
	*GPIO5_GDIR |= (1<<3);
	return 0;
}

static struct file_operations led_fops = {
	.owner	= THIS_MODULE,
	.write	= led_write,
	.open	= led_open,
};

/*入口函数*/
static int __init led_init(void)
{

	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/*注册字符设备*/
	major = register_chrdev(0, "100ask_led", &led_fops);
	
	/*ioremap 将物理地址映射为虚拟地址*/
	//IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3地址:0x02290000 + 0x14
	IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x02290000 + 0x14, 4);
	
	//GPIO5_GDIR地址:0x020AC004
	GPIO5_GDIR = ioremap(0x020AC004, 4);
	
	//GPIO5_DR地址:0x020AC000
	GPIO5_DR = ioremap(0x020AC000, 4);
	
	led_class = class_create(THIS_MODULE, "myled");
	device_create(led_class, NULL, MKDEV(major, 0), NULL, "myled"); /*系统会创建 /dev/myled的设备节点*/

	return 0;
}

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, "100ask_led");
};
3-2 LED驱动程序框架

字符设备框架
韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型),Linux嵌入式,驱动开发,ubuntu,linux,嵌入式,c语言
LED驱动框架
韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型),Linux嵌入式,驱动开发,ubuntu,linux,嵌入式,c语言
应用程序访问驱动程序,会打开一个设备节点,根据设备节点的主、次设备号,在内核里找到file_operations结构体。
LED 驱动能支持多个板子的基础: 分层 思想
这里的目的其实就是把硬件部分代码剥离出来,这种构思的方法以此来引出后面的设备树
韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型),Linux嵌入式,驱动开发,ubuntu,linux,嵌入式,c语言

程序解析
(1) led_opr.h 面向对象编程,将LED抽象为一个led_operations 结构体

#ifndef _LED_OPR_H
#define _LED_OPR_H

struct led_operations{
	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
/*宏定义作用:防止头文件重复定义*/

#ifndef 宏名(_LED_OPR)
#define 宏名(_LED_OPR)

#endif
作用:防止头文件重复定义出错,即当a.h和b.h引用了该头文件,但是d.c又引用a.h和b.h文件,会导致引用两次。

(2) board_demo.c

#include <linux/gfp.h>
#include "led_opr.h"

static int board_demo_led_init(int which) /*初始化LED,which-哪个LED*/
{
	printk("%s %s line %d, led %d\n", __FILE__,__FUNCTION__, __LINE__, which);
	return 0;
}

static int board_demo_led_ctl(int which, char status) /*控制LED,which-哪个LED,status:1 亮 0灭*/
{
	printk("%s %s line %d, led %d\n", __FILE__,__FUNCTION__, __LINE__, which);
	return 0;
}

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

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

对led_operations结构体的实例化board_demo_led_opr以及函数体的实现

(3) lederv.c

p_led_opr = get_board_led_opr();

最终p_led_opr指向board_demo_led_opr结构体,通过调用其结构体成员(2个函数指针来实现相应内容)

3-3 课后作业1

实现读LED状态的功能:涉及APP和驱动。
思路单独编写read部分代码
韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型),Linux嵌入式,驱动开发,ubuntu,linux,嵌入式,c语言

3-4 具体单板的LED驱动程序

位于02_led_drv_for_boards文件夹中
其实就是在01文件夹中抽象的结构体对应的函数中的基础上添加了iMX6ULL具体硬件LED的信息,部分下面代码所示

		//效率太低 进行了两次读-修改-写操作
		//*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~(0xf); //清零
		//*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= (5);

		//2、设置GPIO5_IO03用于GPIO
		//只进行一次读操作 一次写操作
		val = *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
		val &= ~(0xf);
		val |= (5);
		*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = val;

课后作业1:在出口函数处添加iounmap()函数

static void __exit led_exit(void)
{
	iounmap(IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3);
	iounmap(GPIO5_GDIR);
	iounmap(GPIO5_DR);
	int i;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	for(i = 0; i < p_led_opr->num; i++){
		device_destroy(led_class, MKDEV(major, i)); //MKDEV(major, i)主设备号 次设备号
	}
	
	class_destroy(led_class);
	unregister_chrdev(0, "100ask_led"); /*注销驱动*/
}

课后作业2

/*部分代码*/
static int board_demo_led_init(int which) /*初始化LED,which-哪个LED*/
{
	unsigned int val;
	int i = 0;
	//printk("%s %s line %d, led %d\n", __FILE__,__FUNCTION__, __LINE__, which);
	if(which == 0){   /*which==次设备号*/
		if(!i){
		CCM_CCGR1 = ioremap(0x20C406C, 4);
		IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x02290014, 4);
		GPIO5_GDIR = ioremap(0x020AC004, 4);
		GPIO5_DR = ioremap(0x020AC000, 4);
		i++;
		}

		//1、使能GPIO5
		*CCM_CCGR1 |= (3<<30);

		//效率太低 进行了两次读-修改-写操作
		//*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~(0xf); //清零
		//*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= (5);
		
		//2、设置GPIO5_IO03用于GPIO
		//只进行一次读操作 一次写操作
		val = *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
		val &= ~(0xf);
		val |= (5);
		*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = val;
		
		//3、设置GPIO5_IO03作为output引脚
		*GPIO5_GDIR |= (1<<3);
	}
	else{
		/*由于IM6ULL只有一个灯可以点,故难以找到对应LED的GIIO口,所以提供思路即可。若有LED,在此处配置第二个LED即可*/
	}
	return 0;
}

static int board_demo_led_ctl(int which, char status) /*控制LED,which-哪个LED,status:1 亮 0灭*/
{
	//printk("%s %s line %d, led %d\n", __FILE__,__FUNCTION__, __LINE__, which);
	if(which == 0){
		if(status){		/*on: output 0 点灯*/
			*GPIO5_DR &= ~(1<<3);
		}
		else{			/*off: output 1 熄灭*/
			*GPIO5_DR |= (1<<3);
		}	
	}
	else{
		if(status){		/*on: output 0 点灯*/
			//同理,若有LED灯,在此处配置即可 *GPIO5_DR &= ~(1<<3);
		}
		else{			/*off: output 1 熄灭*/
			//*GPIO5_DR |= (1<<3);
		}		
	}
	return 0;
}
static struct led_operations board_demo_led_opr = {
	.num = 2,
	.init = board_demo_led_init,
	.ctl = board_demo_led_ctl,

};

四、驱动设计的思想

在面向对象时,字符设备驱动程序抽象出一个file_operations结构体;LED驱动程序针对硬件部分抽象出led_operations结构体。
分层思想:
韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型),Linux嵌入式,驱动开发,ubuntu,linux,嵌入式,c语言
分离思想:
韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型),Linux嵌入式,驱动开发,ubuntu,linux,嵌入式,c语言
对于引脚的操作是相同的,那可以针对该芯片写出比较通用的硬件操作代码;
比如board_A.c 使用芯片Y ,那就可以写出 chipY_gpio.c,它实现芯片Y的GPIO 操作,适用于芯片Y的所有GPIO引脚。使用时,我们只需要在board_A_led.c 中指定使用哪一个引脚即可。

五、总线设备驱动模型

5-1 简介

对于硬件资源,用platform_device结构体来表示;对于硬件的操作,用platform_driver结构体表示。
内核源码: include\linux\platform_device.h
韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型),Linux嵌入式,驱动开发,ubuntu,linux,嵌入式,c语言
在内核中有一个虚拟的总线platform_bus_type,它有2个链表结构,左边是
设备Dev链表,右边是驱动Drv链表。左边的设备链表和右边的驱动链表会进行一一比较(通过platform_match函数),若匹配成功,就会调用platform_driver中的probe函数。
如何进行匹配?

(1) platform_device结构体
韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型),Linux嵌入式,驱动开发,ubuntu,linux,嵌入式,c语言
(2) platform_driver结构体
韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型),Linux嵌入式,驱动开发,ubuntu,linux,嵌入式,c语言
(3) platform_match
韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型),Linux嵌入式,驱动开发,ubuntu,linux,嵌入式,c语言
韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型),Linux嵌入式,驱动开发,ubuntu,linux,嵌入式,c语言
总线设备驱动编写程序步骤
(1) 分配、设置、注册platform_device结构体,在里面定义所用资源,指定设备名字。
(2) 分配、设置、注册platform_driver结构体,在其中的probe函数里,分配、设置、注册file_operations结构体,并从platform_device 中确实所用硬件资源,指定platform_driver的名字。

5-2 LED总线设备驱动模型

之前的LED驱动框架
韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型),Linux嵌入式,驱动开发,ubuntu,linux,嵌入式,c语言
现目标框架如下
韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型),Linux嵌入式,驱动开发,ubuntu,linux,嵌入式,c语言
解释:
1、leddrv.c注册驱动程序,创建设备节点类,platform_device(相当于后面的设备树)指定硬件信息,platform_driver指定对硬件信息进行操作(初始化、控制)。
2、注册平台设备驱动两个结构体,并且调用register_led_operations(&board_demo_led_opr)()提前为leddrv.c打开接口。平台总线设备驱动匹配后,调用chip_demo_gpio_probe()来获取硬件资源信息,并且再调用led_class_create_device(),创建设备节点。/dev/100ask_led0,1,…
3、此时应用层open函数可以打开设备,在调用驱动程序的led_drv_open(),在函数中初始化硬件p_led_opr->init(minor),最终调用到底层board_demo_led_init()函数。

1、返回该dev中某类型type资源中的第几个num

struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num)

2、返回该dev所用的第几个num中断

int platform_get_irq(struct platform_device *dev, unsigned int num)

3、通过名字name)返回该 dev的某类型type资源

struct resource *platform_get_resource_byname(struct platform_device *dev, unsigned int type, const char *name)

4、通过名字name)返回该dev的中断号

int platform_get_irq_byname(struct platform_device *dev const char *name)

程序解析
board_A_led.c中构造platform_device,提供LED硬件相关资源

static void led_dev_release(struct device *dev)
{
}

static struct resource resources[] = {
        {
                .start = GROUP_PIN(3,1),
                .flags = IORESOURCE_IRQ, /*表示是哪一类资源 这里是假设IRQ表示引脚*/
                .name = "100ask_led_pin",
        },
        {
                .start = GROUP_PIN(5,8),
                .flags = IORESOURCE_IRQ,
                .name = "100ask_led_pin",
        },
};

static struct platform_device board_A_led_dev = {
        .name = "100ask_led",
        .num_resources = ARRAY_SIZE(resources),
        .resource = resources,
        .dev = {
                .release = led_dev_release, /*防止在调用
platform_device_unregister 时会出现警告*/
         },
};

static int __init led_dev_init(void)
{
    int err;
    err = platform_device_register(&board_A_led_dev);   /*注册platform_device*/
    return 0;
}

static void __exit led_dev_exit(void)
{
    platform_device_unregister(&board_A_led_dev);
}

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

chip_demo_gpio.c中构造platform_device结构体与之匹配

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

/*
*省略部分代码
*/

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

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

static int chip_demo_gpio_probe(struct platform_device *pdev)
{
    struct resource *res;
    int i = 0;

    while (1){
        res = platform_get_resource(pdev, IORESOURCE_IRQ, i++);
        if (!res)
            break;
        
        g_ledpins[g_ledcnt] = res->start; 	/*记录引脚 */
        led_class_create_device(g_ledcnt);  /*创建device_create 有多少引脚,创建多少个*/
        g_ledcnt++;
    }
    return 0;
    
}

static int chip_demo_gpio_remove(struct platform_device *pdev)
{
    struct resource *res;
    int i = 0;

    while (1){
        res = platform_get_resource(pdev, IORESOURCE_IRQ, i);
        if (!res)
            break;
        
        led_class_destroy_device(i);
        i++;
        g_ledcnt--;
    }
    return 0;
}

static struct platform_driver chip_demo_gpio_driver = {
    .probe      = chip_demo_gpio_probe,
    .remove     = chip_demo_gpio_remove,
    .driver     = {
        .name   = "100ask_led",
    },
};

static int __init chip_demo_gpio_drv_init(void)
{
    int err;
    err = platform_driver_register(&chip_demo_gpio_driver); 
    register_led_operations(&board_demo_led_opr);
    
    return 0;
}

static void __exit lchip_demo_gpio_drv_exit(void)
{
    platform_driver_unregister(&chip_demo_gpio_driver);
}

module_init(chip_demo_gpio_drv_init);
module_exit(lchip_demo_gpio_drv_exit);

MODULE_LICENSE("GPL");

a.c编译为a.ko ,里面定义了func_a;如果它想让b.ko使用该函数,那
么a .c里需要导出此函数。并且,使用时要先加载a.ko 。如果先加载b.ko

EXPORT_SYMBOL(led_device_create);
5-3 平台总线驱动函数关系调用图解

韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型),Linux嵌入式,驱动开发,ubuntu,linux,嵌入式,c语言
韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型),Linux嵌入式,驱动开发,ubuntu,linux,嵌入式,c语言文章来源地址https://www.toymoban.com/news/detail-857277.html

到了这里,关于韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • 嵌入式LinuxLED驱动开发实验

    我们在裸机实验的时候,都是通过配置底层的寄存器来进行点亮LED灯的操作的。我们现在还没有学习到设备树的相关知识,所以,我们也是通过在字符设备驱动框架的基础上来配置底层寄存器来实现LED灯的点亮,但是,与之前不同的是,在Linux系统中会存在地址映射的方式,

    2024年02月15日
    浏览(56)
  • 全志V3S嵌入式驱动开发(驱动开发准备)

    【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】         之前的文章都是教大家怎么搭建环境、看原理图、编译内核和根文件系统、做镜像,直到现在才进入驱动开发的主题。 毕竟整个专栏的目的,还是希望大家能够学会驱动外部硬件。

    2024年02月13日
    浏览(65)
  • 嵌入式驱动开发需要会哪些技能?

    嵌入式驱动开发是指在嵌入式系统中编写驱动程序,实现设备与计算机之间的通信。嵌入式驱动开发是指编写设备驱动程序,实现设备与计算机之间的通信。 以下是一些嵌入式驱动开发的具体操作方法:  1)了解硬件设备结构: 在进行嵌入式驱动开发之前,需要对所使用的硬

    2024年01月25日
    浏览(58)
  • 嵌入式Linux驱动开发之点灯

      使用驱动开发的方式点亮一个LED灯。看看两者有啥区别不? 首先查看原理图,看看我们的板子上的LED等接在哪一个IO口上面。 好了,看原理图我们知道LED灯接在芯片的GPIO1的第三个引脚上面,也就是GPIO1_IO03。 先掌握三个名词 CCM: Clock Controller Module (时钟控制模块) IOMUXC : I

    2024年02月01日
    浏览(101)
  • 嵌入式Linux开发-USB驱动

    哥们马上就要被裁了,总得整理一下技术方面的积累,准备开始下一轮的面试和找工作之旅了。。。。 通用串行总线(USB)是主机和外围设备之间的一种连接。 从拓扑上来看,是一颗由几个点对点的连接构建而成的树。这些连接是连接设备和集线器(hub)的四线电缆(底线、电源线

    2024年02月20日
    浏览(73)
  • 嵌入式:驱动开发 Day4

    驱动程序:myled.c 应用程序:test.c 头文件:head.h

    2024年02月09日
    浏览(51)
  • 嵌入式Linux驱动开发 04:基于设备树的驱动开发

    前面文章 《嵌入式Linux驱动开发 03:平台(platform)总线驱动模型》 引入了资源和驱动分离的概念,这篇文章将在前面基础上更进一步,引入设备树的概念。 在平台总线驱动模型中资源和驱动已经从逻辑上和代码组织上进行了分离,但每次调整资源还是会涉及到内核,所以现

    2024年02月16日
    浏览(69)
  • 【嵌入式Linux驱动】驱动开发调试相关的关系记录

    https://www.processon.com/mindmap/64537772b546c76a2f37bd2f

    2024年02月02日
    浏览(56)
  • 嵌入式Linux驱动开发——常见框架梳理

    本文主要介绍了Linux驱动开发中一些常用的驱动框架,platform、input、iic、spi等,硬件平台使用的是正点原子的imx6ull开发板。 不管什么框架最后都是要追溯到配置IO的电气属性和复用功能 如果要使用外部中断,设备树节点中还需添加相关信息,什么边沿触发 1:module_init和mod

    2024年02月15日
    浏览(67)
  • 华清远见嵌入式学习——驱动开发——作业1

    通过字符设备驱动分步注册过程实现LED驱动的编写,编写应用程序测试,发布到CSDN

    2024年02月20日
    浏览(57)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包