我有一个自定义设备,可以使用两个 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()
函数将继续。
如果自定义设备使用两个 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);