doc: write documentation about DT-based device instantiation
Add more HOWTO information for the two current devicetree-based device instantiation styles, and a bit more information on how to create devices that depend on others. Point to this from the Kconfig tips page, since it is meant as a replacement for existing Kconfig practice. Update macros.bnf. Signed-off-by: Martí Bolívar <marti.bolivar@nordicsemi.no>
This commit is contained in:
parent
90ffbcb9d3
commit
6f35d3bd16
4 changed files with 293 additions and 87 deletions
|
@ -104,8 +104,7 @@ a node identifier and pass it to ``DT_LABEL`` to get the right string to pass
|
|||
to ``device_get_binding()``.
|
||||
|
||||
If you're having trouble, see :ref:`dt-trouble`. The first thing to check is
|
||||
that the node is enabled (``status = "okay"``) and has a matching binding, like
|
||||
this:
|
||||
that the node is has ``status = "okay"`` and has a matching binding, like this:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
|
@ -114,11 +113,11 @@ this:
|
|||
#if DT_HAS_NODE_STATUS_OKAY(MY_SERIAL)
|
||||
struct device *uart_dev = device_get_binding(DT_LABEL(MY_SERIAL));
|
||||
#else
|
||||
#error "Node is disabled or has no matching binding"
|
||||
#error "Node is disabled, has no matching binding, or initialization failed"
|
||||
#endif
|
||||
|
||||
If you see the ``#error`` output, something is wrong with either your
|
||||
devicetree or bindings.
|
||||
If you see the ``#error`` output, either something is wrong with either your
|
||||
devicetree or bindings, or the device's initialization function failed.
|
||||
|
||||
.. _dts-find-binding:
|
||||
|
||||
|
@ -307,104 +306,263 @@ Assuming you have a suitable device driver associated with the
|
|||
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-driver-howto:
|
||||
.. _dt-create-devices:
|
||||
|
||||
Create struct devices in a driver
|
||||
*********************************
|
||||
Write device drivers using devicetree APIs
|
||||
******************************************
|
||||
|
||||
If you're writing a device driver, it should be devicetree aware so that
|
||||
applications can configure it and access devices as described above. In short,
|
||||
you must create a ``struct device`` for every enabled instance of the
|
||||
compatible that the device driver supports, and set each device's name to the
|
||||
``DT_LABEL()`` of its devicetree node.
|
||||
"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.
|
||||
|
||||
The :file:`devicetree.h` API has helpers for writing device drivers based on
|
||||
:ref:`DT_INST node identifiers <dt-node-identifiers>` for each of the possible
|
||||
instance numbers on your SoC.
|
||||
.. note::
|
||||
|
||||
Assuming you're using instances, start by defining ``DT_DRV_COMPAT`` at the top
|
||||
of the file to the lowercase-and-underscores version of the :ref:`compatible
|
||||
<dt-important-props>` that the device driver is handling. For example, if your
|
||||
driver is handling nodes with compatible ``"vnd,my-device"``, you should put
|
||||
this at the top of your driver:
|
||||
Historically, Zephyr has used Kconfig options like :option:`CONFIG_SPI_0` and
|
||||
:option:`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 {
|
||||
u32_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(struct device *dev, u32_t *foo) { /* ... */ }
|
||||
static int my_driver_api_func2(struct device *dev, u64_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::
|
||||
|
||||
The DT_DRV_COMPAT macro should have neither quotes nor special characters.
|
||||
Remove quotes and convert special characters to underscores.
|
||||
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.
|
||||
|
||||
The typical pattern after that is to define the API functions, then define a
|
||||
macro which creates the device by instance number, and then call it for each
|
||||
enabled instance. Currently, this looks like this:
|
||||
Finally, define an instantiation macro, which creates each ``struct device``
|
||||
using instance numbers. Do this after defining ``my_api_funcs``.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
#include <drivers/some_api.h>
|
||||
|
||||
#include <devicetree.h>
|
||||
#define DT_DRV_COMPAT vnd_my_device
|
||||
|
||||
/*
|
||||
* Define RAM and ROM structures:
|
||||
* 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".
|
||||
*/
|
||||
|
||||
struct my_dev_data {
|
||||
/* per-device values to store in RAM */
|
||||
};
|
||||
|
||||
struct my_dev_cfg {
|
||||
u32_t freq; /* Just an example: clock frequency in Hz */
|
||||
/* other device configuration to store in ROM */
|
||||
};
|
||||
|
||||
/*
|
||||
* Implement some_api.h callbacks:
|
||||
*/
|
||||
|
||||
struct some_api my_api_funcs = { /* ... */ };
|
||||
|
||||
/*
|
||||
* Now use DT_INST APIs to create a struct device for each enabled node:
|
||||
*/
|
||||
|
||||
#define CREATE_MY_DEVICE(inst) \
|
||||
static struct my_dev_data my_dev_data_##inst = { \
|
||||
/* initialize RAM values as needed */ \
|
||||
}; \
|
||||
static const struct my_dev_cfg my_dev_cfg_##inst = { \
|
||||
/* initialize ROM values, usually from devicetree */ \
|
||||
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_AND_API_INIT(my_dev_##inst, \
|
||||
DT_INST_LABEL(inst), \
|
||||
my_dev_init_function, \
|
||||
&my_dev_data_##inst, \
|
||||
&my_dev_cfg_##inst, \
|
||||
&my_data_##inst, \
|
||||
&my_cfg_##inst, \
|
||||
MY_DEV_INIT_LEVEL, MY_DEV_INIT_PRIORITY, \
|
||||
&my_api_funcs);
|
||||
|
||||
/* Call the device creation macro for every compatible node: */
|
||||
Notice the use of APIs like :c:func:`DT_INST_LABEL` and :c:func:`DT_INST_PROP`
|
||||
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)
|
||||
|
||||
Notice the use of :c:func:`DT_INST_PROP` and :c:func:`DT_INST_FOREACH`.
|
||||
These are helpers which rely on ``DT_DRV_COMPAT`` to choose devicetree nodes
|
||||
of a chosen compatible at a given index.
|
||||
``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.
|
||||
|
||||
As shown above, the driver uses additional information from
|
||||
:file:`devicetree.h` to create :ref:`struct device <device_struct>` instances
|
||||
than just the node label. Devicetree property values used to configure the
|
||||
device at boot time are stored in ROM in the value pointed to by a
|
||||
``device->config_info`` field. This allows users to configure your
|
||||
driver using overlays.
|
||||
Option 2: create devices using node labels
|
||||
==========================================
|
||||
|
||||
The Zephyr convention is to name each ``struct device`` using its devicetree
|
||||
node's ``label`` property using ``DT_INST_LABEL()``. This allows applications
|
||||
to :ref:`dt-get-device`.
|
||||
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:: DTS
|
||||
|
||||
/ {
|
||||
soc {
|
||||
mydevice0: dev@... {
|
||||
compatible = "vnd,my-device";
|
||||
};
|
||||
mydevice1: dev@... {
|
||||
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_AND_API_INIT(my_dev_##idx, \
|
||||
DT_LABEL(MYDEV(idx)), \
|
||||
my_dev_init_function, \
|
||||
&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_LABEL` and :c:func:`DT_PROP` 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_HAS_NODE_STATUS_OKAY(DT_NODELABEL(mydevice0))
|
||||
CREATE_MY_DEVICE(0)
|
||||
#endif
|
||||
|
||||
#if DT_HAS_NODE_STATUS_OKAY(DT_NODELABEL(mydevice1))
|
||||
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>`.
|
||||
|
||||
.. _dt-trouble:
|
||||
|
||||
|
@ -537,4 +695,4 @@ Errors with DT_INST_() APIs
|
|||
|
||||
If you're using an API like :c:func:`DT_INST_PROP`, you must define
|
||||
``DT_DRV_COMPAT`` to the lowercase-and-underscores version of the compatible
|
||||
you are interested in. See :ref:`dt-driver-howto`.
|
||||
you are interested in. See :ref:`dt-create-devices-inst`.
|
||||
|
|
|
@ -46,6 +46,8 @@ node-macro =/ %s"DT_N" path-id %s"_PARENT"
|
|||
; These are used internally by DT_FOREACH_CHILD, which iterates over
|
||||
; each child node.
|
||||
node-macro =/ %s"DT_N" path-id %s"_FOREACH_CHILD"
|
||||
; The node's status macro
|
||||
node-macro =/ %s"DT_N" path-id %s"_STATUS_" dt-name
|
||||
|
||||
; --------------------------------------------------------------------
|
||||
; property-macro: a macro related to a node property
|
||||
|
@ -132,11 +134,10 @@ prop-suf = 1*( "_" gen-name ["_" dt-name] )
|
|||
; See examples below.
|
||||
other-macro = %s"DT_N_" alternate-id
|
||||
; Total count of enabled instances of a compatible.
|
||||
other-macro =/ %s"DT_N_INST_" dt-name %s"_NUM"
|
||||
other-macro =/ %s"DT_N_INST_" dt-name %s"_NUM_OKAY"
|
||||
; These are used internally by DT_INST_FOREACH, which iterates over
|
||||
; each enabled instance of a compatible.
|
||||
other-macro =/ %s"DT_FOREACH_INST_" dt-name
|
||||
other-macro =/ %s"DT_FOREACH_IMPL_" DIGIT
|
||||
other-macro =/ %s"DT_FOREACH_OKAY_INST_" dt-name
|
||||
; E.g.: #define DT_CHOSEN_zephyr_flash
|
||||
other-macro =/ %s"DT_CHOSEN_" dt-name
|
||||
; Declares that a compatible has at least one node on a bus.
|
||||
|
@ -145,6 +146,8 @@ other-macro =/ %s"DT_CHOSEN_" dt-name
|
|||
;
|
||||
; #define DT_COMPAT_vnd_dev_BUS_spi 1
|
||||
other-macro =/ %s"DT_COMPAT_" dt-name %s"_BUS_" dt-name
|
||||
; #define DT_COMPAT_HAS_OKAY_vnd_dev 1
|
||||
other-macro =/ %s"DT_COMPAT_HAS_OKAY_" dt-name
|
||||
|
||||
; --------------------------------------------------------------------
|
||||
; alternate-id: another way to specify a node besides a path-id
|
||||
|
|
|
@ -37,11 +37,6 @@ Symbols without prompts are called *hidden* or *invisible* symbols, because
|
|||
they don't show up in ``menuconfig`` and ``guiconfig``. Symbols that have
|
||||
prompts can also be invisible, when their dependencies are not satisfied.
|
||||
|
||||
In Zephyr, Kconfig configuration is done after selecting a machine, so in
|
||||
general, it does not make sense to put a prompt on a symbol that corresponds to
|
||||
a fixed machine-specific setting. Usually, such settings should be handled via
|
||||
devicetree (``.dts``) files instead.
|
||||
|
||||
Symbols without prompts can't be configured directly by the user (they derive
|
||||
their value from other symbols), so less restrictions apply to them. If some
|
||||
derived setting is easier to calculate in Kconfig than e.g. during the build,
|
||||
|
@ -51,6 +46,52 @@ without prompts in mind.
|
|||
See the `optional prompts`_ section for a way to deal with settings that are
|
||||
fixed on some machines and configurable on other machines.
|
||||
|
||||
What not to turn into Kconfig options
|
||||
*************************************
|
||||
|
||||
In Zephyr, Kconfig configuration is done after selecting a target board. In
|
||||
general, it does not make sense to use Kconfig for a value that corresponds to
|
||||
a fixed machine-specific setting. Usually, such settings should be handled via
|
||||
:ref:`devicetree <dt-guide>` instead.
|
||||
|
||||
In particular, avoid adding new Kconfig options of the following types:
|
||||
|
||||
Options enabling individual devices
|
||||
===================================
|
||||
|
||||
Existing examples like :option:`CONFIG_SPI_0` and :option:`CONFIG_I2C_1` were
|
||||
introduced before Zephyr supported devicetree, and new cases are discouraged.
|
||||
See :ref:`dt-create-devices` for details on how to do this with devicetree
|
||||
instead.
|
||||
|
||||
Options that specify a device in the system by name
|
||||
===================================================
|
||||
|
||||
For example, if you are writing an I2C device driver, avoid creating an option
|
||||
named ``MY_DEVICE_I2C_BUS_NAME`` for specifying the bus node your device is
|
||||
controlled by. See :ref:`dt-drivers-that-depend` for alternatives.
|
||||
|
||||
Similarly, if your application depends on a hardware-specific PWM device to
|
||||
control an RGB LED, avoid creating an option like ``MY_PWM_DEVICE_NAME``. See
|
||||
:ref:`dt-apps-that-depend` for alternatives.
|
||||
|
||||
Options that specify fixed hardware configuration
|
||||
=================================================
|
||||
|
||||
For example, avoid Kconfig options specifying a GPIO pin.
|
||||
|
||||
An alternative applicable to device drivers is to define a GPIO specifier with
|
||||
type phandle-array in the device binding, and using the
|
||||
:ref:`devicetree-gpio-api` devicetree API from C. Similar advice applies to
|
||||
other cases where devicetree.h provides :ref:`devicetree-hw-api` for referring
|
||||
to other nodes in the system. Search the source code for drivers using these
|
||||
APIs for examples.
|
||||
|
||||
An application-specific devicetree :ref:`binding <dt-bindings>` to identify
|
||||
board specific properties may be appropriate. See
|
||||
:zephyr_file:`tests/drivers/gpio/gpio_basic_api` for an example.
|
||||
|
||||
For applications, see :ref:`blinky-sample` for a devicetree-based alternative.
|
||||
|
||||
``select`` statements
|
||||
*********************
|
||||
|
|
|
@ -11,6 +11,8 @@ Some of these require a special macro named ``DT_DRV_COMPAT`` to be defined
|
|||
before they can be used; these are discussed individually below. These macros
|
||||
are generally meant for use within device drivers.
|
||||
|
||||
.. _devicetree-generic-apis:
|
||||
|
||||
Generic APIs
|
||||
************
|
||||
|
||||
|
@ -85,6 +87,8 @@ Clocks
|
|||
.. doxygengroup:: devicetree-clocks
|
||||
:project: Zephyr
|
||||
|
||||
.. _devicetree-gpio-api:
|
||||
|
||||
GPIO
|
||||
====
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue