The Clock Framework in RT-Thread provides a comprehensive infrastructure for managing hardware clocks in embedded systems. It implements a hierarchical clock tree model with support for clock gating, frequency scaling, parent switching, and phase adjustment - all essential for power management and system performance tuning.
Clock management is fundamental to embedded systems for:
Common clock types include:
The RT-Thread clock framework, located in components/drivers/clk/, provides:
Architecture:
┌─────────────────────────────────────────────────────────┐
│ Consumer Drivers │
│ (UART, SPI, MMC, CPU, Peripheral drivers) │
└────────────────────┬────────────────────────────────────┘
│ Consumer API
│ (get, enable, set_rate, etc.)
┌────────────────────┴────────────────────────────────────┐
│ Clock Framework Core │
│ - Clock Tree Management │
│ - Reference Counting (prepare/enable) │
│ - Rate Calculation and Propagation │
│ - Parent Switching │
│ - Notifier Chains │
└────────────────────┬────────────────────────────────────┘
│ Provider API
│ (ops callbacks)
┌────────────────────┴────────────────────────────────────┐
│ Clock Drivers │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │Fixed-Rate │ │ Gate │ │ PLL │ │
│ │ Clock │ │ Clock │ │ Clock │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Divider │ │ Multiplexer│ │ Composite │ │
│ │ Clock │ │ Clock │ │ Clock │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└────────────────────┬────────────────────────────────────┘
│
┌────────────────────┴────────────────────────────────────┐
│ Hardware Clock Sources │
│ (Oscillators, PLLs, Clock Controllers) │
└──────────────────────────────────────────────────────────┘
menuconfig RT_USING_CLK
bool "Using Common Clock Framework (CLK)"
depends on RT_USING_DM
select RT_USING_ADT_REF
default y
Location in menuconfig:
RT-Thread Components → Device Drivers → Using Common Clock Framework (CLK)
Dependencies:
RT_USING_DM: Must be enabled firstRT_USING_ADT_REF: Reference counting support (automatic)Default: Enabled when DM is enabled
config RT_CLK_SCMI
bool "Clock driver controlled via SCMI interface"
depends on RT_USING_CLK
depends on RT_FIRMWARE_ARM_SCMI
default n
Supports clocks controlled through ARM System Control and Management Interface (SCMI).
Dependencies:
RT_FIRMWARE_ARM_SCMI: ARM SCMI firmware interfaceif RT_USING_CLK
osource "$(SOC_DM_CLK_DIR)/Kconfig"
endif
Allows SoC-specific clock drivers to add their own Kconfig options via the SOC_DM_CLK_DIR variable.
Clock providers export clocks using these properties:
#clock-cells = <n>; /* Number of cells in clock specifier */
clock-output-names = "name1", "name2"; /* Names of output clocks */
Devices reference clocks using:
clocks = <&clk_provider idx>; /* Clock phandle and index */
clock-names = "name"; /* Clock connection names */
clocks {
/* Simple fixed-rate oscillator */
osc24M: oscillator-24M {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <24000000>;
clock-output-names = "osc24M";
};
/* Fixed-rate with accuracy specification */
osc32k: oscillator-32k {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <32768>;
clock-accuracy = <50>; /* ±50 PPM */
clock-output-names = "osc32k";
};
};
ccu: clock-controller@1c20000 {
compatible = "vendor,clock-controller";
reg = <0x1c20000 0x400>;
#clock-cells = <1>;
/* Parent clocks */
clocks = <&osc24M>, <&osc32k>;
clock-names = "hosc", "losc";
/* Output clock names (optional) */
clock-output-names = "pll-cpu", "pll-ddr", "ahb1", "apb1",
"bus-uart0", "bus-uart1";
};
/* Single clock consumer */
uart0: serial@1c28000 {
compatible = "vendor,uart";
reg = <0x1c28000 0x400>;
interrupts = <0 0 4>;
clocks = <&ccu 64>; /* Clock index 64 */
clock-names = "baudclk";
status = "okay";
};
/* Multiple clocks consumer */
mmc0: mmc@1c0f000 {
compatible = "vendor,mmc";
reg = <0x1c0f000 0x1000>;
interrupts = <0 32 4>;
clocks = <&ccu 8>, <&ccu 9>;
clock-names = "ahb", "mmc";
status = "okay";
};
/* Clock with parent reference */
cpu0: cpu@0 {
compatible = "arm,cortex-a7";
device_type = "cpu";
reg = <0>;
clocks = <&ccu 0>;
clock-names = "cpu";
operating-points-v2 = <&cpu_opp_table>;
};
The consumer API provides functions for device drivers to manage their clocks. The framework uses a two-level prepare/enable model similar to Linux:
struct rt_clk *rt_clk_get_by_name(struct rt_device *dev, const char *name);
Get a clock by connection name.
Parameters:
dev: Device structure pointername: Clock connection name (matches clock-names in device tree)Returns:
Example:
struct rt_device *dev = &pdev->parent;
struct rt_clk *clk;
/* Get the "baudclk" clock */
clk = rt_clk_get_by_name(dev, "baudclk");
if (!clk) {
LOG_E("Failed to get baudclk");
return -RT_ERROR;
}
struct rt_clk *rt_clk_get_by_index(struct rt_device *dev, int index);
Get a clock by index in the clocks property.
Parameters:
dev: Device structure pointerindex: Clock index (0-based)Returns:
Example:
/* Get the first clock */
struct rt_clk *clk = rt_clk_get_by_index(dev, 0);
struct rt_clk_array *rt_clk_get_array(struct rt_device *dev);
Get all clocks for a device as an array.
Parameters:
dev: Device structure pointerReturns:
rt_is_err())Example:
struct rt_clk_array *clk_arr = rt_clk_get_array(dev);
if (rt_is_err(clk_arr)) {
return rt_ptr_err(clk_arr);
}
/* Access individual clocks */
for (int i = 0; i < clk_arr->count; i++) {
struct rt_clk *clk = clk_arr->clks[i];
/* ... */
}
void rt_clk_put(struct rt_clk *clk);
Release a clock reference.
Parameters:
clk: Clock pointer obtained from get functionsvoid rt_clk_array_put(struct rt_clk_array *clk_arr);
Release a clock array.
Parameters:
clk_arr: Clock array pointerrt_err_t rt_clk_prepare(struct rt_clk *clk);
Prepare a clock for enabling. This may sleep and can configure PLLs or perform other operations that cannot be done atomically.
Parameters:
clk: Clock pointerReturns:
RT_EOK on successNotes:
rt_clk_enable()void rt_clk_unprepare(struct rt_clk *clk);
Unprepare a previously prepared clock.
Parameters:
clk: Clock pointerrt_err_t rt_clk_enable(struct rt_clk *clk);
Enable a clock. This is an atomic operation that must not sleep.
Parameters:
clk: Clock pointerReturns:
RT_EOK on successNotes:
rt_clk_prepare()void rt_clk_disable(struct rt_clk *clk);
Disable a previously enabled clock.
Parameters:
clk: Clock pointerrt_err_t rt_clk_prepare_enable(struct rt_clk *clk);
Convenience function that prepares and enables a clock.
Parameters:
clk: Clock pointerReturns:
RT_EOK on successExample:
/* Typical usage */
ret = rt_clk_prepare_enable(clk);
if (ret != RT_EOK) {
LOG_E("Failed to enable clock: %d", ret);
return ret;
}
void rt_clk_disable_unprepare(struct rt_clk *clk);
Convenience function that disables and unprepares a clock.
Parameters:
clk: Clock pointerrt_err_t rt_clk_array_prepare_enable(struct rt_clk_array *clk_arr);
Prepare and enable all clocks in an array.
Parameters:
clk_arr: Clock array pointerReturns:
RT_EOK on successvoid rt_clk_array_disable_unprepare(struct rt_clk_array *clk_arr);
Disable and unprepare all clocks in an array.
Parameters:
clk_arr: Clock array pointerrt_err_t rt_clk_set_rate(struct rt_clk *clk, rt_ubase_t rate);
Set clock frequency.
Parameters:
clk: Clock pointerrate: Desired frequency in HzReturns:
RT_EOK on successNotes:
rt_clk_get_rate() to verify actual rateExample:
/* Set UART clock to 48MHz */
ret = rt_clk_set_rate(uart_clk, 48000000);
if (ret != RT_EOK) {
LOG_E("Failed to set clock rate: %d", ret);
}
/* Verify actual rate */
rt_ubase_t actual_rate = rt_clk_get_rate(uart_clk);
LOG_I("Clock rate: %u Hz", actual_rate);
rt_ubase_t rt_clk_get_rate(struct rt_clk *clk);
Get current clock frequency.
Parameters:
clk: Clock pointerReturns:
rt_base_t rt_clk_round_rate(struct rt_clk *clk, rt_ubase_t rate);
Get the closest supported rate without changing the clock.
Parameters:
clk: Clock pointerrate: Desired frequency in HzReturns:
Example:
/* Check if desired rate is supported */
rt_base_t rounded = rt_clk_round_rate(clk, 50000000);
if (rounded < 0) {
LOG_E("Cannot determine supported rate");
} else {
LOG_I("Closest supported rate: %d Hz", rounded);
}
rt_err_t rt_clk_set_rate_range(struct rt_clk *clk, rt_ubase_t min, rt_ubase_t max);
Set acceptable rate range for this consumer.
Parameters:
clk: Clock pointermin: Minimum acceptable rate in Hzmax: Maximum acceptable rate in HzReturns:
RT_EOK on successExample:
/* UART requires 48MHz ±2% */
rt_clk_set_rate_range(uart_clk, 47040000, 48960000);
rt_err_t rt_clk_set_parent(struct rt_clk *clk, struct rt_clk *parent);
Change clock parent (for multiplexer clocks).
Parameters:
clk: Clock pointerparent: New parent clockReturns:
RT_EOK on successstruct rt_clk *rt_clk_get_parent(struct rt_clk *clk);
Get current parent clock.
Parameters:
clk: Clock pointerReturns:
rt_err_t rt_clk_set_phase(struct rt_clk *clk, int degrees);
Set clock phase in degrees.
Parameters:
clk: Clock pointerdegrees: Phase in degrees (0-359)Returns:
RT_EOK on success-RT_ENOSYS if not supportedrt_base_t rt_clk_get_phase(struct rt_clk *clk);
Get current clock phase.
Parameters:
clk: Clock pointerReturns:
rt_err_t rt_clk_notifier_register(struct rt_clk *clk,
struct rt_clk_notifier *notifier);
Register a notifier for clock events.
Parameters:
clk: Clock pointernotifier: Notifier structure with callbackReturns:
RT_EOK on successNotifier Structure:
struct rt_clk_notifier {
rt_list_t list;
struct rt_clk *clk;
rt_clk_notifier_callback callback;
void *priv;
};
typedef rt_err_t (*rt_clk_notifier_callback)(
struct rt_clk_notifier *notifier,
rt_ubase_t msg,
rt_ubase_t old_rate,
rt_ubase_t new_rate);
Event Messages:
RT_CLK_MSG_PRE_RATE_CHANGE: Before rate changeRT_CLK_MSG_POST_RATE_CHANGE: After successful rate changeRT_CLK_MSG_ABORT_RATE_CHANGE: Rate change abortedExample:
static rt_err_t clk_notifier_cb(struct rt_clk_notifier *notifier,
rt_ubase_t msg,
rt_ubase_t old_rate,
rt_ubase_t new_rate)
{
if (msg == RT_CLK_MSG_PRE_RATE_CHANGE) {
LOG_I("Clock rate changing: %u -> %u Hz", old_rate, new_rate);
/* Prepare for rate change */
} else if (msg == RT_CLK_MSG_POST_RATE_CHANGE) {
LOG_I("Clock rate changed successfully");
/* Update hardware configuration */
}
return RT_EOK;
}
struct rt_clk_notifier my_notifier = {
.callback = clk_notifier_cb,
.priv = NULL,
};
rt_clk_notifier_register(clk, &my_notifier);
#include <rtthread.h>
#include <drivers/platform.h>
#include <drivers/clk.h>
#include <drivers/serial.h>
struct uart_device {
void *base;
int irq;
struct rt_clk *clk;
struct rt_serial_device serial;
};
static rt_err_t uart_configure(struct rt_serial_device *serial,
struct serial_configure *cfg)
{
struct uart_device *uart = rt_container_of(serial,
struct uart_device, serial);
rt_ubase_t clk_rate;
rt_uint32_t divisor;
/* Get clock rate */
clk_rate = rt_clk_get_rate(uart->clk);
if (clk_rate == 0) {
LOG_E("Invalid clock rate");
return -RT_ERROR;
}
/* Calculate baud rate divisor */
divisor = clk_rate / (16 * cfg->baud_rate);
/* Configure hardware */
writel(divisor & 0xFF, uart->base + UART_DLL);
writel((divisor >> 8) & 0xFF, uart->base + UART_DLH);
LOG_D("UART: clk=%u Hz, baud=%u, divisor=%u",
clk_rate, cfg->baud_rate, divisor);
return RT_EOK;
}
static rt_err_t uart_probe(struct rt_platform_device *pdev)
{
rt_err_t ret;
struct rt_device *dev = &pdev->parent;
struct uart_device *uart;
/* Allocate device structure */
uart = rt_calloc(1, sizeof(*uart));
if (!uart)
return -RT_ENOMEM;
/* Map MMIO region */
uart->base = rt_dm_dev_iomap(dev, 0);
if (!uart->base) {
ret = -RT_ERROR;
goto err_free;
}
/* Get IRQ */
uart->irq = rt_dm_dev_get_irq(dev, 0);
if (uart->irq < 0) {
ret = uart->irq;
goto err_unmap;
}
/* Get clock */
uart->clk = rt_clk_get_by_name(dev, "baudclk");
if (!uart->clk) {
LOG_E("Failed to get baudclk");
ret = -RT_ERROR;
goto err_unmap;
}
/* Prepare and enable clock */
ret = rt_clk_prepare_enable(uart->clk);
if (ret != RT_EOK) {
LOG_E("Failed to enable clock: %d", ret);
goto err_put_clk;
}
/* Log clock rate */
LOG_I("UART clock rate: %u Hz", rt_clk_get_rate(uart->clk));
/* Initialize serial device */
uart->serial.ops = &uart_ops;
uart->serial.config = RT_SERIAL_CONFIG_DEFAULT;
/* Register serial device */
ret = rt_hw_serial_register(&uart->serial,
rt_dm_dev_get_name(dev),
RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX,
uart);
if (ret != RT_EOK) {
goto err_disable_clk;
}
pdev->priv = uart;
LOG_I("UART device registered");
return RT_EOK;
err_disable_clk:
rt_clk_disable_unprepare(uart->clk);
err_put_clk:
rt_clk_put(uart->clk);
err_unmap:
rt_iounmap(uart->base);
err_free:
rt_free(uart);
return ret;
}
static rt_err_t uart_remove(struct rt_platform_device *pdev)
{
struct uart_device *uart = pdev->priv;
/* Unregister serial device */
rt_device_unregister(&uart->serial.parent);
/* Disable and release clock */
rt_clk_disable_unprepare(uart->clk);
rt_clk_put(uart->clk);
/* Release resources */
rt_iounmap(uart->base);
rt_free(uart);
return RT_EOK;
}
static const struct rt_ofw_node_id uart_ofw_ids[] = {
{ .compatible = "vendor,uart" },
{ /* sentinel */ }
};
static struct rt_platform_driver uart_driver = {
.name = "uart",
.ids = uart_ofw_ids,
.probe = uart_probe,
.remove = uart_remove,
};
RT_PLATFORM_DRIVER_EXPORT(uart_driver);
struct rt_clk_cell {
struct rt_clk_node *clk_np; /* Parent clock node */
const char *name; /* Clock name */
const struct rt_clk_ops *ops; /* Operations */
rt_uint8_t parents_nr; /* Number of parents */
union {
const char *parent_name; /* Single parent */
const char *const *parent_names; /* Multiple parents */
};
rt_ubase_t rate; /* Cached rate */
struct rt_clk *clk; /* Consumer reference */
struct rt_clk *parent; /* Current parent */
int prepare_count; /* Prepare reference count */
int enable_count; /* Enable reference count */
rt_uint32_t flags; /* Clock flags */
void *priv; /* Private data */
};
struct rt_clk_ops {
/* Prepare/unprepare (may sleep) */
rt_err_t (*prepare)(struct rt_clk_cell *cell);
void (*unprepare)(struct rt_clk_cell *cell);
rt_bool_t (*is_prepared)(struct rt_clk_cell *cell);
/* Enable/disable (atomic) */
rt_err_t (*enable)(struct rt_clk_cell *cell);
void (*disable)(struct rt_clk_cell *cell);
rt_bool_t (*is_enabled)(struct rt_clk_cell *cell);
/* Rate control */
rt_ubase_t (*recalc_rate)(struct rt_clk_cell *cell, rt_ubase_t parent_rate);
rt_base_t (*round_rate)(struct rt_clk_cell *cell, rt_ubase_t drate, rt_ubase_t *prate);
rt_err_t (*set_rate)(struct rt_clk_cell *cell, rt_ubase_t rate, rt_ubase_t parent_rate);
/* Parent control */
rt_err_t (*set_parent)(struct rt_clk_cell *cell, rt_uint8_t idx);
rt_uint8_t (*get_parent)(struct rt_clk_cell *cell);
/* Phase control */
rt_err_t (*set_phase)(struct rt_clk_cell *cell, int degrees);
rt_base_t (*get_phase)(struct rt_clk_cell *cell);
};
#include <rtthread.h>
#include <drivers/platform.h>
#include <drivers/clk.h>
struct clk_fixed {
struct rt_clk_node parent;
struct rt_clk_fixed_rate fcell;
struct rt_clk_cell *cells[1];
};
static rt_ubase_t fixed_clk_recalc_rate(struct rt_clk_cell *cell,
rt_ubase_t parent_rate)
{
struct rt_clk_fixed_rate *fr = rt_container_of(cell,
struct rt_clk_fixed_rate,
cell);
return fr->fixed_rate;
}
static struct rt_clk_ops fixed_clk_ops = {
.recalc_rate = fixed_clk_recalc_rate,
};
static rt_err_t fixed_clk_probe(struct rt_platform_device *pdev)
{
rt_err_t err;
rt_uint32_t val;
struct rt_device *dev = &pdev->parent;
struct clk_fixed *cf;
/* Allocate driver structure */
cf = rt_calloc(1, sizeof(*cf));
if (!cf)
return -RT_ENOMEM;
/* Read clock frequency from device tree */
if ((err = rt_dm_dev_prop_read_u32(dev, "clock-frequency", &val))) {
LOG_E("Missing clock-frequency property");
goto _fail;
}
cf->fcell.fixed_rate = val;
/* Read optional accuracy */
val = 0;
rt_dm_dev_prop_read_u32(dev, "clock-accuracy", &val);
cf->fcell.fixed_accuracy = val;
/* Read optional clock name */
rt_dm_dev_prop_read_string(dev, "clock-output-names",
&cf->fcell.cell.name);
/* Initialize clock node */
cf->parent.dev = dev;
cf->parent.cells_nr = 1;
cf->parent.cells = cf->cells;
cf->cells[0] = &cf->fcell.cell;
cf->fcell.cell.ops = &fixed_clk_ops;
/* Register with framework */
if ((err = rt_clk_register(&cf->parent))) {
LOG_E("Failed to register clock: %d", err);
goto _fail;
}
LOG_I("Fixed clock '%s' registered: %u Hz",
cf->fcell.cell.name, cf->fcell.fixed_rate);
return RT_EOK;
_fail:
rt_free(cf);
return err;
}
static const struct rt_ofw_node_id fixed_clk_ofw_ids[] = {
{ .compatible = "fixed-clock" },
{ /* sentinel */ }
};
static struct rt_platform_driver fixed_clk_driver = {
.name = "clk-fixed-rate",
.ids = fixed_clk_ofw_ids,
.probe = fixed_clk_probe,
};
static int fixed_clk_drv_register(void)
{
rt_platform_driver_register(&fixed_clk_driver);
return 0;
}
INIT_SUBSYS_EXPORT(fixed_clk_drv_register);
rt_clk_set_rate_range() when neededget_rate()/* Get and enable clock */
struct rt_clk *clk = rt_clk_get_by_name(dev, "clk");
if (!clk)
return -RT_ERROR;
ret = rt_clk_prepare_enable(clk);
if (ret != RT_EOK) {
rt_clk_put(clk);
return ret;
}
/* Use hardware */
/* Cleanup */
rt_clk_disable_unprepare(clk);
rt_clk_put(clk);
/* Change frequency based on workload */
switch (perf_level) {
case PERF_HIGH:
rt_clk_set_rate(cpu_clk, 1000000000); /* 1GHz */
break;
case PERF_NORMAL:
rt_clk_set_rate(cpu_clk, 800000000); /* 800MHz */
break;
case PERF_LOW:
rt_clk_set_rate(cpu_clk, 400000000); /* 400MHz */
break;
}
Clock not found
clocks and clock-names properties existEnable fails
Wrong frequency
rt_clk_round_rate(): Verify supported ratesSystem instability
components/drivers/clk/components/drivers/include/drivers/clk.h