当前最佳实践设备树布局和 API 用法,为由多个 SPI 设备组成的设备实现 Linux 内核设备驱动程序

问题描述 投票:0回答:1

我有一个自定义设备,可以使用两个 SPI 和几条 GPIO 线(其中一根用作中断请求线)连接到各种 SoC。两个 SPI 连接到同一总线/控制器是可以接受的。设备的操作需要单个 Linux 内核设备驱动程序来控制两个 SPI(子)设备。

有哪些可能性可以实现这一目标?推荐哪一种?我感兴趣的是设备树布局是什么样子,以及可以使用哪些内核 API 来避免这看起来像是一个肮脏的黑客行为。

第一个变体:

控制器的设备树节点中有两个条目(我们称设备为“bar”)

...
bar_a@0 {
   compatible = "foo,bar-function-a-1.0";
   id = "primary";
   ...
}
bar_b@1 {
   compatible = "foo,bar-function-b-1.0";
   id = "primary";
   ...
}

并使驱动程序与

foo,bar-function-[ab]-1.0
相匹配,在
spi_device
函数中收集并注册
id
结构及其关联的
probe()
。如果刚刚遇到的一个是给定
id
的第二个(两者都已找到),则
probe()
函数将继续。

linux-kernel linux-device-driver embedded-linux spi device-tree
1个回答
0
投票

如果自定义设备使用两个 I2C 设备而不是两个 SPI 设备(我们稍后会介绍),则自定义设备可以是“平台”设备,并且其设备树节点可以具有引用 I2C 节点的属性它想要使用的客户端设备(没有自己的驱动程序)。然后,自定义设备的平台驱动程序可以在其“探针”处理程序中使用

of_parse_phandle()
of_node_put()
of_find_i2c_device_by_node()
函数来检索指向
struct i2c_device
对象的指针。在其“删除”处理程序中,它可以调用
put_device()
来释放每个 I2C 设备。请注意,如果
of_find_i2c_device_by_node()
返回
NULL
,可能是因为 I2C 设备尚未被探测到。在这种情况下,自定义设备的“探针”处理程序应清理并返回
-EPROBE_DEFER
,以便稍后可以再次调用自定义设备的“探针”处理程序。

对于使用 I2C 设备来说这一切都很好,但是我们想使用 SPI 设备,并且没有等效的

of_find_spi_device_by_node()
函数可供我们使用。但不用担心,用于创建我们自己的
of_find_spi_device_by_node()
函数(我称之为
my_of_find_spi_device_by_node()
)的构建块就在那里。我们想给它一个指向与我们使用
struct device_node 找到的 I2C 设备关联的 
of_parse_phandle()
 的指针
,我们希望它返回一个指向相应
struct spi_device
的指针,或者
NULL
如果设备尚未被探测。

下面是一个示例 DTS 片段来描述我们要使用的 SPI 设备。即使 SPI 设备驱动程序不会使用这些设备,它们也需要具有“兼容”字符串属性。我选择对两个 SPI 设备使用相同的“兼容”值,但它们不必相同。自定义设备的平台驱动程序将有一个用于 SPI 设备节点的

struct device_node
指针,因此它可以使用它来检查“兼容”属性是否符合预期(如果需要)。另外,在本例中,我已将它们设为同一 SPI 控制器 (
&spi0
) 的子级,但它们可以位于不同的控制器上。他们的设备树节点需要可由自定义设备的节点引用的标签。

&spi0 {
    status = "okay";

    bar_a: spi@0 {
        compatible = "vendor,foo-bar-1.0";
        reg = <0>;
        spi-max-frequency = <1000000>;
    };
    bar_b: spi@1 {
        compatible = "vendor,foo-bar-1.0";
        reg = <1>;
        spi-max-frequency = <1000000>;
    };
};

对于自定义平台设备的 devicetree 节点,我选择使用名为

spis
的属性来存储 SPI 设备节点的 phandle 值数组,但也可以为每个 SPI 设备节点 phandle 使用单独的属性如果需要的话。我已在设备树的根目录中创建了自定义设备节点,但如果需要,可以将其放置在设备树中的其他位置。

/ {
    foo {
        compatible = "vendor,foo-1.0";
        spis = <&bar_a &bar_b>;
    };
};

以下代码是自定义设备的示例内核模式驱动程序。除了在“探测”期间检索指向 SPI 设备的指针并在“删除”期间释放它们之外,它没有做太多事情。它结合了前面提到的

my_of_find_spi_device_by_node()
函数来获取 SPI 设备的指针,并调用
spi_dev_put()
来释放它们。

/* SPDX-Licence-Identifier: GPL-2.0+ */

#include <linux/version.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/spi/spi.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>

struct foo_private {
    struct device *hwdev;
    struct spi_device *bar[2];
};

static const struct of_device_id foo_of_ids[] = {
    {
        .compatible = "vendor,foo-1.0"
    },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, foo_of_ids);

/* Must call spi_dev_put() when done with returned spi_device */
static struct spi_device *my_spi_find_device_by_fwnode(struct fwnode_handle *fwnode)
{
    struct device *dev;

    if (!fwnode) {
        return NULL;
    }
    dev = bus_find_device_by_fwnode(&spi_bus_type, fwnode);
    if (!dev) {
        return NULL;
    }
    return to_spi_device(dev);
}

/* Must call spi_dev_put() when done with returned spi_device */
static struct spi_device *my_of_find_spi_device_by_node(struct device_node *node)
{
    return my_spi_find_device_by_fwnode(of_fwnode_handle(node));
}

static int foo_platform_probe(struct platform_device *pdev)
{
    struct device *hwdev = &pdev->dev;
    struct foo_private *foo;
    struct device_node *np = hwdev->of_node;
    unsigned int i;
    int rc;

    foo = kzalloc(sizeof(*foo), GFP_KERNEL);
    if (!foo) {
        dev_err(hwdev, "failed to allocate private data\n");
        rc = -ENOMEM;
        goto fail_alloc_private;
    }
    foo->hwdev = hwdev;
    dev_set_drvdata(hwdev, foo);
    /* Look for our SPI devices. */
    for (i = 0; i < 2; i++) {
        struct device_node *spinode =
            of_parse_phandle(np, "spis", i);

        if (!spinode) {
            dev_err(hwdev, "missing spi phandle[%u]\n", i);
            rc = -ENOENT;
            goto fail_find_spi;
        }
        foo->bar[i] = my_of_find_spi_device_by_node(spinode);
        of_node_put(spinode);
        if (!foo->bar[i]) {
            /* SPI device Not probed yet. Try again later. */
            rc = -EPROBE_DEFER;
            goto fail_find_spi;
        }
    }

    dev_info(hwdev, "probed OK\n");
    rc = 0;
    goto out;

fail_find_spi:
    for (i = 0; i < 2; i++) {
        spi_dev_put(foo->bar[i]);
    }

    kfree(foo);
fail_alloc_private:

out:
    return rc;
}

#if LINUX_VERSION_CODE < KERNEL_VERSION(6,11,0)
typedef int remove_ret_t;
#define REMOVE_RETURN   return 0
#else
typedef void remove_ret_t
#define REMOVE_RETURN   return
#endif

static remove_ret_t foo_platform_remove(struct platform_device *pdev)
{
    struct foo_private *foo = dev_get_drvdata(&pdev->dev);
    unsigned int i;

    for (i = 0; i < 2; i++) {
        spi_dev_put(foo->bar[i]);
    }
    kfree(foo);
    REMOVE_RETURN;
}

static struct platform_driver foo_platform_driver = {
    .probe = foo_platform_probe,
    .remove = foo_platform_remove,
    .driver = {
        .name = "foo",
        .owner = THIS_MODULE,
        .of_match_table = of_match_ptr(foo_of_ids),
    },
};

/* Module info. */
MODULE_LICENSE("GPL");

module_platform_driver(foo_platform_driver);
最新问题
© www.soinside.com 2019 - 2025. All rights reserved.