Search code examples
embeddeddevice-treezephyr-rtos

Cannot get device binding in Zephyr


I want to get a device binding using the device name, but for some reason, it fails when I use just a node id. When I use the "full" node id it works.

Here is my code

#include <zephyr/kernel.h>
#include <zephyr/devicetree.h>
#include <zephyr/device.h>
#include <zephyr/drivers/i2c.h>

#define I2C_DEV_NAME "i2c@3ff53000" // works
//#define I2C_DEV_NAME "i2c0" // does not work, dev is null

void check_i2c(void)
{
    const struct device* const dev = device_get_binding(I2C_DEV_NAME);

    if (!dev) {
        printk("Failed to get device %s\n", I2C_DEV_NAME);

        return;
    }

    printk("i2c device is ready to use\n");
}

void main(void)
{
    check_i2c();

    while (1) {
        k_sleep(K_SECONDS(1));
    }
}

*.dtsi file:

i2c0: i2c@3ff53000 {
    compatible = "espressif,esp32-i2c";
    #address-cells = <1>;
    #size-cells = <0>;
    reg = <0x3ff53000 0x1000>;
    interrupts = <I2C_EXT0_INTR_SOURCE>;
    interrupt-parent = <&intc>;
    clocks = <&rtc ESP32_I2C0_MODULE>;
    status = "disabled";
};

*.dts

&i2c0 {
    status = "okay";
    clock-frequency = <I2C_BITRATE_STANDARD>;
    sda-gpios = <&gpio0 21 GPIO_OPEN_DRAIN>;
    scl-gpios = <&gpio0 22 GPIO_OPEN_DRAIN>;
    pinctrl-0 = <&i2c0_default>;
    pinctrl-names = "default";
};

The node with id 'i2c0' exists, and it's enabled, so why doesn't it work?

I'm using esp32 board.


Solution

  • As mentioned in the comments, generally speaking, the node labels such as i2c0 don't end up as part of the final devicetree when compiling it; and it is not used as the device name used by device_get_binding() (which is yet again different from node id used in the devicetree macros). But it's important to note though that Zephyr uses the devicetree at compile time and typically accesses it through macros and not at runtime like Linux does - so it has access to more things than Linux does as the build system handles the devicetree.

    To be maybe a bit more clear and separate out the different concepts when it comes to devices and the devicetree in Zephyr:

    device - a structure pointer provided by the driver which is used in all API calls so it's what we want to get

    device name - a string used to identify the device at runtime, it's what is given to device_get_binding() and is either the label property or the nodes full name

    node full name - the node name and unit address in devicetree as a string, in the devicetree; this is the name@address string which will be used as the device name (like i2c@3ff53000 in your case above)

    node label - the node label is given to a node in the devicetree as a way to reference a node easier; this is your i2c0. One complexity is typically a node can have many labels but usually a node in a devicetree only gets one.

    label property - if a devicetree node has label as a property of the node, this value is used instead of the nodes full name for the device name in Zephyr (like label = "DEV";; you don't have this but I want to mention it so you are aware of it)

    node id - this is typically what is used by Zephyr to refer to a node in the devicetree in the devicetree macros, it acts as a handle to the node so you can access it and navigate the tree. It isn't something in the tree itself, just the way the build system refers to the node.

    With all this laid out, there are two ways to handle your situation. As mentioned, node labels are not used as device names, but we can use the devicetree macros to get the name from the label:

    // First get the node id from the node label, then get the name from the node id
    #define I2C_DEV_NAME DEVICE_DT_NAME(DT_NODELABEL(i2c0))
    ...
    dev = device_get_binding(I2C_DEV_NAME);
    

    However, using the device name to get the device structure pointer at runtime using the device name string isnt the most efficient thing to do when you know everything at compile time (this approach dates back to the beginnings of Zephyr; and has to loop through the device array doing string comparisons on each device name to the requested name). Thanks to the devicetree macros, we should be able to get the device structure pointer directly from the node id, which we can get from the label, all at compile time:

    dev = DEVICE_DT_GET(DT_NODELABEL(i2c0));
    // Shouldn't need to null pointer check as the above will fail in compilation if i2c0 doesn't exist
    

    There are even more options like aliases, but hopefully this is helpful for trying to understand what Zephyr is doing and how to get the device easier.