Linux Platform设备驱动分析2

Linux Platform设备驱动分析2(基于Linux6.6)---设备树platform驱动使用介绍

一、设备树基础概念

1. 设备树概述

设备树(Device Tree)是一个数据结构,用来描述硬件系统的布局,包括处理器、内存、总线、外设等。设备树数据通常以.dts(设备树源代码)文件形式编写,并通过编译工具(如dtc)转化为二进制格式(.dtb),该文件在Linux引导过程中加载。

设备树描述了硬件的结构、属性和配置,而内核驱动程序基于设备树中的描述来初始化和驱动硬件。

2. 平台设备与设备树的关系

在Platform设备驱动开发中,设备树扮演着非常重要的角色。平台设备通常是指那些不依赖于特定总线(如PCI、USB、I2C等)的设备,而是直接连接到主板上的设备。典型的Platform设备包括GPIO、SPI、I2C、串口、内存映射外设等。

设备树可以为平台设备提供必要的硬件描述信息,驱动程序通过解析设备树来获取这些信息,并初始化设备。

平台设备驱动与设备树匹配

设备树中每个设备节点的定义通常包括:

  • 设备名称:设备的标识符
  • 硬件资源:如IO地址、IRQ线、时钟等
  • 驱动绑定信息:设备驱动程序需要哪些硬件资源

平台设备驱动程序会通过内核的of_platform_driver等接口与设备树进行绑定。

二、设备树在platform设备驱动开发中的使用解析

2.1、设备树在平台设备驱动中的应用

定义设备树节点

平台设备的描述在设备树中以节点的形式存在。节点包含硬件信息,如设备名称、物理地址、IRQ号、时钟等。

例如,假设我们有一个SPI设备,其设备树节点可能如下:dts

spi@40008000 {
    compatible = "vendor,spi";
    reg = <0x40008000 0x1000>;
    interrupts = <5>;
    clocks = <&clk>;
    status = "okay";
};

在这个例子中:

  • spi@40008000是设备的名称(即设备节点)。
  • compatible字段表示设备的兼容性字符串,通常是设备型号和厂商的标识。
  • reg表示设备的寄存器基地址和长度。
  • interrupts表示设备的中断号。
  • clocks是该设备使用的时钟资源。
  • status字段表示设备是否启用。

3.2、有了设备树,如何实现device 与 driver 的匹配?

在上一篇还有 platform_device 中,是利用 .name 来实现device与driver的匹配的,但现在设备树替换掉了device,那我们将如何实现二者的匹配呢?有了设备树后,platform比较的名字存在哪?

先看一下原来是如何匹配的 ,platform_bus_type 下有个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);
 
	/* 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);
}

其中又调用了of_driver_match_device(dev, drv) ,其定义如下:

include/linux/of_device.h

/**
 * of_driver_match_device - Tell if a driver's of_match_table matches a device.
 * @drv: the device_driver structure to test
 * @dev: the device structure to match against
 */
static inline int of_driver_match_device(struct device *dev,
					 const struct device_driver *drv)
{
	return of_match_device(drv->of_match_table, dev) != NULL;
}

其调用of_match_device(drv->of_match_table, dev) ,继续追踪下去,注意这里的参数 drv->of_match_table

 drivers/of/device.c 

const struct of_device_id *of_match_device(const struct of_device_id *matches,
					   const struct device *dev)
{
	if ((!matches) || (!dev->of_node))
		return NULL;
	return of_match_node(matches, dev->of_node);
}
EXPORT_SYMBOL(of_match_device);

又调用 of_match_node(matches, dev->of_node)  ,其中matches 是 struct of_device_id 类型

 drivers/of/device.c  

/**
 * of_match_node - Tell if a device_node has a matching of_match structure
 * @matches:	array of of device match structures to search in
 * @node:	the of device structure to match against
 *
 * Low level utility function used by device matching.
 */
const struct of_device_id *of_match_node(const struct of_device_id *matches,
					 const struct device_node *node)
{
	const struct of_device_id *match;
	unsigned long flags;

	raw_spin_lock_irqsave(&devtree_lock, flags);
	match = __of_match_node(matches, node);
	raw_spin_unlock_irqrestore(&devtree_lock, flags);
	return match;
}
EXPORT_SYMBOL(of_match_node);

找到 match = __of_match_node(matches, node); 注意着里的node是struct device_node 类型的

 drivers/of/device.c  

static
const struct of_device_id *__of_match_node(const struct of_device_id *matches,
					   const struct device_node *node)
{
	const struct of_device_id *best_match = NULL;
	int score, best_score = 0;

	if (!matches)
		return NULL;

	for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) {
		score = __of_device_is_compatible(node, matches->compatible,
						  matches->type, matches->name);
		if (score > best_score) {
			best_match = matches;
			best_score = score;
		}
	}

	return best_match;
}

继续追踪下去

 drivers/of/device.c  

static int __of_device_is_compatible(const struct device_node *device,
				     const char *compat, const char *type, const char *name)
{
	struct property *prop;
	const char *cp;
	int index = 0, score = 0;
 
	/* Compatible match has highest priority */
	if (compat && compat[0]) {
		prop = __of_find_property(device, "compatible", NULL);
		for (cp = of_prop_next_string(prop, NULL); cp;
		     cp = of_prop_next_string(prop, cp), index++) {
			if (of_compat_cmp(cp, compat, strlen(compat)) == 0) {
				score = INT_MAX/2 - (index << 2);
				break;
			}
		}
		if (!score)
			return 0;
	}
 
	/* Matching type is better than matching name */
	if (type && type[0]) {
		if (!device->type || of_node_cmp(type, device->type))
			return 0;
		score += 2;
	}
 
	/* Matching name is a bit better than not */
	if (name && name[0]) {
		if (!device->name || of_node_cmp(name, device->name))
			return 0;
		score++;
	}
 
	return score;
}

看这句 prop = __of_find_property(device, "compatible", NULL);

可以发先追溯到底,是利用"compatible"来匹配的,即设备树加载之后,内核会自动把设备树节点转换成 platform_device这种格式,同时把名字放到of_node这个地方

platform_driver 部分

可以看到原来是利用platform_driver 下的 struct driver 结构体中的 name 成员来匹配的,看一下 struct 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;
}

  成员中有const struct of_device_id *of_match_table; 是struct of_device_id 类型,定义如下:

/*
 * Struct used for matching a device
 */
struct of_device_id
{
	char	name[32];
	char	type[32];
	char	compatible[128];
	const void *data;
};

可以看到其作用就是为了匹配一个设备。我们所要做的就是对 char compatible[128] 的填充;设备树加载之后,内核会自动把设备树节点转换成 platform_device这种格式,同时把名字放到of_node这个地方。

3.3、基于设备树的driver的结构体的填充

匹配的方式发生了改变,那我们的platform_driver 也要修改了

基于设备树的driver的结构体的填充:

static struct of_device_id beep_table[] = {
    {.compatible = "fs4412,beep"},
};
static struct platform_driver beep_driver=
{
    .probe = beep_probe,
    .remove = beep_remove,
    .driver={
        .name = "bigbang",
        .of_match_table = beep_table,
    },
};

原来的driver是这样的,可以对比一下

static struct platform_driver beep_driver=
{
    .driver.name = "bigbang",
    .probe = beep_probe,
    .remove = beep_remove,
};

三、举例应用

 

在Linux内核中,设备树(Device Tree)是描述硬件的结构化数据,它使得内核可以在不同硬件平台之间共享驱动代码。设备树广泛应用于嵌入式系统和没有标准总线(如PCI、USB等)的平台设备上。Platform驱动则是负责处理这些平台相关的设备,通常与具体硬件密切相关,直接连接到处理器或其他基础硬件接口。

下面通过一个实际的例子来演示如何使用设备树配置和加载一个Platform驱动。

1. 硬件和设备树配置示例

假设我们有一个简单的嵌入式平台,平台上有一个LED设备,该LED设备通过内存映射IO连接到处理器。我们将通过设备树来描述该LED设备,并编写一个Platform驱动来控制这个LED。

设备树描述LED设备

在设备树源文件(example.dts)中,我们可以为LED设备创建一个节点,描述该设备的硬件资源,如内存映射地址、时钟等。

dts

/ {
    model = "My Embedded Platform";
    compatible = "my,embedded-platform";

    leds {
        compatible = "gpio-leds";
        led1 {
            label = "LED1";
            gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>;
            default-state = "off";
        };
    };
};

在这个设备树文件中:

  • leds节点描述了LED相关的硬件资源。
  • compatible = "gpio-leds"指定了这是一个GPIO控制的LED。
  • gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>指定LED连接到gpio1控制器的第5个引脚,且LED处于高电平时点亮。
  • default-state = "off"定义设备的默认状态为关闭。

编译设备树

在设备树源文件完成后,可以使用dtc工具编译为二进制设备树文件(.dtb)。

dtc -I dts -O dtb -o example.dtb example.dts

2. Platform驱动代码

接下来,我们编写Platform驱动代码,使用设备树提供的GPIO资源来控制LED。假设我们的LED通过GPIO控制,我们将使用gpio_request()来申请GPIO,使用gpio_set_value()来控制LED的开关。

驱动代码示例

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/leds.h>
#include <linux/of.h>
#include <linux/of_device.h>

struct led_device {
    int gpio;
};

static int led_probe(struct platform_device *pdev)
{
    struct led_device *led;
    struct device *dev = &pdev->dev;
    int ret;

    led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
    if (!led)
        return -ENOMEM;

    platform_set_drvdata(pdev, led);

    /* 从设备树获取GPIO资源 */
    led->gpio = of_get_named_gpio(dev->of_node, "gpios", 0);
    if (led->gpio < 0) {
        dev_err(dev, "Failed to get GPIO from device tree\n");
        return led->gpio;
    }

    /* 申请GPIO */
    ret = gpio_request(led->gpio, "LED GPIO");
    if (ret) {
        dev_err(dev, "Failed to request GPIO %d\n", led->gpio);
        return ret;
    }

    /* 设置GPIO方向 */
    ret = gpio_direction_output(led->gpio, 0);
    if (ret) {
        dev_err(dev, "Failed to set GPIO direction\n");
        gpio_free(led->gpio);
        return ret;
    }

    /* 控制LED(默认关闭) */
    gpio_set_value(led->gpio, 0);

    dev_info(dev, "LED device probed\n");
    return 0;
}

static int led_remove(struct platform_device *pdev)
{
    struct led_device *led = platform_get_drvdata(pdev);

    /* 释放GPIO资源 */
    gpio_free(led->gpio);

    dev_info(&pdev->dev, "LED device removed\n");
    return 0;
}

static const struct of_device_id led_of_match[] = {
    { .compatible = "my,led", },
    { },
};
MODULE_DEVICE_TABLE(of, led_of_match);

static struct platform_driver led_driver = {
    .driver = {
        .name = "my-led",
        .of_match_table = led_of_match,
    },
    .probe = led_probe,
    .remove = led_remove,
};

module_platform_driver(led_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Simple Platform Driver for LED");

在这个驱动中:

  • led_probe()函数通过of_get_named_gpio()函数从设备树中获取LED所连接的GPIO。
  • 使用gpio_request()申请GPIO资源,并通过gpio_direction_output()设置GPIO的方向为输出。
  • 通过gpio_set_value()来控制LED的开关状态。
  • led_remove()函数在设备移除时释放GPIO资源。

设备树匹配

在驱动中,of_device_id结构体指定了驱动与设备树中的节点匹配关系:

static const struct of_device_id led_of_match[] = {
    { .compatible = "my,led", },
    { },
};

compatible字段指定了设备树中LED设备的compatible字符串。只有在设备树节点的compatible字符串与驱动中的字符串匹配时,内核才会加载这个驱动。

3. 加载驱动和测试

将编译好的设备树文件(example.dtb)拷贝到设备的启动分区中,并确保设备能够正确加载该设备树。然后,我们编写的驱动代码将会在设备启动时加载并初始化。

可以通过以下命令检查驱动是否成功加载:

dmesg | grep "LED device probed"

如果驱动加载成功,日志中会显示"LED device probed"。接着,你可以通过控制GPIO的状态来控制LED。

4. 总结

通过这个例子,我们展示了如何使用设备树来描述一个平台设备(LED),并编写Platform驱动来控制它。主要步骤包括:

  1. 设备树描述:在设备树文件中定义硬件资源,如GPIO。
  2. 驱动程序编写:在驱动中使用of_get_named_gpio()等函数来读取设备树中的资源信息,并控制硬件。
  3. 驱动与设备树匹配:通过of_device_id结构体使驱动与设备树节点进行匹配。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值