![]() |
RT-Thread RTOS
An open source embedded real-time operating system
|
Most MMIO / on-SoC peripherals under **RT_USING_DM** bind through the platform bus: a **rt_platform_driver** matches a **rt_platform_device**, then **probe** runs after the bus has applied common setup (pinctrl, clocks, power domain, IOMMU).
components/drivers/include/drivers/platform.h**components/drivers/core/platform.c**components/drivers/core/platform_ofw.c**In **probe**, almost all DM helpers take **struct rt_device *dev = &pdev->parent**.
Init levels (linker sort key, see **rtdef.h**):
| Level | Typical use |
|---|---|
**INIT_CORE_EXPORT** ("1.0") | **platform_bus_init** — **rt_bus_register(&platform_bus)** |
**INIT_SUBSYS_EXPORT** ("1.1") | Early drivers: manual **rt_platform_driver_register()** (e.g. **pin-pl061**, **pinctrl-single**) |
**INIT_PLATFORM_EXPORT** ("1.2") | **platform_ofw_device_probe** — walk DT, **rt_platform_device_register** |
**INIT_DEVICE_EXPORT** ("3") | **RT_PLATFORM_DRIVER_EXPORT** — registers driver, then probes all devices already on the bus |
Takeaway: DT devices appear at **INIT_PLATFORM**. Drivers using **RT_PLATFORM_DRIVER_EXPORT** register later and then bind via **rt_bus_add_driver -> rt_bus_for_each_dev**. Drivers that must run before the bulk DT scan use **INIT_SUBSYS_EXPORT** + manual **rt_platform_driver_register**.
Macro expansion (**driver.h**):
Requirements:
name**: driver name string — used for name-based match (see below); keep unique.ids**: optional — **compatible** table for DT match; omit or set **RT_NULL** when using name-only binding.probe**: return **RT_EOK** on success, negative rt_err_t on failure.remove**: optional but recommended if **probe** allocates resources.**platform_match** tries both (in order):
| Step | Condition | Match when |
|---|---|---|
| 1 | **dev->ofw_node** set and **pdrv->ids** non-NULL | **compatible** matches an entry in **ids** → **pdev->id** points at that entry |
| 2 | **pdev->name** and **pdrv->name** both set | Strings equal (same pointer or **rt_strcmp == 0**) |
Implications:
INIT_PLATFORM**): normally matched by **ids** / **compatible**. This is what most drivers use.ids: set **.ids = RT_NULL** (or omit the field) and match by **name** — typical when the device is created in board code with **rt_platform_device_alloc("same-string")** and **pdev->name** equals **pdrv->name**.pdev->name** is a non-empty string).Name-only driver + device:
Used when the driver must register before **INIT_PLATFORM** (dependency ordering) or the file predates the export macro:
Same **struct rt_platform_driver** layout as method A.
pdrv->parent.bus = &platform_bus**pdrv->name** into **pdrv->parent.parent.name**rt_driver_register** → **rt_bus_add_driver**rt_bus_for_each_dev** — for each device without **dev->drv**, **platform_match**; on match, **platform_probe**Registering the driver never creates devices; it only enables binding.
**platform_ofw_device_probe** walks the tree (root, **/clocks**, **/firmware**, chosen framebuffer, …). For each available child with **compatible** (or a valid node name):
alloc_ofw_platform_device(np)** — **rt_platform_device_alloc**, **pdev->parent.ofw_node = np**, **np->dev = &pdev->parent**rt_platform_device_register(pdev)** → **rt_bus_add_device** → try all driversBus nodes (simple-bus, simple-mfd, arm,amba-bus, …): the walker recurses into children first without instantiating a device for the bus node itself (unless something else requests it).
Device naming: **ofw_device_rename** — e.g. **fe300000.serial:uart0** from **reg** + node name + alias.
Called when another driver needs a provider (clock, gpio, pinctrl, regulator, phye, …) before or without global probe:
np->dev | Action |
|---|---|
| NULL | Allocate platform device, register on bus → match + probe |
Set, **dev->drv NULL** | **rt_bus_reload_driver_device** — retry match |
Set, **dev->drv set** | **RT_EOK** (already probed) |
Rare on DM + DT boards; used for legacy or test bring-up.
Registers one node as a platform device when it is not under **/** and not already **RT_OFW_F_PLATFORM**. Used by umbrella drivers that expose children explicitly.
See Matching: ids vs name above. Summary:
rt_ofw_node_match(np, pdrv->ids)** when **ids** is set — **pdev->id** → use **pdev->id->data** for SoC variants.pdev->name** vs **pdrv->name** — works without **ids**; primary path for manually registered devices.DT enumeration sets **pdev->name** from **rt_platform_device_alloc("")** (empty); binding those nodes still relies on **compatible** unless board code assigns a non-empty **pdev->name** that matches the driver.
Only one driver binds per device. No match → device stays on **dev_list** until a driver registers later.
The bus runs before **pdrv->probe(pdev)**:
| Step | Function | Notes |
|---|---|---|
| 1 | **rt_pin_ctrl_confs_apply_by_name(dev, NULL)** / **pinctrl-0** | Pin control (pinctrl) |
| 2 | **rt_ofw_clk_set_defaults(dev->ofw_node)** | Assigned clocks |
| 3 | **rt_dm_power_domain_attach(dev, RT_TRUE)** | **-RT_EEMPTY** OK |
| 4 | **rt_iommu_attach(dev)** | **-RT_EEMPTY** OK if no IOMMU |
| 5 | **pdrv->probe(pdev)** | Your driver |
On **probe failure**: IOMMU detach, power domain detach, **dev->drv cleared**.
On success: **RT_OFW_F_READLY** set on the OFW node.
Your **probe** should: parse resources (**rt_dm_dev_iomap**, **rt_dm_dev_get_irq**, **rt_clk_get_by_name**, …) → enable hardware → register the functional **rt_device** (UART, pin, LED, …). Return negative **rt_err_t** on hard errors and free what you already allocated.
**remove / shutdown**: bus calls **pdrv->remove**, then **rt_iommu_detach**, **rt_dm_power_domain_detach**, **rt_platform_ofw_free(pdev)** (drops OFW node ref, **rt_free(pdev)**).
| Field | Use |
|---|---|
**&pdev->parent** | Pass to **rt_dm_dev_***, **rt_pin_get_named_pin**, **rt_ofw_*** |
**pdev->id** | After match: SoC variant via **pdev->id->data** (see **pinctrl-single** **pcs_ofw_ids**) |
**pdev->priv** | Optional; core does not allocate it — prefer embedding **rt_platform_device** in your private struct or **rt_dm_dev_bind_fwdata** |
**pdev->parent.ofw_node** | DT node for this instance |
Common pattern: embed **struct rt_platform_device** as not used — instead embed nothing and use **struct rt_device *dev = &pdev->parent** only; provider drivers embed **struct rt_device_pin parent** etc.
Device tree:
Driver:
**INIT_PLATFORM** creates the device from DT; **INIT_DEVICE** registers the driver and **probe** runs. No **rt_platform_device_register** in board code.
No **compatible** binding — driver and device share the same **name** string:
Use for legacy BSP devices not described in DT, or tests. On DM + full DT boards, prefer example A.
| Use platform | Use another bus |
|---|---|
SoC DT node with **compatible**, **reg**, **interrupts** | PCI endpoint → **rt_pci_driver** |
| GPIO LED, UART, GPIO controller, syscon, firmware node | I2C sensor → **rt_i2c_driver** + client |
Provider requested via **rt_platform_ofw_request** | SPI slave → **rt_spi_driver** |
Platform is the default for on-chip devices described in the board DTS.
| Issue | Mitigation |
|---|---|
**compatible typo** | No match on DT path; device never probes — check logs |
| Name mismatch | **pdev->name** must equal **pdrv->name**; empty **pdev->name** does not match |
| Expect name match on DT node | DT devices need **ids** unless you set **pdev->name** explicitly |
| Driver after device is OK | **RT_PLATFORM_DRIVER_EXPORT** relies on **rt_bus_add_driver** re-walk |
| Driver too late for early deps | Use **INIT_SUBSYS_EXPORT** or split provider driver earlier |
**probe success but no driver** | Returning **RT_EOK** without work — still marks node **READLY** |
Missing **remove** | OFW node freed by bus; leaks if you allocated IRQ/DMA without teardown |
| Provider not probed | **rt_platform_ofw_request(np)** from consumer before using phandle |
**pdev->priv unset** | Do not assume bus sets it; use **rt_calloc** / **bind_fwdata** |
Console not ready in early **probe** | **INIT_DEVICE** is late; use **LOG_D** or defer prints |
| Re-probe | **rt_bus_reload_driver_device** when driver registers after deferred provider |
platform_probe** before driver **probe**rt_dm_dev_iomap**, clocks, IRQdocumentation/6.components/device-driver/core/bus.md — generic **rt_bus** / **RT_DRIVER_EXPORT**components/drivers/core/platform.c, platform_ofw.c