i.MX6ULL(二十) linux platform 设备驱动

news2024/9/24 15:29:04
Linux 系统要考虑到驱动的可重用性,因

 

此提出了驱动的分离与分层这样的软件思路,在这个思路下诞生了我们将来最常打交道的
platform 设备驱动,也叫做平台设备驱动。

1 Linux 驱动的分离与分层

1.1 驱动的分隔与分离

对于 Linux 这样一个成熟、庞大、复杂的操作系统,代码的重用性非常重要,否则的话就
会在 Linux 内核中存在大量无意义的重复代码。尤其是驱动程序,因为驱动程序占用了 Linux
内核代码量的大头,如果不对驱动程序加以管理,任由重复的代码肆意增加,那么用不了多久
Linux 内核的文件数量就庞大到无法接受的地步。
假如现在有三个平台 A B C ,这三个平台 ( 这里的平台说的是 SOC) 上都有 MPU6050
I2C 接口的六轴传感器,按照我们写裸机 I2C 驱动的时候的思路,每个平台都有一个 MPU6050
的驱动,因此编写出来的最简单的驱动框架如图 54.1.1 所示:

每种平台下都有一个主机驱动和设备驱动,主机驱动肯定是必须
要的,毕竟不同的平台其 I2C 控制器不同。但是右侧的设备驱动就没必要每个平台都写一个
最好的做法就是每个平台的 I2C 控制器都提供一个统一的接口
( 也叫做主机驱动 ) ,每个设备的话也只提供一个驱动程序 ( 设备驱动 ) ,每个设备通过统一的 I2C
接口驱动来访问

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

这个就是驱动的分隔,也就是将主机驱动和设备驱动分隔开来。

在实际的驱动开发中,一般 I2C 主机控制器驱动已经由
半导体厂家编写好了,而设备驱动一般也由设备器件的厂家编写好了,我们只需要提供设备信 息即可,
比如 I2C 设备的话提供设备连接到了哪个 I2C 接口上, I2C 的速度是多少等等。相当
于将设备信息从设备驱动中剥离开来,驱动使用标准方法去获取到设备信息 ( 比如从设备树中获 取到设备信息) ,然后根据获取到的设备信息来初始化设备。
这样就相当于驱动只负责驱动, 设备只负责设备,想办法将两者进行匹配即可。
这个就是 Linux 中的总线 (bus) 、驱动 (driver) 和 设备(device) 模型,也就是常说的驱动分离。总线就是驱动和设备信息的月老,负责给两者牵线 搭桥,如图 54.1.1.4 所示:
当我们向系统注册一个驱动的时候, 总线就会在右侧的设备中查找,看看有没有与之匹配
的设备,如果有的话就将两者联系起来。同样的,当向系统中注册一个设备的时候,总线就会
在左侧的驱动中查找看有没有与之匹配的设备,有的话也联系起来 Linux 内核中大量的驱动
程序都采用总线、驱动和设备模式,我们一会要重点讲解的 platform 驱动就是这一思想下的产
物。

1.2 驱动的分层

Linux 下的驱动往往也是分层的,分层的目 的也是为了在不同的层处理不同的内容。以其他书籍或者资料常常使用到的input( 输入子系统, 后面会有专门的章节详细的讲解) 为例,简单介绍一下驱动的分层。
input 子系统负责管理所有 跟输入有关的驱动,包括键盘、鼠标、触摸等,最底层的就是设备原始驱动,负责获取输入设 备的原始值,获取到的输入事件上报给 input 核心层。 input 核心层会处理各种 IO 模型,并且提 供 file_operations 操作集合
我们在编写输入设备驱动的时候只需要处理好输入事件的上报即 可,至于如何处理这些上报的输入事件那是上层去考虑的,我们不用管。

2 platform 平台驱动模型简介

前面我们讲了设备驱动的分离,并且引出了总线 (bus) 、驱动 (driver) 和设备 (device) 模型,比
I2C SPI USB 等总线。但是在 SOC 中有些外设是没有总线这个概念的,但是又要使用总
线、驱动和设备模型该怎么办呢?为了解决此问题, Linux 提出了 platform 这个虚拟总线,相应
的就有 platform_driver platform_device

2.1 platform 总线

Linux 系统内核使用 bus_type 结构体表示总线,此结构体定义在文件 include/linux/device.h
bus_type 结构体内容如下:
 struct bus_type {
 	const char *name; /* 总线名字 */
 	const char *dev_name; 
 	struct device *dev_root;
 	struct device_attribute *dev_attrs;
 	const struct attribute_group **bus_groups; /* 总线属性 */
 	const struct attribute_group **dev_groups; /* 设备属性 */
 	const struct attribute_group **drv_groups; /* 驱动属性 */
 
 	int (*match)(struct device *dev, struct device_driver *drv);
 	int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
	 int (*probe)(struct device *dev);
 	int (*remove)(struct device *dev);
 	void (*shutdown)(struct device *dev);
 
 	int (*online)(struct device *dev);
 	int (*offline)(struct device *dev);
	int (*suspend)(struct device *dev, pm_message_t state);
	int (*resume)(struct device *dev);
	const struct dev_pm_ops *pm;
	const struct iommu_ops *iommu_ops;
	struct subsys_private *p;
	struct lock_class_key lock_key;
 };
总线就是使用 match 函数来根据注册的设备来查找对应的驱
动,或者根据注册的驱动来查找相应的设备,因此每一条总线都必须实现此函数。 match 函数有
两个参数: dev drv ,这两个参数分别为 device device_driver 类型,也就是设备和驱动。

2.1.1 platform_bus_type 继承 bus_type

struct bus_type platform_bus_type = {
     .name = "platform",
     .dev_groups = platform_dev_groups,
     .match = platform_match,
     .uevent = platform_uevent,
     .pm = &platform_dev_pm_ops,
 };
platform_bus_type 就是 platform 平台总线,其中 platform_match 就是匹配函数。我们来看
一下驱动和设备是如何匹配的, platform_match 函数定义在文件 drivers/base/platform.c 中,函
数内容如下所示:
static int platform_match(struct device *dev,
struct device_driver *drv)
 {
     struct platform_device *pdev = to_platform_device(dev);
     struct platform_driver *pdrv = to_platform_driver(drv);
 
     /*When driver_override is set,only bind to the matching driver*/
     if (pdev->driver_override)
         return !strcmp(pdev->driver_override, drv->name);
 
     /* Attempt an OF style match first */
     if (of_driver_match_device(dev, drv))
         return 1;

     /* Then try ACPI style match */
     if (acpi_driver_match_device(dev, drv))
         return 1;
    if (pdrv->id_table)
         return platform_match_id(pdrv->id_table, pdev) != NULL;
 
      /* fall-back to driver name match */
     return (strcmp(pdev->name, drv->name) == 0);
   }

2.1.2 驱动和设备的匹配

4 种匹配方式
1.  OF 类型的匹配,也就是设备树采用的匹配方式,
of_driver_match_device 函数定义在文件 include/linux/of_device.h 中。 device_driver 结构体 ( 表示
设备驱动 ) 中有个名为 of_match_table 的成员变量,此成员变量保存着驱动的 compatible 匹配表,
设备树中的每个设备节点的 compatible 属性会和 of_match_table 表中的所有成员比较,查看是
否有相同的条目,如果有的话就表示设备和此驱动匹配,设备和驱动匹配成功以后 probe 函数
就会执行。
2.  ACPI 匹配方式。
3.  id_table 匹配,每个 platform_driver 结构体有一个 id_table
成员变量,顾名思义,保存了很多 id 信息。这些 id 信息存放着这个 platformd 驱动所支持的驱
动类型。
4. 如果第三种匹配方式的 id_table 不存在的话就直接比较驱动和
设备的 name 字段,看看是不是相等,如果相等的话就匹配成功。
对于支持设备树的 Linux 版本号, 一般设备驱动为了兼容性都支持设备树和无设备树两种
匹配方式。 也就是第一种匹配方式一般都会存在,第三种和第四种只要存在一种就可以,一般
用的最多的还是第四种,也就是直接比较驱动和设备的 name 字段,毕竟这种方式最简单了。

2.2 platform 驱动

platform_driver 结 构 体 表 示 platform 驱动,此结构体定义在文件
include/linux/platform_device.h 中,内容如下:
 struct platform_driver {
     int (*probe)(struct platform_device *);
     int (*remove)(struct platform_device *);
     void (*shutdown)(struct platform_device *);
     int (*suspend)(struct platform_device *, pm_message_t state);
     int (*resume)(struct platform_device *);
     struct device_driver driver;
     const struct platform_device_id *id_table;
     bool prevent_deferred_probe;
 };
probe 函数,当驱动与设备匹配成功以后 probe 函数就会执行,非常重要的函数!!
一般驱动的提供者会编写,如果自己要编写一个全新的驱动,那么 probe 就需要自行实现。
driver 成员,为 device_driver 结构体变量, Linux 内核里面大量使用到了面向对象
的思维, device_driver 相当于基类,提供了最基础的驱动框架。 plaform_driver 继承了这个基类,
然后在此基础上又添加了一些特有的成员变量。
id_table 表,也就是我们上一小节讲解 platform 总线匹配驱动和设备的时候采用的
第三种方法, id_table 是个表 ( 也就是数组 ) ,每个元素的类型为 platform_device_id
platform_device_id 结构体内容如下:
 struct platform_device_id {
     char name[PLATFORM_NAME_SIZE];
     kernel_ulong_t driver_data;
 };
device_driver 结构体定义在 include/linux/device.h device_driver 结构体内容如下:
示例代码 54.2.2.3 device_driver 结构体
 struct device_driver {
     const char *name;
     struct bus_type *bus;
 
     struct module *owner;
     const char *mod_name; /* used for built-in modules */
 
     bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
 
     const struct of_device_id *of_match_table;
     const struct acpi_device_id *acpi_match_table;

     int (*probe) (struct device *dev);
     int (*remove) (struct device *dev);
     void (*shutdown) (struct device *dev);
     int (*suspend) (struct device *dev, pm_message_t state);
     int (*resume) (struct device *dev);
     const struct attribute_group **groups;

     const struct dev_pm_ops *pm;

     struct driver_private *p;
 };
of_match_table 就是采用设备树的时候驱动使用的匹配表,同样是数组,每个匹
配项都为 of_device_id 结构体类型,
compatible 非常重要,因为对于设备树而言,就是通过设备节点的 compatible
性值和 of_match_table 中每个项目的 compatible 成员变量进行比较,如果有相等的就表示设备
和此驱动匹配成功。
在编写 platform 驱动的时候,首先定义一个 platform_driver 结构体变量,然后实现结构体
中的各个成员变量,重点是实现匹配方法以及 probe 函数。当驱动和设备匹配成功以后 probe
函数就会执行,具体的驱动程序在 probe 函数里面编写,比如字符设备驱动等等。
int platform_driver_register (struct platform_driver *driver)
platform_driver_unregister(struct platform_driver *drv)
platform 驱动框架如下所示
platform 驱动还是传统的字符设备驱动、块设备驱动或网络设备驱动,只是套
上了一张“platform”的皮,目的是为了使用总线、驱动和设备这个驱动模型来实现驱动的分
离与分层。
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 属性就是用于设备树下的驱动与设备检查。对于一个完整的驱动程序,必须提供
有设备树和无设备树两种匹配方法。

2.3 platform 设备

platform_device 这个结构体表示 platform 设备,这里我们要注意,如果内核支持设备树
的话就不要再使用 platform_device 来描述设备了。
示例代码 54.2.3.1 platform_device 结构体代码段
 struct platform_device {
     const char *name; 
     int id; 
     bool id_auto;
     struct device dev;
     u32 num_resources; 
     struct resource *resource;

     const struct platform_device_id *id_entry;
     char *driver_override; /* Driver name to force a match */

     /* MFD cell pointer */
     struct mfd_cell *mfd_cell;

     /* arch specific additions */
     struct pdev_archdata archdata;
 };

 struct resource {
     resource_size_t start;
     resource_size_t end;
     const char *name;
     unsigned long flags;
     struct resource *parent, *sibling, *child;
 };
23 行, name 表示设备名字,要和所使用的 platform 驱动的 name 字段相同,否则的话设
备就无法匹配到对应的驱动。比如对应的 platform 驱动的 name 字段为“ xxx-gpio ”,那么此 name
字段也要设置为“ xxx-gpio ”。
27 行, num_resources 表示资源数量,一般为第 28 resource 资源的大小。
28 行, resource 表示资源,也就是设备信息,比如外设寄存器等。 Linux 内核使用 resource
结构体表示资源。
start end 分别表示资源的起始和终止信息,对于内存类的资源,就表示内存起始和终止
地址, name 表示资源名字, flags 表示资源类型,可选的资源类型都定义在了文件
include/linux/ioport.h 里面,
在以前不支持设备树的 Linux 版本中,用户需要编写 platform_device 变量来描述设备信息,
然后使用 platform_device_register 函数将设备信息注册到 Linux 内核中,此函数原型如下所示:
int platform_device_register(struct platform_device *pdev)
如果不再使用 platform 的话可以通过 platform_device_unregister 函数注销掉相应的 platform
设备, platform_device_unregister 函数原型如下:
​​​​​​​
void platform_device_unregister(struct platform_device *pdev)

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

示例代码 54.2.3.4 platform 设备框架
 /* 寄存器地址定义*/
 #define PERIPH1_REGISTER_BASE (0X20000000) /* 外设 1 寄存器首地址 */ 
 #define PERIPH2_REGISTER_BASE (0X020E0068) /* 外设 2 寄存器首地址 */
 #define REGISTER_LENGTH 4

 /* 资源 */
 static struct resource xxx_resources[] = {
 [0] = {
 .start = PERIPH1_REGISTER_BASE,
 .end = (PERIPH1_REGISTER_BASE + REGISTER_LENGTH - 1),
 .flags = IORESOURCE_MEM,
 }, 
 [1] = {
 .start = PERIPH2_REGISTER_BASE,
 .end = (PERIPH2_REGISTER_BASE + REGISTER_LENGTH - 1),
 .flags = IORESOURCE_MEM,
 },
 };

 /* platform 设备结构体 */
 static struct platform_device xxxdevice = {
 .name = "xxx-gpio",
 .id = -1,
 .num_resources = ARRAY_SIZE(xxx_resources),
 .resource = xxx_resources,
 };
 
 /* 设备模块加载 */
static int __init xxxdevice_init(void)
 {
 return platform_device_register(&xxxdevice);
 }

 /* 设备模块注销 */
 static void __exit xxx_resourcesdevice_exit(void)
 {
 platform_device_unregister(&xxxdevice);
 }

 module_init(xxxdevice_init);
 module_exit(xxxdevice_exit);
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("zuozhongkai");
相比设备树 复杂多了,当 Linux 内核支持了设
备树以后就不需要用户手动去注册 platform 设备了。因为设备信息都放到了设备树中去描述,
Linux 内核启动的时候会从设备树中读取设备信息,然后将其组织成 platform_device 形式
非设备树下的例程忽略

设备树下的 platform 驱动简介

platform 驱动框架分为总线、设备和驱动,其中总线不需要我们这些驱动程序员去管理,这
个是 Linux 内核提供的,我们在编写驱动的时候只要关注于设备和驱动的具体实现即可。在没
有设备树的 Linux 内核下,我们需要分别编写并注册 platform_device platform_driver ,分别代
表设备和驱动。在使用设备树的时候,设备的描述被放到了设备树中,因此 platform_device
不需要我们去编写了,我们只需要实现 platform_driver 即可。在编写基于设备树的 platform
动的时候我们需要注意一下几点:
1 、在设备树中创建设备节点
毫无疑问,肯定要先在设备树中创建设备节点来描述设备信息,重点是要设置好 compatible
属性的值,因为 platform 总线需要通过设备节点的 compatible 属性值来匹配驱动!这点要切记。
比如,我们可以编写如下所示的设备节点来描述我们本章实验要用到的 LED 这个设备:
示例代码 55.1.1 gpioled 设备节点
 gpioled {
     #address-cells = <1>;
     #size-cells = <1>;
     compatible = "atkalpha-gpioled";
     pinctrl-names = "default";
     pinctrl-0 = <&pinctrl_led>;
     led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
     status = "okay";
 };
2 、编写 platform 驱动的时候要注意兼容属性
在使用设备树的时候 platform 驱动会通过 of_match_table
保存兼容性值,也就是表明此驱动兼容哪些设备。所以, of_match_table 将会尤为重要,比如本
例程的 platform 驱动中 platform_driver 就可以按照如下所示设置:
示例代码 55.1.2 of_match_table 匹配表的设置
 static const struct of_device_id leds_of_match[] = {
     { .compatible = "atkalpha-gpioled" }, /* 兼容属性 */
     { /* Sentinel */ }
 };
 
 MODULE_DEVICE_TABLE(of, leds_of_match); //声明一下 leds_of_match 这个设备匹配表。
 
static struct platform_driver leds_platform_driver = {
     .driver = {
         .name = "imx6ul-led",
         .of_match_table = leds_of_match,
     },
     .probe = leds_probe,
     .remove = leds_remove,
 };
通过 MODULE_DEVICE_TABLE 声明一下 leds_of_match 这个设备匹配表。
3 、编写 platform 驱动
基于设备树的 platform 驱动和上一章无设备树的 platform 驱动基本一样,都是当驱动和设
备匹配成功以后就会执行 probe 函数。我们需要在 probe 函数里面执行字符设备驱动那一套,
当注销驱动模块的时候 remove 函数就会执行,都是大同小异的

3.1 硬件原理图分析

本章实验我们只使用到 IMX6U-ALPHA 开发板上的 LED 灯,因此实验硬件原理图参考 8.3
小节即可。
​​​​​​​

3.2 实验程序编写

本实验对应的例程路径为: 开发板光盘 -> 2 Linux 驱动例程 -> 18_dtsplatform
本章实验我们编写基于设备树的 platform 驱动,所以需要在设备树中添加设备节点,然后
我们只需要编写 platform 驱动即可。
183~186 行,匹配表,描述了此驱动都和什么样的设备匹配,第 184 行添加了一条值为
"atkalpha-gpioled" compatible 属性值,当设备树中某个设备节点的 compatible 属性值也为
atkalpha-gpioled ”的时候就会与此驱动匹配。
120~164 行, platform 驱动的 probe 函数,当设备树中的设备节点与驱动之间匹配成功
以后此函数就会执行,原来在驱动加载函数里面做的工作现在全部放到 probe 函数里面完成。
189~196 行, platform_driver 驱动结构体, 191 行设置这个 platform 驱动的名字为“ imx6ul
led ”,因此,当驱动加载成功以后就会在 /sys/bus/platform/drivers/ 目录下存在一个名为“ imx6u
led ”的文件。 第 192 行设置 of_match_table 为上面的 led_of_match
示例代码 55.3.2.1 leddriver.c 文件代码段
1 #include <linux/types.h>
2 #include <linux/kernel.h>
3 #include <linux/delay.h>
4 #include <linux/ide.h>
5 #include <linux/init.h>
6 #include <linux/module.h>
7 #include <linux/errno.h>
8 #include <linux/gpio.h>
9 #include <linux/cdev.h>
10 #include <linux/device.h>
11 #include <linux/of_gpio.h>
12 #include <linux/semaphore.h>
13 #include <linux/timer.h>
14 #include <linux/irq.h>
15 #include <linux/wait.h>
16 #include <linux/poll.h>
17 #include <linux/fs.h>
18 #include <linux/fcntl.h>
19 #include <linux/platform_device.h>
20 #include <asm/mach/map.h>
21 #include <asm/uaccess.h>
22 #include <asm/io.h>
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 设备 */
50
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");

3.3 编写测试 APP

测试 APP 就直接使用上一章 54.4.2 小节编写的 ledApp.c 即可。

3.4 运行测试

将 编译出来 leddriver.ko 拷贝到 rootfs/lib/modules/4.1.15 目录中,重启开发板,进
入到目录 lib/modules/4.1.15 中,输入如下命令加载 leddriver.ko 这个驱动模块。
depmod   //第一次加载驱动的时候需要运行此命令 使用 depmod命令,可以在终端中输入 depmod -a,这将分析所有可用的模块并生成依赖关系图表。( 可以不输入)
modprobe leddriver.ko // 加载驱动模块  (包含依赖项)
驱动模块加载完成以后到 /sys/bus/platform/drivers/ 目录下查看驱动是否存在,我们在
leddriver.c 中设置 led_driver (platform_driver 类型 ) name 字段为“ imx6ul-led ”,因此会在
/sys/bus/platform/drivers/ 目录下存在名为“ imx6ul-led ”这个文件,结果如图 55.4.2.1 所示:
/sys/bus/platform/devices/ 目录下也存在 led 的设备文件,也就是设备树中 gpioled
个节点,如图 55.4.2.2 所示:
驱动和模块都存在,当驱动和设备匹配成功以后就会输出如图 55.4.2.3 所示一行语句
./ledApp /dev/dtsplatled 1
// 打开 LED
./ledApp /dev/dtsplatled 0
// 关闭 LED
​​​​​​​
rmmod leddriver.ko

​​​​​​​

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/798368.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

查看电脑上NET Framework最高版本

1、使用CMD工具 1、进入路径&#xff1a; cd %WINDIR%\Microsoft.NET\Framework\v4.0.303192、查看版本 MSBuild /version2、效果

【Github】自动监测 SSL 证书过期的轻量级监控方案 - Domain Admin

在现代的企业网络中&#xff0c;网站安全和可靠性是至关重要的。一个不注意的SSL证书过期可能导致网站出现问题&#xff0c;给公司业务带来严重的影响。针对这个问题&#xff0c;手动检测每个域名和机器的证书状态需要花费大量的时间和精力。为了解决这个问题&#xff0c;我想向…

NAT详解(网络地址转换)

一句话说清楚它是干什么的&#xff1a; 网络地址转换&#xff1a;是指通过专用网络地址转换为公用地址&#xff0c;从而对外隐藏内部管理的IP地址&#xff0c;它使得整个专用网只需要一个全球IP就可以访问互联网&#xff0c;由于专用网IP地址是可以重用的&#xff0c;所以NAT大…

基于机器学习的供水管网水力模型

大数据、人工智能、物联网等前沿技术正推动人类社会发展发生深刻变革。2021年12月12日&#xff0c;国务院印发了《“十四五”数字经济发展规划》&#xff0c;进一步指明了各行业数字化转型发展的方向。作为传统的民生保障行业&#xff0c;供水行业也面临着向数字化智慧化转型的…

2023年的前端开发还好找工作么

首先可以明确写在前面的是&#xff0c;前端的确是不像之前一样&#xff0c;只会简单的基础就能轻轻松松的找到工作的&#xff0c;随着学习的人不断增多&#xff0c;市场自然会逐步提高对就业人员的要求&#xff0c;那么再以之前的要求来看待前端的话的确是不可取的 互联网在&a…

机器学习深度学习——多层感知机的从零开始实现

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位即将上大四&#xff0c;正专攻机器学习的保研er &#x1f30c;上期文章&#xff1a;机器学习&&深度学习——多层感知机 &#x1f4da;订阅专栏&#xff1a;机器学习&&深度学习 希望文章对你们有所帮助 为…

计算机二级Python基本操作题-序号41

1. 输入字符串s&#xff0c;按要求把s输出到屏幕&#xff0c;格式要求&#xff1a;宽度为30个字符&#xff0c;星号字符*填充&#xff0c;居中对齐。如果输入字符串超过30位&#xff0c;则全部输出。 例如&#xff1a;键盘输入字符串s为"Congratulations”&#xff0c;屏幕…

基于Kaggle训练集预测的多层人工神经网络的能源消耗的时间序列预测(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f308;3 Matlab代码实现 &#x1f389;4 参考文献 &#x1f4a5;1 概述 本文为能源消耗的时间序列预测&#xff0c;在Matlab中实现。该预测采用多层人工神经网络&#xff0c;基于Kaggle训练集预测未来能源消耗。 &am…

自动化测试如何做?分层自动化测试如何实施?一篇概全...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 分层的自动化测试…

qt简易闹钟

#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);ui->stopBtn->setDisabled(true);this->setFixedSize(this->size()); //设置固定大小this->s…

【Spring Cloud Alibaba】限流--Sentinel

文章目录 概述一、Sentinel 是啥&#xff1f;二、Sentinel 的生态环境三、Sentinel 核心概念3.1、资源3.2、规则 四、Sentinel 限流4.1、单机限流4.1.1、引入依赖4.1.2、定义限流规则4.1.3、定义限流资源4.1.4、运行结果 4.2、控制台限流4.2.1、客户端接入控制台4.2.2、引入依赖…

(双指针) 剑指 Offer 57. 和为s的两个数字 ——【Leetcode每日一题】

❓ 剑指 Offer 57. 和为s的两个数字 难度&#xff1a;简单 输入一个递增排序的数组和一个数字 s&#xff0c;在数组中查找两个数&#xff0c;使得它们的和正好是 s。如果有多对数字的和等于 s&#xff0c;则输出任意一对即可。 示例 1&#xff1a; 输入&#xff1a;nums [2…

1400*C. Strong Password

Example input 5 88005553535123456 2 50 56 123412341234 3 111 444 1234 4 4321 4321 459 2 49 59 00010 2 10 11output YES NO YES NO YES解析&#xff1a; 题目要求有一种密码不在数据库中即可&#xff0c;所以枚举每一位的所有可能的数字&#xff0c;记录这一位数字在数…

帆软 FineReport/FineBI channel反序列化漏洞分析

事件背景 热点漏洞 漏洞说明 1. 漏洞原理&#xff1a;FineReport/FineBI channel接口能接受序列化数据并对其进行反序列化。配合帆软内置CB链会导致任意代码执行。 2. 组件描述&#xff1a;FineReport是一款企业级报表设计和数据分析工具&#xff0c;它提供了丰富多样的组件…

教你轻松又简单的绘制地铁线路图

地铁是主要以地下运输为主的交通系统。其轨道通常在地下隧道内。其中&#xff1a;地铁线路中的M,L,S,R分别代表&#xff1a;MMetro地&#xff0c;LLight轻轨&#xff0c;SSuburb市郊铁路&#xff0c;Rrapid&#xff0c;快速铁路。地铁线路图则可以简单理解为地铁的运行路线轨道…

基于Java+SpringBoot+vue前后端分离足球青训俱乐部管理后台系统设计实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

【自动话化运维】Ansible常见模块的运用

目录 一、Ansible简介二、Ansible安装部署2.1环境准备 三、ansible 命令行模块3.1&#xff0e;command 模块3.2&#xff0e;shell 模块3.3&#xff0e;cron 模块3.4&#xff0e;user 模块3.5&#xff0e;group 模块3.6&#xff0e;copy 模块3.7&#xff0e;file 模块8&#xff…

Java阶段五Day15

Java阶段五Day15 文章目录 Java阶段五Day15分层其他依赖dao-apidao-implinfrustructuredomainadaptermain 测试整合项目main前台师傅功能luban-front配置师傅相关表格ER图ER练习案例鲁班表格ER关系&#xff08;非常重要&#xff09; 前台师傅接口——师傅入驻adapterdomaininfr…

【C语言】指针进阶(二)

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

使用xtcp映射穿透指定服务

使用xtcp映射穿透指定服务 管理员Ubuntu配置公网服务端frps配置service自启(可选) 配置内网服务端frpc配置service自启(可选) 使用者配置service自启(可选) 通过frp实现内网client访问另外一个内网服务器 管理员 1&#xff09;配置公网服务端frps2&#xff09;配置内网服务端…