RT-Thread RTOS
An open source embedded real-time operating system
I2C device model (DM)

I2C bus framework under <tt>RT_USING_DM</tt>

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

Kconfig

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**.


Architecture

Platform I2C controller (e.g. rk3x, bit-bang)
|
| probe: i2c_timings_ofw_parse, rt_dm_dev_iomap, clocks
| bus->parent.ofw_node = controller np
| rt_i2c_bus_device_register(&bus->parent, "i2cN")
v
rt_device "i2cN" (RT_Device_Class_I2CBUS, user_data -> rt_i2c_bus_device)
|
| i2c_bus_scan_clients(bus) /* dev_i2c_core.c */
v
For each available child in DT:
alloc rt_i2c_client, reg -> client_addr, ofw_node
rt_i2c_device_register(client) -> rt_bus_add_device("i2c", ...)
v
i2c_match: driver ids[] name OR ofw_ids compatible
|
v
i2c_probe -> driver->probe(client)
|
v
Client driver uses client->bus + rt_i2c_transfer / master_send

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.


<tt>struct rt_bus i2c</tt> (dev_i2c_bus.c)

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:

rt_err_t rt_i2c_driver_register(struct rt_i2c_driver *driver);
/* RT_I2C_DRIVER_EXPORT(drv) -> RT_DRIVER_EXPORT(drv, i2c, BUILIN) */

Sets **driver->parent.bus = &i2c_bus** then **rt_driver_register**.

Client registration:

rt_err_t rt_i2c_device_register(struct rt_i2c_client *client);
/* internally: rt_bus_add_device(&i2c_bus, &client->parent) */
I2C Client.
Definition: dev_i2c.h:273

Device tree: controller + clients

Controller node

i2c1: i2c@ff160000 {
compatible = "rockchip,rk3588-i2c", "rockchip,rk3066-i2c";
reg = <0x0 0xff160000 0x0 0x1000>;
interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru CLK_I2C1>, <&cru PCLK_I2C1>;
clock-names = "i2c", "pclk";
#address-cells = <1>;
#size-cells = <0>;
rtc@68 {
compatible = "dallas,ds1307";
reg = <0x68>;
};
};
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**

Client discovery (<tt>i2c_bus_scan_clients</tt>)

Called automatically when **rt_i2c_bus_device_register** succeeds (**RT_USING_DM** only).

For each available child of **bus->parent.ofw_node**:

  1. If child has **compatible**, use it as client node; else look one level deeper (i2c-mux style).
  2. **rt_calloc** **struct rt_i2c_client**.
  3. Read **reg** → **client_addr**.
  4. **client->parent.ofw_node**, **client->name = rt_ofw_node_name**, **client->bus = bus**.
  5. **rt_dm_dev_set_name(&client->parent, "%s", name)**.
  6. **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).


Bus controller driver (adapter)

Pattern from SoC drivers (e.g. **bsp/rockchip/dm/i2c/i2c-rk3x.c**):

static rt_err_t my_i2c_probe(struct rt_platform_device *pdev)
{
struct rt_device *dev = &pdev->parent;
struct my_i2c *i2c = rt_calloc(1, sizeof(*i2c));
i2c_timings_ofw_parse(dev->ofw_node, &i2c->timings, RT_TRUE);
i2c->regs = rt_dm_dev_iomap(dev, 0);
i2c->irq = rt_dm_dev_get_irq(dev, 0);
/* clocks: rt_clk_get_by_name, prepare_enable */
rt_dm_dev_set_name_auto(&i2c->parent.parent, "i2c");
i2c->parent.ops = &my_i2c_ops;
i2c->parent.parent.ofw_node = dev->ofw_node;
dev->user_data = i2c;
return rt_i2c_bus_device_register(&i2c->parent, rt_dm_dev_get_name(dev));
}
rt_err_t rt_i2c_bus_device_register(struct rt_i2c_bus_device *bus, const char *bus_name)
I2C Bus Device Register.
rt_weak void * rt_calloc(rt_size_t count, rt_size_t size)
This function will contiguously allocate enough space for count objects that are size bytes of memory...
Definition: kservice.c:1210
Definition: rtdef.h:1369
struct rt_object parent
Definition: rtdef.h:1370
void * user_data
Definition: rtdef.h:1419
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.


Client driver (I2C slave)

static const struct rt_ofw_node_id my_ids[] = {
{ .compatible = "vendor,my-sensor", .data = (void *)&chip_info },
{ /* sentinel */ },
};
static const struct rt_i2c_device_id my_tbl[] = {
{ .name = "my-sensor", .data = (void *)&chip_info },
{ /* sentinel */ },
};
static struct rt_i2c_driver my_driver = {
.ids = my_tbl, /* optional: match by DT node name */
.ofw_ids = my_ids, /* match by compatible */
.probe = my_probe,
.remove = my_remove,
};
RT_I2C_DRIVER_EXPORT(my_driver);
static rt_err_t my_probe(struct rt_i2c_client *client)
{
struct rt_i2c_bus_device *bus = client->bus;
rt_uint16_t addr = client->client_addr;
const void *cfg = rt_i2c_client_id_data(client);
/* Use rt_i2c_master_send/recv or rt_i2c_transfer on client->bus */
return RT_EOK;
}
I2C Bus Device.
Definition: dev_i2c.h:258
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.


<tt>struct i2c_timings</tt> (OFW)

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).


Transfer path (unchanged by DM)

DM only changes how clients appear. Data path is still:

rt_i2c_bus_lock(bus, timeout);
rt_i2c_transfer(bus, msgs, num); /* -> ops->master_xfer */
rt_i2c_bus_unlock(bus);
rt_ssize_t rt_i2c_transfer(struct rt_i2c_bus_device *bus, struct rt_i2c_msg msgs[], rt_uint32_t num)
I2C data transmission.

Or **rt_i2c_master_send / rt_i2c_master_recv**. Bus mutex serializes masters on one adapter.


DM vs non-DM summary

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

Engineer checklist

  1. Controller: platform **probe** → timings + resources → **rt_i2c_bus_device_register** with **ofw_node** on **bus->parent**.
  2. Client driver: **ofw_ids** + optional **ids**, **RT_I2C_DRIVER_EXPORT**, **probe** uses **client->bus** and **client_addr**.
  3. DT: controller **#address-cells / #size-cells**, child **reg** + **compatible**.
  4. Transfers: always through **client->bus**, respect **rt_i2c_bus_lock** if sharing with other code.
  5. Remove: controller **remove** should **rt_device_unregister** bus; clients removed via **i2c_remove** when bus tears down (verify BSP order).

Pitfalls

  • **client->bus == NULL** in **i2c_probe** → **-RT_EINVAL** — scan did not run or client registered manually without **bus**.
  • Wrong reg address — probe succeeds but **rt_i2c_transfer** NACKs on wire.
  • Driver before bus — no automatic re-scan; child never **probe** until bus registers.
  • i2c-mux depth — scan only handles one level of non-compatible wrapper; deeper mux trees may need explicit client creation.
  • Name vs compatible match — **ids[].name** must match DT node name, not **compatible** string; prefer **ofw_ids** for compatible-based binding.
  • Mixing DM client with legacy app — app can still **rt_i2c_bus_device_find("i2c1")**; ensure mutex discipline if both access the same bus.

See also