[RFC PATCH 13/15] doc: dm: Document uclass adjunct support

Simon Glass sjg at chromium.org
Thu Mar 19 22:35:43 CET 2026


Add a section to the driver model design document describing uclass
adjuncts: what problem they solve, how to register them, and the key
design decisions (probe-time registration, passive lifecycle, shared
iteration lists, automatic data allocation).

Signed-off-by: Simon Glass <sjg at chromium.org>
---

 doc/develop/driver-model/design.rst | 134 ++++++++++++++++++++++++++++
 1 file changed, 134 insertions(+)

diff --git a/doc/develop/driver-model/design.rst b/doc/develop/driver-model/design.rst
index 633545944d1..f31d63bf026 100644
--- a/doc/develop/driver-model/design.rst
+++ b/doc/develop/driver-model/design.rst
@@ -1101,6 +1101,140 @@ While the current implementation is quite simple, it will get evolved
 as the feature is more extensively used in U-Boot subsystems.
 
 
+Uclass Adjuncts
+---------------
+
+In U-Boot's driver model, each device belongs to exactly one uclass. When
+hardware exposes multiple types of functionality (e.g. a Qualcomm clock
+controller that also provides resets and power domains, or a PMIC that also
+provides sysreset), drivers must work around this by creating child devices
+in different uclasses via ``device_bind_driver_to_node()``. This creates
+separate ``U_BOOT_DRIVER`` declarations, ``dev_get_priv(dev->parent)``
+indirection, and scaffolding that has no counterpart in Linux.
+
+Uclass adjuncts (``CONFIG_DM_UC_ADJUNCT``) allow a device to register in
+additional uclasses without creating child devices. Each adjunct carries its
+own ops pointer, per-device uclass private data, and sequence number, just
+like a primary uclass membership.
+
+Adjuncts are declared statically in the driver using a sentinel-terminated
+array of ``struct driver_adjunct``::
+
+   static const struct driver_adjunct my_adjuncts[] = {
+       { UCLASS_RESET, &my_reset_ops },
+       { UCLASS_POWER_DOMAIN, &my_pd_ops },
+       { }
+   };
+
+   U_BOOT_DRIVER(my_driver) = {
+       .name     = "my_driver",
+       .id       = UCLASS_CLK,
+       .adjuncts = my_adjuncts,
+       ...
+   };
+
+Driver model registers static adjuncts automatically at bind time and
+allocates their private data at probe time. Adjuncts may also be
+registered dynamically using ``device_add_uclass_adj()`` for cases where
+membership is conditional (e.g. based on device-tree properties)::
+
+   static int my_driver_probe(struct udevice *dev)
+   {
+       if (dev_read_bool(dev, "has-reset"))
+           device_add_uclass_adj(dev, UCLASS_RESET, &my_reset_ops);
+
+       return 0;
+   }
+
+The device then appears in lookup functions for each adjunct uclass (e.g.
+``uclass_find_device_by_ofnode()`` for ``UCLASS_RESET`` finds this device).
+Subsystem code retrieves the adjunct ops with ``device_get_uclass_ops()``::
+
+   const struct reset_ops *ops = device_get_uclass_ops(dev, UCLASS_RESET);
+
+``device_add_uclass_adj()`` binds the adjunct (allocating uclass platform
+data and a sequence number) and, if the device is already probed, also
+allocates uclass private data. Per-device uclass platform and private data
+are allocated automatically if the adjunct's uclass driver requests them
+via ``per_device_plat_auto`` / ``per_device_auto``. Sequence numbers are
+allocated from the adjunct uclass's namespace to avoid collisions.
+
+Adjunct uclasses participate in lifecycle hooks: ``post_bind`` is called
+when the adjunct is bound, ``post_probe`` when private data is allocated,
+``pre_remove`` when private data is freed, and ``pre_unbind`` when the
+adjunct is torn down. Child hooks (``child_post_bind``, etc.) do not apply
+to adjuncts since they do not change the parent-child relationship.
+
+At remove time, ``device_remove_adjs()`` calls ``pre_remove`` and frees only
+the uclass private data; the adjunct remains bound.
+``device_unbind_adjs()`` at unbind time calls ``pre_unbind`` and fully
+tears down the adjunct memberships.
+
+Primary and adjunct entries share the same ``dev_head`` list on each
+uclass. Adjuncts are full members of their uclass: they have sequence
+numbers, appear in iteration, and are found by lookup functions. This
+means a lookup may return a device whose primary uclass differs from the
+one searched. This is by design: the device genuinely provides that
+uclass's functionality.
+
+All ``uclass_`` lookup and iteration functions include adjuncts:
+
+- Attribute lookups: ``uclass_find_device_by_ofnode()``,
+  ``uclass_find_device_by_name()``, ``uclass_find_device_by_seq()``,
+  ``uclass_find_device_by_phandle()``, ``uclass_get_device_by_driver()``
+- Index-based: ``uclass_find_device()``, ``uclass_id_count()``
+- Iteration: ``uclass_foreach_dev()``, ``uclass_foreach_dev_safe()``,
+  ``uclass_foreach_dev_probe()``
+
+The only exceptions are ``uclass_find_first_device()`` and
+``uclass_find_next_device()``, which use ``struct udevice *`` as the
+iteration cursor. For an adjunct entry, ``dev->ucm.uc_node`` points into
+the primary uclass's list rather than the adjunct uclass's, so there is
+no way to advance to the next member. The following functions all skip
+adjuncts for this reason:
+
+- ``uclass_find_first_device()`` / ``uclass_find_next_device()``
+- ``uclass_first_device()`` / ``uclass_next_device()``
+- ``uclass_first_device_err()`` / ``uclass_next_device_err()``
+- ``uclass_first_device_check()`` / ``uclass_next_device_check()``
+- ``uclass_probe_all()``
+
+Code that needs to iterate all members including adjuncts should use
+``uclass_foreach_dev()`` or the ``ucm_`` API instead.
+
+The ``ucm_`` API provides adjunct-aware iteration and lookup using
+``struct uclass_member *`` as the cursor. Since the cursor sits directly on
+the uclass's member list, it can traverse all entries including adjuncts
+without ambiguity. The API mirrors the existing ``uclass_`` iterator
+functions:
+
+- ``ucm_find_first()`` / ``ucm_find_next()`` -- iterate without probing
+- ``ucm_first()`` / ``ucm_next()`` -- probe and skip failures
+- ``ucm_first_check()`` / ``ucm_next_check()`` -- probe and report errors
+- ``ucm_first_drvdata()`` -- find by driver data
+- ``ucm_try_first()`` -- no probe, NULL on missing uclass
+- ``ucm_find_seq()`` -- find by sequence number
+- ``ucm_find_by_ofnode()`` -- find by device-tree node
+
+Use ``ucm->dev`` to obtain the device from a member pointer.
+
+``device_get_uclass_ops()`` resolves ops for a given uclass ID, searching
+both primary and adjunct memberships. ``device_get_uclass_priv()``
+and ``device_get_uclass_plat()`` do the same for per-device uclass
+private and platform data. Subsystem code should use these instead of the
+plain ``dev_get_uclass_*()`` accessors when the device may have been found
+via an adjunct lookup. When ``CONFIG_DM_UC_ADJUNCT`` is disabled, these map
+to the plain accessors with no overhead.
+
+``dev_get_priv()`` and ``dev_get_plat()`` always return the driver's own
+data, regardless of which uclass the device was found through. This is one
+of the main benefits of adjuncts over child devices: no
+``dev_get_parent()`` indirection is needed.
+
+When ``CONFIG_DM_UC_ADJUNCT`` is disabled, all adjunct functions are
+stubbed as inlines so callers do not need ``#ifdef`` guards.
+
+
 Changes since v1
 ----------------
 
-- 
2.43.0



More information about the U-Boot mailing list