summaryrefslogtreecommitdiff
path: root/documentation/6.components/device-driver/platform/platform.md
blob: d89c08eb120e6094ac360ef65544b3ba13035939 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
@page page_device_platform Platform bus

# Platform bus

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

- Header: **`components/drivers/include/drivers/platform.h`**
- Bus logic: **`components/drivers/core/platform.c`**
- OFW enumeration: **`components/drivers/core/platform_ofw.c`**

---

## Data structures

```c
struct rt_platform_device {
    struct rt_device parent;   /* parent.ofw_node, parent.bus, parent.drv */
    int dev_id;
    const char *name;        /* non-OFW matching only */
    const struct rt_ofw_node_id *id;  /* filled by match; use id->data */
    void *priv;              /* optional driver private pointer */
};

struct rt_platform_driver {
    struct rt_driver parent;
    const char *name;
    const struct rt_ofw_node_id *ids;   /* compatible table, sentinel last */
    rt_err_t (*probe)(struct rt_platform_device *pdev);
    rt_err_t (*remove)(struct rt_platform_device *pdev);
    rt_err_t (*shutdown)(struct rt_platform_device *pdev);
};
```

In **`probe`**, almost all DM helpers take **`struct rt_device *dev = &pdev->parent`**.

---

## Boot and init order

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 |

```
INIT_CORE          → platform bus exists
INIT_SUBSYS        → some drivers registered (optional)
INIT_PLATFORM      → DT nodes → platform devices on bus → match + probe if driver already present
INIT_DEVICE        → RT_PLATFORM_DRIVER_EXPORT → register driver → probe every unmatched device
```

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

---

## Registering a driver (important)

### Method A — `RT_PLATFORM_DRIVER_EXPORT` (most common)

```c
static const struct rt_ofw_node_id my_ofw_ids[] = {
    { .compatible = "vendor,my-device" },
    { /* sentinel */ }
};

static rt_err_t my_probe(struct rt_platform_device *pdev)
{
    struct rt_device *dev = &pdev->parent;
    /* iomap, clk, init hardware, register char/block device */
    return RT_EOK;
}

static rt_err_t my_remove(struct rt_platform_device *pdev)
{
    /* reverse probe: unregister, free */
    return RT_EOK;
}

static struct rt_platform_driver my_driver = {
    .name = "my-device",
    .ids = my_ofw_ids,
    .probe = my_probe,
    .remove = my_remove,
};

RT_PLATFORM_DRIVER_EXPORT(my_driver);
```

Macro expansion (**`driver.h`**):

```c
#define RT_PLATFORM_DRIVER_EXPORT(driver)  RT_DRIVER_EXPORT(driver, platform, BUILIN)

/* → static init calls rt_platform_driver_register(&driver) at INIT_DEVICE_EXPORT */
```

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.

### Matching: `ids` (compatible) vs `name`

**`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:

- **DT devices** (from **`INIT_PLATFORM`**): normally matched by **`ids`** / **`compatible`**. This is what most drivers use.
- **No `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`**.
- **Both** can be filled: compatible is tried first; if it fails, **name** is still tried (useful only if **`pdev->name`** is a non-empty string).

Name-only driver + device:

```c
static rt_err_t legacy_probe(struct rt_platform_device *pdev) { ... return RT_EOK; }

static struct rt_platform_driver legacy_driver = {
    .name = "board-uart",   /* must equal pdev->name */
    .ids  = RT_NULL,        /* no compatible table */
    .probe = legacy_probe,
};

RT_PLATFORM_DRIVER_EXPORT(legacy_driver);

/* In board init, after bus is up: */
void board_uart_init(void)
{
    struct rt_platform_device *pdev = rt_platform_device_alloc("board-uart");
    pdev->name = "board-uart";
    rt_platform_device_register(pdev);
}
```

### Method B — manual `rt_platform_driver_register`

Used when the driver must register **before** **`INIT_PLATFORM`** (dependency ordering) or the file predates the export macro:

```c
static int my_drv_register(void)
{
    return rt_platform_driver_register(&my_driver);
}
INIT_SUBSYS_EXPORT(my_drv_register);
```

Same **`struct rt_platform_driver`** layout as method A.

### What `rt_platform_driver_register` does

1. Sets **`pdrv->parent.bus = &platform_bus`**
2. Copies **`pdrv->name`** into **`pdrv->parent.parent.name`**
3. **`rt_driver_register`** → **`rt_bus_add_driver`**
4. **`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.

---

## How devices get on the bus

### Automatic — device tree (`INIT_PLATFORM`)

**`platform_ofw_device_probe`** walks the tree (root, **`/clocks`**, **`/firmware`**, chosen framebuffer, …). For each **available** child with **`compatible`** (or a valid node name):

1. **`alloc_ofw_platform_device(np)`** — **`rt_platform_device_alloc`**, **`pdev->parent.ofw_node = np`**, **`np->dev = &pdev->parent`**
2. **`rt_platform_device_register(pdev)`** → **`rt_bus_add_device`** → try all drivers

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

### On demand — `rt_platform_ofw_request(np)`

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

### Manual — non-OFW / board code

```c
struct rt_platform_device *pdev = rt_platform_device_alloc("my-uart");
pdev->name = "my-uart";   /* must match pdrv->name for name-based match */
rt_platform_device_register(pdev);
```

Rare on DM + DT boards; used for legacy or test bring-up.

### Child node only — `rt_platform_ofw_device_probe_child(np)`

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.

---

## Match rules (`platform_match`)

See **Matching: `ids` vs `name`** above. Summary:

1. **OFW / DT**: **`rt_ofw_node_match(np, pdrv->ids)`** when **`ids`** is set — **`pdev->id`** → use **`pdev->id->data`** for SoC variants.
2. **Name**: **`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.

---

## `platform_probe` wrapper (before your `probe`)

The bus runs **before** **`pdrv->probe(pdev)`**:

| Step | Function | Notes |
| --- | --- | --- |
| 1 | **`rt_pin_ctrl_confs_apply_by_name(dev, NULL)`** / **`pinctrl-0`** | @ref page_device_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.

---

## Typical `probe` / `remove` pattern

```c
static rt_err_t my_probe(struct rt_platform_device *pdev)
{
    rt_err_t err;
    struct rt_device *dev = &pdev->parent;
    struct my_dev *priv = rt_calloc(1, sizeof(*priv));

    if (!priv)
        return -RT_ENOMEM;

    priv->regs = rt_dm_dev_iomap(dev, 0);
    if (!priv->regs) {
        err = -RT_EIO;
        goto err_free;
    }

    priv->clk = rt_clk_get_by_index(dev, 0);
    if (rt_is_err(priv->clk)) {
        err = rt_ptr_err(priv->clk);
        goto err_unmap;
    }

    if ((err = rt_clk_prepare_enable(priv->clk)))
        goto err_clk_put;

    /* register rt_device, set rt_ofw_data for providers */
    rt_dm_dev_bind_fwdata(dev, RT_NULL, priv);

    return RT_EOK;

err_clk_put:
    rt_clk_put(priv->clk);
err_unmap:
    rt_iounmap(priv->regs);
err_free:
    rt_free(priv);
    return err;
}

static rt_err_t my_remove(struct rt_platform_device *pdev)
{
    struct my_dev *priv = rt_dm_dev_get_fwdata(&pdev->parent);
    /* unregister child device, disable clk, iounmap, rt_free(priv) */
    return RT_EOK;
}
```

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

---

## `pdev` fields in practice

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

---

## Examples

### A — DT + `ids` (typical)

Device tree:

```dts
mydev: device@40010000 {
    compatible = "vendor,my-device";
    reg = <0x40010000 0x100>;
    interrupts = <GIC_SPI 42 IRQ_TYPE_LEVEL_HIGH>;
};
```

Driver:

```c
static rt_err_t my_probe(struct rt_platform_device *pdev) { ... }

static const struct rt_ofw_node_id my_ids[] = {
    { .compatible = "vendor,my-device" },
    { },
};

static struct rt_platform_driver my_driver = {
    .name = "vendor-my-device",
    .ids = my_ids,              /* match by compatible */
    .probe = my_probe,
};
RT_PLATFORM_DRIVER_EXPORT(my_driver);
```

**`INIT_PLATFORM`** creates the device from DT; **`INIT_DEVICE`** registers the driver and **`probe`** runs. No **`rt_platform_device_register`** in board code.

### B — name only, no `ids`

No **`compatible`** binding — driver and device share the same **`name`** string:

```c
static struct rt_platform_driver board_uart_driver = {
    .name  = "uart0",
    .ids   = RT_NULL,
    .probe = board_uart_probe,
};
RT_PLATFORM_DRIVER_EXPORT(board_uart_driver);

/* board.c */
    struct rt_platform_device *pdev = rt_platform_device_alloc("uart0");
    pdev->name = "uart0";
    rt_platform_device_register(pdev);
```

Use for legacy BSP devices not described in DT, or tests. On DM + full DT boards, prefer **example A**.

---

## When to use platform vs other buses

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

---

## Pitfalls

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

---

## See also

- @ref page_device_pinctrl — applied in **`platform_probe`** before driver **`probe`**
- @ref page_device_ofw — DT nodes, phandles
- @ref page_device_dm — **`rt_dm_dev_iomap`**, clocks, IRQ
- `documentation/6.components/device-driver/core/bus.md` — generic **`rt_bus`** / **`RT_DRIVER_EXPORT`**
- `components/drivers/core/platform.c`, `platform_ofw.c`