0

I have a custom device that can be connected to various SoCs using two SPIs and a couple of GPIO wires (one of them functioning as an interrupt request wire). It would be acceptable to expect both SPIs to be connected to the same bus/controller. The operation of the device calls for a single Linux kernel device driver to control both SPI (sub)devices.

Which possibilities are there to achieve this and which one is recommended? I'm interested in how the device tree layout could look like and which kernel APIs could be used to not make this look like a dirty hack.

First variant:

Have two entries in the controller's device tree node like (let's call the device "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";
   ...
}

and to have the driver match with both foo,bar-function-[ab]-1.0, collecting and registering spi_device structures along with their associated id in the probe() function. If the one just encountered is the second one for a given id (both have been found), the probe() function continues.

13
  • Have you studied the Linux kernel documentation that's in the kernel source? Kernel APIs would be determined by the kernel version you write for. Your driver would need an accompanying Device Tree bindings document that describes the required and optional properties of your driver. "using two SPIs" - That's bizarre usage of the name of an interface. Commented Nov 27, 2024 at 0:27
  • "... to not make this look like a dirty hack" - Use kernel coding style. If you're not trying to evade GPL requirements, then base your driver on existing examples of similar devices in the kernel. Be sure to maintain copyright notices and attributions. If any of this doesn't make sense, then you have extra work to do. Commented Nov 27, 2024 at 1:43
  • "That's bizarre usage of the name of an interface." I don't think so. It has two sets of CS, CLK, MISO and MOSI wires, each commonly referred to as an SPI. I've written dozens of Linux kernel drivers for a variety of busses, some of them SPI. I don't think I've come across any drivers that handle two SPIs (or SPI slave devices, if you prefer that) yet. And with the way slave devices are placed as sub-nodes of the controller in the DT (<name>@<n> where n is the corresponding CS), having two of them be controlled by the same driver instance does not come naturally, hence the question. Commented Nov 27, 2024 at 3:11
  • Check "drivers/misc/gehc-achc.c". It registers an "ancillary" SPI device to go with the main SPI device (but they need to be on the same SPI controller), and it also uses GPIOs. Commented Nov 27, 2024 at 13:47
  • I'm not sure if what the gehc-achc driver does is best practice though, since it's the only driver that uses this "ancillary" SPI device trick! If they were I2C client devices, there are existing functions to find I2C devices from a device tree phandle (or in general, a fwnode). There are no existing functions to find SPI devices in the same way, but it should be possible to knock up some code to do it, based on the way it is done for I2C client devices. This would also allow the custom device to find SPI client devices on different controllers, if necessary. Commented Nov 27, 2024 at 13:57

1 Answer 1

1

If the custom device was using two I2C devices instead of two SPI devices (we will get to those later), the custom device could be a "platform" device and its devicetree node could have a property that refers to the nodes of the I2C client devices (that do not have their own driver) that it wants to consume. The platform driver for the custom device could then use the of_parse_phandle(), of_node_put(), and of_find_i2c_device_by_node() functions in its "probe" handler to retrieve pointers to struct i2c_device objects. (For Linux kernel v6.4 onwards, it could instead use fwnode_find_reference(), fwnode_handle_put(), and i2c_find_device_by_fwnode() which are more bus-agnostic than the of_ functions.) In its "remove" handler, it can call put_device() to release each I2C device. Note that if of_find_i2c_device_by_node() (or i2c_find_device_by_fwnode()) returns NULL it is probably because the I2C device is not ready to be probed yet. In that case the custom device's "probe" handler should clean up and return -EPROBE_DEFER so that the custom device's "probe" handler can be called again later.

That's all well and good for using I2C devices, but we want to use SPI devices, and there is no equivalent of_find_spi_device_by_node() or spi_find_device_by_fwnode() function that we can use. But fear not, the building blocks for creating our own of_find_spi_device_by_node() (that I will call my_of_find_spi_device_by_node()) and/or our own spi_find_device_by_fwnode() (that I will call my_spi_find_device_by_fwnode()) are there. We want to give it a pointer to the struct device_node (or struct fwnode_handle) associated with the SPI device that we have found using of_parse_phandle() (or fwnode_find_property()), and we want it to return a pointer to the corresponding struct spi_device, or NULL if the device has not been probed yet.

There follows an example DTS snippet to describe the SPI devices we want to use. They need to have a "compatible" string property even though the devices will not be used by an SPI device driver. I have chosen to use the same "compatible" value for both of SPI devices, but they do not have to be the same. The platform driver for the custom device will have a struct device_node pointer for the SPI device nodes, so it can use that to check the "compatible" properties are as expected if it wants. Also, I have made them children of the same SPI controller (&spi0) in this example, but they could be on separate controllers. Their devicetree nodes need labels that can be referenced by the node for the custom device.

&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>;
    };
};

For the devicetree node for the custom platform device, I have chosen to use a property called spis storing an array of phandle values for the SPI device nodes, but it would also be possible to use separate properties for each SPI device node phandle if desired. I have created the custom device node in the root of the devicetree, but it can be placed somewhere else in the devicetree if necessary.

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

The following code is for an example kernel mode driver for the custom device. It does not do very much apart from retrieving pointers to the SPI devices during "probe" and releasing them during "remove". It incorporates the my_of_find_spi_device_by_node() and my_spi_find_device_by_fwnode() functions mentioned earlier to get the pointers to the SPI devices, and calls spi_dev_put() to release them.

This version should work on Linux kernel v4.12 or later.

/* 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/platform_device.h>
#include <linux/mod_devicetable.h>
#include <linux/err.h>
#include <linux/errno.h>

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

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

/* 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 ready 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);

For Linux kernel v5.3 or later, we can make the code less OF-specific by making use of the fwnode_find_reference() function. The my_of_find_spi_device_by_node() function has been removed because it nothing is using it in this version. The my_spi_find_device_by_fwnode() function is called instead. The only OF-specific code in this version is the module device table.

/* 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/platform_device.h>
#include <linux/mod_devicetable.h>
#include <linux/err.h>
#include <linux/errno.h>

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

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

/* 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);
}

static int foo_platform_probe(struct platform_device *pdev)
{
    struct device *hwdev = &pdev->dev;
    struct foo_private *foo;
    struct fwnode_handle *np = dev_fwnode(hwdev);
    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 fwnode_handle *spinode =
            fwnode_find_reference(np, "spis", i);

        if (IS_ERR(spinode)) {
            dev_err(hwdev, "missing spi fwnode[%u]\n", i);
            rc = PTR_ERR(spinode);
            goto fail_find_spi;
        }
        foo->bar[i] = my_spi_find_device_by_fwnode(spinode);
        fwnode_handle_put(spinode);
        if (!foo->bar[i]) {
            /* SPI device not ready 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);
Sign up to request clarification or add additional context in comments.

12 Comments

Please, try to avoid OF-centric solutions, we may use agnostic APIs. Moreover, the header includes are semi-transom, and outdated. E.g., of_device.h most likely is used instead of mod_devicetable.h.
Btw, of_match_ptr() should not be used at all. It’s nowadays just a useless noise and prone of minor bugs (leading to a compiler warning).
@0andriy It can be easily adapted to ACPI if necessary. The my_spi_find_device_by_fwnode() function would be called by both the OF and ACPI inerfaces. I'm not very familiar with writing ACPI device node information, so concentrated on OF support. Still waiting for your audio codec example…
@0andriy I've removed #include <of_device.h>. It was redundant here, as you pointed out.
@0andriy Also, I don't know what the ACPI equivalent of an OF phandle reference is.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.