![]() |
RT-Thread RTOS
An open source embedded real-time operating system
|
When **RT_USING_DM** and **RT_USING_I2C** are enabled, RT-Thread adds an I2C-specific rt_bus (name = "i2c") on top of the core DM bus layer (Bus and driver binding (core)). Bus controllers still register as **RT_Device_Class_I2CBUS** devices (i2c0, i2c1, …); slave drivers bind as **struct rt_i2c_client** devices discovered from device tree children.
Legacy manual usage (rt_device_find("i2c1") + rt_i2c_transfer) remains valid — see I2C Bus Device. This page covers automatic DT binding and **RT_I2C_DRIVER_EXPORT**.
Sources:
| File | Role |
|---|---|
**dev_i2c_bus.c** | **struct rt_bus i2c_bus**, match/probe, **i2c_bus_scan_clients** |
**dev_i2c_dm.c** | **i2c_timings_ofw_parse** |
**dev_i2c_core.c** | Calls **i2c_bus_scan_clients** after **rt_i2c_bus_device_register** |
**dev_i2c.h** | **rt_i2c_client**, **rt_i2c_driver**, **RT_I2C_DRIVER_EXPORT** |
**dev_i2c_dm.h** | **struct i2c_timings**, scan/timings declarations |
| Option | Role |
|---|---|
**RT_USING_I2C** | Core I2C stack (dev_i2c_core.c, dev_i2c_dev.c, …) |
**RT_USING_DM** | Adds **dev_i2c_bus.c** + **dev_i2c_dm.c** (I2C bus + OFW scan) |
**RT_USING_OFW** | Required for **i2c_bus_scan_clients** and **i2c_timings_ofw_parse** |
**SOC_DM_I2C_DIR** | BSP SoC adapters (e.g. i2c-rk3x.c) |
Without **RT_USING_DM**, **struct rt_i2c_client** has no embedded **rt_device** — only **bus** + **client_addr**.
Two bus layers:
| Layer | Object | Example |
|---|---|---|
| I2C bus device | **struct rt_i2c_bus_device** | Hardware adapter; **master_xfer**, mutex **lock** |
| DM I2C bus | **struct rt_bus i2c_bus** | Binds **rt_i2c_driver** ↔ **rt_i2c_client** |
Core DM **platform** bus probes the controller; subsystem **i2c** bus probes clients under that controller’s OFW node.
Registered at boot: **INIT_CORE_EXPORT(i2c_bus_init)** → **rt_bus_register(&i2c_bus)**.
| Callback | Behavior |
|---|---|
**i2c_match** | 1) Match **driver->ids[].name** to **client->name** (from DT node name). 2) Else **rt_ofw_node_match(client->ofw_node, driver->ofw_ids)**. Sets **client->id** or **client->ofw_id**. |
**i2c_probe** | Requires **client->bus**; calls **driver->probe(client)**. |
**i2c_remove / i2c_shutdown** | Forward to driver hooks. |
Driver registration:
Sets **driver->parent.bus = &i2c_bus** then **rt_driver_register**.
Client registration:
| Property | Role |
|---|---|
**clock-frequency** | Parsed into **struct i2c_timings.bus_freq_hz** |
**i2c-scl-rising-time-ns**, **i2c-scl-falling-time-ns**, … | Optional timing cells — **i2c_timings_ofw_parse** |
**#address-cells = <1>**, **#size-cells = <0>** | Standard I2C child addressing |
Child reg | 7-bit (or 10-bit) slave address → **client->client_addr** |
Called automatically when **rt_i2c_bus_device_register** succeeds (**RT_USING_DM** only).
For each available child of **bus->parent.ofw_node**:
compatible**, use it as client node; else look one level deeper (i2c-mux style).rt_calloc** **struct rt_i2c_client**.reg** → **client_addr**.client->parent.ofw_node**, **client->name = rt_ofw_node_name**, **client->bus = bus**.rt_dm_dev_set_name(&client->parent, "%s", name)**.rt_i2c_device_register(client)** → triggers **i2c_match** + **probe** if a driver is already registered.Order note: If the client driver registers before the bus, clients are not scanned yet. Typically controller probe runs first (platform init), then scan instantiates clients while I2C drivers are already linked via **RT_I2C_DRIVER_EXPORT**. If a driver loads late, you may need manual **rt_i2c_device_register** or re-scan (not provided in-tree — ensure init order).
Pattern from SoC drivers (e.g. **bsp/rockchip/dm/i2c/i2c-rk3x.c**):
| Step | API |
|---|---|
| Timings | **i2c_timings_ofw_parse(np, &timings, use_defaults)** |
| Resources | **rt_dm_dev_iomap**, **rt_dm_dev_get_irq**, **rt_clk_*** (Clock framework (CLK)) |
| Naming | **rt_dm_dev_set_name** / **rt_dm_dev_set_name_auto** → **i2c0**, **i2c1**, … |
| Publish bus | **rt_i2c_bus_device_register(&i2c->parent, name)** — triggers client scan |
**rt_i2c_bus_device** embeds **struct rt_device parent**; **user_data** on that device points at the full adapter struct (controller private data).
Bit-bang buses (**RT_USING_I2C_BITOPS**) call **rt_i2c_bit_add_bus** → same **rt_i2c_bus_device_register** path.
| Field / API | Role |
|---|---|
**client->bus** | Parent adapter — must be set (scan sets it; manual clients must assign). |
**client->client_addr** | From DT **reg** (scan) or driver setup. |
**client->parent.ofw_node** | Child DT node for **rt_dm_dev_get_irq** etc. |
**rt_i2c_client_id_data(client)** | **id->data** or **ofw_id->data** from match table |
After **probe**, many drivers register a functional device (RTC, sensor, regulator) on top of the client, using **client->parent** only as the DM binding handle.
Defined in **dev_i2c_dm.h**. **i2c_timings_ofw_parse(dev_np, timings, use_defaults)** reads:
| DT property | Field |
|---|---|
**clock-frequency** | **bus_freq_hz** (default 100 kHz if missing and **use_defaults**) |
**i2c-scl-rising-time-ns** | **scl_rise_ns** |
**i2c-scl-falling-time-ns** | **scl_fall_ns** |
**i2c-scl-internal-delay-ns** | **scl_int_delay_ns** |
**i2c-sda-falling-time-ns** | **sda_fall_ns** |
**i2c-sda-hold-time-ns** | **sda_hold_ns** |
**i2c-digital-filter-width-ns** | **digital_filter_width_ns** |
**i2c-analog-filter-cutoff-frequency** | **analog_filter_cutoff_freq_hz** |
Controller **probe** passes parsed timings into IP-specific **calc_timings** (SoC-specific).
DM only changes how clients appear. Data path is still:
Or **rt_i2c_master_send / rt_i2c_master_recv**. Bus mutex serializes masters on one adapter.
| Topic | With **RT_USING_DM** | Without DM |
|---|---|---|
| Client struct | **rt_i2c_client** embeds **rt_device parent** | **bus + client_addr only |
| Binding | **RT_I2C_DRIVER_EXPORT** + DT scan | App finds bus, bit-bangs address manually |
| Bus register side effect | **i2c_bus_scan_clients** | No scan |
| Timing helpers | **i2c_timings_ofw_parse** | Driver parses DT itself or fixed Hz |
probe** → timings + resources → **rt_i2c_bus_device_register** with **ofw_node** on **bus->parent**.ofw_ids** + optional **ids**, **RT_I2C_DRIVER_EXPORT**, **probe** uses **client->bus** and **client_addr**.#address-cells / #size-cells**, child **reg** + **compatible**.client->bus**, respect **rt_i2c_bus_lock** if sharing with other code.remove** should **rt_device_unregister** bus; clients removed via **i2c_remove** when bus tears down (verify BSP order).client->bus == NULL** in **i2c_probe** → **-RT_EINVAL** — scan did not run or client registered manually without **bus**.reg address — probe succeeds but **rt_i2c_transfer** NACKs on wire.probe** until bus registers.ids[].name** must match DT node name, not **compatible** string; prefer **ofw_ids** for compatible-based binding.rt_i2c_bus_device_find("i2c1")**; ensure mutex discipline if both access the same bus.rt_i2c_transfer**, application usagert_bus** / **rt_driver**rt_dm_dev_iomap**, namingRT_PLATFORM_DRIVER_EXPORT**components/drivers/include/drivers/dev_i2c.hcomponents/drivers/i2c/dev_i2c_bus.c, dev_i2c_core.c, dev_i2c_dm.c