1、简介
rk3576+buildroot。
正常的驱动会在内核里产生大量重复的设备驱动代码,而且硬件信息都写在了驱动里,需要修改时整个代码就要重新修改才能使用,这样太不方便了,所以引入了设备驱动模型分层概念,将驱动代码分成两块:设备和驱动,设备负责提供硬件资源,而驱动代码去使用这些设备提供的硬件资源,并由总线将他们联系起来。
设备模型通过几个数据结构来反映当前系统中总线、设备以及驱动的工作状况,提出了以下几个重要概念:
- 设备 (device) :挂载在某个总线的物理设备;
- 驱动 (driver) :与特定设备相关的软件,负责初始化该设备以及提供一些操作该设备的操作方式;
- 总线 (bus) :负责管理挂载对应总线的设备以及驱动;
- 类 (class) :对于具有相同功能的设备,归结到一种类别,进行分类管理;
我们知道在 Linux 中一切皆"文件",在根文件系统中有个/sys 文件目录,里面记录各个设备之间的关系。下面介绍/sys 下几个较为重要目录的作用。
- /sys/bus 目录下的每个子目录都是注册好了的总线类型。这里是设备按照总线类型分层放置的目录结构,每个子目录 (总线类型) 下包含两个子目录------devices 和 drivers 文件夹;其中 devices 下是该总线类型下的所有设备,而这些设备都是符号链接,它们分别指向真正的设备 (/sys/devices/下);如下图 bus 下的 usb 总线中的 device 则是 Devices 目录下/pci()/dev 0:10/usb2 的符号链接。而 drivers下是所有注册在这个总线上的驱动,每个 driver 子目录下是一些可以观察和修改的 driver 参数
- /sys/devices 目录下是全局设备结构体系,包含所有被发现的注册在各种总线上的各种物理设备。一般来说,所有的物理设备都按其在总线上的拓扑结构来显示。 /sys/devices 是内核对系统中所有设备的分层次表达模型,也是/sys 文件系统管理设备的最重要的目录结构。
- /sys/class 目录下则是包含所有注册在 kernel 里面的设备类型,这是按照设备功能分类的设备模型,我们知道每种设备都具有自己特定的功能,比如:鼠标的功能是作为人机交互的输入,按照设备功能分类无论它挂载在哪条总线上都是归类到/sys/class/input 下
在总线上管理着两个链表,分别管理着设备和驱动,当我们向系统注册一个驱动时,便会向驱动的管理链表插入我们的新驱动,同样当我们向系统注册一个设备时,便会向设备的管理链表插入我们的新设备。在插入的同时总线会执行一个 bus_type 结构体中 match 的方法对新插入的设备/驱动进行匹配。 (它们之间最简单的匹配方式则是对比名字,存在名字相同的设备/驱动便成功匹配)。在匹配成功的时候会调用驱动 device_driver 结构体中 probe 方法 (通常在 probe 中获取设备资源,具体的功能可由驱动编写人员自定义),并且在移除设备或驱动时,会调用 device_driver结构体中 remove 方法。
一般对于 I2C、 SPI、 USB 这些常见类型的物理总线来说, Linux 内核会自动创建与之相应的驱动总线,因此 I2C 设备、 SPI 设备、 USB 设备自然是注册挂载在相应的总线上。但是,实际项目开发中还有很多结构简单的设备,对它们进行控制并不需要特殊的时序。它们也就没有相应的物理总线,比如 led、 rtc 时钟、蜂鸣器、按键等等, Linux 内核将不会为它们创建相应的驱动总线。为了使这部分设备的驱动开发也能够遵循设备驱动模型, Linux 内核引入了一种虚拟的总线------平台总线 (platform bus)。
平台总线用于管理、挂载那些没有相应物理总线的设备,这些设备被称为平台设备,对应的设备驱动则被称为平台驱动。平台设备驱动的核心依然是 Linux 设备驱动模型,平台设备使用 platform_device 结构体来进行表示,其继承了设备驱动模型中的 device 结构体。而平台驱动使用platform_driver 结构体来进行表示,其则是继承了设备驱动模型中的 device_driver 结构体
2、平台设备
2.1、platform_device 结构体
内核使用 platform_device 结构体来描述平台设备,结构体原型如下:
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u64 platform_dma_mask;
struct device_dma_parameters dma_parms;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
/*
* Driver name to force a match. Do not set directly, because core
* frees it. Use driver_set_override() to set or clear it.
*/
const char *driver_override;
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
其中常用的如下:
- name: 设备名称,总线进行匹配时,会比较设备和驱动的名称是否一致;
- id: 指定设备的编号, Linux 支持同名的设备,而同名设备之间则是通过该编号进行区分;
- dev: Linux 设备模型中的 device 结构体, linux 内核大量使用了面向对象思想, platform_device通过继承该结构体可复用它的相关代码,方便内核管理平台设备;
- num_resources: 记录资源的个数,当结构体成员 resource 存放的是数组时,需要记录 resource数组的个数,内核提供了宏定义 ARRAY_SIZE 用于计算数组的个数;
- resource: 平台设备提供给驱动的资源,如 irq, dma,内存等等。该结构体会在接下来的内容进行讲解;
- id_entry: 平台总线提供的另一种匹配方式,原理依然是通过比较字符串
平台设备的工作是为驱动程序提供设备信息, 设备信息包括硬件信息和软件信息两部分。
- 硬件信息:驱动程序需要使用到什么寄存器,占用哪些中断号、内存资源、 IO 口等等
- 软件信息:以太网卡设备中的 MAC 地址、 I2C 设备中的设备地址、 SPI 设备的片选信号线等等
对于硬件信息,使用结构体 struct resource 来保存设备所提供的资源,比如设备使用的中断编号,寄存器物理地址等,结构体原型如下
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
unsigned long desc;
struct resource *parent, *sibling, *child;
};
- name: 指定资源的名字,可以设置为 NULL;
- start、 end: 指定资源的起始地址以及结束地址
- flags: 用于指定该资源的类型,在 Linux 中,资源包括 I/O、 Memory、 Register、 IRQ、 DMA、Bus 等多种类型
flags最常见的类型有以下几种:
|----------------|--------------------------|
| 资源宏定义 | 描述 |
| IORESOURCE_IO | 用于 IO 地址空间,对应于 IO 端口映射方式 |
| IORESOURCE_MEM | 用于外设的可直接寻址的地址空间 |
| IORESOURCE_IRQ | 用于指定该设备使用某个中断 |
| IORESOURCE_DMA | 用于指定使用的 DMA 通道 |
设备驱动程序的主要目的是操作设备的寄存器。不同架构的计算机提供不同的操作接口,主要有IO 端口映射和 IO内存映射两种方式。
- IO 端口映射方式,只能通过专门的接口函数 (如inb、 outb) 才能访问;
- IO 内存映射的方式,可以像访问内存一样,去读写寄存器。
在嵌入式中,基本上没有 IO 地址空间,所以通常使用 IORESOURCE_MEM。
在资源的起始地址和结束地址中,对于 IORESOURCE_IO 或者是 IORESOURCE_MEM,他们表示要使用的内存的起始位置以及结束位置;若是只用一个中断引脚或者是一个通道,则它们的start 和 end 成员值必须是相等的。
而对于软件信息,这种特殊信息需要我们以私有数据的形式进行封装保存,我们注意到 platform_device 结构体中,有个 device 结构体类型的成员 dev。在前面章节,我们提到过 Linux 设备模型使用 device 结构体来抽象物理设备,该结构体的成员 platform_data 可用于保存设备的私有数据。 platform_data 是 void * 类型的万能指针,无论你想要提供的是什么内容,只需要把数据的地址赋值给 platform_data 即可,还是以 GPIO 引脚号为例
unsigned int pin = 10;
struct platform_device pdev = {
.dev = {
.platform_data = &pin;
}
}
将保存了 GPIO 引脚号的变量 pin 地址赋值给 platform_data 指针,在驱动程序中通过调用平台设备总线中的核心函数,可以获取到我们需要的引脚号
对于硬件信息的描述,新方法都是通过设备树来描述
2.2、注册/注销平台设备
int platform_device_register(struct platform_device *pdev)
void platform_device_unregister(struct platform_device *pdev)
- pdev: platform_device 类型结构体指针
3、平台驱动
3.1、platform_driver 结构体
内核中使用 platform_driver 结构体来描述平台驱动,结构体原型如下所示:
struct platform_driver {
int (*probe)(struct platform_device *);
/*
* Traditionally the remove callback returned an int which however is
* ignored by the driver core. This led to wrong expectations by driver
* authors who thought returning an error code was a valid error
* handling strategy. To convert to a callback returning void, new
* drivers should implement .remove_new() until the conversion it done
* that eventually makes .remove() return void.
*/
int (*remove)(struct platform_device *);
void (*remove_new)(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;
/*
* For most device drivers, no need to care about this flag as long as
* all DMAs are handled through the kernel DMA API. For some special
* ones, for example VFIO drivers, they know how to manage the DMA
* themselves and set this flag so that the IOMMU layer will allow them
* to setup and manage their own I/O address space.
*/
bool driver_managed_dma;
};
主要使用的如下:
- probe: 函数指针,驱动开发人员需要在驱动程序中初始化该函数指针,当总线为设备和驱动匹配上之后,会回调执行该函数。我们一般通过该函数,对设备进行一系列的初始化。
- remove: 函数指针,驱动开发人员需要在驱动程序中初始化该函数指针,当我们移除某个平台设备时,会回调执行该函数指针,该函数实现的操作,通常是 probe 函数实现操作的逆过程。
- driver: Linux 设备模型中用于抽象驱动的 device_driver 结构体, platform_driver 继承该结构体,也就获取了设备模型驱动对象的特性;
- id_table: 表示该驱动能够兼容的设备类型。
其中platform_device_id 结构体原型如下所示:
struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
kernel_ulong_t driver_data;
}
在 platform_device_id 这个结构体中,有两个成员,
- 第一个是数组用于指定驱动的名称,总线进行匹配时,会依据该结构体的 name 成员与 platform_device 中的变量 name 进行比较匹配,
- 另一个成员变量 driver_data,则是用于来保存设备的配置。
我们知道在同系列的设备中,往往只是某些寄存器的配置不一样,为了减少代码的冗余,尽量做到一个驱动可以匹配多个设备的目的。接下来以 imx 芯片为例,具体看下这个结构体的作用:
static struct platform_device_id fec_devtype[] = {
{
/* keep it for coldfire */
.name = DRIVER_NAME,
.driver_data = 0,
}, {
.name = "imx25-fec",
.driver_data = (kernel_ulong_t)&fec_imx25_info,
}, {
.name = "imx27-fec",
.driver_data = (kernel_ulong_t)&fec_imx27_info,
}, {
.name = "imx28-fec",
.driver_data = (kernel_ulong_t)&fec_imx28_info,
}, {
.name = "imx6q-fec",
.driver_data = (kernel_ulong_t)&fec_imx6q_info,
}, {
.name = "mvf600-fec",
.driver_data = (kernel_ulong_t)&fec_mvf600_info,
}, {
.name = "imx6sx-fec",
.driver_data = (kernel_ulong_t)&fec_imx6x_info,
}, {
.name = "imx6ul-fec",
.driver_data = (kernel_ulong_t)&fec_imx6ul_info,
}, {
.name = "imx8mq-fec",
.driver_data = (kernel_ulong_t)&fec_imx8mq_info,
}, {
.name = "imx8qm-fec",
.driver_data = (kernel_ulong_t)&fec_imx8qm_info,
}, {
.name = "s32v234-fec",
.driver_data = (kernel_ulong_t)&fec_s32v234_info,
}, {
/* sentinel */
}
};
在上面的代码中,支持三种设备的串口,支持多种不同系列芯片,他们之间区别在于寄存器地址不同。当总线成功配对平台驱动以及平台设备时,会将对应的id_table 条目赋值给平台设备的 id_entry 成员,而平台驱动的 probe 函数是以平台设备为参数,这样的话,就可以拿到当前设备寄存器地址了
3.2、注册和注销平台驱动
int platform_driver_register(struct platform_driver *drv);
void platform_driver_unregister(struct platform_driver *drv);
- 参数: drv: platform_driver 类型结构体指针
3.3、平台驱动获取设备信息
我们知道平台设备使用结构体 resource 来抽象表示硬件信息,而软件信息则可以利用设备结构体 device 中的成员 platform_data 来保存。先看一下如何获取平台设备中结构体 resource 提供的资源。
platform_get_resource() 函数通常会在驱动的 probe 函数中执行,用于获取平台设备提供的资源结构体,最终会返回一个 struct resource 类型的指针,该函数原型如下:
struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num);
参数:
- dev: 指定要获取哪个平台设备的资源;
- type: 指定获取资源的类型,如 IORESOURCE_MEM、 IORESOURCE_IO 等;
- num: 指定要获取的资源编号。每个设备所需要资源的个数是不一定的,为此内核对这些资源进行了编号,对于不同的资源,编号之间是相互独立的。
返回值:
- 成功: struct resource 结构体类型指针
- 失败: NULL
4、平台总线
4.1、平台总线注册和匹配方式
在 Linux 的设备驱动模型中,总线是最重要的一环。上一章中,我们提到过总线是负责匹配设备和驱动,它维护着两个链表,里面记录着各个已经注册的平台设备和平台驱动。每当有新的设备或者是新的驱动加入到总线时,总线便会调用 platform_match 函数对新增的设备或驱动,进行配对。内核中使用 bus_type 来抽象描述系统中的总线,平台总线结构体原型如下所示:
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.probe = platform_probe,
.remove = platform_remove,
.shutdown = platform_shutdown,
.dma_configure = platform_dma_configure,
.dma_cleanup = platform_dma_cleanup,
.pm = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);
内核用 platform_bus_type 来描述平台总线,该总线在 linux 内核启动的时候自动进行注册。
int __init platform_bus_init(void)
{
int error;
early_platform_cleanup();
error = device_register(&platform_bus);
if (error) {
put_device(&platform_bus);
return error;
}
error = bus_register(&platform_bus_type);
if (error)
device_unregister(&platform_bus);
of_platform_register_reconfig_notifier();
return error;
}
这里重点是 platform 总线的 match 函数指针,该函数指针指向的函数将负责实现平台总线和平台设备的匹配过程。对于每个驱动总线,它都必须实例化该函数指针。 platform_match 的函数原型如下:
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);
}
platform 总线提供了四种匹配方式,并且这四种方式存在着优先级:设备树机制 >ACPI 匹配模式 >id_table 方式 > 字符串比较。虽然匹配方式五花八门,但是并没有涉及到任何复杂的算法,都只是在匹配的过程中,比较一下设备和驱动提供的某个成员的字符串是否相同。设备树是一种描述硬件的数据结构,它用一个非 C 语言的脚本来描述这些硬件设备的信息。驱动和设备之间的匹配时通过比较 compatible 的值。 acpi 主要是用于电源管理,基本上用不到,这里就并不进行讲解了。关于设备树的匹配机制,会在设备树章节进行详细分析
4.2、id_table 匹配方式
平台总线 id_table 匹配方式,在定义结构体 platform_driver 时,我们需要提供一个 id_table 的数组,该数组说明了当前的驱动能够支持的设备。当加载该驱动时,总线的 match 函数发现 id_table 非空,则会比较 id_table 中的 name 成员和平台设备的 name 成员,若相同,则会返回匹配的条目,具体的实现过程如下:
static const struct platform_device_id *platform_match_id(
const struct platform_device_id *id,
struct platform_device *pdev)
{
while (id->name[0]) {
if (strcmp(pdev->name, id->name) == 0) {
pdev->id_entry = id;
return id;
}
id++;
}
return NULL;
}
可以看到这里的代码实现并不复杂,只是通过字符串进行配对。每当有新的驱动或者设备添加到总线时,总线便会调用 match 函数对新的设备或者驱动进行配对。 platform_match_id 函数中第一个参数为驱动提供的 id_table,第二个参数则是待匹配的平台设备。当待匹配的平台设备的name 字段的值等于驱动提供的 id_table 中的值时,会将当前匹配的项赋值给 platform_device 中的id_entry,返回一个非空指针。若没有成功匹配,则返回空指针。
5、代码实现
5.1、设备文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#define GPIO4_BASE (0x2AE40000)
#define TOP_IOC_BASE (0x26044000)
#define VCCIO_IOC_BASE (0x26046000)
#define GPIO4_DR_L (GPIO4_BASE + 0x0000)
#define GPIO4_DDR_L (GPIO4_BASE + 0x0008)
#define VCCIO_IOC_GPIO4A_PULL (VCCIO_IOC_BASE + 0x0140)
#define VCCIO_IOC_GPIO4A_DS_H (VCCIO_IOC_BASE + 0x0084)
#define TOP_IOC_GPIO4A_IOMUX_SEL_H (TOP_IOC_BASE + 0x0084)
unsigned int gpio_hwinfo[1] = { 4};
static struct resource gpio_resource[] = {
[0] = DEFINE_RES_MEM(GPIO4_DR_L, 4),
[1] = DEFINE_RES_MEM(GPIO4_DDR_L, 4),
[2] = DEFINE_RES_MEM(TOP_IOC_GPIO4A_IOMUX_SEL_H, 4),
[3] = DEFINE_RES_MEM(VCCIO_IOC_GPIO4A_DS_H, 4),
[4] = DEFINE_RES_MEM(VCCIO_IOC_GPIO4A_PULL, 4),
};
static void gpio_release(struct device *dev)
{
printk("gpio_release\r\n");
}
static struct platform_device gpio_pdev = {
.name = "gpio_pdev",
.id = 0,
.num_resources = ARRAY_SIZE(gpio_resource),
.resource = gpio_resource,
.dev = {
.release = gpio_release,
.platform_data = gpio_hwinfo,
},
};
static __init int gpio_pdev_init(void)
{
printk("gpio_pdev_init\n");
platform_device_register(&gpio_pdev );
return 0;
}
module_init(gpio_pdev_init);
static __exit void gpio_pdev_exit(void)
{
printk("gpio_pdev_exit\n");
platform_device_unregister(&gpio_pdev);
}
module_exit(gpio_pdev_exit);
MODULE_LICENSE("GPL");
5.2、驱动文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/platform_device.h>
//Pin Name是GPIO4_A4
#define DEV_NAME "platformgpio"
#define DEV_CNT (1)
#define OUTPUT_HIGH 1
#define OUTPUT_LOW 0
#define GPIO4_BASE (0x2AE40000)
#define TOP_IOC_BASE (0x26044000)
#define VCCIO_IOC_BASE (0x26046000)
#define GPIO4_DR_L (GPIO4_BASE + 0x0000)
#define GPIO4_DDR_L (GPIO4_BASE + 0x0008)
#define VCCIO_IOC_GPIO4A_PULL (VCCIO_IOC_BASE + 0x0140)
#define VCCIO_IOC_GPIO4A_DS_H (VCCIO_IOC_BASE + 0x0084)
#define TOP_IOC_GPIO4A_IOMUX_SEL_H (TOP_IOC_BASE + 0x0084)
static void __iomem *pVCCIO_IOC_GPIO4A_PULL;
static void __iomem *pVCCIO_IOC_GPIO4A_DS_H;
static void __iomem *pTOP_IOC_GPIO4A_IOMUX_SEL_H;
static void __iomem *pGPIO4_DR_L;
static void __iomem *pGPIO4_DDR_L;
struct gpiodev_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
};
struct gpiodev_dev gpiodev;
void gpio_switch(u8 sta)
{
u32 val = 0;
if(sta == OUTPUT_HIGH) {
val = readl(pGPIO4_DR_L);
val &= ~(0X1 << 4); /* bit0 清零*/
val |= ((0X1 << 20) | (0X1 << 4));
writel(val, pGPIO4_DR_L);
}else if(sta == OUTPUT_LOW) {
val = readl(pGPIO4_DR_L);
val &= ~(0X1 << 4); /* bit0 清零*/
val |= ((0X1 << 20) | (0X0 << 4));
writel(val, pGPIO4_DR_L);
}
}
void gpio_unmap(void)
{
/* 取消映射 */
iounmap(pVCCIO_IOC_GPIO4A_PULL);
iounmap(pVCCIO_IOC_GPIO4A_DS_H);
iounmap(pTOP_IOC_GPIO4A_IOMUX_SEL_H);
iounmap(pGPIO4_DR_L);
iounmap(pGPIO4_DDR_L);
}
static int gpio_dev_open(struct inode *inode, struct file *filp)
{
filp->private_data = &gpiodev; /* 设置私有数据 */
return 0;
}
static ssize_t gpio_dev_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt)
{
return 0;
}
static ssize_t gpio_dev_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
{
int retvalue;
unsigned char databuf[1];
unsigned char stat;
retvalue = copy_from_user(databuf, buf, cnt);
if(retvalue < 0) {
printk("kernel write failed!\r\n");
return -EFAULT;
}
stat = databuf[0]; /* 获取状态值 */
if(stat == OUTPUT_HIGH) {
gpio_switch(OUTPUT_HIGH);
} else if(stat == OUTPUT_LOW) {
gpio_switch(OUTPUT_LOW);
}
return 0;
}
static int gpio_dev_release(struct inode *inode, struct file *filp)
{
return 0;
}
static struct file_operations gpio_dev_fops = {
.owner = THIS_MODULE,
.open = gpio_dev_open,
.release = gpio_dev_release,
.write = gpio_dev_write,
.read = gpio_dev_read,
};
static int gpio_pdrv_probe(struct platform_device *dev)
{
int i = 0, ret;
int ressize[5];
u32 val = 0;
struct resource *gpiosource[5];
for(int i = 0;i < 5;i++){
gpiosource[i] = platform_get_resource(dev, IORESOURCE_MEM, i);
if (!gpiosource[i]) {
dev_err(&dev->dev, "No MEM resource for always on\n");
return -ENXIO;
}
ressize[i] = resource_size(gpiosource[i]);
}
pGPIO4_DR_L= ioremap(gpiosource[0]->start,ressize[0]);
pGPIO4_DDR_L= ioremap(gpiosource[1]->start,ressize[1]);
pTOP_IOC_GPIO4A_IOMUX_SEL_H= ioremap(gpiosource[2]->start,ressize[2]);
pVCCIO_IOC_GPIO4A_DS_H= ioremap(gpiosource[3]->start,ressize[3]);
pVCCIO_IOC_GPIO4A_PULL= ioremap(gpiosource[4]->start,ressize[4]);
/* 设置 GPIO4_A4 为 GPIO 功能。 */
val = readl(pTOP_IOC_GPIO4A_IOMUX_SEL_H);
val &= ~(0XF << 0);
val |= ((0XF << 16) | (0X0 << 0));
writel(val, pTOP_IOC_GPIO4A_IOMUX_SEL_H);
/* 设置 GPIO4_A4 驱动能力为 level5 */
val = readl(pVCCIO_IOC_GPIO4A_DS_H);
val &= ~(0X7 << 0);
val |= ((0X7 << 16) | (0X5 << 0));
writel(val, pVCCIO_IOC_GPIO4A_DS_H);
/* 设置上下拉 */
val = readl(pVCCIO_IOC_GPIO4A_PULL);
val &= ~(0X3 << 8);
val |= ((0X3 << 24) | (0X0 << 8));
writel(val, pVCCIO_IOC_GPIO4A_PULL);
/* 设置为输出 */
val = readl(pGPIO4_DDR_L);
val &= ~(0X1 << 4);
val |= ((0X1 << 20) | (0X1 << 4));
writel(val, pGPIO4_DDR_L);
/* 设置 默认输出低电平 */
val = readl(pGPIO4_DR_L);
val &= ~(0X1 << 4);
val |= ((0X1 << 20) | (0X0 << 4));
writel(val, pGPIO4_DR_L);
ret = alloc_chrdev_region(&gpiodev.devid,0,DEV_CNT,DEV_NAME);
if(ret < 0){
printk("%s alloc_chrdev_region fail,ret = %d\r\n", DEV_NAME,ret);
goto fail_map;
}
cdev_init(&gpiodev.cdev, &gpio_dev_fops );
gpiodev.cdev.owner = THIS_MODULE;
ret = cdev_add(&gpiodev.cdev, gpiodev.devid,DEV_CNT);
if(ret < 0){
printk("%s cdev_add fail,ret = %d\r\n", DEV_NAME,ret);
goto del_unregister;
}
gpiodev.class = class_create(THIS_MODULE, DEV_NAME);
if (IS_ERR(gpiodev.class)) {
printk("%s class_create fail\r\n", DEV_NAME);
goto del_cdev;
}
/* 5、创建设备 */
gpiodev.device = device_create(gpiodev.class,
NULL,
gpiodev.devid,
NULL,
DEV_NAME);
if (IS_ERR(gpiodev.device)) {
printk("%s device_create fail\r\n", DEV_NAME);
goto destroy_class;
}
return 0;
destroy_class:
class_destroy(gpiodev.class);
del_cdev:
cdev_del(&gpiodev.cdev);
del_unregister:
unregister_chrdev_region(gpiodev.devid, DEV_CNT);
fail_map:
gpio_unmap();
return -EIO;
}
static int gpio_pdrv_remove(struct platform_device *pdev)
{
gpio_unmap(); /* 取消映射 */
cdev_del(&gpiodev.cdev); /* 删除 cdev */
unregister_chrdev_region(gpiodev.devid, DEV_CNT);
device_destroy(gpiodev.class, gpiodev.devid); /* 注销设备 */
class_destroy(gpiodev.class); /* 注销类 */
return 0;
}
static struct platform_device_id gpio_pdev_ids[] = {
{.name = "gpio_pdev"},
{}
};
MODULE_DEVICE_TABLE(platform, gpio_pdev_ids);
static struct platform_driver gpio_pdrv = {
.probe = gpio_pdrv_probe,
.remove = gpio_pdrv_remove,
.driver.name = "gpio_pdev",
.id_table = gpio_pdev_ids,
};
static __init int gpio_pdrv_init(void)
{
printk("gpio_pdrv_init\r\n");
platform_driver_register(&gpio_pdrv);
return 0;
}
module_init(gpio_pdrv_init);
static void __exit gpio_pdrv_exit(void)
{
printk("gpio_pdrv_exit\r\n");
platform_driver_unregister(&gpio_pdrv);
}
module_exit(gpio_pdrv_exit);
MODULE_LICENSE("GPL");
6、基于设备树的平台总线驱动
基于设备树的驱动程序与平台总线驱动非常相似,差别是平台总线驱动中的平台驱动要和平台 设备进行匹配,使用设备树后设备树取代"平台设备"的作用,平台驱动只需要和与之对应的设 备树节点匹配即可。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/platform_device.h>
//Pin Name是GPIO4_A4
#define DEV_NAME "platformdtsgpio"
#define DEV_CNT (1)
#define OUTPUT_HIGH 1
#define OUTPUT_LOW 0
#define GPIO4_BASE (0x2AE40000)
#define TOP_IOC_BASE (0x26044000)
#define VCCIO_IOC_BASE (0x26046000)
#define GPIO4_DR_L (GPIO4_BASE + 0x0000)
#define GPIO4_DDR_L (GPIO4_BASE + 0x0008)
#define VCCIO_IOC_GPIO4A_PULL (VCCIO_IOC_BASE + 0x0140)
#define VCCIO_IOC_GPIO4A_DS_H (VCCIO_IOC_BASE + 0x0084)
#define TOP_IOC_GPIO4A_IOMUX_SEL_H (TOP_IOC_BASE + 0x0084)
static void __iomem *pVCCIO_IOC_GPIO4A_PULL;
static void __iomem *pVCCIO_IOC_GPIO4A_DS_H;
static void __iomem *pTOP_IOC_GPIO4A_IOMUX_SEL_H;
static void __iomem *pGPIO4_DR_L;
static void __iomem *pGPIO4_DDR_L;
struct gpiodev_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd;/* 设备节点 */
};
struct gpiodev_dev gpiodev;
void gpio_switch(u8 sta)
{
u32 val = 0;
if(sta == OUTPUT_HIGH) {
val = readl(pGPIO4_DR_L);
val &= ~(0X1 << 4); /* bit0 清零*/
val |= ((0X1 << 20) | (0X1 << 4));
writel(val, pGPIO4_DR_L);
}else if(sta == OUTPUT_LOW) {
val = readl(pGPIO4_DR_L);
val &= ~(0X1 << 4); /* bit0 清零*/
val |= ((0X1 << 20) | (0X0 << 4));
writel(val, pGPIO4_DR_L);
}
}
void gpio_unmap(void)
{
/* 取消映射 */
iounmap(pVCCIO_IOC_GPIO4A_PULL);
iounmap(pVCCIO_IOC_GPIO4A_DS_H);
iounmap(pTOP_IOC_GPIO4A_IOMUX_SEL_H);
iounmap(pGPIO4_DR_L);
iounmap(pGPIO4_DDR_L);
}
static int gpio_dev_open(struct inode *inode, struct file *filp)
{
filp->private_data = &gpiodev; /* 设置私有数据 */
return 0;
}
static ssize_t gpio_dev_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt)
{
return 0;
}
static ssize_t gpio_dev_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
{
int retvalue;
unsigned char databuf[1];
unsigned char stat;
retvalue = copy_from_user(databuf, buf, cnt);
if(retvalue < 0) {
printk("kernel write failed!\r\n");
return -EFAULT;
}
stat = databuf[0]; /* 获取状态值 */
if(stat == OUTPUT_HIGH) {
gpio_switch(OUTPUT_HIGH);
} else if(stat == OUTPUT_LOW) {
gpio_switch(OUTPUT_LOW);
}
return 0;
}
static int gpio_dev_release(struct inode *inode, struct file *filp)
{
return 0;
}
static struct file_operations gpio_dev_fops = {
.owner = THIS_MODULE,
.open = gpio_dev_open,
.release = gpio_dev_release,
.write = gpio_dev_write,
.read = gpio_dev_read,
};
static int gpio_pdrv_probe(struct platform_device *dev)
{
u32 val = 0;
int ret;
u32 regdata[16];
const char *str;
struct property *proper;
gpiodev.nd = of_find_node_by_path("/rk3576_gpio");
if(gpiodev.nd == NULL) {
printk("rk3576_gpio node not find!\r\n");
return 1;
} else {
printk("rk3576_gpio node find!\r\n");
}
/* 2、获取 compatible 属性内容 */
proper = of_find_property(gpiodev.nd, "compatible", NULL);
if(proper == NULL) {
printk("compatible property find failed\r\n");
return 1;
} else {
printk("compatible = %s\r\n", (char*)proper->value);
}
/* 3、获取 status 属性内容 */
ret = of_property_read_string(gpiodev.nd, "status", &str);
if(ret < 0){
printk("status read failed!\r\n");
return 1;
} else {
printk("status = %s\r\n",str);
}
/* 4、获取 reg 属性内容 */
ret = of_property_read_u32_array(gpiodev.nd, "reg", regdata, 10);
if(ret < 0) {
printk("reg property read failed!\r\n");
return 1;
} else {
u8 i = 0;
printk("reg data:\r\n");
for(i = 0; i < 10; i++)
printk("%#X ", regdata[i]);
printk("\r\n");
}
pGPIO4_DR_L = of_iomap(gpiodev.nd, 0);
pGPIO4_DDR_L = of_iomap(gpiodev.nd, 1);
pTOP_IOC_GPIO4A_IOMUX_SEL_H = of_iomap(gpiodev.nd, 2);
pVCCIO_IOC_GPIO4A_DS_H = of_iomap(gpiodev.nd, 3);
pVCCIO_IOC_GPIO4A_PULL = of_iomap(gpiodev.nd, 4);
/* 设置 GPIO4_A4 为 GPIO 功能。 */
val = readl(pTOP_IOC_GPIO4A_IOMUX_SEL_H);
val &= ~(0XF << 0);
val |= ((0XF << 16) | (0X0 << 0));
writel(val, pTOP_IOC_GPIO4A_IOMUX_SEL_H);
/* 设置 GPIO4_A4 驱动能力为 level5 */
val = readl(pVCCIO_IOC_GPIO4A_DS_H);
val &= ~(0X7 << 0);
val |= ((0X7 << 16) | (0X5 << 0));
writel(val, pVCCIO_IOC_GPIO4A_DS_H);
/* 设置上下拉 */
val = readl(pVCCIO_IOC_GPIO4A_PULL);
val &= ~(0X3 << 8);
val |= ((0X3 << 24) | (0X0 << 8));
writel(val, pVCCIO_IOC_GPIO4A_PULL);
/* 设置为输出 */
val = readl(pGPIO4_DDR_L);
val &= ~(0X1 << 4);
val |= ((0X1 << 20) | (0X1 << 4));
writel(val, pGPIO4_DDR_L);
/* 设置 默认输出低电平 */
val = readl(pGPIO4_DR_L);
val &= ~(0X1 << 4);
val |= ((0X1 << 20) | (0X0 << 4));
writel(val, pGPIO4_DR_L);
ret = alloc_chrdev_region(&gpiodev.devid,0,DEV_CNT,DEV_NAME);
if(ret < 0){
printk("%s alloc_chrdev_region fail,ret = %d\r\n", DEV_NAME,ret);
goto fail_map;
}
cdev_init(&gpiodev.cdev, &gpio_dev_fops );
gpiodev.cdev.owner = THIS_MODULE;
ret = cdev_add(&gpiodev.cdev, gpiodev.devid,DEV_CNT);
if(ret < 0){
printk("%s cdev_add fail,ret = %d\r\n", DEV_NAME,ret);
goto del_unregister;
}
gpiodev.class = class_create(THIS_MODULE, DEV_NAME);
if (IS_ERR(gpiodev.class)) {
printk("%s class_create fail\r\n", DEV_NAME);
goto del_cdev;
}
/* 5、创建设备 */
gpiodev.device = device_create(gpiodev.class,
NULL,
gpiodev.devid,
NULL,
DEV_NAME);
if (IS_ERR(gpiodev.device)) {
printk("%s device_create fail\r\n", DEV_NAME);
goto destroy_class;
}
return 0;
destroy_class:
class_destroy(gpiodev.class);
del_cdev:
cdev_del(&gpiodev.cdev);
del_unregister:
unregister_chrdev_region(gpiodev.devid, DEV_CNT);
fail_map:
gpio_unmap();
return -EIO;
}
static int gpio_pdrv_remove(struct platform_device *pdev)
{
gpio_unmap(); /* 取消映射 */
cdev_del(&gpiodev.cdev); /* 删除 cdev */
unregister_chrdev_region(gpiodev.devid, DEV_CNT);
device_destroy(gpiodev.class, gpiodev.devid); /* 注销设备 */
class_destroy(gpiodev.class); /* 注销类 */
return 0;
}
static const struct of_device_id gpio_pdev_ids[] = {
{.compatible = "rk3576_gpio_test"},
{}
};
static struct platform_driver gpio_pdrv = {
.probe = gpio_pdrv_probe,
.remove = gpio_pdrv_remove,
.driver = {
.name = "gpio_pdev",
.owner = THIS_MODULE,
.of_match_table = gpio_pdev_ids,
}
};
static __init int gpio_pdrv_init(void)
{
printk("gpio_pdrv_init\r\n");
platform_driver_register(&gpio_pdrv);
return 0;
}
module_init(gpio_pdrv_init);
static void __exit gpio_pdrv_exit(void)
{
printk("gpio_pdrv_exit\r\n");
platform_driver_unregister(&gpio_pdrv);
}
module_exit(gpio_pdrv_exit);
MODULE_LICENSE("GPL");
7、App
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char *argv[])
{
printf("led_tiny test\n");
char *filename;
/*判断输入的命令是否合法*/
if(argc != 3)
{
printf(" command error ! \n");
printf(" usage : sudo test_app num [num can be 0 or 1]\n");
return -1;
}
filename = argv[1];
/*打开文件*/
int fd = open(filename, O_RDWR);
if(fd < 0)
{
printf("open file : %s failed !\n", filename);
return -1;
}
unsigned char command = atoi(argv[2]); //将受到的命令值转化为数字;
/*写入命令*/
int error = write(fd,&command,sizeof(command));
if(error < 0)
{
printf("write file error! \n");
close(fd);
/*判断是否关闭成功*/
}
/*关闭文件*/
error = close(fd);
if(error < 0)
{
printf("close file error! \n");
}
return 0;
}
这里出现了内存溢出的报错,自己怀疑用的设备树是不是不对,就看了一下设备树下的reg节点,一开始用cat发觉显示的是乱码,改用hexdump后正常显示