沈阳企业网站建设公司,济南正规做网站公司,网页设计代码大全添加音乐,北京做vr网站我们在前面几章编写的设备驱动都非常的简单#xff0c;都是对IO进行最简单的读写操作像I2C、 SPI、LCD 等这些复杂外设的驱动就不能这么去写了#xff0c;Linux 系统要考虑到驱动的可重用性#xff0c;因此提出了驱动的分离与分层这样的软件思路#xff0c;在这个思路下诞生…我们在前面几章编写的设备驱动都非常的简单都是对IO进行最简单的读写操作像I2C、 SPI、LCD 等这些复杂外设的驱动就不能这么去写了Linux 系统要考虑到驱动的可重用性因此提出了驱动的分离与分层这样的软件思路在这个思路下诞生了我们将来最常打交道的 platform 设备驱动也叫做平台设备驱动。本章我们就来学习一下 Linux 下的驱动分离与分层 以及 platform 框架下的设备驱动该如何编写。平台设备驱动也属于字符设备驱动
1Linux 驱动的分离与分层
1.1驱动的分隔与分离
对于 Linux 这样一个成熟、庞大、复杂的操作系统代码的重用性非常重要否则的话就 会在 Linux 内核中存在大量无意义的重复代码。尤其是驱动程序因为驱动程序占用了 Linux 内核代码量的大头如果不对驱动程序加以管理任由重复的代码肆意增加那么用不了多久 Linux 内核的文件数量就庞大到无法接受的地步。 假如现在有三个平台 A、B 和 C这三个平台(这里的平台说的是 SOC)上都有 MPU6050 这 个 I2C 接口的六轴传感器按照我们写裸机 I2C 驱动的时候的思路每个平台都有一个MPU6050 的驱动因此编写出来的最简单的驱动框架如图 54.1.1 所示 从图 54.1.1.1 可以看出每种平台下都有一个主机驱动和设备驱动主机驱动肯定是必须 要的毕竟不同的平台其 I2C 控制器不同。但是右侧的设备驱动就没必要每个平台都写一个 因为不管对于那个 SOC 来说MPU6050 都是一样通过 I2C 接口读写数据就行了只需要一 个 MPU6050 的驱动程序即可。如果再来几个 I2C 设备比如 AT24C02、FT5206(电容触摸屏) 等如果按照图 54.1.1 中的写法那么设备端的驱动将会重复的编写好几次。显然在 Linux 驱 动程序中这种写法是不推荐的最好的做法就是每个平台的 I2C 控制器都提供一个统一的接口 (也叫做主机驱动)每个设备的话也只提供一个驱动程序(设备驱动)每个设备通过统一的 I2C 接口驱动来访问这样就可以大大简化驱动文件比如 54.1.1 中三种平台下的 MPU6050 驱动 框架就可以简化为图 54.1.1.2 所示 实际的 I2C 驱动设备肯定有很多种不止 MPU6050 这一个那么实际的驱动架构如图 54.1.1.3 所示 这个就是驱动的分隔也就是将主机驱动和设备驱动分隔开来比如 I2C、SPI 等等都会采 用驱动分隔的方式来简化驱动的开发。在实际的驱动开发中一般 I2C 主机控制器驱动已经由 半导体厂家编写好了而设备驱动一般也由设备器件的厂家编写好了我们只需要提供设备信 息即可比如 I2C 设备的话提供设备连接到了哪个 I2C 接口上I2C 的速度是多少等等。相当 于将设备信息从设备驱动中剥离开来驱动使用标准方法去获取到设备信息(比如从设备树中获 取到设备信息)然后根据获取到的设备信息来初始化设备。 这样就相当于驱动只负责驱动 设备只负责设备想办法将两者进行匹配即可。这个就是 Linux 中的总线(bus)、驱动(driver)和 设备(device)模型也就是常说的驱动分离。总线就是驱动和设备信息的月老负责给两者牵线 搭桥如图 54.1.1.4 所示 当我们向系统注册一个驱动的时候总线就会在右侧的设备中查找看看有没有与之匹配 的设备如果有的话就将两者联系起来。同样的当向系统中注册一个设备的时候总线就会 在左侧的驱动中查找看有没有与之匹配的设备有的话也联系起来。Linux 内核中大量的驱动 程序都采用总线、驱动和设备模式我们一会要重点讲解的 platform 驱动就是这一思想下的产 物。
1.2驱动的分层
上一小节讲了驱动的分隔与分离本节我们来简单看一下驱动的分层大家应该听说过网 络的 7 层模型不同的层负责不同的内容。同样的Linux 下的驱动往往也是分层的分层的目 的也是为了在不同的层处理不同的内容。以其他书籍或者资料常常使用到的input(输入子系统 后面会有专门的章节详细的讲解)为例简单介绍一下驱动的分层。input 子系统负责管理所有 跟输入有关的驱动包括键盘、鼠标、触摸等最底层的就是设备原始驱动负责获取输入设 备的原始值获取到的输入事件上报给 input 核心层。input 核心层会处理各种 IO 模型并且提 供 file_operations 操作集合。我们在编写输入设备驱动的时候只需要处理好输入事件的上报即 可至于如何处理这些上报的输入事件那是上层去考虑的我们不用管。可以看出借助分层模 型可以极大的简化我们的驱动编写对于驱动编写来说非常的友好。
2platform 平台驱动模型简介
前面我们讲了设备驱动的分离并且引出了总线(bus)、驱动(driver)和设备(device)模型比 如 I2C、SPI、USB 等总线。但是在 SOC 中有些外设是没有总线这个概念的但是又要使用总 线、驱动和设备模型该怎么办呢为了解决此问题Linux 提出了 platform 这个虚拟总线相应 的就有 platform_driver 和 platform_device。
2.1platform 总线
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;
};
第 10 行match 函数此函数很重要单词 match 的意思就是“匹配、相配”因此此函数 就是完成设备和驱动之间匹配的总线就是使用 match 函数来根据注册的设备来查找对应的驱 动或者根据注册的驱动来查找相应的设备因此每一条总线都必须实现此函数。match 函数有 两个参数dev 和 drv这两个参数分别为 device 和 device_driver 类型也就是设备和驱动。 platform 总线是 bus_type 的一个具体实例定义在文件 drivers/base/platform.cplatform 总 线定义如下 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;/* Then try to match against the id table */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);
}
驱动和设备的匹配有四种方法我们依次来看一下 第 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.2platform 驱动
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;
};
第 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 结构体内容如下
struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
kernel_ulong_t driver_data;
};device_driver 结构体定义在 include/linux/device.hdevice_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;
};
第 10 行of_match_table 就是采用设备树的时候驱动使用的匹配表同样是数组每个匹 配项都为 of_device_id 结构体类型此结构体定义在文件 include/linux/mod_devicetable.h 中内 容如下
struct of_device_id {char name[32];char type[32];char compatible[128];const void *data;
};第 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 驱动框架如下所示
/* 设备结构体 */
struct xxx_dev{
struct cdev cdev;
/* 设备结构体其他具体内容 */
};struct xxx_dev xxxdev; /* 定义个设备结构体变量 */static int xxx_open(struct inode *inode, struct file *filp)
{ /* 函数具体内容 */return 0;}static ssize_t xxx_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt){/* 函数具体内容 */return 0;}/*
* 字符设备驱动操作集
*/
static struct file_operations xxx_fops {
.owner THIS_MODULE,
.open xxx_open,
.write xxx_write,
};/*
* platform 驱动的 probe 函数* 驱动与设备匹配成功以后此函数就会执行*/
static int xxx_probe(struct platform_device *dev)
{
......
cdev_init(xxxdev.cdev, xxx_fops); /* 注册字符设备驱动 */
/* 函数具体内容 */
return 0;}static int xxx_remove(struct platform_device *dev){......cdev_del(xxxdev.cdev);/* 删除 cdev *//* 函数具体内容 */return 0;}/* 匹配列表 */static const struct of_device_id xxx_of_match[] {{ .compatible xxx-gpio },{ /* Sentinel */ }};/* * platform 平台驱动结构体*/static struct platform_driver xxx_driver {.driver {.name xxx,.of_match_table xxx_of_match,},.probe xxx_probe,
.remove xxx_remove,};/* 驱动模块加载 */static int __init xxxdriver_init(void)
{return platform_driver_register(xxx_driver);}/* 驱动模块卸载 */static void __exit xxxdriver_exit(void){platform_driver_unregister(xxx_driver);}module_init(xxxdriver_init);
module_exit(xxxdriver_exit);MODULE_LICENSE(GPL);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”的皮目的是为了使用总线、驱动和设备这个驱动模型来实现驱动的分 离与分层。
2.3 platform 设备
platform 驱动已经准备好了我们还需要 platform 设备否则的话单单一个驱动也做不了 什么。platform_device 这个结构体表示 platform 设备这里我们要注意如果内核支持设备树 的话就不要再使用 platform_device 来描述设备了因为改用设备树去描述了。当然了你如果 一定要用 platform_device 来描述设备信息的话也是可以的。platform_device 结构体定义在文件 include/linux/platform_device.h 中结构体内容如下
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;
};第 23 行name 表示设备名字要和所使用的 platform 驱动的 name 字段相同否则的话设 备就无法匹配到对应的驱动。比如对应的 platform 驱动的 name 字段为“xxx-gpio”那么此 name 字段也要设置为“xxx-gpio”。 第 27 行num_resources 表示资源数量一般为第 28 行 resource 资源的大小。 第 28 行resource 表示资源也就是设备信息比如外设寄存器等。Linux 内核使用 resource 结构体表示资源resource 结构体内容如下
struct resource {resource_size_t start;resource_size_t end;const char *name;unsigned long flags;struct resource *parent, *sibling, *child;
};start 和 end 分别表示资源的起始和终止信息对于内存类的资源就表示内存起始和终止 地址name 表示资源名字flags 表示资源类型可选的资源类型都定义在了文件 include/linux/ioport.h 里面如下所示
#define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */#define IORESOURCE_TYPE_BITS 0x00001f00 /* Resource type */
#define IORESOURCE_IO 0x00000100 /* PCI/ISA I/O ports */
#define IORESOURCE_MEM 0x00000200
#define IORESOURCE_REG 0x00000300 /* Register offsets */
#define IORESOURCE_IRQ 0x00000400
#define IORESOURCE_DMA 0x00000800
#define IORESOURCE_BUS 0x00001000
......
/* PCI control bits. Shares IORESOURCE_BITS with above PCI ROM. */
#define IORESOURCE_PCI_FIXED (14) /* 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 设备信息框架如下所示 /* 寄存器地址定义*/
#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);
第 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 设备。
示例代码 54.2.3.4 主要是在不支持设备树的 Linux 版本中使用的当 Linux 内核支持了设 备树以后就不需要用户手动去注册 platform 设备了。因为设备信息都放到了设备树中去描述 Linux 内核启动的时候会从设备树中读取设备信息然后将其组织成 platform_device 形式至于设备树到 platform_device 的具体过程就不去详细的追究了感兴趣的可以去看一下网上也 有很多博客详细的讲解了整个过程。 关于 platform 下的总线、驱动和设备就讲解到这里我们接下来就使用 platform 驱动框架 来编写一个 LED 灯驱动本章我们不使用设备树来描述设备信息我们采用自定义 platform_device这种“古老”方式来编写LED的设备信息。下一章我们来编写设备树下的platform 驱动这样我们就掌握了无设备树和有设备树这两种 platform 驱动的开发方式。
3试验程序编写
本章实验我们需要编写一个驱动模块和一个设备模块其中驱动模块是 platform 驱动程序 设备模块是 platform 的设备信息。当这两个模块都加载成功以后就会匹配成功然后 platform 驱动模块中的 probe 函数就会执行probe 函数中就是传统的字符设备驱动那一套。
3.1platform 设备与驱动程序编写
新建名为“17_platform”的文件夹然后在 17_platform 文件夹里面创建 vscode 工程工作 区命名为“platform”。新建名为 leddevice.c 和 leddriver.c 这两个文件这两个文件分别为 LED 灯的 platform 设备文件和 LED 灯的 platform 的驱动文件。在 leddevice.c 中输入如下所示内容
#include linux/types.h
#include linux/kernel.h
#include linux/delay.h
#include linux/ide.h
#include linux/init.h
#include linux/module.h
#include linux/errno.h
#include linux/gpio.h
#include linux/cdev.h
#include linux/device.h
#include linux/of_gpio.h
#include linux/semaphore.h
#include linux/timer.h
#include linux/irq.h
#include linux/wait.h
#include linux/poll.h
#include linux/fs.h
#include linux/fcntl.h
#include linux/platform_device.h
#include asm/mach/map.h
#include asm/uaccess.h
#include asm/io.h/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名 : leddevice.c
作者 : 左忠凯
版本 : V1.0
描述 : platform设备
其他 : 无
论坛 : www.openedv.com
日志 : 初版V1.0 2019/8/13 左忠凯创建
***************************************************************//* * 寄存器地址定义*/
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)
#define REGISTER_LENGTH 4/* description : 释放flatform设备模块的时候此函数会执行 * param - dev : 要释放的设备 * return : 无*/
static void led_release(struct device *dev)
{printk(led device released!\r\n);
}/* * 设备资源信息也就是LED0所使用的所有寄存器*/
static struct resource led_resources[] {[0] {.start CCM_CCGR1_BASE,.end (CCM_CCGR1_BASE REGISTER_LENGTH - 1),.flags IORESOURCE_MEM,}, [1] {.start SW_MUX_GPIO1_IO03_BASE,.end (SW_MUX_GPIO1_IO03_BASE REGISTER_LENGTH - 1),.flags IORESOURCE_MEM,},[2] {.start SW_PAD_GPIO1_IO03_BASE,.end (SW_PAD_GPIO1_IO03_BASE REGISTER_LENGTH - 1),.flags IORESOURCE_MEM,},[3] {.start GPIO1_DR_BASE,.end (GPIO1_DR_BASE REGISTER_LENGTH - 1),.flags IORESOURCE_MEM,},[4] {.start GPIO1_GDIR_BASE,.end (GPIO1_GDIR_BASE REGISTER_LENGTH - 1),.flags IORESOURCE_MEM,},
};/** platform设备结构体 */
static struct platform_device leddevice {.name imx6ul-led,.id -1,.dev {.release led_release,},.num_resources ARRAY_SIZE(led_resources),.resource led_resources,
};/** description : 设备模块加载 * param : 无* return : 无*/
static int __init leddevice_init(void)
{return platform_device_register(leddevice);
}/** description : 设备模块注销* param : 无* return : 无*/
static void __exit leddevice_exit(void)
{platform_device_unregister(leddevice);
}module_init(leddevice_init);
module_exit(leddevice_exit);
MODULE_LICENSE(GPL);
MODULE_AUTHOR(zuozhongkai);
leddevice.c 文件内容就是按照示例代码 54.2.3.4 的 platform 设备模板编写的。
第 56~82 行led_resources 数组也就是设备资源描述了 LED 所要使用到的寄存器信息 也就是 IORESOURCE_MEM 资源。
第 88~96platform 设备结构体变量 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 里 面输入如下内容
#include linux/types.h
#include linux/kernel.h
#include linux/delay.h
#include linux/ide.h
#include linux/init.h
#include linux/module.h
#include linux/errno.h
#include linux/gpio.h
#include linux/cdev.h
#include linux/device.h
#include linux/of_gpio.h
#include linux/semaphore.h
#include linux/timer.h
#include linux/irq.h
#include linux/wait.h
#include linux/poll.h
#include linux/fs.h
#include linux/fcntl.h
#include linux/platform_device.h
#include asm/mach/map.h
#include asm/uaccess.h
#include asm/io.h
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名 : leddriver.c
作者 : 左忠凯
版本 : V1.0
描述 : platform驱动
其他 : 无
论坛 : www.openedv.com
日志 : 初版V1.0 2019/8/13 左忠凯创建
***************************************************************/#define LEDDEV_CNT 1 /* 设备号长度 */
#define LEDDEV_NAME platled /* 设备名字 */
#define LEDOFF 0
#define LEDON 1/* 寄存器名 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;/* leddev设备结构体 */
struct leddev_dev{dev_t devid; /* 设备号 */struct cdev cdev; /* cdev */struct class *class; /* 类 */struct device *device; /* 设备 */int major; /* 主设备号 */
};struct leddev_dev leddev; /* led设备 *//** description : LED打开/关闭* param - sta : LEDON(0) 打开LEDLEDOFF(1) 关闭LED* return : 无*/
void led0_switch(u8 sta)
{u32 val 0;if(sta LEDON){val readl(GPIO1_DR);val ~(1 3); writel(val, GPIO1_DR);}else if(sta LEDOFF){val readl(GPIO1_DR);val| (1 3); writel(val, GPIO1_DR);}
}/** description : 打开设备* param - inode : 传递给驱动的inode* param - filp : 设备文件file结构体有个叫做private_data的成员变量* 一般在open的时候将private_data指向设备结构体。* return : 0 成功;其他 失败*/
static int led_open(struct inode *inode, struct file *filp)
{filp-private_data leddev; /* 设置私有数据 */return 0;
}/** description : 向设备写数据 * param - filp : 设备文件表示打开的文件描述符* param - buf : 要写给设备写入的数据* param - cnt : 要写入的数据长度* param - offt : 相对于文件首地址的偏移* return : 写入的字节数如果为负值表示写入失败*/
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue;unsigned char databuf[1];unsigned char ledstat;retvalue copy_from_user(databuf, buf, cnt);if(retvalue 0) {return -EFAULT;}ledstat databuf[0]; /* 获取状态值 */if(ledstat LEDON) {led0_switch(LEDON); /* 打开LED灯 */}else if(ledstat LEDOFF) {led0_switch(LEDOFF); /* 关闭LED灯 */}return 0;
}/* 设备操作函数 */
static struct file_operations led_fops {.owner THIS_MODULE,.open led_open,.write led_write,
};/** description : flatform驱动的probe函数当驱动与* 设备匹配以后此函数就会执行* param - dev : platform设备* return : 0成功;其他负值,失败*/
static int led_probe(struct platform_device *dev)
{ int i 0;int ressize[5];u32 val 0;struct resource *ledsource[5];printk(led driver and device has matched!\r\n);/* 1、获取资源 */for (i 0; i 5; i) {ledsource[i] platform_get_resource(dev, IORESOURCE_MEM, i); /* 依次MEM类型资源 */if (!ledsource[i]) {dev_err(dev-dev, No MEM resource for always on\n);return -ENXIO;}ressize[i] resource_size(ledsource[i]); } /* 2、初始化LED *//* 寄存器地址映射 */IMX6U_CCM_CCGR1 ioremap(ledsource[0]-start, ressize[0]);SW_MUX_GPIO1_IO03 ioremap(ledsource[1]-start, ressize[1]);SW_PAD_GPIO1_IO03 ioremap(ledsource[2]-start, ressize[2]);GPIO1_DR ioremap(ledsource[3]-start, ressize[3]);GPIO1_GDIR ioremap(ledsource[4]-start, ressize[4]);val readl(IMX6U_CCM_CCGR1);val ~(3 26); /* 清除以前的设置 */val | (3 26); /* 设置新值 */writel(val, IMX6U_CCM_CCGR1);/* 设置GPIO1_IO03复用功能将其复用为GPIO1_IO03 */writel(5, SW_MUX_GPIO1_IO03);writel(0x10B0, SW_PAD_GPIO1_IO03);/* 设置GPIO1_IO03为输出功能 */val readl(GPIO1_GDIR);val ~(1 3); /* 清除以前的设置 */val | (1 3); /* 设置为输出 */writel(val, GPIO1_GDIR);/* 默认关闭LED1 */val readl(GPIO1_DR);val | (1 3) ; writel(val, GPIO1_DR);/* 注册字符设备驱动 *//*1、创建设备号 */if (leddev.major) { /* 定义了设备号 */leddev.devid MKDEV(leddev.major, 0);register_chrdev_region(leddev.devid, LEDDEV_CNT, LEDDEV_NAME);} else { /* 没有定义设备号 */alloc_chrdev_region(leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME); /* 申请设备号 */leddev.major MAJOR(leddev.devid); /* 获取分配号的主设备号 */}/* 2、初始化cdev */leddev.cdev.owner THIS_MODULE;cdev_init(leddev.cdev, led_fops);/* 3、添加一个cdev */cdev_add(leddev.cdev, leddev.devid, LEDDEV_CNT);/* 4、创建类 */leddev.class class_create(THIS_MODULE, LEDDEV_NAME);if (IS_ERR(leddev.class)) {return PTR_ERR(leddev.class);}/* 5、创建设备 */leddev.device device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME);if (IS_ERR(leddev.device)) {return PTR_ERR(leddev.device);}return 0;
}/** description : platform驱动的remove函数移除platform驱动的时候此函数会执行* param - dev : platform设备* return : 0成功;其他负值,失败*/
static int led_remove(struct platform_device *dev)
{iounmap(IMX6U_CCM_CCGR1);iounmap(SW_MUX_GPIO1_IO03);iounmap(SW_PAD_GPIO1_IO03);iounmap(GPIO1_DR);iounmap(GPIO1_GDIR);cdev_del(leddev.cdev);/* 删除cdev */unregister_chrdev_region(leddev.devid, LEDDEV_CNT); /* 注销设备号 */device_destroy(leddev.class, leddev.devid);class_destroy(leddev.class);return 0;
}/* platform驱动结构体 */
static struct platform_driver led_driver {.driver {.name imx6ul-led, /* 驱动名字用于和设备匹配 */},.probe led_probe,.remove led_remove,
};/** description : 驱动模块加载函数* param : 无* return : 无*/
static int __init leddriver_init(void)
{return platform_driver_register(led_driver);
}/** description : 驱动模块卸载函数* param : 无* return : 无*/
static void __exit leddriver_exit(void)
{platform_driver_unregister(led_driver);
}module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE(GPL);
MODULE_AUTHOR(zuozhongkai);
leddriver.c 文件内容就是按照示例代码 54.2.2.5 的 platform 驱动模板编写的。
第 34~122 行传统的字符设备驱动。
第 130~206 行probe 函数当设备和驱动匹配以后此函数就会执行当匹配成功以后会在 终端上输出“led driver and device has matched!”这样语句。在 probe 函数里面初始化 LED、注 册字符设备驱动。也就是将原来在驱动加载函数里面做的工作全部放到 probe 函数里面完成。
第 213~226 行remobe 函数当卸载 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 驱动。
3.2测试 APP 编写
#include stdio.h
#include unistd.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include stdlib.h
#include string.h
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名 : ledApp.c
作者 : 左忠凯
版本 : V1.0
描述 : platform驱动驱测试APP。
其他 : 无
使用方法 ./ledApp /dev/platled 0 关闭LED./ledApp /dev/platled 1 打开LED
论坛 : www.openedv.com
日志 : 初版V1.0 2019/8/16 左忠凯创建
***************************************************************/
#define LEDOFF 0
#define LEDON 1/** description : main主程序* param - argc : argv数组元素个数* param - argv : 具体参数* return : 0 成功;其他 失败*/
int main(int argc, char *argv[])
{int fd, retvalue;char *filename;unsigned char databuf[2];if(argc ! 3){printf(Error Usage!\r\n);return -1;}filename argv[1];/* 打开led驱动 */fd open(filename, O_RDWR);if(fd 0){printf(file %s open failed!\r\n, argv[1]);return -1;}databuf[0] atoi(argv[2]); /* 要执行的操作打开或关闭 */retvalue write(fd, databuf, sizeof(databuf));if(retvalue 0){printf(LED Control Failed!\r\n);close(fd);return -1;}retvalue close(fd); /* 关闭文件 */if(retvalue 0){printf(file %s close failed!\r\n, argv[1]);return -1;}return 0;
}ledApp.c 文件内容很简单就是控制 LED 灯的亮灭和第四十一章的测试 APP 基本一致 这里就不重复讲解了。
4运行测试
4.1Makefile文件编写
KERNELDIR :/home/zhulinux/linux/alientek_linux/linuxCURRENT_PATH : $(shell pwd)
obj-m : leddevice.o leddriver.obuild: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M$(CURRENT_PATH) clean
4.2编译测试 APP
在名为Compiletest.sh的shell脚本内 将fun改变为 led即可运行shell脚本
#!/bin/bash
#把dts编译的dtb文件拷贝到 tftpboot目录下
cp -r ~/linux/alientek_linux/linux/arch/arm/boot/dts/imx6ull_alientek_emmc.dtb ~/linux/tftpboot/ -fdrivertrue
funledfunko${fun}.ko
funoApp${fun}App
funcApp${fun}App.cif [[ $driver true ]]; thenfundeviceko${fun}device.kofundriverko${fun}driver.ko
fiif [ -f ./$funoApp ]; thenecho 文件存在正在删除...rm ./$funoAppecho 文件已删除
elseecho 文件不存在不执行删除操作。
fiarm-linux-gnueabihf-gcc $funcApp -o $funoApp
if [[ $driver true ]];thensudo cp $fundeviceko $fundriverko $funoApp ~/linux/nfs/rootfs/lib/modules/4.1.15/ -f
elsesudo cp $funko $funoApp ~/linux/nfs/rootfs/lib/modules/4.1.15/ -f
fi
make
./Compiletest.sh
将 上 一 小 节 编 译 出 来 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”的文件否则说明我们的设备模块加载失败结果如图 54.4.2.1 同理查看/sys/bus/platform/drivers/目录看一下驱动是否存在我们在 leddriver.c 中设置 led_driver (platform_driver 类型)的 name 字段为“imx6ul-led”因此会在/sys/bus/platform/drivers/ 目录下存在名为“imx6ul-led”这个文件结果如图 54.4.2.2 所示 驱动模块和设备模块加载成功以后 platform 总线就会进行匹配当驱动和设备匹配成功以 后就会输出如图 54.4.2.3 所示一行语句 驱动和设备匹配成功以后就可以测试 LED 灯驱动了输入如下命令打开 LED 灯
/ledApp /dev/platled 1 //打开 LED 灯
在输入如下命令关闭 LED 灯
./ledApp /dev/platled 0 //关闭 LED 灯观察一下 LED 灯能否打开和关闭如果可以的话就说明驱动工作正常如果要卸载驱动的 话输入如下命令即可
rmmod leddevice.ko
rmmod leddriver.ko总之这就是老版本的linux方法新版本的是有设备树。
就是搞一个平台把我们写的设备驱动与它相匹配。平台驱动里面基本是些物理信息像寄存器地址、寄存器大小
设备驱动还是老样子只不过多了个probe函数原来在驱动入口里的注册都挪到这了出口里的挪到了remove里了。还是要手动内存映射。
本文仅在记录学习正点原子imx6ull-mini开发板的过程不做他用。 文章转载自: http://www.morning.gynkr.cn.gov.cn.gynkr.cn http://www.morning.prxqd.cn.gov.cn.prxqd.cn http://www.morning.zcnwg.cn.gov.cn.zcnwg.cn http://www.morning.yrcxg.cn.gov.cn.yrcxg.cn http://www.morning.cmfkp.cn.gov.cn.cmfkp.cn http://www.morning.wtsr.cn.gov.cn.wtsr.cn http://www.morning.lhztj.cn.gov.cn.lhztj.cn http://www.morning.fgkwh.cn.gov.cn.fgkwh.cn http://www.morning.lhhdy.cn.gov.cn.lhhdy.cn http://www.morning.qshxh.cn.gov.cn.qshxh.cn http://www.morning.bqnhh.cn.gov.cn.bqnhh.cn http://www.morning.jcyrs.cn.gov.cn.jcyrs.cn http://www.morning.thzgd.cn.gov.cn.thzgd.cn http://www.morning.iknty.cn.gov.cn.iknty.cn http://www.morning.ryztl.cn.gov.cn.ryztl.cn http://www.morning.ltkms.cn.gov.cn.ltkms.cn http://www.morning.gzgwn.cn.gov.cn.gzgwn.cn http://www.morning.lqpzb.cn.gov.cn.lqpzb.cn http://www.morning.nxnrt.cn.gov.cn.nxnrt.cn http://www.morning.nydgg.cn.gov.cn.nydgg.cn http://www.morning.qcbhb.cn.gov.cn.qcbhb.cn http://www.morning.tntbs.cn.gov.cn.tntbs.cn http://www.morning.pjrgb.cn.gov.cn.pjrgb.cn http://www.morning.fpxyy.cn.gov.cn.fpxyy.cn http://www.morning.dwfzm.cn.gov.cn.dwfzm.cn http://www.morning.dplmq.cn.gov.cn.dplmq.cn http://www.morning.dlbpn.cn.gov.cn.dlbpn.cn http://www.morning.fqyqm.cn.gov.cn.fqyqm.cn http://www.morning.yfwygl.cn.gov.cn.yfwygl.cn http://www.morning.kqfdrqb.cn.gov.cn.kqfdrqb.cn http://www.morning.beeice.com.gov.cn.beeice.com http://www.morning.mxhcf.cn.gov.cn.mxhcf.cn http://www.morning.dhtdl.cn.gov.cn.dhtdl.cn http://www.morning.rxwnc.cn.gov.cn.rxwnc.cn http://www.morning.rsnd.cn.gov.cn.rsnd.cn http://www.morning.rrwgh.cn.gov.cn.rrwgh.cn http://www.morning.ngqdp.cn.gov.cn.ngqdp.cn http://www.morning.ljsxg.cn.gov.cn.ljsxg.cn http://www.morning.mcndn.cn.gov.cn.mcndn.cn http://www.morning.cwzzr.cn.gov.cn.cwzzr.cn http://www.morning.tynqy.cn.gov.cn.tynqy.cn http://www.morning.bpknt.cn.gov.cn.bpknt.cn http://www.morning.rbylq.cn.gov.cn.rbylq.cn http://www.morning.gmwdl.cn.gov.cn.gmwdl.cn http://www.morning.kjsft.cn.gov.cn.kjsft.cn http://www.morning.bkryb.cn.gov.cn.bkryb.cn http://www.morning.yfrlk.cn.gov.cn.yfrlk.cn http://www.morning.cpnlq.cn.gov.cn.cpnlq.cn http://www.morning.jxlnr.cn.gov.cn.jxlnr.cn http://www.morning.ndynz.cn.gov.cn.ndynz.cn http://www.morning.qnxtz.cn.gov.cn.qnxtz.cn http://www.morning.fgppj.cn.gov.cn.fgppj.cn http://www.morning.cwrpd.cn.gov.cn.cwrpd.cn http://www.morning.c-ae.cn.gov.cn.c-ae.cn http://www.morning.xkyqq.cn.gov.cn.xkyqq.cn http://www.morning.htbgz.cn.gov.cn.htbgz.cn http://www.morning.gqflj.cn.gov.cn.gqflj.cn http://www.morning.wsrcy.cn.gov.cn.wsrcy.cn http://www.morning.sxlrg.cn.gov.cn.sxlrg.cn http://www.morning.ngqty.cn.gov.cn.ngqty.cn http://www.morning.rkxqh.cn.gov.cn.rkxqh.cn http://www.morning.trqhd.cn.gov.cn.trqhd.cn http://www.morning.jglqn.cn.gov.cn.jglqn.cn http://www.morning.bwfsn.cn.gov.cn.bwfsn.cn http://www.morning.mqlsf.cn.gov.cn.mqlsf.cn http://www.morning.nykzl.cn.gov.cn.nykzl.cn http://www.morning.zsrjn.cn.gov.cn.zsrjn.cn http://www.morning.rfrx.cn.gov.cn.rfrx.cn http://www.morning.kjjbz.cn.gov.cn.kjjbz.cn http://www.morning.slwqt.cn.gov.cn.slwqt.cn http://www.morning.hwljx.cn.gov.cn.hwljx.cn http://www.morning.nmymn.cn.gov.cn.nmymn.cn http://www.morning.qtrlh.cn.gov.cn.qtrlh.cn http://www.morning.kgsws.cn.gov.cn.kgsws.cn http://www.morning.ypktc.cn.gov.cn.ypktc.cn http://www.morning.shxrn.cn.gov.cn.shxrn.cn http://www.morning.mm27.cn.gov.cn.mm27.cn http://www.morning.bftr.cn.gov.cn.bftr.cn http://www.morning.qbzdj.cn.gov.cn.qbzdj.cn http://www.morning.tldfp.cn.gov.cn.tldfp.cn