![]() |
RT-Thread RTOS
An open source embedded real-time operating system
|
When **RT_USING_DM** and **RT_USING_SPI** are enabled, RT-Thread adds an SPI-specific rt_bus (name = "spi") on top of the core DM layer (Bus and driver binding (core)). Bus controllers register as **RT_Device_Class_SPIBUS** devices (spi0, spi1, …); slave drivers bind as **struct rt_spi_device** instances discovered from device-tree children.
Legacy manual attach (rt_spi_bus_attach_device, rt_hw_spi_device_attach, GPIO **user_data**) remains valid — see SPI device. This page covers DT scan, **spi_device_ofw_parse**, and **RT_SPI_DRIVER_EXPORT**.
Sources:
| File | Role |
|---|---|
**dev_spi_bus.c** | **struct rt_bus spi_bus**, match/probe, **spi_bus_scan_devices** |
**dev_spi_dm.c** | **spi_device_ofw_parse** |
**dev_spi_core.c** | **spi_bus_register** → scan + CS GPIO from DT |
**dev_spi.h** | **rt_spi_driver**, **RT_SPI_DRIVER_EXPORT** |
**dev_spi_dm.h** | Parse / scan declarations |
| Option | Role |
|---|---|
**RT_USING_SPI** | Core (dev_spi_core.c, dev_spi.c, …) |
**RT_USING_DM** | Adds **dev_spi_bus.c** + **dev_spi_dm.c** |
**RT_USING_OFW** | Required for **spi_bus_scan_devices** and **spi_device_ofw_parse** |
**RT_USING_QSPI** | QSPI bus mode **RT_SPI_BUS_MODE_QSPI**, alloc **rt_qspi_device** in scan |
**SOC_DM_SPI_DIR** | BSP SoC SPI/QSPI platform drivers |
Without **RT_USING_DM**, **struct rt_spi_device** has no **name / id / ofw_id / chip_select[]** — use manual attach only.
Two layers:
| Layer | Object | Example |
|---|---|---|
| SPI bus | **struct rt_spi_bus** | Hardware adapter; **ops->configure**, **ops->xfer**, mutex **lock** |
| DM SPI bus | **struct rt_bus spi_bus** | Binds **rt_spi_driver** ↔ **rt_spi_device** |
The platform bus probes the controller; subsystem **spi** bus probes children under that controller’s OFW node.
**INIT_CORE_EXPORT(spi_bus_init)** → **rt_bus_register(&spi_bus)**.
| Callback | Behavior |
|---|---|
**spi_match** | 1) **driver->ids[].name** vs **device->name** (DT node name). 2) Else **rt_ofw_node_match(ofw_node, driver->ofw_ids)**. |
**spi_probe** | Requires **device->bus**; **driver->probe(device)**; then **cs_pin = bus->cs_pins[chip_select[0]]** if controller listed **cs GPIOs**; if **parent.type == RT_Device_Class_Unknown**, **rt_spidev_device_init** registers a **RT_Device_Class_SPIDevice** for **rt_device_find**. |
**spi_remove / spi_shutdown** | Driver hook, **rt_free(device)**. |
Driver registration:
Client registration:
| Property (controller) | Role |
|---|---|
**#address-cells / #size-cells** | Standard SPI child addressing |
**cs-gpios** | Optional; parsed via **rt_pin_get_named_pin(&bus->parent, "cs", i, …)** into **bus->cs_pins[]** |
**num-cs** (driver-specific) | Some BSPs set **bus->num_chipselect** |
| Property | Role |
|---|---|
**compatible** | Required for scan (children without it are skipped) |
**reg** | Chip select index(es) → **chip_select[]** (up to **RT_SPI_CS_CNT_MAX**) |
**spi-max-frequency** | **config.max_hz** |
**spi-cpol**, **spi-cpha**, **spi-cs-high**, **spi-lsb-first**, **spi-3wire** | **config.mode** bits |
**spi-tx-bus-width**, **spi-rx-bus-width** | Dual/quad line width hints (DM **data_width_tx/rx**) |
**spi-cs-setup-delay-ns**, **spi-cs-hold-delay-ns**, **spi-cs-inactive-delay-ns** | **rt_spi_delay** on device |
**spi_device_ofw_parse** (dev_spi_dm.c) fills **struct rt_spi_configuration** and CS indices before **rt_spi_device_register**.
Called from **spi_bus_register** when **RT_USING_DM** and **bus->parent.ofw_node** is set.
For each available child with **compatible**:
struct rt_spi_device** or **struct rt_qspi_device** from **bus->mode**.spi_dev->bus = bus**, **name = rt_ofw_node_name**, **parent.ofw_node = child**.rt_dm_dev_set_name(..., rt_ofw_node_full_name)** (full path string).spi_device_ofw_parse(spi_dev)** — on error, skip device.rt_spi_device_register(spi_dev)** → match + probe.Order: Controller **probe** should run before client drivers need the bus; **RT_SPI_DRIVER_EXPORT** drivers link at **INIT_DEVICE_EXPORT**. If a driver registers before the bus exists, no auto re-scan — ensure bus registers first (typical platform init order).
Pattern (SoC-specific sources under **SOC_DM_SPI_DIR** or BSP):
| Step | API |
|---|---|
| Resources | **rt_dm_dev_iomap**, **rt_dm_dev_get_irq**, **rt_clk_*** (Clock framework (CLK)) |
| Pins | **cs-gpios** on controller → automatic in **spi_bus_register**; **pinctrl-*** on controller (Pin control (pinctrl)) |
| Publish | **rt_spi_bus_register** or **rt_qspi_bus_register** — triggers scan |
Bit-bang: **rt_spi_bit_add_bus** → same **rt_spi_bus_register** path.
| Field / API | Role |
|---|---|
**device->bus** | Parent adapter — required (**-RT_EINVAL** in **spi_probe** if NULL) |
**device->chip_select[0]** | From DT **reg**; used with **bus->cs_pins[]** |
**device->cs_pin** | Set in **spi_probe** from GPIO table or **PIN_NONE** |
**rt_spi_device_id_data(device)** | **id->data** or **ofw_id->data** |
Built-in **spidev_driver** matches known **compatible** strings and DT node names (table in **dev_spi.c**). After **spidev_probe**, **spi_probe** may call **rt_spidev_device_init** so users get **rt_device_find("spi1_0")**-style names (**s_d**, bus name + CS).
Note: Nodes whose **compatible** is exactly **"spidev"** are rejected in **spidev_probe** — use a vendor entry from the table (e.g. **rockchip,spidev** is listed but contains **spidev** substring — the check uses **rt_dm_dev_prop_index_of_string(..., "spidev")** which may match **rockchip,spidev** — that would fail! Line 175-179 checks index of "spidev" in compatible - rockchip,spidev would match and return error. That might be intentional to block linux spidev nodes.
DM only affects how devices appear. Data path is still:
Bus mutex: **rt_spi_take_bus** / **rt_spi_release_bus**. Do not call transfer APIs from ISR (they take mutex).
| Topic | With **RT_USING_DM** | Without DM |
|---|---|---|
| Device struct | **rt_spi_device** + **rt_device parent**, OFW fields | **bus**, **config**, **cs_pin**, **user_data** |
| Binding | **RT_SPI_DRIVER_EXPORT** + DT scan | **rt_spi_bus_attach_device**, manual **cs_pin** |
| Bus register | **spi_bus_scan_devices**, **cs-gpios** parse | No scan |
| Config from DT | **spi_device_ofw_parse** | **rt_spi_configure** in init code |
probe** → resources → **bus->parent.ofw_node** → **rt_spi_bus_register**.#address-cells / #size-cells**, **cs-gpios** if needed; child **reg** + **compatible** + **spi-max-frequency**.ofw_ids** (+ optional **ids** by node name), **RT_SPI_DRIVER_EXPORT**, **probe** uses **device->bus** and **rt_spi_*** APIs.cs-gpios** + child **reg**; legacy boards can still pass GPIO in **rt_spi_bus_attach_device** **user_data** without DM.spi_remove** frees scanned devices when bus unregisters (verify BSP order).device->bus == NULL** → probe fails — scan did not run or manual device missing **bus**.compatible on child — skipped by scan silently.reg (CS index) — probe OK, wrong chip selected on wire.ids[].name** must match DT node name, not **compatible** — prefer **ofw_ids**.bus->mode**; mismatch causes alloc/assert issues.rt_spi_transfer**, attach API, QSPI, examplesrt_bus**RT_PLATFORM_DRIVER_EXPORT**cs-gpios**components/drivers/include/drivers/dev_spi.hcomponents/drivers/spi/dev_spi_bus.c, dev_spi_core.c, dev_spi_dm.c