![]() |
RT-Thread RTOS
An open source embedded real-time operating system
|
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** — Fixed clock provider |
**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** |
| 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).
| 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.
Typical **probe** sequence on a DM device (struct rt_device *dev with **dev->ofw_node**):
| 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.
| 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.
| 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:
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.
| 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.
| 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** |
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.
**rt_clk_notifier_register(clk, notifier)** — callback on **RT_CLK_MSG_PRE_RATE_CHANGE**, **POST_RATE_CHANGE**, **ABORT_RATE_CHANGE**.
prepare_count** / **enable_count** are per **rt_clk_cell**, shared by all consumers of that cell.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.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: SCMI firmware protocol and components/drivers/clk/clk-scmi.c.
**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.
MSH ( **RT_USING_CONSOLE** + **RT_USING_MSH** ):
Dumps each registered cell: name, enable/prepare counts, rate, consumer dev_id / con_id, parent name.
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**) |
rt_clk_ops**, **ofw_parse**, **rt_clk_register**; publish **clock-output-names**.rt_clk_get_by_name** → **rt_clk_prepare_enable** → **rt_clk_get_rate** for divider config.rt_clk_disable_unprepare** in reverse order, then **rt_clk_put** / **rt_clk_array_put**.NULL**; do not enable.rt_clk_put without disable** — leaves clocks gated on and burns power.#clock-cells — ofw_get_clk fails or picks wrong mux output.RT_CLK_F_SET_RATE_GATE** or enable-first ordering.assigned-clocks property when that node’s provider registers.prepare** / **prepare_enable** ( **RT_DEBUG_NOT_IN_INTERRUPT** ).fixed-clock** providercomponents/drivers/include/drivers/clk.hcomponents/drivers/clk/clk.cclock-indices