rk3576(7)之平台设备

1、简介

rk3576+buildroot。

正常的驱动会在内核里产生大量重复的设备驱动代码,而且硬件信息都写在了驱动里,需要修改时整个代码就要重新修改才能使用,这样太不方便了,所以引入了设备驱动模型分层概念,将驱动代码分成两块:设备和驱动,设备负责提供硬件资源,而驱动代码去使用这些设备提供的硬件资源,并由总线将他们联系起来。

设备模型通过几个数据结构来反映当前系统中总线、设备以及驱动的工作状况,提出了以下几个重要概念:

  1. 设备 (device) :挂载在某个总线的物理设备;
  2. 驱动 (driver) :与特定设备相关的软件,负责初始化该设备以及提供一些操作该设备的操作方式;
  3. 总线 (bus) :负责管理挂载对应总线的设备以及驱动;
  4. 类 (class) :对于具有相同功能的设备,归结到一种类别,进行分类管理;

我们知道在 Linux 中一切皆"文件",在根文件系统中有个/sys 文件目录,里面记录各个设备之间的关系。下面介绍/sys 下几个较为重要目录的作用。

  1. /sys/bus 目录下的每个子目录都是注册好了的总线类型。这里是设备按照总线类型分层放置的目录结构,每个子目录 (总线类型) 下包含两个子目录------devices 和 drivers 文件夹;其中 devices 下是该总线类型下的所有设备,而这些设备都是符号链接,它们分别指向真正的设备 (/sys/devices/下);如下图 bus 下的 usb 总线中的 device 则是 Devices 目录下/pci()/dev 0:10/usb2 的符号链接。而 drivers下是所有注册在这个总线上的驱动,每个 driver 子目录下是一些可以观察和修改的 driver 参数
  2. /sys/devices 目录下是全局设备结构体系,包含所有被发现的注册在各种总线上的各种物理设备。一般来说,所有的物理设备都按其在总线上的拓扑结构来显示。 /sys/devices 是内核对系统中所有设备的分层次表达模型,也是/sys 文件系统管理设备的最重要的目录结构。
  3. /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后正常显示

相关推荐
阿拉斯攀登17 天前
第 11 篇 RK 平台安卓驱动实战 4:I2C 设备驱动开发,以 OLED 屏为例
android·驱动开发·i2c·瑞芯微·嵌入式驱动·rk3576·嵌入式安卓
阿拉斯攀登17 天前
第 9 篇 RK 平台安卓驱动实战 2:中断驱动开发,按键中断的完整实现
驱动开发·嵌入式硬件·rk3568·中断·瑞芯微·rk3576·rk安卓驱动
阿拉斯攀登17 天前
第 18 篇 综合项目实战:基于 RK3568 的安卓智能门禁系统,全栈开发
android·驱动开发·瑞芯微·嵌入式驱动·rk3576·安卓驱动
阿拉斯攀登17 天前
第 10 篇 RK 平台安卓驱动实战 3:PWM 驱动开发,实现 LED 呼吸灯 + 电机调速
驱动开发·嵌入式硬件·pwm·瑞芯微·嵌入式驱动·rk3576·嵌入式安卓
阿拉斯攀登22 天前
【瑞芯微 RK 系列 + 安卓驱动全栈教程】博客系列
嵌入式硬件·安卓·瑞芯微·rk3576·嵌入式安卓·安卓驱动
Suifqwu23 天前
rk3576(2)之添加qt以及添加linux旋转补丁
rk3576
Suifqwu23 天前
rk3576(4)之buildroot添加package以及Qt程序开机自启
qt·buildroot·rk3576
peixiuhui2 个月前
G8701 RK3576 RealTime Linux 测试报告
linux·运维·边缘计算·rk3588·rk3576·rt-linux·实时内核
Industio_触觉智能2 个月前
解锁多路GMSL摄像头输入,基于瑞芯微RK3576的触觉智能硬件方案
瑞芯微·rk3576·ahd·gmsl·rk3576j·多路摄像头·gmsl解串