| .. _dt-howtos: |
| |
| Devicetree HOWTOs |
| ################# |
| |
| This page has step-by-step advice for getting things done with devicetree. |
| |
| .. tip:: See :ref:`dt-trouble` for troubleshooting advice. |
| |
| .. _get-devicetree-outputs: |
| |
| Get your devicetree and generated header |
| **************************************** |
| |
| A board's devicetree (:ref:`BOARD.dts <devicetree-in-out-files>`) pulls in |
| common node definitions via ``#include`` preprocessor directives. This at least |
| includes the SoC's ``.dtsi``. One way to figure out the devicetree's contents |
| is by opening these files, e.g. by looking in |
| ``dts/<ARCH>/<vendor>/<soc>.dtsi``, but this can be time consuming. |
| |
| If you just want to see the "final" devicetree for your board, build an |
| application and open the :file:`zephyr.dts` file in the build directory. |
| |
| .. tip:: |
| |
| You can build :ref:`hello_world` to see the "base" devicetree for your board |
| without any additional changes from :ref:`overlay files <dt-input-files>`. |
| |
| For example, using the :ref:`qemu_cortex_m3` board to build :ref:`hello_world`: |
| |
| .. code-block:: sh |
| |
| # --cmake-only here just forces CMake to run, skipping the |
| # build process to save time. |
| west build -b qemu_cortex_m3 -s samples/hello_world --cmake-only |
| |
| You can change ``qemu_cortex_m3`` to match your board. |
| |
| CMake prints the input and output file locations like this: |
| |
| .. code-block:: none |
| |
| -- Found BOARD.dts: .../zephyr/boards/arm/qemu_cortex_m3/qemu_cortex_m3.dts |
| -- Generated zephyr.dts: .../zephyr/build/zephyr/zephyr.dts |
| -- Generated devicetree_unfixed.h: .../zephyr/build/zephyr/include/generated/devicetree_unfixed.h |
| |
| The :file:`zephyr.dts` file is the final devicetree in DTS format. |
| |
| The :file:`devicetree_unfixed.h` file is the corresponding generated header. |
| |
| See :ref:`devicetree-in-out-files` for details about these files. |
| |
| .. _dt-get-device: |
| |
| Get a struct device from a devicetree node |
| ****************************************** |
| |
| When writing Zephyr applications, you'll often want to get a driver-level |
| :ref:`struct device <device_model_api>` corresponding to a devicetree node. |
| |
| For example, with this devicetree fragment, you might want the struct device |
| for ``serial@40002000``: |
| |
| .. code-block:: devicetree |
| |
| / { |
| soc { |
| serial0: serial@40002000 { |
| status = "okay"; |
| current-speed = <115200>; |
| /* ... */ |
| }; |
| }; |
| |
| aliases { |
| my-serial = &serial0; |
| }; |
| |
| chosen { |
| zephyr,console = &serial0; |
| }; |
| }; |
| |
| Start by making a :ref:`node identifier <dt-node-identifiers>` for the device |
| you are interested in. There are different ways to do this; pick whichever one |
| works best for your requirements. Here are some examples: |
| |
| .. code-block:: c |
| |
| /* Option 1: by node label */ |
| #define MY_SERIAL DT_NODELABEL(serial0) |
| |
| /* Option 2: by alias */ |
| #define MY_SERIAL DT_ALIAS(my_serial) |
| |
| /* Option 3: by chosen node */ |
| #define MY_SERIAL DT_CHOSEN(zephyr_console) |
| |
| /* Option 4: by path */ |
| #define MY_SERIAL DT_PATH(soc, serial_40002000) |
| |
| Once you have a node identifier there are two ways to proceed. The |
| classic way is to get the ``struct device`` by combining |
| :c:func:`DT_LABEL` with :c:func:`device_get_binding`: |
| |
| .. code-block:: c |
| |
| const struct device *uart_dev = device_get_binding(DT_LABEL(MY_SERIAL)); |
| |
| You can then use ``uart_dev`` with :ref:`uart_api` API functions like |
| :c:func:`uart_configure`. Similar code will work for other device types; just |
| make sure you use the correct API for the device. |
| |
| There's no need to override the ``label`` property to something else: just make |
| a node identifier and pass it to ``DT_LABEL`` to get the right string to pass |
| to ``device_get_binding()``. |
| |
| The second way to get a device is to use :c:func:`DEVICE_DT_GET`: |
| |
| .. code-block:: c |
| |
| const struct device *uart_dev = DEVICE_DT_GET(MY_SERIAL); |
| |
| if (!device_is_ready(uart_dev)) { |
| /* Not ready, do not use */ |
| return -ENODEV; |
| } |
| |
| This idiom fetches the device pointer at build-time, which is useful when you |
| want to store the device pointer as configuration data. But because the |
| device may not be initialized, or may have failed to initialize, you must |
| verify that the device is ready to be used before passing it to any API |
| functions. (This check is done for you by :c:func:`device_get_binding`.) |
| |
| If you're having trouble, see :ref:`dt-trouble`. The first thing to check is |
| that the node has ``status = "okay"``, like this: |
| |
| .. code-block:: c |
| |
| #define MY_SERIAL DT_NODELABEL(my_serial) |
| |
| #if DT_NODE_HAS_STATUS(MY_SERIAL, okay) |
| const struct device *uart_dev = device_get_binding(DT_LABEL(MY_SERIAL)); |
| #else |
| #error "Node is disabled" |
| #endif |
| |
| If you see the ``#error`` output, make sure to enable the node in your |
| devicetree. If you don't see the ``#error`` but ``uart_dev`` is NULL, then |
| there's likely either a Kconfig issue preventing the device driver from |
| creating the device, or the device's initialization function failed. |
| |
| .. _dts-find-binding: |
| |
| Find a devicetree binding |
| ************************* |
| |
| :ref:`dt-bindings` are YAML files which declare what you can do with the nodes |
| they describe, so it's critical to be able to find them for the nodes you are |
| using. |
| |
| If you don't have them already, :ref:`get-devicetree-outputs`. To find a node's |
| binding, open the generated header file, which starts with a list of nodes in a |
| block comment: |
| |
| .. code-block:: c |
| |
| /* |
| * [...] |
| * Nodes in dependency order (ordinal and path): |
| * 0 / |
| * 1 /aliases |
| * 2 /chosen |
| * 3 /flash@0 |
| * 4 /memory@20000000 |
| * (etc.) |
| * [...] |
| */ |
| |
| Make note of the path to the node you want to find, like ``/flash@0``. Search |
| for the node's output in the file, which starts with something like this if the |
| node has a matching binding: |
| |
| .. code-block:: c |
| |
| /* |
| * Devicetree node: |
| * /flash@0 |
| * |
| * Binding (compatible = soc-nv-flash): |
| * $ZEPHYR_BASE/dts/bindings/mtd/soc-nv-flash.yaml |
| * [...] |
| */ |
| |
| See :ref:`missing-dt-binding` for troubleshooting. |
| |
| .. _set-devicetree-overlays: |
| |
| Set devicetree overlays |
| *********************** |
| |
| Devicetree overlays are explained in :ref:`devicetree-intro`. The CMake |
| variable :makevar:`DTC_OVERLAY_FILE` contains a space- or semicolon-separated |
| list of overlays. If :makevar:`DTC_OVERLAY_FILE` specifies multiple files, they |
| are included in that order by the C preprocessor. |
| |
| Here are some ways to set it: |
| |
| 1. on the cmake build command line |
| (``-DDTC_OVERLAY_FILE="file1.overlay;file2.overlay"``) |
| #. with the CMake ``set()`` command in the application ``CMakeLists.txt``, |
| before including zephyr's :file:`boilerplate.cmake` file |
| #. using a ``DTC_OVERLAY_FILE`` environment variable (deprecated) |
| #. create a ``boards/<BOARD>_<revision>.overlay`` file in the application |
| folder for the current board revision. This requires that the board supports |
| multiple revisions, see :ref:`porting_board_revisions`. |
| The ``boards/<BOARD>_<revision>.overlay`` file will be merged with |
| ``boards/<BOARD>.overlay`` if this file also exists. |
| #. create a ``boards/<BOARD>.overlay`` file in the application |
| folder, for the current board |
| #. create a ``<BOARD>.overlay`` file in the application folder |
| #. create an ``app.overlay`` file in the application folder |
| |
| Here is an example :ref:`using west build <west-building-dtc-overlay-file>`. |
| However you set the value, it is saved in the CMake cache between builds. |
| |
| The :ref:`build system <build_overview>` prints all the devicetree overlays it |
| finds in the configuration phase, like this: |
| |
| .. code-block:: none |
| |
| -- Found devicetree overlay: .../some/file.overlay |
| |
| .. _use-dt-overlays: |
| |
| Use devicetree overlays |
| *********************** |
| |
| See :ref:`set-devicetree-overlays` for how to add an overlay to the build. |
| |
| Overlays can override node property values in multiple ways. |
| For example, if your BOARD.dts contains this node: |
| |
| .. code-block:: devicetree |
| |
| / { |
| soc { |
| serial0: serial@40002000 { |
| status = "okay"; |
| current-speed = <115200>; |
| /* ... */ |
| }; |
| }; |
| }; |
| |
| These are equivalent ways to override the ``current-speed`` value in an |
| overlay: |
| |
| .. code-block:: none |
| |
| /* Option 1 */ |
| &serial0 { |
| current-speed = <9600>; |
| }; |
| |
| /* Option 2 */ |
| &{/soc/serial@40002000} { |
| current-speed = <9600>; |
| }; |
| |
| We'll use the ``&serial0`` style for the rest of these examples. |
| |
| You can add aliases to your devicetree using overlays: an alias is just a |
| property of the ``/aliases`` node. For example: |
| |
| .. code-block:: none |
| |
| / { |
| aliases { |
| my-serial = &serial0; |
| }; |
| }; |
| |
| Chosen nodes work the same way. For example: |
| |
| .. code-block:: none |
| |
| / { |
| chosen { |
| zephyr,console = &serial0; |
| }; |
| }; |
| |
| To delete a property (in addition to deleting properties in general, this is |
| how to set a boolean property to false if it's true in BOARD.dts): |
| |
| .. code-block:: none |
| |
| &serial0 { |
| /delete-property/ some-unwanted-property; |
| }; |
| |
| You can add subnodes using overlays. For example, to configure a SPI or I2C |
| child device on an existing bus node, do something like this: |
| |
| .. code-block:: none |
| |
| /* SPI device example */ |
| &spi1 { |
| my_spi_device: temp-sensor@0 { |
| compatible = "..."; |
| label = "TEMP_SENSOR_0"; |
| /* reg is the chip select number, if needed; |
| * If present, it must match the node's unit address. */ |
| reg = <0>; |
| |
| /* Configure other SPI device properties as needed. |
| * Find your device's DT binding for details. */ |
| spi-max-frequency = <4000000>; |
| }; |
| }; |
| |
| /* I2C device example */ |
| &i2c2 { |
| my_i2c_device: touchscreen@76 { |
| compatible = "..."; |
| label = "TOUCHSCREEN"; |
| /* reg is the I2C device address. |
| * It must match the node's unit address. */ |
| reg = <76>; |
| |
| /* Configure other I2C device properties as needed. |
| * Find your device's DT binding for details. */ |
| }; |
| }; |
| |
| Other bus devices can be configured similarly: |
| |
| - create the device as a subnode of the parent bus |
| - set its properties according to its binding |
| |
| Assuming you have a suitable device driver associated with the |
| ``my_spi_device`` and ``my_i2c_device`` compatibles, you should now be able to |
| enable the driver via Kconfig and :ref:`get the struct device <dt-get-device>` |
| for your newly added bus node, then use it with that driver API. |
| |
| .. _dt-create-devices: |
| |
| Write device drivers using devicetree APIs |
| ****************************************** |
| |
| "Devicetree-aware" :ref:`device drivers <device_model_api>` should create a |
| ``struct device`` for each ``status = "okay"`` devicetree node with a |
| particular :ref:`compatible <dt-important-props>` (or related set of |
| compatibles) supported by the driver. |
| |
| .. note:: |
| |
| Historically, Zephyr has used Kconfig options like :kconfig:`CONFIG_I2C_0` and |
| :kconfig:`CONFIG_I2C_1` to enable driver support for individual devices of |
| some type. For example, if ``CONFIG_I2C_1=y``, the SoC's I2C peripheral |
| driver would create a ``struct device`` for "I2C bus controller number 1". |
| |
| This style predates support for devicetree in Zephyr and its use is now |
| discouraged. Existing device drivers may be made "devicetree-aware" |
| in future releases. |
| |
| Writing a devicetree-aware driver begins by defining a :ref:`devicetree binding |
| <dt-bindings>` for the devices supported by the driver. Use existing bindings |
| from similar drivers as a starting point. A skeletal binding to get started |
| needs nothing more than this: |
| |
| .. code-block:: yaml |
| |
| description: <Human-readable description of your binding> |
| compatible: "foo-company,bar-device" |
| include: base.yaml |
| |
| See :ref:`dts-find-binding` for more advice on locating existing bindings. |
| |
| After writing your binding, your driver C file can then use the devicetree API |
| to find ``status = "okay"`` nodes with the desired compatible, and instantiate |
| a ``struct device`` for each one. There are two options for instantiating each |
| ``struct device``: using instance numbers, and using node labels. |
| |
| In either case: |
| |
| - Each ``struct device``\ 's name should be set to its devicetree node's |
| ``label`` property. This allows the driver's users to :ref:`dt-get-device` in |
| the usual way. |
| |
| - Each device's initial configuration should use values from devicetree |
| properties whenever practical. This allows users to configure the driver |
| using :ref:`devicetree overlays <use-dt-overlays>`. |
| |
| Examples for how to do this follow. They assume you've already implemented the |
| device-specific configuration and data structures and API functions, like this: |
| |
| .. code-block:: c |
| |
| /* my_driver.c */ |
| #include <drivers/some_api.h> |
| |
| /* Define data (RAM) and configuration (ROM) structures: */ |
| struct my_dev_data { |
| /* per-device values to store in RAM */ |
| }; |
| struct my_dev_cfg { |
| uint32_t freq; /* Just an example: initial clock frequency in Hz */ |
| /* other configuration to store in ROM */ |
| }; |
| |
| /* Implement driver API functions (drivers/some_api.h callbacks): */ |
| static int my_driver_api_func1(const struct device *dev, uint32_t *foo) { /* ... */ } |
| static int my_driver_api_func2(const struct device *dev, uint64_t bar) { /* ... */ } |
| static struct some_api my_api_funcs = { |
| .func1 = my_driver_api_func1, |
| .func2 = my_driver_api_func2, |
| }; |
| |
| .. _dt-create-devices-inst: |
| |
| Option 1: create devices using instance numbers |
| =============================================== |
| |
| Use this option, which uses :ref:`devicetree-inst-apis`, if possible. However, |
| they only work when devicetree nodes for your driver's ``compatible`` are all |
| equivalent, and you do not need to be able to distinguish between them. |
| |
| To use instance-based APIs, begin by defining ``DT_DRV_COMPAT`` to the |
| lowercase-and-underscores version of the compatible that the device driver |
| supports. For example, if your driver's compatible is ``"vnd,my-device"`` in |
| devicetree, you would define ``DT_DRV_COMPAT`` to ``vnd_my_device`` in your |
| driver C file: |
| |
| .. code-block:: c |
| |
| /* |
| * Put this near the top of the file. After the includes is a good place. |
| * (Note that you can therefore run "git grep DT_DRV_COMPAT drivers" in |
| * the zephyr Git repository to look for example drivers using this style). |
| */ |
| #define DT_DRV_COMPAT vnd_my_device |
| |
| .. important:: |
| |
| As shown, the DT_DRV_COMPAT macro should have neither quotes nor special |
| characters. Remove quotes and convert special characters to underscores |
| when creating ``DT_DRV_COMPAT`` from the compatible property. |
| |
| Finally, define an instantiation macro, which creates each ``struct device`` |
| using instance numbers. Do this after defining ``my_api_funcs``. |
| |
| .. code-block:: c |
| |
| /* |
| * This instantiation macro is named "CREATE_MY_DEVICE". |
| * Its "inst" argument is an arbitrary instance number. |
| * |
| * Put this near the end of the file, e.g. after defining "my_api_funcs". |
| */ |
| #define CREATE_MY_DEVICE(inst) \ |
| static struct my_dev_data my_data_##inst = { \ |
| /* initialize RAM values as needed, e.g.: */ \ |
| .freq = DT_INST_PROP(inst, clock_frequency), \ |
| }; \ |
| static const struct my_dev_cfg my_cfg_##inst = { \ |
| /* initialize ROM values as needed. */ \ |
| }; \ |
| DEVICE_DT_INST_DEFINE(inst, \ |
| my_dev_init_function, \ |
| NULL, \ |
| &my_data_##inst, \ |
| &my_cfg_##inst, \ |
| MY_DEV_INIT_LEVEL, MY_DEV_INIT_PRIORITY, \ |
| &my_api_funcs); |
| |
| Notice the use of APIs like :c:func:`DT_INST_PROP` and |
| :c:func:`DEVICE_DT_INST_DEFINE` to access devicetree node data. These |
| APIs retrieve data from the devicetree for instance number ``inst`` of |
| the node with compatible determined by ``DT_DRV_COMPAT``. |
| |
| Finally, pass the instantiation macro to :c:func:`DT_INST_FOREACH_STATUS_OKAY`: |
| |
| .. code-block:: c |
| |
| /* Call the device creation macro for each instance: */ |
| DT_INST_FOREACH_STATUS_OKAY(CREATE_MY_DEVICE) |
| |
| ``DT_INST_FOREACH_STATUS_OKAY`` expands to code which calls |
| ``CREATE_MY_DEVICE`` once for each enabled node with the compatible determined |
| by ``DT_DRV_COMPAT``. It does not append a semicolon to the end of the |
| expansion of ``CREATE_MY_DEVICE``, so the macro's expansion must end in a |
| semicolon or function definition to support multiple devices. |
| |
| Option 2: create devices using node labels |
| ========================================== |
| |
| Some device drivers cannot use instance numbers. One example is an SoC |
| peripheral driver which relies on vendor HAL APIs specialized for individual IP |
| blocks to implement Zephyr driver callbacks. Cases like this should use |
| :c:func:`DT_NODELABEL` to refer to individual nodes in the devicetree |
| representing the supported peripherals on the SoC. The devicetree.h |
| :ref:`devicetree-generic-apis` can then be used to access node data. |
| |
| For this to work, your :ref:`SoC's dtsi file <dt-input-files>` must define node |
| labels like ``mydevice0``, ``mydevice1``, etc. appropriately for the IP blocks |
| your driver supports. The resulting devicetree usually looks something like |
| this: |
| |
| .. code-block:: devicetree |
| |
| / { |
| soc { |
| mydevice0: dev@0 { |
| compatible = "vnd,my-device"; |
| }; |
| mydevice1: dev@1 { |
| compatible = "vnd,my-device"; |
| }; |
| }; |
| }; |
| |
| The driver can use the ``mydevice0`` and ``mydevice1`` node labels in the |
| devicetree to operate on specific device nodes: |
| |
| .. code-block:: c |
| |
| /* |
| * This is a convenience macro for creating a node identifier for |
| * the relevant devices. An example use is MYDEV(0) to refer to |
| * the node with label "mydevice0". |
| */ |
| #define MYDEV(idx) DT_NODELABEL(mydevice ## idx) |
| |
| /* |
| * Define your instantiation macro; "idx" is a number like 0 for mydevice0 |
| * or 1 for mydevice1. It uses MYDEV() to create the node label from the |
| * index. |
| */ |
| #define CREATE_MY_DEVICE(idx) \ |
| static struct my_dev_data my_data_##idx = { \ |
| /* initialize RAM values as needed, e.g.: */ \ |
| .freq = DT_PROP(MYDEV(idx), clock_frequency), \ |
| }; \ |
| static const struct my_dev_cfg my_cfg_##idx = { /* ... */ }; \ |
| DEVICE_DT_DEFINE(MYDEV(idx), \ |
| my_dev_init_function, \ |
| NULL, \ |
| &my_data_##idx, \ |
| &my_cfg_##idx, \ |
| MY_DEV_INIT_LEVEL, MY_DEV_INIT_PRIORITY, \ |
| &my_api_funcs) |
| |
| Notice the use of APIs like :c:func:`DT_PROP` and |
| :c:func:`DEVICE_DT_DEFINE` to access devicetree node data. |
| |
| Finally, manually detect each enabled devicetree node and use |
| ``CREATE_MY_DEVICE`` to instantiate each ``struct device``: |
| |
| .. code-block:: c |
| |
| #if DT_NODE_HAS_STATUS(DT_NODELABEL(mydevice0), okay) |
| CREATE_MY_DEVICE(0) |
| #endif |
| |
| #if DT_NODE_HAS_STATUS(DT_NODELABEL(mydevice1), okay) |
| CREATE_MY_DEVICE(1) |
| #endif |
| |
| Since this style does not use ``DT_INST_FOREACH_STATUS_OKAY()``, the driver |
| author is responsible for calling ``CREATE_MY_DEVICE()`` for every possible |
| node, e.g. using knowledge about the peripherals available on supported SoCs. |
| |
| .. _dt-drivers-that-depend: |
| |
| Device drivers that depend on other devices |
| ******************************************* |
| |
| At times, one ``struct device`` depends on another ``struct device`` and |
| requires a pointer to it. For example, a sensor device might need a pointer to |
| its SPI bus controller device. Some advice: |
| |
| - Write your devicetree binding in a way that permits use of |
| :ref:`devicetree-hw-api` from devicetree.h if possible. |
| - In particular, for bus devices, your driver's binding should include a |
| file like :zephyr_file:`dts/bindings/spi/spi-device.yaml` which provides |
| common definitions for devices addressable via a specific bus. This enables |
| use of APIs like :c:func:`DT_BUS` to obtain a node identifier for the bus |
| node. You can then :ref:`dt-get-device` for the bus in the usual way. |
| |
| Search existing bindings and device drivers for examples. |
| |
| .. _dt-apps-that-depend: |
| |
| Applications that depend on board-specific devices |
| ************************************************** |
| |
| One way to allow application code to run unmodified on multiple boards is by |
| supporting a devicetree alias to specify the hardware specific portions, as is |
| done in the :ref:`blinky-sample`. The application can then be configured in |
| :ref:`BOARD.dts <devicetree-in-out-files>` files or via :ref:`devicetree |
| overlays <use-dt-overlays>`. |