目录
- 目标
- 一、平台设备总线的概念
- 1、什么是平台设备总线
- 2、平台设备总线 platform 的匹配
- 3、设备树和平台设备总线的关系,以及匹配
 
- 二、平台设备总线的函数接口
- 1、注册设备端的资源信息
- 2、设备端提供的资源的信息
- 3、注销申请的设备端的资源
- 4、驱动端的函数(全)
- 5、从设备端获取资源
- 6、设备树和驱动端匹配的时候需要用到的结构体
- 7、设备树和驱动端设备树使用的节点信息
 
- 三、代码与现象
- 一、只用平台设备总线
- 1、代码:
- 2、现象
 
- 二、平台设备总线与设备树
- 1、代码:
- 2、现象
 
 
目标
1.利用平台设备总线编写LED灯驱动代码
 2.利用驱动端+设备树编写LED灯驱动代码
一、平台设备总线的概念
1、什么是平台设备总线
I2c spi 等他们是真实存在的总线,他不像今天讲的 platform(平台) 总线他是内核虚拟出来的一条总线,这个总线的作用就是为了匹配设备端和驱动端,在咱们没有讲设备树之前,编写的驱动代码都是采取平台设备总线的形式去编写的,就是老内核他们是怎么去编写设备驱动的,他们都是采取平台设备总线的形式,因为设备的资源比较多,要管理的内容也比较多,就会造成内核比较凌乱。此时他把一个驱动的代码给分为了两个端,一个是设备端,一个是驱动端,这个两个端他们分别负责不同的功能,设备端主要就是负责提供设备的信息,就和硬件相关的,只提供信息资源,不做任何设备的驱动,这个一般是咱们自己写的,比如前面说的的设备树就是设备端,而驱动端他统一交给了内核去管理,他主要负责驱动设备,他要从设备端先去获取设备的资源信息。对于一些大的设备来说,一般驱动端内核里都会自带,比如 LCD 屏,内核已经自动写入了一部分驱动端代码。
 在后期驱动开发一般是有两种方法
 1.平台设备总线
 2.使用设备树+杂项/linux2.6
 3.你也可以使用设备树+平台设备总线(驱动端)
 驱动端:负责字符设备的框架使用 Linux2.6/杂项
 设备端:只负责提供你驱动端所需要的所有的设备信息资源。
2、平台设备总线 platform 的匹配
因为的驱动代码已经分为了两端,一个是驱动端,一个设备端。驱动端和
 设备端,怎么找到对方,这里有一个比较重要的信息,就是你要相互找到对
 方,只有设备端和驱动端匹配成功了,那么你的驱动端,才可以去驱动对应的硬件,这里驱动端和设备端匹配的方法也是比较多的,目前最长用的就是通过名字找对方。这里你编译出来会有两个.ko ,他们加载的顺序是没有关系的,没有先后的顺序,不管你先加载谁他们都可以通过名字找对方。设备端和驱动端都有一个属于自己的核心结构体在他们各自的核心结构体里都有一个成员变量Name,你在编码的时候需要保证这个来个结构体里的名字必须是一样的,否则他们两个是无非进行匹配的。
 以下是驱动和设备端编译出来的文件。
 
3、设备树和平台设备总线的关系,以及匹配
平台设备总线他是分为了两个端设备端和驱动端,其实这里的设备端被咱们设备树给取代了。设备端的作用就只有一个,那就是提供设备的信息,而咱们的设备恰好是描述设备硬件信息,所以设备树就可以代替设备端,同样你也可以使用平台设备总线的驱动断和设备树做匹配,既然设备树替代了设备端,那么资源驱动端也可以从设备树里去获取资源,其实这里就通过设备树里的 Compatible属性来获取信息的。这样我们就可以少些一部分设备层的代码。
 
二、平台设备总线的函数接口
1、注册设备端的资源信息
函数功能:注册设备端的资源信息
 函数原型:int platform_device_register(struct platform_device *pdev)
 函数头文件:#include<linux/platform_device.h>
 函数参数:pdev:设备端的核心结构体
 设备端的核心结构体:
 struct platform_device {
 const char *name;//他就是和驱动端进行匹配的名字
 int id;// 写 -1
 struct device dev;//关于设备的信息的结构体
 这里有一个函数需要咱们填写,这个函数里什么都不用
 写,函数必须要有。void (*release)(struct device *dev);
 u32 num_resources;//设备端提供资源的数量
 ARRAY_SIZE() — 他来计算你提供了多少资源
 struct resource *resource;//你提供的具体的资源信息
 }
2、设备端提供的资源的信息
设备端提供的资源的信息
 struct resource {resource_size_t start;resource_size_t end;const char *name;unsigned long flags;};
 start:设备资源的起始资源
 end:设备资源的结束资源
 name:没有什么太大的意义一般是表示 给那个设备提供资源 取一个名
 字
 flags:你提供资源的类型
 
 资源的类型内核已经帮你写好了固定的宏
 
 #define IORESOURCE_IO 0x00000100//IO 空间,一般在 x86 框
 架中存 在,ARM 一般没有
 #define IORESOURCE_MEM 0x00000200//内存资源,大部分资源
 都是这个
 #define IORESOURCE_IRQ 0x00000400//中断资源 实际上就是
 中断号
 #define IORESOURCE_DMA 0x00000800//DMA
3、注销申请的设备端的资源
函数返回值:成功返回 0 失败负数
 函数功能:注销申请的设备端的资源
 函数原型:void platform_device_unregister( struct platform_device *pdev)
 函数头文件:#include<linux/platform_device.h>
 函数参数:pdev:定义的核心结构体
 函数返回值:无
4、驱动端的函数(全)
函数功能:注册驱动端的资源
 函数原型:int platform_driver_register( struct platform_driver *drv)
 函数头文件:#include<linux/platform_device.h>
 函数参数:drv
 函数返回值:成功返回 0 失败负数
 
 struct platform_driver {
 int (*probe)(struct platform_device *);
 int (*remove)(struct platform_device *);
 struct device_driver driver;
 };
 probe:探测函数。一但驱动端和设备端匹配成功就会自动的去调用此函数,此函数里一般做字符设备的注册使用杂项/Linux2.6 以及设备资源获取。
 remove:移除函数。 一但驱动端或者是设备端有一个卸载了,那么 此函数就会自动的调用,次函数里主要就是注销你在探测函数里申请的资源。
 driver:你需要填写里边的名字 。注意这里的 name 要和设备端里的 name保持一致
 函数功能:注销驱动端申请的资源
 函数原型:void platform_driver_unregister(struct platform_driver *dev);
 函数头文件:#include<linux/platform_device.h>
 函数参数:dev:定义的核心结构体
 函数返回值:无
5、从设备端获取资源
函数功能:从设备端获取资源
 函数原型:struct resource *platform_get_resource(
 struct platform_device *dev,
 unsigned int type,
 unsigned int num)
 函数头文件:#include<linux/platform_device.h>
 函数参数:dev:设备端的核心结构体
 type:获取资源类型
 num:你要获取资源的编号 — 第几个资源
 函数返回值:成功返回指向 struct resource 指针 失败返回 NULL
6、设备树和驱动端匹配的时候需要用到的结构体
struct device_driver driver;
 struct device dev{
 struct device_driver *driver;
 {
 const struct of_device_id *of_match_table;
 struct of_device_id {
 charcompatible[128]; — 你这里的名字要和设备树里的
 compatible
 保持一致,否则无法从设备树里获取设备节点
 };
 }
 
7、设备树和驱动端设备树使用的节点信息
struct platform_device
 {
 struct device dev
 {
 struct device_node of_node; / 设备树使用的节点信息 */
 }
 }
 这个成员是给设备树和驱动端匹配成功之后获取 gpio 口资源函数第一个参数
 使用的。
 
三、代码与现象
一、只用平台设备总线
1、代码:
应用层函数 app.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
	int fd = 0;
	while(1)
		{
			fd = open("/dev/myqxjled",O_RDWR); // --- 底层的open函数
			sleep(3);
			close(fd);//底层的close
			sleep(3);
		}
	return 0;
}
驱动层:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/gpio.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
struct resource *led[2];
int i;
int all;
dev_t dev;
struct cdev mydev;
struct class *myclass=NULL;
int myled_open (struct inode *inode, struct file *fp)
{
	gpio_set_value(led[0]->start,led[1]->end);
	gpio_set_value(led[0]->end,led[1]->end);
	printk("两个灯打开\n");
	printk("myled open 正确打开\n");
	return 0;
}
int myled_close (struct inode *inode, struct file *fp)
{
	gpio_set_value(led[0]->start,led[1]->start);
	gpio_set_value(led[0]->end,led[1]->start);
	printk("两个灯关闭\n");
	printk("myled close 关闭正确\n");
	return 0;
}
struct file_operations myfops={
	.open = myled_open,
	.release = myled_close,
};
int myled_probe(struct platform_device *pdev)
{
	printk("探测函数:设备端和驱动端匹配成功\n");
	for(i=0;i<=1;i++)
	{
		led[i]=platform_get_resource(pdev,IORESOURCE_MEM,i);
 	}
 	gpio_request(led[0]->start, "led1 pc5");
	gpio_request(led[0]->end, "led2 pc6");
	gpio_direction_output(led[0]->start, led[1]->start);
	gpio_direction_output(led[0]->end, led[1]->start);	
	all=alloc_chrdev_region(&dev,0, 1,"led");
	if(all<0)
	{
		printk("alloc_chrdev_region error\n");
		printk("动态创建失败\n");
		return -1;
	}
	printk("主设备号:%d\n",MAJOR(dev));
	printk("次设备号:%d\n",MINOR(dev));
	cdev_init(&mydev,&myfops);
	cdev_add(&mydev,dev,1);
	myclass=class_create(THIS_MODULE,"class_led");//创建类
	if(myclass == NULL)
	{
		printk("class_create error\n");
		printk("class_create 类创建失败\n");
		return -1;
	}
	device_create(myclass,NULL,dev,NULL,"myqxjled");
 	return 0;
}
int myled_remove (struct platform_device *pdev)
{
	printk("移除函数成功\n");
	device_destroy(myclass,dev);
	class_destroy(myclass);
	cdev_del(&mydev);
	unregister_chrdev_region(dev,1);
	gpio_free(led[0]->start);
	gpio_free(led[0]->end);
	return 0;
}
struct platform_driver drv={
	.probe = myled_probe,
	.remove = myled_remove,
	.driver = {
		.name = "myxyd_leds",//与设备端必须保持一致
	},
};
static int __init myled_init(void)
{	
	platform_driver_register(&drv);
	return 0;
}
static void __exit myled_exit(void)
{
	platform_driver_unregister(&drv);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
设备层:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/gpio.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
struct resource led_resou[2]={
	[0]={
		.start=21,
		.end=22,
		.name="xydleds",
		.flags=IORESOURCE_MEM,
	},
	
	[1]={
		.start=0,
		.end=1,
		.name="xydled", 
		.flags=IORESOURCE_MEM,
	},
};
void xyd_release(struct device *dev)
{
}
struct platform_device pdev={
	.name="myxyd_leds",
	.id=-1,
	.dev={
		.release=xyd_release,
	},
	.num_resources=ARRAY_SIZE(led_resou),
	.resource=led_resou,
};
static int __init myled_init(void)
{	int plat = 0;
	plat = platform_device_register(&pdev);
	if(plat < 0)
	{
		printk("platform_device_register error\n");
		printk("注册设备端的资源信息失败\n");
		return -1;
	}
	printk("注册设备端的资源信息成功\n");
	return 0;
}
static void __exit myled_exit(void)
{
	platform_device_unregister(&pdev);//注销设备资源
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
2、现象

 
 代码运行现象:
 
 灯现象:
 
 
二、平台设备总线与设备树
1、代码:
应用层:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
	int fd = 0;
	while(1)
		{
			fd = open("/dev/myqxjled",O_RDWR); // --- 底层的open函数
			sleep(1);
			close(fd);//底层的close
			sleep(1);
		}
	return 0;
}
驱动层:用的设备树
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/gpio.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
int led[2]={0};
int i;
int all;
dev_t dev;
struct cdev mydev;
struct class *myclass=NULL;
int myled_open (struct inode *inode, struct file *fp)
{
	gpio_set_value(led[0],1);
	gpio_set_value(led[1],1);
	printk("两个灯打开\n");
	printk("myled open 正确打开\n");
	return 0;
}
int myled_close (struct inode *inode, struct file *fp)
{
	gpio_set_value(led[0],0);
	gpio_set_value(led[1],0);
	printk("两个灯关闭\n");
	printk("myled close 关闭正确\n");
	return 0;
}
struct file_operations myfops={
	.open = myled_open,
	.release = myled_close,
};
int myled_probe(struct platform_device *pdev)
{
	printk("探测函数:设备端和驱动端匹配成功\n");
	//led[0] led[1]返回的是gpio编口号
	led[0]=of_get_named_gpio(pdev->dev.of_node,"led-gpios",0);
 	led[1]=of_get_named_gpio(pdev->dev.of_node,"led-gpios",1);
 	gpio_request(led[0], "led1 pc5");//21
	gpio_request(led[1], "led2 pc6");//22
	gpio_direction_output(led[0],0);
	gpio_direction_output(led[1],0);	
	all=alloc_chrdev_region(&dev,0, 1,"led");
	if(all<0)
	{
		printk("alloc_chrdev_region error\n");
		printk("动态创建失败\n");
		return -1;
	}
	printk("主设备号:%d\n",MAJOR(dev));
	printk("次设备号:%d\n",MINOR(dev));
	cdev_init(&mydev,&myfops);
	cdev_add(&mydev,dev,1);
	myclass=class_create(THIS_MODULE,"class_led");//创建类
	if(myclass == NULL)
	{
		printk("class_create error\n");
		printk("class_create 类创建失败\n");
		return -1;
	}
	device_create(myclass,NULL,dev,NULL,"myqxjled");
 	return 0;
}
int myled_remove (struct platform_device *pdev)
{
	printk("移除函数成功\n");
	device_destroy(myclass,dev);
	class_destroy(myclass);
	cdev_del(&mydev);
	unregister_chrdev_region(dev,1);
	gpio_free(led[0]);
	gpio_free(led[1]);
	return 0;
}
struct of_device_id	mydev_node={
	.compatible="myqxj_led",
};
struct platform_driver drv={
	.probe = myled_probe,
	.remove = myled_remove,
	.driver = {
		.name = "myxyd_leds",//与设备端必须保持一致
		.of_match_table = &mydev_node,
	},
};
static int __init myled_init(void)
{	
	platform_driver_register(&drv);
	return 0;
}
static void __exit myled_exit(void)
{
	platform_driver_unregister(&drv);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
不需要再写一个设备端了,而是由设备树上获取相应资源。
2、现象

 代码运行状态:
 
 灯现象:灯亮灭交替闪烁。
 
 



















