platform驱动

目录

1.Linux驱动的分离与分层

1)驱动的分隔与分离

2)驱动的分层

2.platform平台驱动模型简介

1)platform总线

2)platform驱动

3)platform设备

3.试验程序编写

1)platform设备与驱动程序编写

2)测试APP编写

4.运行测试

1)编译驱动程序和测试APP

2)运行测试

5.设备树下的platform驱动简介

6.实验程序编写

1)修改设备树文件

2)platform驱动程序编写

7.运行测试

1)编译驱动程序和测试APP

2)运行测试

我们在前面几章编写的设备驱动都非常的简单,都是对IO进行最简单的读写操作。像I2C、SPI、

LCD等这些复杂外设的驱动就不能这么去写了,Linux系统要考虑到驱动的可重用性,因此提出了

驱动的分离与分层这样的软件思路,在这个思路下诞生了我们将来最常打交道的platform设备驱

,也叫做平台设备驱动。本章我们就来学习一下Linux下的驱动分离与分层,以及platform框架下

的设备驱动该如何编写。

1.Linux驱动的分离与分层

1)驱动的分隔与分离

对于Linux这样一个成熟、庞大、复杂的操作系统,代码的重用性非常重要,否则的话就会在Linux

内核中存在大量无意义的重复代码。尤其是驱动程序,因为驱动程序占用了Linux内核代码量的大

头,如果不对驱动程序加以管理,任由重复的代码肆意增加,那么用不了多久Linux内核的文件数

量就庞大到无法接受的地步。假如现在有三个平台A、B和C,这三个平台(这里的平台说的是SOC)

上都有MPU6050这个I2C接口的六轴传感器,按照我们写裸机I2C驱动的时候的思路,每个平台都

有一个MPU6050的驱动,因此编写出来的最简单的驱动框架如图所示:

platform驱动_第1张图片

从图可以看出,每种平台下都有一个主机驱动和设备驱动,主机驱动肯定是必须要的,毕竟不同的

平台其I2C控制器不同。但是右侧的设备驱动就没必要每个平台都写一个,因为不管对于那个SOC

来说,MPU6050都是一样,通过I2C接口读写数据就行了,只需要一个MPU6050的驱动程序即

。如果再来几个I2C设备,比如AT24C02、FT5206(电容触摸屏)等,如果按照图1中的写法,那

么设备端的驱动将会重复的编写好几次。显然在Linux驱动程序中这种写法是不推荐的,最好的做

法就是每个平台的I2C控制器都提供一个统一的接口(也叫做主机驱动),每个设备的话也只提供一

个驱动程序(设备驱动),每个设备通过统一的I2C接口驱动来访问,这样就可以大大简化驱动文

,比如图中三种平台下的MPU6050驱动框架就可以简化为图所示:

platform驱动_第2张图片

实际的I2C驱动设备肯定有很多种,不止MPU6050这一个,那么实际的驱动架构如图所示:

platform驱动_第3张图片

这个就是驱动的分隔,也就是将主机驱动和设备驱动分隔开来,比如I2C、SPI等等都会采用驱动

分隔的方式来简化驱动的开发。在实际的驱动开发中,一般I2C主机控制器驱动已经由半导体厂家

编写好了,而设备驱动一般也由设备器件的厂家编写好了,我们只需要提供设备信息即可,比如

I2C设备的话提供设备连接到了哪个I2C接口上,I2C的速度是多少等等。相当于将设备信息从设备

驱动中剥离开来,驱动使用标准方法去获取到设备信息(比如从设备树中获取到设备信息),然后根

据获取到的设备信息来初始化设备。这样就相当于驱动只负责驱动,设备只负责设备,想办法将两

者进行匹配即可。这个就是Linux中的总线(bus)、驱动(driver)和设备(device)模型,也就是常说

的驱动分离。总线就是驱动和设备信息的月老,负责给两者牵线搭桥,如图所示:

platform驱动_第4张图片

当我们向系统注册一个驱动的时候,总线就会在右侧的设备中查找,看看有没有与之匹配的设备,

如果有的话就将两者联系起来。同样的,当向系统中注册一个设备的时候,总线就会在左侧的驱动

中查找看有没有与之匹配的设备,有的话也联系起来。Linux内核中大量的驱动程序都采用总线、

驱动和设备模式,我们一会要重点讲解的platform驱动就是这一思想下的产物。

2)驱动的分层

上一小节讲了驱动的分隔与分离,本节我们来简单看一下驱动的分层,大家应该听说过网络的7层

模型,不同的层负责不同的内容。同样的,Linux下的驱动往往也是分层的,分层的目的也是为了

在不同的层处理不同的内容。以其他书籍或者资料常常使用到的input(输入子系统,后面会有专门

的章节详细的讲解)为例,简单介绍一下驱动的分层。input子系统负责管理所有跟输入有关的驱

动,包括键盘、鼠标、触摸等,最底层的就是设备原始驱动,负责获取输入设备的原始值,获取到

的输入事件上报给input核心层。input核心层会处理各种IO模型,并且提供file_operations操作集

合。我们在编写输入设备驱动的时候只需要处理好输入事件的上报即可,至于如何处理这些上报的

输入事件那是上层去考虑的,我们不用管。可以看出借助分层模型可以极大的简化我们的驱动编

写,对于驱动编写来说非常的友好。

2.platform平台驱动模型简介

前面我们讲了设备驱动的分离,并且引出了总线(bus)、驱动(driver)和设备(device)模型,比如

I2C、SPI、USB等总线。但是在SOC中有些外设是没有总线这个概念的,但是又要使用总线、驱

动和设备模型该怎么办呢?为了解决此问题,Linux提出了platform这个虚拟总线,相应的就有

platform_driverplatform_device

1)platform总线

Linux系统内核使用bus_type结构体表示总线,此结构体定义在文件include/linux/device.h,

bus_type结构体内容如下:

1 struct bus_type {
2 	const char *name; /* 总线名字 */
3 	const char *dev_name;
4 	struct device *dev_root;
5 	struct device_attribute *dev_attrs;
6 	const struct attribute_group **bus_groups; /* 总线属性 */
7 	const struct attribute_group **dev_groups; /* 设备属性 */
8 	const struct attribute_group **drv_groups; /* 驱动属性 */
9
10 	int (*match)(struct device *dev, struct device_driver *drv);
11 	int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
12 	int (*probe)(struct device *dev);
13 	int (*remove)(struct device *dev);
14 	void (*shutdown)(struct device *dev);
15
16 	int (*online)(struct device *dev);
17 	int (*offline)(struct device *dev);
18 	int (*suspend)(struct device *dev, pm_message_t state);
19 	int (*resume)(struct device *dev);
20 	const struct dev_pm_ops *pm;
21 	const struct iommu_ops *iommu_ops;
22 	struct subsys_private *p;
23 	struct lock_class_key lock_key;
24 };

第10行,match函数,此函数很重要,单词match的意思就是“匹配、相配”,因此此函数就是完成

设备和驱动之间匹配的,总线就是使用match函数来根据注册的设备来查找对应的驱动,或者根据

注册的驱动来查找相应的设备,因此每一条总线都必须实现此函数。match函数有两个参数:dev

和drv,这两个参数分别为device和device_driver类型,也就是设备和驱动。

platform总线是bus_type的一个具体实例,定义在文件drivers/base/platform.c,platform总线定义

如下:

1 struct bus_type platform_bus_type = {
2 	.name = "platform",
3 	.dev_groups = platform_dev_groups,
4 	.match = platform_match,
5 	.uevent = platform_uevent,
6 	.pm = &platform_dev_pm_ops,
7 };

platform_bus_type就是platform平台总线,其中platform_match就是匹配函数。我们来看一下驱

动和设备是如何匹配的,platform_match()函数定义在文件drivers/base/platform.c中,函数内容

如下所示:

1 static int platform_match(struct device *dev,struct device_driver *drv)
2 {
3 	struct platform_device *pdev = to_platform_device(dev);
4 	struct platform_driver *pdrv = to_platform_driver(drv);
5
6 	/*When driver_override is set,only bind to the matching driver*/
7 	if (pdev->driver_override)
8 		return !strcmp(pdev->driver_override, drv->name);
9
10 	/* Attempt an OF style match first */
11 	if (of_driver_match_device(dev, drv))
12 		return 1;
13
14 	/* Then try ACPI style match */
15 	if (acpi_driver_match_device(dev, drv))
16 		return 1;
17
18 	/* Then try to match against the id table */
19 	if (pdrv->id_table)
20 		return platform_match_id(pdrv->id_table, pdev) != NULL;
21
22 	/* fall-back to driver name match */
23 	return (strcmp(pdev->name, drv->name) == 0);
24 }

驱动和设备的匹配有四种方法,我们依次来看一下:

第11~12行,第一种匹配方式,OF类型的匹配,也就是设备树采用的匹配方式,

of_driver_match_device函数定义在文件include/linux/of_device.h中。device_driver结构体(表示设

备驱动)中有个名为of_match_table的成员变量,此成员变量保存着驱动的compatible匹配表,设备

树中的每个设备节点的compatible属性会和of_match_table表中的所有成员比较,查看是否有相同

的条目,如果有的话就表示设备和此驱动匹配,设备和驱动匹配成功以后probe函数就会执行。

第15~16行,第二种匹配方式,ACPI匹配方式。

第19~20行,第三种匹配方式,id_table匹配,每个platform_driver结构体有一个id_table成员变

量,顾名思义,保存了很多id信息。这些id信息存放着这个platformd驱动所支持的驱动类型。

第23行,第四种匹配方式,如果第三种匹配方式的id_table不存在的话就直接比较驱动和设备的

name字段,看看是不是相等,如果相等的话就匹配成功。

对于支持设备树的Linux版本号,一般设备驱动为了兼容性都支持设备树和无设备树两种匹配方

式。也就是第一种匹配方式一般都会存在,第三种和第四种只要存在一种就可以,一般用的最多的

还是第四种,也就是直接比较驱动和设备的name字段,毕竟这种方式最简单了。

2)platform驱动

platform_driver结构体表示platform驱动,此结构体定义在文件include/linux/platform_device.h

中,内容如下:

1 struct platform_driver {
2 	int (*probe)(struct platform_device *);
3 	int (*remove)(struct platform_device *);
4 	void (*shutdown)(struct platform_device *);
5 	int (*suspend)(struct platform_device *, pm_message_t state);
6 	int (*resume)(struct platform_device *);
7 	struct device_driver driver;
8 	const struct platform_device_id *id_table;
9 	bool prevent_deferred_probe;
10 };

第2行,probe函数,当驱动与设备匹配成功以后probe函数就会执行,非常重要的函数!!一般驱

动的提供者会编写,如果自己要编写一个全新的驱动,那么probe就需要自行实现。

第7行,driver成员,为device_driver结构体变量,Linux内核里面大量使用到了面向对象的思维,

device_driver相当于基类,提供了最基础的驱动框架。plaform_driver继承了这个基类,然后在此

基础上又添加了一些特有的成员变量。

第8行,id_table表,也就是我们上一小节讲解platform总线匹配驱动和设备的时候采用的第三种方

法,id_table是个表(也就是数组),每个元素的类型为platform_device_id,platform_device_id结构

体内容如下:

1 struct platform_device_id {
2 	char name[PLATFORM_NAME_SIZE];
3 	kernel_ulong_t driver_data;
4 };

device_driver 结构体定义在 include/linux/device.h,device_driver结构体内容如下:

1 struct device_driver {
2 	const char *name;
3 	struct bus_type *bus;
4
5 	struct module *owner;
6 	const char *mod_name; /* used for built-in modules */
7
8 	bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
9
10 	const struct of_device_id *of_match_table;
11 	const struct acpi_device_id *acpi_match_table;
12
13 	int (*probe) (struct device *dev);
14 	int (*remove) (struct device *dev);
15 	void (*shutdown) (struct device *dev);
16 	int (*suspend) (struct device *dev, pm_message_t state);
17 	int (*resume) (struct device *dev);
18 	const struct attribute_group **groups;
19
20 	const struct dev_pm_ops *pm;
21
22 	struct driver_private *p;
23 };

第10行,of_match_table就是采用设备树的时候驱动使用的匹配表,同样是数组,每个匹配项都

为of_device_id结构体类型,此结构体定义在文件include/linux/mod_devicetable.h中,内容如下:

1 struct of_device_id {
2 	char name[32];
3 	char type[32];
4 	char compatible[128];
5 	const void *data;
6 };

第4行的compatible非常重要,因为对于设备树而言,就是通过设备节点的compatible属性值和

of_match_table中每个项目的compatible成员变量进行比较,如果有相等的就表示设备和此驱动匹

配成功。

在编写platform驱动的时候,首先定义一个platform_driver结构体变量,然后实现结构体中的各

个成员变量,重点是实现匹配方法以及probe函数。当驱动和设备匹配成功以后probe函数就会执

行,具体的驱动程序在probe函数里面编写,比如字符设备驱动等等。当我们定义并初始化好

platform_driver结构体变量以后,需要在驱动入口函数里面调用platform_driver_register()函数

向Linux内核注册一个platform驱动,platform_driver_register函数原型如下所示:

int platform_driver_register (struct platform_driver *driver)

函数参数和返回值含义如下:

driver:要注册的platform驱动。

返回值:负数,失败;0,成功。

还需要在驱动卸载函数中通过platform_driver_unregister函数卸载platform驱动,

platform_driver_unregister函数原型如下:

void platform_driver_unregister(struct platform_driver *drv)

函数参数和返回值含义如下:

drv:要卸载的platform驱动。

返回值:无。

platform驱动框架如下所示:

/* 设备结构体 */
1 struct xxx_dev{
2 	struct cdev cdev;
3 	/* 设备结构体其他具体内容 */
4 };
5
6 struct xxx_dev xxxdev; /* 定义个设备结构体变量 */
7
8 static int xxx_open(struct inode *inode, struct file *filp)
9 {
10 	/* 函数具体内容 */
11 	return 0;
12 }
13
14 static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
15 {
16 	/* 函数具体内容 */
17 	return 0;
18 }
19
20 /*
21 * 字符设备驱动操作集
22 */
23 static struct file_operations xxx_fops = {
24 	.owner = THIS_MODULE,
25 	.open = xxx_open,
26 	.write = xxx_write,
27 };
28
29 /*
30 * platform 驱动的 probe 函数
31 * 驱动与设备匹配成功以后此函数就会执行
32 */
33 static int xxx_probe(struct platform_device *dev)
34 {
35 	......
36 	cdev_init(&xxxdev.cdev, &xxx_fops); /* 注册字符设备驱动 */
37 	/* 函数具体内容 */
38 	return 0;
39 }
40
41 static int xxx_remove(struct platform_device *dev)
42 {
43 	......
44 	cdev_del(&xxxdev.cdev);/* 删除 cdev */
45 	/* 函数具体内容 */
46 	return 0;
47 }
48
49 /* 匹配列表 */
50 static const struct of_device_id xxx_of_match[] = {
51 	{ .compatible = "xxx-gpio" },
52 	{ /* Sentinel */ }
53 };
54
55 /*
56 * platform 平台驱动结构体
57 */
58 static struct platform_driver xxx_driver = {
59 	.driver = {
60 		.name = "xxx",
61 		.of_match_table = xxx_of_match,
62 },
63 	.probe = xxx_probe,
64 	.remove = xxx_remove,
65 };
66
67 /* 驱动模块加载 */
68 static int __init xxxdriver_init(void)
69 {
70 	return platform_driver_register(&xxx_driver);
71 }
72
73 /* 驱动模块卸载 */
74 static void __exit xxxdriver_exit(void)
75 {
76 	platform_driver_unregister(&xxx_driver);
77 }
78
79 module_init(xxxdriver_init);
80 module_exit(xxxdriver_exit);
81 MODULE_LICENSE("GPL");
82 MODULE_AUTHOR("zuozhongkai");

第1~27行,传统的字符设备驱动,所谓的platform驱动并不是独立于字符设备驱动、块设备驱动和

网络设备驱动之外的其他种类的驱动。platform只是为了驱动的分离与分层而提出来的一种框架,

其驱动的具体实现还是需要字符设备驱动、块设备驱动或网络设备驱动。

第33~39行,xxx_probe函数,当驱动和设备匹配成功以后此函数就会执行,以前在驱动入口init函

数里面编写的字符设备驱动程序就全部放到此probe函数里面。比如注册字符设备驱动、添加

cdev、创建类等等。

第41~47行,xxx_remove函数,platform_driver结构体中的remove成员变量,当关闭platfor备驱动

的时候此函数就会执行,以前在驱动卸载exit函数里面要做的事情就放到此函数中来。比如,使用

iounmap释放内存、删除cdev,注销设备号等等。

第50~53行,xxx_of_match匹配表,如果使用设备树的话将通过此匹配表进行驱动和设备的匹

第51行设置了一个匹配项,此匹配项的compatible值为“xxx-gpio”,因此当设备树中设备节点的

compatible属性值为“xxx-gpio”的时候此设备就会与此驱动匹配。第52行是一个标记,of_device_id

表最后一个匹配项必须是空的。

第58~65行,定义一个platform_driver结构体变量xxx_driver,表示platform驱动,第59~62行设置

paltform_driver中的device_driver成员变量的name和of_match_table这两个属性。其中name属性

用于传统的驱动与设备匹配,也就是检查驱动和设备的name字段是不是相同。of_match_table属

性就是用于设备树下的驱动与设备检查。对于一个完整的驱动程序,必须提供有设备树和无设备树

两种匹配方法。最后63和64这两行设置probe和remove这两成员变量。

第68~71行,驱动入口函数,调用platform_driver_register函数向Linux内核注册一个platform驱

动,也就是上面定义的xxx_driver结构体变量。

第74~77行,驱动出口函数,调用platform_driver_unregister函数卸载前面注册的platform驱动。

总体来说,platform驱动还是传统的字符设备驱动、块设备驱动或网络设备驱动,只是套上了一张

“platform”的皮,目的是为了使用总线、驱动和设备这个驱动模型来实现驱动的分离与分层

3)platform设备

platform驱动已经准备好了,我们还需要platform设备,否则的话单单一个驱动也做不了什么。

platform_device这个结构体表示platform设备,这里我们要注意,如果内核支持设备树的话就不

要再使用platform_device来描述设备了,因为改用设备树去描述了。当然了,你如果一定要用

platform_device来描述设备信息的话也是可以的。platform_device结构体定义在文件

include/linux/platform_device.h中,结构体内容如下:

22 struct platform_device {
23 	const char *name;
24 	int id;
25 	bool id_auto;
26 	struct device dev;
27 	u32 num_resources;
28 	struct resource *resource;
29
30 	const struct platform_device_id *id_entry;
31 	char *driver_override; /* Driver name to force a match */
32
33 	/* MFD cell pointer */
34 	struct mfd_cell *mfd_cell;
35
36 	/* arch specific additions */
37 	struct pdev_archdata archdata;
38 };

第23行,name表示设备名字,要和所使用的platform驱动的name字段相同,否则的话设备就无法

匹配到对应的驱动。比如对应的platform驱动的name字段为“xxx-gpio”,那么此name字段也要设置

为“xxx-gpio”。

第27行,num_resources表示资源数量,一般为第28行resource资源的大小。

第28行,resource表示资源,也就是设备信息,比如外设寄存器等。Linux内核使用resource结构

体表示资源,resource结构体内容如下:

18 struct resource {
19 	resource_size_t start;
20 	resource_size_t end;
21 	const char *name;
22 	unsigned long flags;
23 	struct resource *parent, *sibling, *child;
24 };

start和end分别表示资源的起始和终止信息,对于内存类的资源,就表示内存起始和终止地址,

name表示资源名字,flags表示资源类型,可选的资源类型都定义在了文件include/linux/ioport.h里

面,如下所示:

29 #define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */
30
31 #define IORESOURCE_TYPE_BITS 0x00001f00 /* Resource type */
32 #define IORESOURCE_IO 0x00000100 /* PCI/ISA I/O ports */
33 #define IORESOURCE_MEM 0x00000200
34 #define IORESOURCE_REG 0x00000300 /* Register offsets */
35 #define IORESOURCE_IRQ 0x00000400
36 #define IORESOURCE_DMA 0x00000800
37 #define IORESOURCE_BUS 0x00001000
......
104 /* PCI control bits. Shares IORESOURCE_BITS with above PCI ROM. */
105 #define IORESOURCE_PCI_FIXED (1<<4) /* Do not move resource */

在以前不支持设备树的Linux版本中,用户需要编写platform_device变量来描述设备信息,然后使

platform_device_register函数将设备信息注册到Linux内核中,此函数原型如下所示:

int platform_device_register(struct platform_device *pdev)

函数参数和返回值含义如下:

pdev:要注册的platform设备。

返回值:负数,失败;0,成功。

如果不再使用platform的话可以通过platform_device_unregister函数注销掉相应的platform设

备,platform_device_unregister函数原型如下:

void platform_device_unregister(struct platform_device *pdev)

函数参数和返回值含义如下:

pdev:要注销的platform设备。

返回值:无。

platform设备信息框架如下所示:

1 /* 寄存器地址定义*/
2 #define PERIPH1_REGISTER_BASE (0X20000000) /* 外设 1 寄存器首地址 */
3 #define PERIPH2_REGISTER_BASE (0X020E0068) /* 外设 2 寄存器首地址 */
4 #define REGISTER_LENGTH 4
5
6 /* 资源 */
7 static struct resource xxx_resources[] = {
8 	[0] = {
9 		.start = PERIPH1_REGISTER_BASE,
10 		.end = (PERIPH1_REGISTER_BASE + REGISTER_LENGTH - 1),
11 		.flags = IORESOURCE_MEM,
12 	},
13 	[1] = {
14 		.start = PERIPH2_REGISTER_BASE,
15 		.end = (PERIPH2_REGISTER_BASE + REGISTER_LENGTH - 1),
16 		.flags = IORESOURCE_MEM,
17 	},
18 };
19
20 /* platform 设备结构体 */
21 static struct platform_device xxxdevice = {
22 	.name = "xxx-gpio",
23 	.id = -1,
24 	.num_resources = ARRAY_SIZE(xxx_resources),
25 	.resource = xxx_resources,
26 };
27
28 /* 设备模块加载 */
29 static int __init xxxdevice_init(void)
30 {
31 	return platform_device_register(&xxxdevice);
32 }
33
34 /* 设备模块注销 */
35 static void __exit xxx_resourcesdevice_exit(void)
36 {
37 	platform_device_unregister(&xxxdevice);
38 }
39
40 module_init(xxxdevice_init);
41 module_exit(xxxdevice_exit);
42 MODULE_LICENSE("GPL");
43 MODULE_AUTHOR("zuozhongkai");

第7~18行,数组xxx_resources表示设备资源,一共有两个资源,分别为设备外设1和外设2的寄存

器信息。因此flags都为IORESOURCE_MEM,表示资源为内存类型的。

第21~26行,platform设备结构体变量,注意name字段要和所使用的驱动中的name字段一致,否

则驱动和设备无法匹配成功。num_resources表示资源大小,其实就是数组xxx_resources的元素

数量,这里用ARRAY_SIZE来测量一个数组的元素个数。

第29~32行,设备模块加载函数,在此函数中调用platform_device_register向Linux内核注册

platform设备。

第35~38行,设备模块卸载函数,在此函数中调用platform_device_unregister从Linux内核中卸载

platform设备。

以上代码主要是在不支持设备树的Linux版本中使用的,当Linux内核支持了设备树以后就不需要用

户手动去注册platform设备了。因为设备信息都放到了设备树中去描述,Linux内核启动的时候会从

设备树中读取设备信息,然后将其组织成platform_device形式,至于设备树到platform_device的具

体过程就不去详细的追究了,感兴趣的可以去看一下,网上也有很多博客详细的讲解了整个过程。

关于platform下的总线、驱动和设备就讲解到这里,我们接下来就使用platform驱动框架来编写一

个LED灯驱动,本章我们不使用设备树来描述设备信息,我们采用自定义platform_device这种“古

老”方式来编写LED的设备信息。下一章我们来编写设备树下的platform驱动,这样我们就掌握了无

设备树和有设备树这两种platform驱动的开发方式。

3.试验程序编写

本章实验我们需要编写一个驱动模块和一个设备模块,其中驱动模块是platform驱动程序,设备模

块是platform的设备信息。当这两个模块都加载成功以后就会匹配成功,然后platform驱动模块中

的probe函数就会执行,probe函数中就是传统的字符设备驱动那一套。

1)platform设备与驱动程序编写

新建名为“17_platform”的文件夹,然后在17_platform文件夹里面创建vscode工程,工作区命名为

“platform”。新建名为leddevice.c和leddriver.c这两个文件,这两个文件分别为LED灯的platform设

备文件和LED灯的platform的驱动文件。在leddevice.c中输入如下所示内容:

1 #include 
2 #include 
3 #include 
4 #include 
5 #include 
6 #include 
7 #include 
8 #include 
9 #include 
10 #include 
11 #include 
12 #include 
13 #include 
14 #include 
15 #include 
16 #include 
17 #include 
18 #include 
19 #include 
20 #include 
21 #include 
22 #include 
23 /***************************************************************
24 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
25 文件名 : leddevice.c
26 作者 : 左忠凯
27 版本 : V1.0
28 描述 : platform 设备
29 其他 : 无
30 论坛 : www.openedv.com
31 日志 : 初版 V1.0 2019/8/13 左忠凯创建
32 ***************************************************************/
33
34 /*
35 * 寄存器地址定义
36 */
37 #define CCM_CCGR1_BASE (0X020C406C)
38 #define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
39 #define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
40 #define GPIO1_DR_BASE (0X0209C000)
41 #define GPIO1_GDIR_BASE (0X0209C004)
42 #define REGISTER_LENGTH 4
43
44 /* @description : 释放 flatform 设备模块的时候此函数会执行
45 * @param - dev : 要释放的设备
46 * @return : 无
47 */
48 static void led_release(struct device *dev)
49 {
50 	printk("led device released!\r\n");
51 }
52
53 /*
54 * 设备资源信息,也就是 LED0 所使用的所有寄存器
55 */
56 static struct resource led_resources[] = {
57 	[0] = {
58 		.start = CCM_CCGR1_BASE,
59 		.end = (CCM_CCGR1_BASE + REGISTER_LENGTH - 1),
60 		.flags = IORESOURCE_MEM,
61 	},
62 	[1] = {
63 		.start = SW_MUX_GPIO1_IO03_BASE,
64 		.end = (SW_MUX_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),
65 		.flags = IORESOURCE_MEM,
66 	},
67 	[2] = {
68 		.start = SW_PAD_GPIO1_IO03_BASE,
69 		.end = (SW_PAD_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),
70 		.flags = IORESOURCE_MEM,
71 	},
72 	[3] = {
73 		.start = GPIO1_DR_BASE,
74 		.end = (GPIO1_DR_BASE + REGISTER_LENGTH - 1),
75 		.flags = IORESOURCE_MEM,
76 	},
77 	[4] = {
78 		.start = GPIO1_GDIR_BASE,
79 		.end = (GPIO1_GDIR_BASE + REGISTER_LENGTH - 1),
80 		.flags = IORESOURCE_MEM,
81 	},
82 };
83
84
85 /*
86 * platform 设备结构体
87 */
88 static struct platform_device leddevice = {
89 	.name = "imx6ul-led",
90 	.id = -1,
91 	.dev = {
92 	.release = &led_release,
93 	},
94 	.num_resources = ARRAY_SIZE(led_resources),
95 	.resource = led_resources,
96 };
97
98 /*
99 * @description : 设备模块加载
100 * @param : 无
101 * @return : 无
102 */
103 static int __init leddevice_init(void)
104 {
105 	return platform_device_register(&leddevice);
106 }
107
108 /*
109 * @description : 设备模块注销
110 * @param : 无
111 * @return : 无
112 */
113 static void __exit leddevice_exit(void)
114 {
115 	platform_device_unregister(&leddevice);
116 }
117
118 module_init(leddevice_init);
119 module_exit(leddevice_exit);
120 MODULE_LICENSE("GPL");
121 MODULE_AUTHOR("zuozhongkai");

leddevice.c文件内容就是按照之前的示例代码的platform设备模板编写的。

第56~82行,led_resources数组,也就是设备资源,描述了LED所要使用到的寄存器信息,也就是

IORESOURCE_MEM资源。

第88~96,platform设备结构体变量leddevice,这里要注意name字段为“imx6ul-led”,所以稍后编

写platform驱动中的name字段也要为“imx6ul-led”,否则设备和驱动匹配失败。

第103~106行,设备模块加载函数,在此函数里面通过platform_device_register向Linux内核注册

leddevice这个platform设备。

第113~116行,设备模块卸载函数,在此函数里面通过platform_device_unregister从Linux内核中

删除掉leddevice这个platform设备。

leddevice.c文件编写完成以后就编写leddriver.c这个platform驱动文件,在leddriver.c里面输入如下

内容:

1 #include 
2 #include 
3 #include 
4 #include 
5 #include 
6 #include 
7 #include 
8 #include 
9 #include 
10 #include 
11 #include 
12 #include 
13 #include 
14 #include 
15 #include 
16 #include 
17 #include 
18 #include 
19 #include 
20 #include 
21 #include 
22 #include 
23 /***************************************************************
24 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
25 文件名 : leddriver.c
26 作者 : 左忠凯
27 版本 : V1.0
28 描述 : platform 驱动
29 其他 : 无
30 论坛 : www.openedv.com
31 日志 : 初版 V1.0 2019/8/13 左忠凯创建
32 ***************************************************************/
33
34 #define LEDDEV_CNT 1 /* 设备号长度 */
35 #define LEDDEV_NAME "platled" /* 设备名字 */
36 #define LEDOFF 0
37 #define LEDON 1
38
39 /* 寄存器名 */
40 static void __iomem *IMX6U_CCM_CCGR1;
41 static void __iomem *SW_MUX_GPIO1_IO03;
42 static void __iomem *SW_PAD_GPIO1_IO03;
43 static void __iomem *GPIO1_DR;
44 static void __iomem *GPIO1_GDIR;
45
46 /* leddev 设备结构体 */
47 struct leddev_dev{
48 	dev_t devid; /* 设备号 */
49 	struct cdev cdev; /* cdev */
50 	struct class *class; /* 类 */
51 	struct device *device; /* 设备 */
52 	int major; /* 主设备号 */
53 };
54
55 struct leddev_dev leddev; /* led 设备 */
56
57 /*
58 * @description : LED 打开/关闭
59 * @param - sta : LEDON(0) 打开 LED,LEDOFF(1) 关闭 LED
60 * @return : 无
61 */
62 void led0_switch(u8 sta)
63 {
64 	u32 val = 0;
65 	if(sta == LEDON){
66 		val = readl(GPIO1_DR);
67 		val &= ~(1 << 3);
68 		writel(val, GPIO1_DR);
69 	}else if(sta == LEDOFF){
70 		val = readl(GPIO1_DR);
71 		val|= (1 << 3);
72 		writel(val, GPIO1_DR);
73 	}
74 }
75
76 /*
77 * @description : 打开设备
78 * @param – inode : 传递给驱动的 inode
79 * @param - filp : 设备文件,file 结构体有个叫做 private_data 的成员变量
80 * 一般在 open 的时候将 private_data 指向设备结构体。
81 * @return : 0 成功;其他 失败
82 */
83 static int led_open(struct inode *inode, struct file *filp)
84 {
85 	filp->private_data = &leddev; /* 设置私有数据 */
86 	return 0;
87 }
88
89 /*
90 * @description : 向设备写数据
91 * @param – filp : 设备文件,表示打开的文件描述符
92 * @param - buf : 要写给设备写入的数据
93 * @param - cnt : 要写入的数据长度
94 * @param - offt : 相对于文件首地址的偏移
95 * @return : 写入的字节数,如果为负值,表示写入失败
96 */
97 static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
98 {
99 		int retvalue;
100 	unsigned char databuf[1];
101 	unsigned char ledstat;
102
103 	retvalue = copy_from_user(databuf, buf, cnt);
104 	if(retvalue < 0) {
105 		return -EFAULT;
106 	}
107
108 	ledstat = databuf[0]; /* 获取状态值 */
109 	if(ledstat == LEDON) {
110 		led0_switch(LEDON); /* 打开 LED 灯 */
111 		}else if(ledstat == LEDOFF) {
112 			led0_switch(LEDOFF); /* 关闭 LED 灯 */
113 		}
114 	return 0;
115 }
116
117 /* 设备操作函数 */
118 static struct file_operations led_fops = {
119 	.owner = THIS_MODULE,
120 	.open = led_open,
121 	.write = led_write,
122 };
123
124 /*
125 * @description : flatform 驱动的 probe 函数,当驱动与设备
126 * 匹配以后此函数就会执行
127 * @param - dev : platform 设备
128 * @return : 0,成功;其他负值,失败
129 */
130 static int led_probe(struct platform_device *dev)
131 {
132 	int i = 0;
133 	int ressize[5];
134 	u32 val = 0;
135 	struct resource *ledsource[5];
136
137 	printk("led driver and device has matched!\r\n");
138 	/* 1、获取资源 */
139 	for (i = 0; i < 5; i++) {
140 		ledsource[i] = platform_get_resource(dev, IORESOURCE_MEM, i);
141 		if (!ledsource[i]) {
142 			dev_err(&dev->dev, "No MEM resource for always on\n");
143 			return -ENXIO;
144 		}
145 		ressize[i] = resource_size(ledsource[i]);
146 	}
147
148 	/* 2、初始化 LED */
149 	/* 寄存器地址映射 */
150 	IMX6U_CCM_CCGR1 = ioremap(ledsource[0]->start, ressize[0]);
151 	SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start, ressize[1]);
152 	SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start, ressize[2]);
153 	GPIO1_DR = ioremap(ledsource[3]->start, ressize[3]);
154 	GPIO1_GDIR = ioremap(ledsource[4]->start, ressize[4]);
155
156 	val = readl(IMX6U_CCM_CCGR1);
157 	val &= ~(3 << 26); /* 清除以前的设置 */
158 	val |= (3 << 26); /* 设置新值 */
159 	writel(val, IMX6U_CCM_CCGR1);
160
161 	/* 设置 GPIO1_IO03 复用功能,将其复用为 GPIO1_IO03 */
162 	writel(5, SW_MUX_GPIO1_IO03);
163 	writel(0x10B0, SW_PAD_GPIO1_IO03);
164
165 	/* 设置 GPIO1_IO03 为输出功能 */
166 	val = readl(GPIO1_GDIR);
167 	val &= ~(1 << 3); /* 清除以前的设置 */
168 	val |= (1 << 3); /* 设置为输出 */
169 	writel(val, GPIO1_GDIR);
170
171 	/* 默认关闭 LED1 */
172 	val = readl(GPIO1_DR);
173 	val |= (1 << 3) ;
174 	writel(val, GPIO1_DR);
175
176 	/* 注册字符设备驱动 */
177 	/*1、创建设备号 */
178 	if (leddev.major) { /* 定义了设备号 */
179 		leddev.devid = MKDEV(leddev.major, 0);
180 		register_chrdev_region(leddev.devid, LEDDEV_CNT,LEDDEV_NAME);
181 	} else { /* 没有定义设备号 */
182 		alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT,LEDDEV_NAME);
183 		leddev.major = MAJOR(leddev.devid);
184 	}
185
186 	/* 2、初始化 cdev */
187 	leddev.cdev.owner = THIS_MODULE;
188 	cdev_init(&leddev.cdev, &led_fops);
189
190 	/* 3、添加一个 cdev */
191 	cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);
192
193 	/* 4、创建类 */
194 	leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
195 	if (IS_ERR(leddev.class)) {
196 		return PTR_ERR(leddev.class);
197 	}
198
199 	/* 5、创建设备 */
200 	leddev.device = device_create(leddev.class, NULL, leddev.devid,NULL, LEDDEV_NAME);
201 	if (IS_ERR(leddev.device)) {
202 		return PTR_ERR(leddev.device);
203 	}
204
205 	return 0;
206 }
207
208 /*
209 * @description :移除 platform 驱动的时候此函数会执行
210 * @param - dev : platform 设备
211 * @return : 0,成功;其他负值,失败
212 */
213 static int led_remove(struct platform_device *dev)
214 {
215 	iounmap(IMX6U_CCM_CCGR1);
216 	iounmap(SW_MUX_GPIO1_IO03);
217 	iounmap(SW_PAD_GPIO1_IO03);
218 	iounmap(GPIO1_DR);
219 	iounmap(GPIO1_GDIR);
220
221 	cdev_del(&leddev.cdev); /* 删除 cdev */
222 	unregister_chrdev_region(leddev.devid, LEDDEV_CNT);
223 	device_destroy(leddev.class, leddev.devid);
224 	class_destroy(leddev.class);
225 	return 0;
226 }
227
228 /* platform 驱动结构体 */
229 static struct platform_driver led_driver = {
230 	.driver = {
231 	.name = "imx6ul-led", /* 驱动名字,用于和设备匹配 */
232 },
233 	.probe = led_probe,
234 	.remove = led_remove,
235 };
236
237 /*
238 * @description : 驱动模块加载函数
239 * @param : 无
240 * @return : 无
241 */
242 static int __init leddriver_init(void)
243 {
244 	return platform_driver_register(&led_driver);
245 }
246
247 /*
248 * @description : 驱动模块卸载函数
249 * @param : 无
250 * @return : 无
251 */
252 static void __exit leddriver_exit(void)
253 {
254 	platform_driver_unregister(&led_driver);
255 }
256
257 module_init(leddriver_init);
258 module_exit(leddriver_exit);
259 MODULE_LICENSE("GPL");
260 MODULE_AUTHOR("zuozhongkai");

leddriver.c文件内容就是按照之前示例的platform驱动模板编写的。

第34~122行,传统的字符设备驱动。

第130~206行,probe函数,当设备和驱动匹配以后此函数就会执行,当匹配成功以后会在终端上

输出“leddriveranddevicehasmatched!”这样语句。在probe函数里面初始化LED、注册字符设备驱

动。也就是将原来在驱动加载函数里面做的工作全部放到probe函数里面完成。

第213~226行,remove函数,当卸载platform驱动的时候此函数就会执行。在此函数里面释放内

存、注销字符设备等。也就是将原来驱动卸载函数里面的工作全部都放到remove函数中完成。

第229~235行,platform_driver驱动结构体,注意name字段为"imx6ul-led",和我们在leddevice.c

文件里面设置的设备name字段一致。第242~245行,驱动模块加载函数,在此函数里面通过

platform_driver_register向Linux内核注册led_driver驱动。

第252~255行,驱动模块卸载函数,在此函数里面通过platform_driver_unregister从Linux内核卸载

led_driver驱动。

2)测试APP编写

测试APP的内容很简单,就是打开和关闭LED灯,新建ledApp.c这个文件,然后在里面输入如下内

容:

1 #include "stdio.h"
2 #include "unistd.h"
3 #include "sys/types.h"
4 #include "sys/stat.h"
5 #include "fcntl.h"
6 #include "stdlib.h"
7 #include "string.h"
8 /***************************************************************
9 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
10 文件名 : ledApp.c
11 作者 : 左忠凯
12 版本 : V1.0
13 描述 : platform 驱动驱测试 APP。
14 其他 : 无
15 使用方法 :./ledApp /dev/platled 0 关闭 LED
16 ./ledApp /dev/platled 1 打开 LED
17 论坛 : www.openedv.com
18 日志 : 初版 V1.0 2019/8/16 左忠凯创建
19 ***************************************************************/
20 #define LEDOFF 0
21 #define LEDON 1
22
23 /*
24 * @description : main 主程序
25 * @param - argc : argv 数组元素个数
26 * @param - argv : 具体参数
27 * @return : 0 成功;其他 失败
28 */
29 int main(int argc, char *argv[])
30 {
31 	int fd, retvalue;
32 	char *filename;
33 	unsigned char databuf[2];
34
35 	if(argc != 3){
36 		printf("Error Usage!\r\n");
37 		return -1;
38 }
39
40 	filename = argv[1];
41 	/* 打开 led 驱动 */
42 	fd = open(filename, O_RDWR);
43 	if(fd < 0){
44 		printf("file %s open failed!\r\n", argv[1]);
45 		return -1;
46 	}
47
48 	databuf[0] = atoi(argv[2]); /* 要执行的操作:打开或关闭 */
49 	retvalue = write(fd, databuf, sizeof(databuf));
50 	if(retvalue < 0){
51 		printf("LED Control Failed!\r\n");
52 		close(fd);
53 		return -1;
54 	}
55
56 	retvalue = close(fd); /* 关闭文件 */
57 	if(retvalue < 0){
58 		printf("file %s close failed!\r\n", argv[1]);
59 		return -1;
60 	}
61 	return 0;
62 }

ledApp.c文件内容很简单,就是控制LED灯的亮灭,和第四十一章的测试APP基本一致,这里就不

重复讲解了。

4.运行测试

1)编译驱动程序和测试APP

1、编译驱动程序

编写Makefile文件,本章实验的Makefile文件和之前实验基本一样,只是将obj-m变量的值改为

“leddevice.oleddriver.o”,Makefile内容如下所示:

1 KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-
rel_imx_4.1.15_2.1.0_ga_alientek
......
4 obj-m := leddevice.o leddriver.o
......
11 clean:
12 $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

第4行,设置obj-m变量的值为“leddevice.oleddriver.o”。

输入如下命令编译出驱动模块文件:

make -j32

编译成功以后就会生成一个名为“leddevice.koleddriver.ko”的驱动模块文件。

2、编译测试APP

输入如下命令编译测试ledApp.c这个测试程序:

arm-linux-gnueabihf-gcc ledApp.c -o ledApp

编译成功以后就会生成ledApp这个应用程序。

2)运行测试

将上一小节编译出来leddevice.ko、leddriver.ko和ledApp这两个文件拷贝到

rootfs/lib/modules/4.1.15目录中,重启开发板,进入到目录lib/modules/4.1.15中,输入如下命令加

载leddevice.ko设备模块和leddriver.ko这个驱动模块。

depmod //第一次加载驱动的时候需要运行此命令
modprobe leddevice.ko //加载设备模块
modprobe leddriver.ko //加载驱动模块

根文件系统中/sys/bus/platform/目录下保存着当前板子platform总线下的设备和驱动,其中devices

子目录为platform设备,drivers子目录为plartofm驱动。查看/sys/bus/platform/devices/目录,看看

我们的设备是否存在,我们在leddevice.c中设置leddevice(platform_device类型)的name字段为

“imx6ul-led”,也就是设备名字为imx6ul-led,因此肯定在/sys/bus/platform/devices/目录下存在一

个名字“imx6ul-led”的文件,否则说明我们的设备模块加载失败,结果如图所示:

同理,查看/sys/bus/platform/drivers/目录,看一下驱动是否存在,我们在leddriver.c中设置

led_driver(platform_driver类型)的name字段为“imx6ul-led”,因此会在/sys/bus/platform/drivers/目

录下存在名为“imx6ul-led”这个文件,结果如图所示:

驱动模块和设备模块加载成功以后platform总线就会进行匹配,当驱动和设备匹配成功以后就会输

出如图所示一行语句:

驱动和设备匹配成功以后就可以测试LED灯驱动了,输入如下命令打开LED灯:

./ledApp /dev/platled 1 //打开 LED 灯

在输入如下命令关闭LED灯:

./ledApp /dev/platled 0 //关闭 LED 灯

观察一下LED灯能否打开和关闭,如果可以的话就说明驱动工作正常,如果要卸载驱动的话输入如

下命令即可:

rmmod leddevice.ko
rmmod leddriver.ko

上一章我们详细的讲解了Linux下的驱动分离与分层,以及总线、设备和驱动这样的驱动框架。基

于总线、设备和驱动这样的驱动框架,Linux内核提出来platform这个虚拟总线,相应的也有

platform设备和platform驱动。上一章我们讲解了传统的、未采用设备树的platform设备和驱动编写

方法。最新的Linux内核已经支持了设备树,因此在设备树下如何编写platform驱动就显得尤为重

要,本章我们就来学习一下如何在设备树下编写platform驱动。

5.设备树下的platform驱动简介

platform驱动框架分为总线、设备和驱动,其中总线不需要我们这些驱动程序员去管理,这个是

Linux内核提供的,我们在编写驱动的时候只要关注于设备和驱动的具体实现即可。在没有设备树

的Linux内核下,我们需要分别编写并注册platform_device和platform_driver,分别代表设备和驱

动。在使用设备树的时候,设备的描述被放到了设备树中,因此platform_device就不需要我们去

编写了,我们只需要实现platform_driver即可。在编写基于设备树的platform驱动的时候我们需要

注意一下几点:

1、在设备树中创建设备节点

毫无疑问,肯定要先在设备树中创建设备节点来描述设备信息,重点是要设置好compatible属性的

值,因为platform总线需要通过设备节点的compatible属性值来匹配驱动!这点要切记。比如,

我们可以编写如下所示的设备节点来描述我们本章实验要用到的LED这个设备:

1 gpioled {
2 	#address-cells = <1>;
3 	#size-cells = <1>;
4 	compatible = "atkalpha-gpioled";
5 	pinctrl-names = "default";
6 	pinctrl-0 = <&pinctrl_led>;
7 	led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
8 	status = "okay";
9 };

以上示例中的gpioled节点其实就是之前中创建的gpioled设备节点,我们可以直接拿过来用。注意

第4行的compatible属性值为“atkalpha-gpioled”,因此一会在编写platform驱动的时候

of_match_table属性表中要有“atkalpha-gpioled”。

2、编写platform驱动的时候要注意兼容属性

上一章已经详细的讲解过了,在使用设备树的时候platform驱动会通过of_match_table来保存兼容

性值,也就是表明此驱动兼容哪些设备。所以,of_match_table将会尤为重要,比如本例程的

platform驱动中platform_driver就可以按照如下所示设置:

1 static const struct of_device_id leds_of_match[] = {
2 	{ .compatible = "atkalpha-gpioled" }, /* 兼容属性 */
3 	{ /* Sentinel */ }
4 };
5
6 MODULE_DEVICE_TABLE(of, leds_of_match);
7
8 static struct platform_driver leds_platform_driver = {
9 	.driver = {
10 		.name = "imx6ul-led",
11 		.of_match_table = leds_of_match,
12 	},
13 	.probe = leds_probe,
14 	.remove = leds_remove,
15 };

第1~4行,of_device_id表,也就是驱动的兼容表,是一个数组,每个数组元素为of_device_id类

型。每个数组元素都是一个兼容属性,表示兼容的设备,一个驱动可以跟多个设备匹配。这里我们

仅仅匹配了一个设备,那就是之前中创建的gpioled这个设备。第2行的compatible值为“atkalpha-

gpioled”,驱动中的compatible属性和设备中的compatible属性相匹配,因此驱动中对应的probe函

数就会执行。注意第3行是一个空元素,在编写of_device_id的时候最后一个元素一定要为空

第6行,通过MODULE_DEVICE_TABLE声明一下leds_of_match这个设备匹配表

第11行,设置platform_driver中的of_match_table匹配表为上面创建的leds_of_match,至此我们就

设置好了platform驱动的匹配表了。

3、编写platform驱动

基于设备树的platform驱动和上一章无设备树的platform驱动基本一样,都是当驱动和设备匹配成

功以后就会执行probe函数。我们需要在probe函数里面执行字符设备驱动那一套,当注销驱动模

块的时候remove函数就会执行,都是大同小异的。

6.实验程序编写

本章实验我们编写基于设备树的platform驱动,所以需要在设备树中添加设备节点,然后我们只需

要编写platform驱动即可。

1)修改设备树文件

首先修改设备树文件,加上我们需要的设备信息,本章我们就使用到一个LED灯,因此可以直接使

用之前编写的gpioled子节点即可,不需要再重复添加。

2)platform驱动程序编写

新建名为leddriver.c的驱动文件,在leddriver.c中输入如下所示内容:

1 #include 
2 #include 
3 #include 
4 #include 
5 #include 
6 #include 
7 #include 
8 #include 
9 #include 
10 #include 
11 #include 
12 #include 
13 #include 
14 #include 
15 #include 
16 #include 
17 #include 
18 #include 
19 #include 
20 #include 
21 #include 
22 #include 
23 /***************************************************************
24 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
25 文件名 : leddriver.c
26 作者 : 左忠凯
27 版本 : V1.0
28 描述 : 设备树下的 platform 驱动
29 其他 : 无
30 论坛 : www.openedv.com
31 日志 : 初版 V1.0 2019/8/13 左忠凯创建
32 ***************************************************************/
33 #define LEDDEV_CNT 1 /* 设备号长度 */
34 #define LEDDEV_NAME "dtsplatled" /* 设备名字 */
35 #define LEDOFF 0
36 #define LEDON 1
37
38 /* leddev 设备结构体 */
39 struct leddev_dev{
40 	dev_t devid; /* 设备号 */
41 	struct cdev cdev; /* cdev */
42 	struct class *class; /* 类 */
43 	struct device *device; /* 设备 */
44 	int major; /* 主设备号 */
45 	struct device_node *node; /* LED 设备节点 */
46 	int led0; /* LED 灯 GPIO 标号 */
47 };
48
49 struct leddev_dev leddev; /* led 设备 */
51 /*
52 * @description : LED 打开/关闭
53 * @param - sta : LEDON(0) 打开 LED,LEDOFF(1) 关闭 LED
54 * @return : 无
55 */
56 void led0_switch(u8 sta)
57 {
58 	if (sta == LEDON )
59 		gpio_set_value(leddev.led0, 0);
60 	else if (sta == LEDOFF)
61 		gpio_set_value(leddev.led0, 1);
62 }
63
64 /*
65 * @description : 打开设备
66 * @param – inode : 传递给驱动的 inode
67 * @param - filp : 设备文件,file 结构体有个叫做 private_data 的成员变量
68 * 一般在 open 的时候将 private_data 指向设备结构体。
69 * @return : 0 成功;其他 失败
70 */
71 static int led_open(struct inode *inode, struct file *filp)
72 {
73 		filp->private_data = &leddev; /* 设置私有数据 */
74 		return 0;
75 }
76
77 /*
78 * @description : 向设备写数据
79 * @param - filp : 设备文件,表示打开的文件描述符
80 * @param - buf : 要写给设备写入的数据
81 * @param - cnt : 要写入的数据长度
82 * @param – offt : 相对于文件首地址的偏移
83 * @return : 写入的字节数,如果为负值,表示写入失败
84 */
85 static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
86 {
87 	int retvalue;
88 	unsigned char databuf[2];
89 	unsigned char ledstat;
90
91 	retvalue = copy_from_user(databuf, buf, cnt);
92 	if(retvalue < 0) {
93
94 		printk("kernel write failed!\r\n");
95 		return -EFAULT;
96 	}
97
98 	ledstat = databuf[0];
99 	if (ledstat == LEDON) {
100 	led0_switch(LEDON);
101 } else if (ledstat == LEDOFF) {
102 	led0_switch(LEDOFF);
103 }
104 return 0;
105 }
106
107 /* 设备操作函数 */
108 static struct file_operations led_fops = {
109 	.owner = THIS_MODULE,
110 	.open = led_open,
111 	.write = led_write,
112 };
113
114 /*
115 * @description : flatform 驱动的 probe 函数,当驱动与
116 * 设备匹配以后此函数就会执行
117 * @param - dev : platform 设备
118 * @return : 0,成功;其他负值,失败
119 */
120 static int led_probe(struct platform_device *dev)
121 {
122 	printk("led driver and device was matched!\r\n");
123 	/* 1、设置设备号 */
124 	if (leddev.major) {
125 		leddev.devid = MKDEV(leddev.major, 0);
126 		register_chrdev_region(leddev.devid, LEDDEV_CNT,LEDDEV_NAME);
127 	} else {
128 		alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT,LEDDEV_NAME);
129 		leddev.major = MAJOR(leddev.devid);
130 	}
131
132 	/* 2、注册设备 */
133 	cdev_init(&leddev.cdev, &led_fops);
134 	cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);
135
136 	/* 3、创建类 */
137 	leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
138 	if (IS_ERR(leddev.class)) {
139 		return PTR_ERR(leddev.class);
140 	}
141
142 	/* 4、创建设备 */
143 	leddev.device = device_create(leddev.class, NULL, leddev.devid,NULL, LEDDEV_NAME);
144 	if (IS_ERR(leddev.device)) {
145 		return PTR_ERR(leddev.device);
146 	}
147
148 	/* 5、初始化 IO */
149 	leddev.node = of_find_node_by_path("/gpioled");
150 	if (leddev.node == NULL){
151 		printk("gpioled node nost find!\r\n");
152 		return -EINVAL;
153 	}
154
155 	leddev.led0 = of_get_named_gpio(leddev.node, "led-gpio", 0);
156 	if (leddev.led0 < 0) {
157 		printk("can't get led-gpio\r\n");
158 		return -EINVAL;
159 	}
160
161 	gpio_request(leddev.led0, "led0");
162 	gpio_direction_output(leddev.led0, 1); /*设置为输出,默认高电平 */
163 	return 0;
164 }
165
166 /*
167 * @description : remove 函数,移除 platform 驱动的时候此函数会执行
168 * @param - dev : platform 设备
169 * @return : 0,成功;其他负值,失败
170 */
171 static int led_remove(struct platform_device *dev)
172 {
173 	gpio_set_value(leddev.led0, 1); /* 卸载驱动的时候关闭 LED */
174
175 	cdev_del(&leddev.cdev); /* 删除 cdev */
176 	unregister_chrdev_region(leddev.devid, LEDDEV_CNT);
177 	device_destroy(leddev.class, leddev.devid);
178 	class_destroy(leddev.class);
179 	return 0;
180 }
181
182 /* 匹配列表 */
183 static const struct of_device_id led_of_match[] = {
184 	{ .compatible = "atkalpha-gpioled" },
185 	{ /* Sentinel */ }
186 };
187
188 /* platform 驱动结构体 */
189 static struct platform_driver led_driver = {
190 	.driver = {
191 		.name = "imx6ul-led", /* 驱动名字,用于和设备匹配 */
192 		.of_match_table = led_of_match, /* 设备树匹配表 */
193 	},
194 	.probe = led_probe,
195 	.remove = led_remove,
196 };
197
198 /*
199 * @description : 驱动模块加载函数
200 * @param : 无
201 * @return : 无
202 */
203 static int __init leddriver_init(void)
204 {
205 	return platform_driver_register(&led_driver);
206 }
207
208 /*
209 * @description : 驱动模块卸载函数
210 * @param : 无
211 * @return : 无
212 */
213 static void __exit leddriver_exit(void)
214 {
215 	platform_driver_unregister(&led_driver);
216 }
217
218 module_init(leddriver_init);
219 module_exit(leddriver_exit);
220 MODULE_LICENSE("GPL");
221 MODULE_AUTHOR("zuozhongkai");


第33~112行,传统的字符设备驱动,没什么要说的。

第120~164行,platform驱动的probe函数,当设备树中的设备节点与驱动之间匹配成功以后此函数

就会执行,原来在驱动加载函数里面做的工作现在全部放到probe函数里面完成。

第171~180行,remobe函数,当卸载platform驱动的时候此函数就会执行。在此函数里面释放内

存、注销字符设备等,也就是将原来驱动卸载函数里面的工作全部都放到remove函数中完成。

第183~186行,匹配表,描述了此驱动都和什么样的设备匹配,第184行添加了一条值为"atkalpha-

gpioled"的compatible属性值,当设备树中某个设备节点的compatible属性值也为“atkalpha-

gpioled”的时候就会与此驱动匹配。

第189~196行,platform_driver驱动结构体,191行设置这个platform驱动的名字为“imx6ul-

led”,因此,当驱动加载成功以后就会在/sys/bus/platform/drivers/目录下存在一个名为“imx6u-

led”的文件

第192行设置of_match_table为上面的led_of_match。

第203~206行,驱动模块加载函数,在此函数里面通过platform_driver_register向Linux内核注册

led_driver驱动。

第213~216行,驱动模块卸载函数,在此函数里面通过platform_driver_unregister从Linux内核卸载

led_driver驱动。

7.运行测试

1)编译驱动程序和测试APP

1、编译驱动程序

编写Makefile文件,本章实验的Makefile文件和之前实验基本一样,只是将obj-m变量的值改为

“leddriver.o”,Makefile内容如下所示:

1 KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-
rel_imx_4.1.15_2.1.0_ga_alientek
......
4 obj-m := leddriver.o
......
11 clean:
12 $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

第4行,设置obj-m变量的值为“leddriver.o”。

输入如下命令编译出驱动模块文件:

make -j32

编译成功以后就会生成一个名为“leddriver.o”的驱动模块文件。

2、编译测试APP

测试APP直接使用上一章的ledApp这个测试软件即可。

2)运行测试

将上一小节编译出来leddriver.ko拷贝到rootfs/lib/modules/4.1.15目录中,重启开发板,进入到目录

lib/modules/4.1.15中,输入如下命令加载leddriver.ko这个驱动模块。

depmod //第一次加载驱动的时候需要运行此命令
modprobe leddriver.ko //加载驱动模块

驱动模块加载完成以后到/sys/bus/platform/drivers/目录下查看驱动是否存在,我们在leddriver.c中

设置led_driver(platform_driver类型)的name字段为“imx6ul-led”,因此会

在/sys/bus/platform/drivers/目录下存在名为“imx6ul-led”这个文件,结果如图所示:

同理,在/sys/bus/platform/devices/目录下也存在led的设备文件,也就是设备树中gpioled这个

节点,如图所示:

驱动和模块都存在,当驱动和设备匹配成功以后就会输出如图所示一行语句: 

驱动和设备匹配成功以后就可以测试LED灯驱动了,输入如下命令打开LED灯:

./ledApp /dev/dtsplatled 1 //打开 LED 灯

在输入如下命令关闭LED灯:

./ledApp /dev/dtsplatled 0 //关闭 LED 灯

观察一下LED灯能否打开和关闭,如果可以的话就说明驱动工作正常,如果要卸载驱动的话输入如

下命令即可:

rmmod leddriver.ko

你可能感兴趣的:(Linux,驱动开发,linux,运维)