Zephyr device driver model

From Wiki at Neela Nurseries
Revision as of 16:22, 19 August 2021 by Ted (talk | contribs)
Jump to: navigation, search

Keywords: zephyr device drivers Zephyr device driver model Zephyr device drivers getting started LIS2DH driver study Jared Wolff post Jared Wolff device driver post


New refs:
* https://github.com/zephyrproject-rtos/zephyr/issues/13737
* https://docs.zephyrproject.org/latest/reference/kconfig/CONFIG_GPIO.html
* https://docs.zephyrproject.org/latest/reference/kconfig/index-drivers.html
* https://docs.zephyrproject.org/latest/reference/kconfig/CONFIG_ADC.html#std-kconfig-CONFIG_ADC



- OVERVIEW -
Challenged by Zephyr's way of writing device drivers, capturing some notes here on a study of a Zephyr example project which talks to LIS2DH accelerometer on a Sparkfun Thing Plus board.


^ Early Research and SDK Used

A couple of Zephyr driver tutorials and posts worth noting. Casting net wider for Zephyr device driver "How To" articles, find Jared Wolff's blog post:

Here is a link to a tutorial, part three of a series, which may be helpful:

What does zephyr_include_directories() perform? . . .

From Jared's blog post, he mentions in terms of driver directories and file structure the following paths and files:

 *  ncs/nrf/applications/asset_tracker      . . . example Zephyr application path
 *  ncs/nrf/drivers/sensor/CMakeLists.txt   . . . example top level Zephyr drivers path
 *  ncs/nrf/zephyr/module.yml               . . . file module.yml "in subdirectory of a dependency"

This module.yml file contains:

 build:
    cmake: .
    kconfig: Kconfig.nrf


^ west status

2021-08-17 Note - info in this section can likely be moved to another section or deleted after review.

Before getting too far into Zephyr drivers, important to note the SDK we're using, obtained by git cloning Nordic's ncs v1.6.1 SDK and then executing `west init && west zephyr-export` commands. Turns out west like git supports a status query command:

ted@localhost:~/projects/embedded/ncs$ west status

=== status of manifest (nrf):
HEAD detached at v1.6.1
nothing to commit, working tree clean
=== status of zephyr (zephyr):
Refresh index: 100% (16309/16309), done.
HEAD detached at refs/heads/manifest-rev
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   samples/basic/blinky/prj.conf
	modified:   samples/sensor/lis2dh/prj.conf

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	samples/basic/blinky/z--build-messages.txt
	samples/z--sandbox/

no changes added to commit (use "git add" and/or "git commit -a")
=== status of openthread (modules/lib/openthread):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of mcuboot (bootloader/mcuboot):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of nrfxlib (nrfxlib):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of trusted-firmware-m (modules/tee/tfm):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of tfm-mcuboot (modules/tee/tfm-mcuboot):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of matter (modules/lib/matter):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of cjson (modules/lib/cjson):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of cmock (test/cmock):
HEAD detached at refs/heads/manifest-rev
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   vendor/unity (new commits)

no changes added to commit (use "git add" and/or "git commit -a")
=== status of unity (test/cmock/vendor/unity):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of mbedtls-nrf (mbedtls):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of nanopb (modules/lib/nanopb):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of Alexa-Gadgets-Embedded-Sample-Code (modules/alexa-embedded):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of mbed-cloud-client (modules/lib/pelion-dm):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of cddl-gen (modules/lib/cddl-gen):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of memfault-firmware-sdk (modules/lib/memfault-firmware-sdk):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of cmsis (modules/hal/cmsis):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of canopennode (modules/lib/canopennode):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of civetweb (modules/lib/civetweb):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of fatfs (modules/fs/fatfs):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of hal_nordic (modules/hal/nordic):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of hal_st (modules/hal/st):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of libmetal (modules/hal/libmetal):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of lvgl (modules/lib/gui/lvgl):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of mbedtls (modules/crypto/mbedtls):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of mcumgr (modules/lib/mcumgr):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of net-tools (tools/net-tools):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of open-amp (modules/lib/open-amp):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of loramac-node (modules/lib/loramac-node):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of segger (modules/debug/segger):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of tinycbor (modules/lib/tinycbor):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of tinycrypt (modules/crypto/tinycrypt):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of littlefs (modules/fs/littlefs):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of mipi-sys-t (modules/debug/mipi-sys-t):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of nrf_hw_models (modules/bsim_hw_models/nrf_hw_models):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of TraceRecorderSource (modules/debug/TraceRecorder):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean
=== status of edtt (tools/edtt):
HEAD detached at refs/heads/manifest-rev
nothing to commit, working tree clean

ted@localhost:~/projects/embedded/ncs


Excerpt from Zephyr project drivers documentation:

    Driver Data Structures The device initialization macros populate some data structures at build time which are split into read-only and runtime-mutable parts. At a high level we have:
     struct device {
          const char *name;
          const void *config;
          const void *api;
          void * const data;
     };

Excerpt from file ~/ncs/zephyr/include/drivers/sensor.h:

382 /**
383  * @brief Set an attribute for a sensor
384  *
385  * @param dev Pointer to the sensor device
386  * @param chan The channel the attribute belongs to, if any.  Some
387  * attributes may only be set for all channels of a device, depending on
388  * device capabilities.
389  * @param attr The attribute to set
390  * @param val The value to set the attribute to
391  *
392  * @return 0 if successful, negative errno code if failure.
393  */
394 __syscall int sensor_attr_set(const struct device *dev,
395                               enum sensor_channel chan,
396                               enum sensor_attribute attr,
397                               const struct sensor_value *val);
398 
399 static inline int z_impl_sensor_attr_set(const struct device *dev,
400                                          enum sensor_channel chan,
401                                          enum sensor_attribute attr,
402                                          const struct sensor_value *val)
403 {
404         const struct sensor_driver_api *api =
405                 (const struct sensor_driver_api *)dev->api;
406 
407         if (api->attr_set == NULL) {
408                 return -ENOSYS;
409         }
410 
411         return api->attr_set(dev, chan, attr, val);
412 }
413 

From these two code excerpts the big question is, how is dev->api assigned in early configuring code for an I2C based sensor?


./include/drivers/sensor.h:61: SENSOR_CHAN_ACCEL_XYZ, . . . hmm, this is part of a large enumeration:

 53 enum sensor_channel {
 54         /** Acceleration on the X axis, in m/s^2. */
 55         SENSOR_CHAN_ACCEL_X,
 56         /** Acceleration on the Y axis, in m/s^2. */
 57         SENSOR_CHAN_ACCEL_Y,
 58         /** Acceleration on the Z axis, in m/s^2. */
 59         SENSOR_CHAN_ACCEL_Z,
 60         /** Acceleration on the X, Y and Z axes. */
 61         SENSOR_CHAN_ACCEL_XYZ,
 62         /** Angular velocity around the X axis, in radians/s. */
 63         SENSOR_CHAN_GYRO_X,
 64         /** Angular velocity around the Y axis, in radians/s. */
 65         SENSOR_CHAN_GYRO_Y,
 66         /** Angular velocity around the Z axis, in radians/s. */
 67         SENSOR_CHAN_GYRO_Z,
 68         /** Angular velocity around the X, Y and Z axes. */
 69         SENSOR_CHAN_GYRO_XYZ,
   .
   .
   .

^ edit point

ted@localhost:~/projects/embedded/ncs/zephyr/samples/z--sandbox/blink-plus-uart/build/zephyr/boards/arm/sparkfun_thing_plus_nrf9160$ nm -s libboards__arm__sparkfun_thing_plus_nrf9160.a 

board.c.obj:
0000002c t $d
00000000 r $d
00000000 t $t
00000001 t board_sparkfun_thing_plus_nrf9160_init
00000000 r __init_sys_init_board_sparkfun_thing_plus_nrf9160_init0
         U z_impl_device_get_binding
ted@localhost:~/projects/embedded/ncs/zephyr/samples/z--sandbox/blink-plus-uart/build/zephyr/boards/arm/sparkfun_thing_plus_nrf9160$


File noted:

ted@localhost:~/projects/embedded/ncs/zephyr/samples/z--sandbox/blink-plus-uart/build/zephyr/dev_handles.c
ted@localhost:~/projects/embedded/ncs/zephyr/samples/z--sandbox/blink-plus-uart/build/zephyr$ ls *.c
dev_handles.c  isr_tables.c
ted@localhost:~/projects/embedded/ncs/zephyr/samples/z--sandbox/blink-plus-uart/build/zephyr$ ls *.h
ls: cannot access '*.h': No such file or directory

Example project build directory noted, this dir auto-gen'd:

ted@localhost:~/projects/embedded/ncs/zephyr/samples/z--sandbox/blink-plus-uart/build/zephyr$ ls include/generated/
app_data_alignment.ld  autoconf.h            driver-validation.h  otype-to-size.h     snippets-ram-sections.ld  snippets-sections.ld  version.h
app_smem_aligned.ld    device_extern.h       kobj-types-enum.h    otype-to-str.h      snippets-rodata.ld        syscall_dispatch.c
app_smem.ld            devicetree_fixups.h   ncs_version.h        pm_config.h         snippets-rom-start.ld     syscall_list.h
app_smem_unaligned.ld  devicetree_unfixed.h  offsets.h            snippets-noinit.ld  snippets-rwdata.ld        syscalls


^ Kionix KX132

Useful datasheets and documents for Kionix accelerometer KX132-1211:


^ LIS2DH driver files

Beginning with lis2dh_i2c.c, here we examine where Zephyr driver files for this accelerometer are located in Zephyr's source file project space. We'll also look at how routines and pound defines are factored across these files. Beginning with lis2dh_i2c.c file and its final twenty or so lines, we have a specific sensor API routine, a structure which entails function pointers to all the LIS2DH driver routines in this source file, and finally an initializing function for this driver:

static int lis2dh_i2c_update_reg(const struct device *dev, uint8_t reg_addr,
                                  uint8_t mask, uint8_t value)
{
        struct lis2dh_data *data = dev->data;
        const struct lis2dh_config *cfg = dev->config;

        return i2c_reg_update_byte(data->bus,
                                   cfg->bus_cfg.i2c_slv_addr,
                                   reg_addr, mask, value);
}

static const struct lis2dh_transfer_function lis2dh_i2c_transfer_fn = { 
        .read_data = lis2dh_i2c_read_data,
        .write_data = lis2dh_i2c_write_data,
        .read_reg  = lis2dh_i2c_read_reg,
        .write_reg  = lis2dh_i2c_write_reg,
        .update_reg = lis2dh_i2c_update_reg,
};

int lis2dh_i2c_init(const struct device *dev)
{
        struct lis2dh_data *data = dev->data;

        data->hw_tf = &lis2dh_i2c_transfer_fn;

        return 0;
}
#endif /* DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c) */

We also can see the closing pre-processor conditional directive, which pairs with a test at compile time which only compiles this driver API when at least one instance of the sensor is indicated in developer code:

 #if DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c)

LOG_MODULE_DECLARE(lis2dh, CONFIG_SENSOR_LOG_LEVEL);

static int lis2dh_i2c_read_data(const struct device *dev, uint8_t reg_addr,
                                 uint8_t *value, uint8_t len)
{
        struct lis2dh_data *data = dev->data;
        const struct lis2dh_config *cfg = dev->config;

   .
   .
   .
#endif /* DT_ANY_INST_ON_BUS_STATUS_OKAY(i2c) */

Ok, so already a fair amount going on here. An important point to note and remember is that routines in lis2dh_i2c.c manifest direct calls to Zephyr RTOS's I2C driver. Most likely this is the only file where, following best factoring practices, we will see and want to make direct calls to this peripheral subsystem.

Along side the calls to an I2C driver there is also a LIS2DH initialization function, lis2dh_i2c_init(const struct device *dev). This initializer takes a pointer to a Zephyr device struct instance, and it assigns a value to *dev->data->hw_tf. We may be mistaken but hw_tf probably stands for in this case "hardware transfer function" given the name of the collection of sensor API function pointers just above this assignment.

This LIS2DH Zephyr init function begins our "chain of search", to learn how developers' or app level code interfaces with Zephyr driver code.


^ Chain Of Search

Our search begins with looking for places where lis2dh_i2c_init(const struct device *dev) is called or referenced by run-time code. This token is the name of the initializing routine called when Zephyr RTOS discovers one or more LIS2DH sensors connected (cofigured to use) an I2C bus. This routine is referenced only one place, in file lis2dh.c where it is assigned to a member of a C structure which is defined via a macro. This macro's name is IS2DH_CONFIG_I2C, and this name constitutes our next search step. Search steps are captured in the table below titled "Table x - Zephyr Driver Chain of Search".

Table x - Zephyr Driver Chain of Search

ted@localhost:~/projects/embedded/ncs/zephyr$ grep -nr lis2dh_i2c_init ./*

./drivers/sensor/lis2dh/lis2dh.h:273:int lis2dh_i2c_init(const struct device *dev);
./drivers/sensor/lis2dh/lis2dh_i2c.c:86:int lis2dh_i2c_init(const struct device *dev)
Binary file ./drivers/sensor/lis2dh/.lis2dh_i2c.c.swp matches
./drivers/sensor/lis2dh/lis2dh.c:470:		.bus_init = lis2dh_i2c_init,				\
Binary file ./samples/sensor/lis2dh/build/zephyr/drivers/sensor/lis2dh/libdrivers__sensor__lis2dh.a matches
Binary file ./samples/sensor/lis2dh/build/zephyr/drivers/sensor/lis2dh/CMakeFiles/drivers__sensor__lis2dh.dir/lis2dh.c.obj matches
Binary file ./samples/sensor/lis2dh/build/zephyr/drivers/sensor/lis2dh/CMakeFiles/drivers__sensor__lis2dh.dir/lis2dh_i2c.c.obj matches
./samples/sensor/lis2dh/build/zephyr/zephyr.lst:8026:0001375c <lis2dh_i2c_init>:
./samples/sensor/lis2dh/build/zephyr/zephyr.lst:8028:int lis2dh_i2c_init(const struct device *dev)
./samples/sensor/lis2dh/build/zephyr/zephyr.lst:8034:   1375e:	4a02      	ldr	r2, [pc, #8]	; (13768 <lis2dh_i2c_init+0xc>)
./samples/sensor/lis2dh/build/zephyr/zephyr.map:2548: .text.lis2dh_i2c_init
./samples/sensor/lis2dh/build/zephyr/zephyr.map:2550:                0x000000000001375c                lis2dh_i2c_init
./samples/sensor/lis2dh/build/zephyr/zephyr_prebuilt.map:2531: .text.lis2dh_i2c_init
./samples/sensor/lis2dh/build/zephyr/zephyr_prebuilt.map:2533:                0x000000000001375c                lis2dh_i2c_init
Binary file ./samples/sensor/lis2dh/build/zephyr/zephyr.elf matches
Binary file ./samples/sensor/lis2dh/build/zephyr/zephyr_prebuilt.elf matches
Binary file ./drivers/sensor/lis2dh/.lis2dh.c.swp matches
./drivers/sensor/lis2dh/lis2dh.c:467:#define LIS2DH_CONFIG_I2C(inst)						\
./drivers/sensor/lis2dh/lis2dh.c:480:		LIS2DH_CONFIG_I2C(inst);				\

ted@localhost:~/projects/embedded/ncs/zephyr$

lis2dh_i2c_init
LIS2DH_CONFIG_I2C
LIS2DH_DEFINE_I2C
LIS2DH_DEFINE
DT_INST_FOREACH_STATUS_OKAY

lis2dh_i2c_init --> LIS2DH_CONFIG_I2C

ted@localhost:~/projects/embedded/ncs/zephyr$ grep -nr LIS2DH_CONFIG_I2C ./*
Binary file ./drivers/sensor/lis2dh/.lis2dh.c.swp matches
./drivers/sensor/lis2dh/lis2dh.c:467:#define LIS2DH_CONFIG_I2C(inst)						\
./drivers/sensor/lis2dh/lis2dh.c:480:		LIS2DH_CONFIG_I2C(inst);				\
ted@localhost:~/projects/embedded/ncs/zephyr$

LIS2DH_CONFIG_I2C --> LIS2DH_DEFINE_I2C

ted@localhost:~/projects/embedded/ncs/zephyr$ grep -nr LIS2DH_DEFINE_I2C ./*
Binary file ./drivers/sensor/lis2dh/.lis2dh.c.swp matches
./drivers/sensor/lis2dh/lis2dh.c:358: * LIS2DH_DEFINE_I2C().
./drivers/sensor/lis2dh/lis2dh.c:477:#define LIS2DH_DEFINE_I2C(inst)						\
./drivers/sensor/lis2dh/lis2dh.c:490:		    (LIS2DH_DEFINE_I2C(inst)))

LIS2DH_DEFINE_I2C --> LIS2DH_DEFINE

ted@localhost:~/projects/embedded/ncs/zephyr$ grep -nr LIS2DH_DEFINE ./*
Binary file ./drivers/sensor/lis2dh/.lis2dh.c.swp matches
./drivers/sensor/lis2dh/lis2dh.c:357: * Device creation macro, shared by LIS2DH_DEFINE_SPI() and
./drivers/sensor/lis2dh/lis2dh.c:358: * LIS2DH_DEFINE_I2C().
./drivers/sensor/lis2dh/lis2dh.c:456:#define LIS2DH_DEFINE_SPI(inst)						\
./drivers/sensor/lis2dh/lis2dh.c:477:#define LIS2DH_DEFINE_I2C(inst)						\
./drivers/sensor/lis2dh/lis2dh.c:487:#define LIS2DH_DEFINE(inst)						\
./drivers/sensor/lis2dh/lis2dh.c:489:		    (LIS2DH_DEFINE_SPI(inst)),				\
./drivers/sensor/lis2dh/lis2dh.c:490:		    (LIS2DH_DEFINE_I2C(inst)))
./drivers/sensor/lis2dh/lis2dh.c:492:DT_INST_FOREACH_STATUS_OKAY(LIS2DH_DEFINE)
ted@localhost:~/projects/embedded/ncs/zephyr$

LIS2DH_DEFINE --> DT_INST_FOREACH_STATUS_OKAY

ted@localhost:~/projects/embedded/ncs/zephyr$ grep -nr DT_INST_FOREACH_STATUS_OKAY ./*
./doc/releases/release-notes-2.4.rst:1169:* :github:`27404` - IS_ENABLED not working with C++ (was: Is DT_INST_FOREACH_STATUS_OKAY broken on v2.3?)
./doc/guides/dts/howtos.rst:479:Finally, pass the instantiation macro to :c:func:`DT_INST_FOREACH_STATUS_OKAY`:
./doc/guides/dts/howtos.rst:484:   DT_INST_FOREACH_STATUS_OKAY(CREATE_MY_DEVICE)
./doc/guides/dts/howtos.rst:486:``DT_INST_FOREACH_STATUS_OKAY`` expands to code which calls
./doc/guides/dts/howtos.rst:567:Since this style does not use ``DT_INST_FOREACH_STATUS_OKAY()``, the driver
./doc/reference/devicetree/api.rst:91::c:func:`DT_INST_FOREACH_STATUS_OKAY`, but these require ``DT_DRV_COMPAT`` to
./doc/reference/devicetree/api.rst:192:and :c:func:`DT_INST_FOREACH_STATUS_OKAY` are special-purpose helpers without
   .
   .
   .
./drivers/sensor/lis2dh/lis2dh.c:492:DT_INST_FOREACH_STATUS_OKAY(LIS2DH_DEFINE)
   .
   .
   .
./include/devicetree.h:2183: *     DT_INST_FOREACH_STATUS_OKAY(MY_FN)
./include/devicetree.h:2210:#define DT_INST_FOREACH_STATUS_OKAY(fn) \

From ./zephyr/include/devicetree.h:

2151 /**
2152  * @brief Call "fn" on all nodes with compatible DT_DRV_COMPAT
2153  *        and status "okay"
2154  *
2155  * This macro calls "fn(inst)" on each "inst" number that refers to a
2156  * node with status "okay". Whitespace is added between invocations.
2157  *
2158  * Example devicetree fragment:
2159  *
2160  *     a {
2161  *             compatible = "vnd,device";
2162  *             status = "okay";
2163  *             label = "DEV_A";
2164  *     };
2165  *
2166  *     b {
2167  *             compatible = "vnd,device";
2168  *             status = "okay";
2169  *             label = "DEV_B";
2170  *     };
2171  *
2172  *     c {
2173  *             compatible = "vnd,device";
2174  *             status = "disabled";
2175  *             label = "DEV_C";
2176  *     };
2177  *
2178  * Example usage:
2179  *
2180  *     #define DT_DRV_COMPAT vnd_device
2181  *     #define MY_FN(inst) DT_INST_LABEL(inst),
2182  *
2183  *     DT_INST_FOREACH_STATUS_OKAY(MY_FN)
2184  *
2185  * This expands to:
2186  *
2187  *     MY_FN(0) MY_FN(1)
2188  *
2189  * and from there, to either this:
2190  *
2191  *     "DEV_A", "DEV_B",
2192  *
2193  * or this:
2194  *
2195  *     "DEV_B", "DEV_A",
2196  *
2197  * No guarantees are made about the order that a and b appear in the
2198  * expansion.
2199  *
2200  * Note that "fn" is responsible for adding commas, semicolons, or
2201  * other separators or terminators.
2202  *
2203  * Device drivers should use this macro whenever possible to
2204  * instantiate a struct device for each enabled node in the devicetree
2205  * of the driver's compatible DT_DRV_COMPAT.
2206  *
2207  * @param fn Macro to call for each enabled node. Must accept an
2208  *           instance number as its only parameter.
2209  */
2210 #define DT_INST_FOREACH_STATUS_OKAY(fn) \
2211         COND_CODE_1(DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT),   \
2212                     (UTIL_CAT(DT_FOREACH_OKAY_INST_,            \
2213                               DT_DRV_COMPAT)(fn)),              \
2214                     ())

DT_INST_FOREACH_STATUS_OKAY --> DT_FOREACH_OKAY_INST_xxx . . . where 'xxx' stands for lower case


pattern present --> pattern next</code



<code>pattern present --> pattern next</code


-->



^ Chain Of Search edit point

ted@localhost:~/projects/embedded/ncs$ grep -nr LIS2DH_DEFINE ./*
./v1.4.1/zephyr/drivers/sensor/lis2dh/lis2dh.c:356: * Device creation macro, shared by LIS2DH_DEFINE_SPI() and
./v1.4.1/zephyr/drivers/sensor/lis2dh/lis2dh.c:357: * LIS2DH_DEFINE_I2C().
./v1.4.1/zephyr/drivers/sensor/lis2dh/lis2dh.c:419:#define LIS2DH_DEFINE_SPI(inst)						\
./v1.4.1/zephyr/drivers/sensor/lis2dh/lis2dh.c:437:#define LIS2DH_DEFINE_I2C(inst)						\
./v1.4.1/zephyr/drivers/sensor/lis2dh/lis2dh.c:447:#define LIS2DH_DEFINE(inst)						\
./v1.4.1/zephyr/drivers/sensor/lis2dh/lis2dh.c:449:		    (LIS2DH_DEFINE_SPI(inst)),				\
./v1.4.1/zephyr/drivers/sensor/lis2dh/lis2dh.c:450:		    (LIS2DH_DEFINE_I2C(inst)))
./v1.4.1/zephyr/drivers/sensor/lis2dh/lis2dh.c:452:DT_INST_FOREACH_STATUS_OKAY(LIS2DH_DEFINE)
Binary file ./zephyr/drivers/sensor/lis2dh/.lis2dh.c.swp matches
./zephyr/drivers/sensor/lis2dh/lis2dh.c:357: * Device creation macro, shared by LIS2DH_DEFINE_SPI() and
./zephyr/drivers/sensor/lis2dh/lis2dh.c:358: * LIS2DH_DEFINE_I2C().
./zephyr/drivers/sensor/lis2dh/lis2dh.c:456:#define LIS2DH_DEFINE_SPI(inst)						\
./zephyr/drivers/sensor/lis2dh/lis2dh.c:477:#define LIS2DH_DEFINE_I2C(inst)						\
./zephyr/drivers/sensor/lis2dh/lis2dh.c:487:#define LIS2DH_DEFINE(inst)						\
./zephyr/drivers/sensor/lis2dh/lis2dh.c:489:		    (LIS2DH_DEFINE_SPI(inst)),				\
./zephyr/drivers/sensor/lis2dh/lis2dh.c:490:		    (LIS2DH_DEFINE_I2C(inst)))
./zephyr/drivers/sensor/lis2dh/lis2dh.c:492:DT_INST_FOREACH_STATUS_OKAY(LIS2DH_DEFINE)
ted@localhost:~/projects/embedded/ncs$

From Zephyr documentation ncs/zephyr/doc/reference/devicetree/api.rst:

185 As shown above, the ``DT_INST_*`` APIs are conveniences for addressing nodes by
186 instance number. They are almost all defined in terms of one of the
187 :ref:`devicetree-generic-apis`. The equivalent generic API can be found by
188 removing ``INST_`` from the macro name. For example, ``DT_INST_PROP(inst,
189 prop)`` is equivalent to ``DT_PROP(DT_DRV_INST(inst), prop)``. Similarly,
190 ``DT_INST_REG_ADDR(inst)`` is equivalent to ``DT_REG_ADDR(DT_DRV_INST(inst))``,
191 and so on. There are some exceptions: :c:func:`DT_ANY_INST_ON_BUS_STATUS_OKAY`
192 and :c:func:`DT_INST_FOREACH_STATUS_OKAY` are special-purpose helpers without
193 straightforward generic equivalents.
194 
195 Since ``DT_DRV_INST()`` requires ``DT_DRV_COMPAT`` to be defined, it's an error
196 to use any of these without that macro defined.


^ Tracing from LIS2DH Sample Source Code

This line of code in the LIS2DH sample project appears to instantiate the code to support a present LIS2DH sensor. The macro DEVICE_DT_GET_ANY is defined in <$NCS_ROOT>/zephyr/include/device.h.

 51 void main(void)
 52 {
 53         const struct device *sensor = DEVICE_DT_GET_ANY(st_lis2dh);

Here is the definition of the macro, which looks itself to include the one that seemed to "dead end" in our search a couple sections above:

282 /**
283  * @def DEVICE_DT_GET_ANY
284  *
285  * @brief Obtain a pointer to a device object by devicetree compatible
286  *
287  * If any enabled devicetree node has the given compatible and a
288  * device object was created from it, this returns that device.
289  *
290  * If there no such devices, this returns NULL.
291  *
292  * If there are multiple, this returns an arbitrary one.
293  *
294  * If this returns non-NULL, the device must be checked for readiness
295  * before use, e.g. with device_is_ready().
296  *
297  * @param compat lowercase-and-underscores devicetree compatible
298  * @return a pointer to a device, or NULL
299  */
300 #define DEVICE_DT_GET_ANY(compat)                                           \
301         COND_CODE_1(DT_HAS_COMPAT_STATUS_OKAY(compat),                      \
302                     (DEVICE_DT_GET(DT_COMPAT_GET_ANY_STATUS_OKAY(compat))), \
303                     (NULL))

From file ncs/zephyr/doc/guides/dts/howtos.rst:

417 .. _dt-create-devices-inst:
418 
419 Option 1: create devices using instance numbers
420 ===============================================
421 
422 Use this option, which uses :ref:`devicetree-inst-apis`, if possible. However,
423 they only work when devicetree nodes for your driver's ``compatible`` are all
424 equivalent, and you do not need to be able to distinguish between them.
425 
426 To use instance-based APIs, begin by defining ``DT_DRV_COMPAT`` to the
427 lowercase-and-underscores version of the compatible that the device driver
428 supports. For example, if your driver's compatible is ``"vnd,my-device"`` in
429 devicetree, you would define ``DT_DRV_COMPAT`` to ``vnd_my_device`` in your
430 driver C file:
431 
432 .. code-block:: c
433 
434    /*
435     * Put this near the top of the file. After the includes is a good place.
436     * (Note that you can therefore run "git grep DT_DRV_COMPAT drivers" in
437     * the zephyr Git repository to look for example drivers using this style).
438     */
439    #define DT_DRV_COMPAT vnd_my_device
440 
441 .. important::
442 
443    As shown, the DT_DRV_COMPAT macro should have neither quotes nor special
444    characters. Remove quotes and convert special characters to underscores
445    when creating ``DT_DRV_COMPAT`` from the compatible property.
446 
447 Finally, define an instantiation macro, which creates each ``struct device``
448 using instance numbers. Do this after defining ``my_api_funcs``.
449 
450 .. code-block:: c
451 
452    /*
453     * This instantiation macro is named "CREATE_MY_DEVICE".
454     * Its "inst" argument is an arbitrary instance number.
455     *
456     * Put this near the end of the file, e.g. after defining "my_api_funcs".
457     */
458    #define CREATE_MY_DEVICE(inst)                                       \
459         static struct my_dev_data my_data_##inst = {                    \
460                 /* initialize RAM values as needed, e.g.: */            \
461                 .freq = DT_INST_PROP(inst, clock_frequency),            \
462         };                                                              \
463         static const struct my_dev_cfg my_cfg_##inst = {                \
464                 /* initialize ROM values as needed. */                  \
465         };                                                              \
466         DEVICE_DT_INST_DEFINE(inst,                                     \
467                               my_dev_init_function,                     \
468                               NULL,                                     \
469                               &my_data_##inst,                          \
470                               &my_cfg_##inst,                           \
471                               MY_DEV_INIT_LEVEL, MY_DEV_INIT_PRIORITY,  \
472                               &my_api_funcs);
473 
474 Notice the use of APIs like :c:func:`DT_INST_PROP` and
475 :c:func:`DEVICE_DT_INST_DEFINE` to access devicetree node data. These
476 APIs retrieve data from the devicetree for instance number ``inst`` of
477 the node with compatible determined by ``DT_DRV_COMPAT``.
478 
479 Finally, pass the instantiation macro to :c:func:`DT_INST_FOREACH_STATUS_OKAY`:
480 
481 .. code-block:: c
482 
483    /* Call the device creation macro for each instance: */
484    DT_INST_FOREACH_STATUS_OKAY(CREATE_MY_DEVICE)
485 
486 ``DT_INST_FOREACH_STATUS_OKAY`` expands to code which calls
487 ``CREATE_MY_DEVICE`` once for each enabled node with the compatible determined
488 by ``DT_DRV_COMPAT``. It does not append a semicolon to the end of the
489 expansion of ``CREATE_MY_DEVICE``, so the macro's expansion must end in a
490 semicolon or function definition to support multiple devices.
491


^ Jared Wolff Example Zephyr Driver repo

Proprietor and lead engineer of Circuitdojo, Jared Wolff has a custom Zephyr device driver underway at Github:

This driver's directory structure as of 2021-08-16 looks like:

                                                   air-quality-wing-zephyr-drivers/                         .
                                                                  |                                         .
               +------------+-----------------------+-------------+-------+-----------------+               .
               |            |                       |             |       |                 |               .
        CMakeLists.txt      |                       |             |       |                 |               .
        Kconfig             |                       |             |       |                 |               .
        README.md        drivers/                  dts/        include/  lib/             zephyr/           .
                            |                       |             |       |                 |               .
      +------------+--------+-------+              bindings/    aqw.h    CMakeLists.txt   module.yml        .
      |            |                |               |                    Kconfig                            .
CMakeLists.txt   sgp40/           shtc3/            |                    aqw.c                              .
Kconfig            |                |               |                                                       .
                 CMakeLists.txt   CMakeLists.txt   sensirion,sgp40yaml                                      .
                 Kconfig          Kconfig          sensirion,shtc3.yaml                                     .
                 sgp40.c          shtc3.c                                                                   .
                 sgp40.h          shtc3.h                                                                   .


^ Zephyr Driver versus Zephyr Module

Interesting call for help from one Thomas Oggiers and responses from one Simon and another person and collaborator tejlmand:

^ Zephyr Standard Files to Amend for New Driver

Starting review of Zephyr driver files in <code>~/projects/embedded/ncs/zephyr/drivers/sensor, file Kconfig is about 215 lines long and appears to source (has stanzas beginning with 'source') for each extent driver. First few drivers sourced in this Kconfig file are:

39 comment "Device Drivers"
 40 
 41 source "drivers/sensor/adt7420/Kconfig"
 42 
 43 source "drivers/sensor/adxl345/Kconfig"
 44 
 45 source "drivers/sensor/adxl362/Kconfig"
 46 
 47 source "drivers/sensor/adxl372/Kconfig"
 48 
 49 source "drivers/sensor/ak8975/Kconfig"
 50 
 51 source "drivers/sensor/amg88xx/Kconfig"
 52 
 53 source "drivers/sensor/ams_iAQcore/Kconfig"

Similarly so for the CMakeLists.txt file here.


- - - top of page - - -