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
|
@page page_device_clk Clock framework (CLK)
# Common Clock Framework (CCF)
RT-Thread’s **CLK** subsystem (`components/drivers/clk/`) is the **Device Model (DM)** clock provider layer: device tree **`clocks`** / **`#clock-cells`** resolve to **`struct rt_clk`** handles, with **reference-counted** `prepare` / `enable` and optional **rate** / **parent** changes. It mirrors the Linux common clock model closely enough to reuse **dt-bindings** and DTS fragments.
Header: `components/drivers/include/drivers/clk.h`. Core: `components/drivers/clk/clk.c`.
Built-in providers in-tree:
| Module | Role |
| --- | --- |
| **`clk-fixed-rate.c`** | DT **`fixed-clock`** — @ref page_device_clk_fixed |
| **`clk-scmi.c`** | Clocks owned by **ARM SCMI** firmware — needs **`RT_CLK_SCMI`** |
| **`bsp/*/dm/clk/`** (e.g. Rockchip) | SoC CCM / PLL / mux / gate — via **`SOC_DM_CLK_DIR`** |
---
## Kconfig
| Option | Role |
| --- | --- |
| **`RT_USING_DM`** | Required parent — CLK is a DM feature |
| **`RT_USING_CLK`** | Build `clk.c` + selected providers; selects **`RT_USING_ADT_REF`** |
| **`RT_CLK_SCMI`** | `clk-scmi.c`; depends on **`RT_FIRMWARE_ARM_SCMI`** |
| **`SOC_DM_CLK_DIR`** | SoC adds Kconfig/SConscript under BSP `dm/clk/` |
Boot argument (OFW): **`clk_ignore_unused`** — when set, cells with **`RT_CLK_F_IGNORE_UNUSED`** skip **`disable`** even at refcount zero (debug / bring-up).
---
## Architecture
```
Device tree consumer node
clocks = <&cru CLK_UART2_SCLK>;
clock-names = "baudclk", "apb_pclk";
│
▼
rt_clk_get_by_name(dev, "baudclk") / rt_ofw_get_clk(np, 0)
│
▼
struct rt_clk (consumer handle: dev_id, con_id, min/max rate)
│
▼
struct rt_clk_cell (one output: PLL child, gate, mux, fixed, …)
│
▼
struct rt_clk_node (provider: registered with rt_clk_register)
│
▼
rt_clk_ops (prepare/enable/recalc_rate/set_rate/set_parent/…)
```
| Object | Meaning |
| --- | --- |
| **`rt_clk_node`** | One **clock controller** in DT (e.g. `&cru`, `fixed-clock`, SCMI clock device) |
| **`rt_clk_cell`** | One **output clock** from that controller (index from `#clock-cells`) |
| **`rt_clk`** | **Consumer reference** to a cell; allocated per `get` |
| **`rt_clk_array`** | All clocks listed in consumer’s **`clocks`** property |
Provider registration stores the node on a global list (`RT_CLK_NODE_OBJ_NAME` = **`"CLKNP"`**) and attaches **`rt_ofw_data(np)`** so phandle lookup works.
---
## Consumer API (platform drivers)
Typical **`probe`** sequence on a DM device (`struct rt_device *dev` with **`dev->ofw_node`**):
```c
struct my_dev {
struct rt_clk *clk;
struct rt_clk *pclk;
};
/* By connection name (matches clock-names) */
pdev->clk = rt_clk_get_by_name(dev, "spiclk");
pdev->pclk = rt_clk_get_by_name(dev, "apb_pclk");
/* Or all clocks at once */
pdev->clk_arr = rt_clk_get_array(dev);
if (rt_clk_prepare_enable(pdev->pclk))
return err;
if (rt_clk_prepare_enable(pdev->clk))
goto disable_pclk;
freq = rt_clk_get_rate(pdev->clk);
```
| API | Role |
| --- | --- |
| **`rt_clk_get_array(dev)`** | Parse **`clocks`** + **`clock-names`** → `rt_clk_array` |
| **`rt_clk_get_by_index(dev, i)`** | Nth entry in **`clocks`** |
| **`rt_clk_get_by_name(dev, name)`** | Match **`clock-names`**; fallback: scan registered cells by **`cell->name`** |
| **`rt_clk_prepare` / `unprepare`** | May sleep (rails, firmware); refcounted per **cell** |
| **`rt_clk_enable` / `disable`** | Fast gate; enables **parent** first; refcounted |
| **`rt_clk_prepare_enable`** | `prepare` then `enable` (common in probe) |
| **`rt_clk_disable_unprepare`** | Reverse; use in `remove` / error paths |
| **`rt_clk_get_rate` / `set_rate` / `round_rate`** | Rate query and change |
| **`rt_clk_set_parent` / `get_parent`** | Reparent mux |
| **`rt_clk_set_rate_range`** | Consumer min/max constraints |
| **`rt_clk_put` / `rt_clk_array_put`** | Free consumer handles (does not disable) |
**`NULL` clock**: `rt_clk_enable(NULL)` and `rt_clk_prepare(NULL)` return **`RT_EOK`** — useful for optional clocks; still check **`rt_clk_get_*`** return before use.
Array helpers: **`rt_clk_array_prepare_enable`**, **`rt_clk_array_disable_unprepare`**, etc. — stop on first error during prepare/enable loops.
### OFW-only helpers
| API | Role |
| --- | --- |
| **`rt_ofw_get_clk(np, index)`** | Resolve phandle without `rt_device` |
| **`rt_ofw_get_clk_by_name(np, name)`** | By **`clock-names`** |
| **`rt_ofw_get_clk_array(np)`** | Full **`clocks`** list |
| **`rt_ofw_count_of_clk(clk_np)`** | Provider output count |
| **`rt_ofw_clk_get_parent_name(np, index)`** | Debug / parent name |
| **`rt_ofw_clk_set_defaults(np)`** | Apply **`assigned-clocks*`** (see below) |
If the provider is not registered yet, **`ofw_get_clk`** may call **`rt_platform_ofw_request(clk_ofw_np)`** to defer probe.
---
## Device tree (consumers)
```dts
uart2: serial@ff1a0000 {
compatible = "rockchip,rk3588-uart", "snps,dw-apb-uart";
reg = <0x0 0xff1a0000 0x0 0x100>;
clocks = <&cru CLK_UART2_SRC>, <&cru CLK_UART2_FRAC>, <&cru PCLK_UART2>;
clock-names = "baudclk", "apb_pclk";
assigned-clocks = <&cru CLK_UART2_SRC>;
assigned-clock-rates = <24000000>;
};
```
| Property | Role |
| --- | --- |
| **`clocks`** | Phandle + `#clock-cells` specifier per input |
| **`clock-names`** | Stable names for **`rt_clk_get_by_name`** |
| **`assigned-clocks`** | Clocks to configure when provider registers |
| **`assigned-clock-parents`** | Optional parent for each assigned clock |
| **`assigned-clock-rates`** | Optional Hz for each assigned clock |
**`rt_clk_register()`** calls **`rt_ofw_clk_set_defaults(dev->ofw_node)`** when the provider has a bound OFW node — applies assigned settings **before** consumers rely on rates.
Provider node exports clocks:
```dts
cru: clock-controller@fd7c0000 {
compatible = "rockchip,rk3588-cru";
#clock-cells = <1>;
clock-output-names = "clk_uart2_src", "...";
};
```
Custom providers with multi-cell specifiers implement **`clk_np->ofw_parse(np, args)`**; default parser uses **`args->args[0]`** as cell index. **`clock-indices`** remaps DT indices to internal cell order.
---
## Provider API (clock drivers)
### `struct rt_clk_ops`
| Callback | Typical use |
| --- | --- |
| **`prepare` / `unprepare`** | PMIC / slow path; paired refcount |
| **`enable` / `disable`** | Clock gate bit |
| **`is_prepared` / `is_enabled`** | Query HW state (optional) |
| **`recalc_rate`** | Return Hz from parent rate + dividers |
| **`round_rate`** | Snap requested rate |
| **`set_rate`** | Program dividers / PLL |
| **`set_parent` / `get_parent`** | Mux select |
| **`set_phase` / `get_phase`** | DDR / SDIO phase (optional) |
Leave unused ops **`NULL`**; core skips them.
### `struct rt_clk_cell` flags
| Flag | Effect |
| --- | --- |
| **`RT_CLK_F_SET_RATE_GATE`** | Gate across rate change |
| **`RT_CLK_F_SET_PARENT_GATE`** | Gate across reparent |
| **`RT_CLK_F_SET_RATE_PARENT`** | Propagate rate change to parent |
| **`RT_CLK_F_IGNORE_UNUSED`** | Skip disable when unused (with **`clk_ignore_unused`**) |
| **`RT_CLK_F_IS_CRITICAL`** | Never auto-disable |
| **`RT_CLK_F_GET_RATE_NOCACHE`** | Always call **`recalc_rate`** |
### Register a provider
```c
struct my_cru {
struct rt_clk_node clk_np;
struct rt_clk_cell *cells[N];
/* … */
};
static struct rt_clk_cell *my_cru_ofw_parse(struct rt_clk_node *np,
struct rt_ofw_cell_args *args)
{
rt_uint32_t idx = args->args[0];
if (idx >= np->cells_nr)
return RT_NULL;
return np->cells[idx];
}
/* In probe, after filling cells[] and ops */
cru->clk_np.dev = dev;
cru->clk_np.cells = cru->cells;
cru->clk_np.cells_nr = num_clocks;
cru->clk_np.ofw_parse = my_cru_ofw_parse;
err = rt_clk_register(&cru->clk_np);
```
If the provider itself has parent clocks in DT, set **`clk_np->dev`** before register — core calls **`rt_clk_get_array(dev)`** into **`clk_np->parents_clk`**.
**`rt_clk_unregister()`** — rollback only; does not free cells.
### Notifiers
**`rt_clk_notifier_register(clk, notifier)`** — callback on **`RT_CLK_MSG_PRE_RATE_CHANGE`**, **`POST_RATE_CHANGE`**, **`ABORT_RATE_CHANGE`**.
---
## Reference counting behavior
- **`prepare_count`** / **`enable_count`** are per **`rt_clk_cell`**, shared by all consumers of that cell.
- **Enable** walks **parent** chain first; **disable** walks parent last (after local gate).
- **`rt_clk_put`** frees only the **consumer** `struct rt_clk` if it is not the cell’s primary binding — **does not** disable the clock; call **`rt_clk_disable_unprepare`** before remove.
---
## SCMI clocks
With **`RT_CLK_SCMI`**, firmware exposes clocks via **`struct rt_scmi_device`**. **`clk-scmi.c`** probes SCMI, enumerates clock IDs, and registers one **`rt_clk_node`** with many cells (`enable`/`disable`/`set_rate` via SCMI protocol).
Consumers use the same **`rt_clk_get_*`** APIs; DT points at the SCMI clock provider node. Details: @ref page_device_scmi and `components/drivers/clk/clk-scmi.c`.
---
## SoC drivers (Rockchip example)
**`bsp/rockchip/dm/clk/`** implements **`rockchip,*-cru`** style controllers: PLL (`clk-rk-pll.c`), mux, gate, divider, composite, CPU clock, MMC phase, etc. Kconfig: **`RT_CLK_ROCKCHIP`**, per-SoC **`RT_CLK_ROCKCHIP_RK3588`**, …
Consumer drivers (SPI, UART, ADC, PCIe, …) only call **`rt_clk_get_by_name`** + **`rt_clk_prepare_enable`** — they do not touch CCM registers directly.
---
## Debug
MSH ( **`RT_USING_CONSOLE`** + **`RT_USING_MSH`** ):
```text
list_clk
```
Dumps each registered **cell**: name, enable/prepare counts, rate, consumer `dev_id` / `con_id`, parent name.
---
## When to use CLK vs direct CCM writes
| Use **`rt_clk_*`** | Direct register writes may remain |
| --- | --- |
| DT lists **`clocks = <&provider …>`** | Early asm/ROM before DM |
| Multiple drivers share PLL parents | One-off bring-up with no DT |
| **`assigned-clocks`** / DVFS coordination | Fixed, always-on root osc (often still modeled as **`fixed-clock`**) |
## Engineer checklist
1. **Provider**: implement **`rt_clk_ops`**, **`ofw_parse`**, **`rt_clk_register`**; publish **`clock-output-names`**.
2. **Consumer**: **`rt_clk_get_by_name`** → **`rt_clk_prepare_enable`** → **`rt_clk_get_rate`** for divider config.
3. **Remove**: **`rt_clk_disable_unprepare`** in reverse order, then **`rt_clk_put`** / **`rt_clk_array_put`**.
4. **Probe order**: provider must register before consumers resolve phandles (deferred probe handles late providers).
5. **Optional clocks**: missing phandle → **`NULL`**; do not enable.
## Pitfalls
- **`rt_clk_put` without disable** — leaves clocks gated on and burns power.
- **Wrong `#clock-cells`** — `ofw_get_clk` fails or picks wrong mux output.
- **Rate set before enable** — some IPs need **`RT_CLK_F_SET_RATE_GATE`** or enable-first ordering.
- **assigned-clocks on consumer vs provider** — defaults run on the node that **owns** the `assigned-clocks` property when **that** node’s provider registers.
- **ISR context** — do not call **`prepare`** / **`prepare_enable`** ( **`RT_DEBUG_NOT_IN_INTERRUPT`** ).
## Detailed documents
- @ref page_device_clk_fixed — **`fixed-clock`** provider
## See also
- `components/drivers/include/drivers/clk.h`
- `components/drivers/clk/clk.c`
- @ref page_device_ofw — phandle / `clock-indices`
- @ref page_device_scmi — SCMI clock protocol
- @ref page_device_reset — often sequenced with clock enable
- @ref page_device_regulator, @ref page_device_power_domain — rails with prepare
|