README.md 28 KB

Clock Framework

Introduction

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.

General Overview

Clock management is fundamental to embedded systems for:

  • Power Management: Gating unused clocks to save power
  • Performance Tuning: Adjusting clock frequencies for optimal performance
  • Device Synchronization: Ensuring correct timing relationships
  • Dynamic Voltage and Frequency Scaling (DVFS): Coordinating voltage with frequency
  • Clock Domain Management: Managing multiple clock sources and their relationships

Common clock types include:

  • Fixed-Rate Clocks: Crystal oscillators, fixed PLLs
  • Gates: Enable/disable control
  • Dividers: Frequency division
  • Multiplexers: Parent selection
  • PLLs: Phase-locked loops for frequency multiplication
  • Composite Clocks: Combinations of above types

RT-Thread Implementation

The RT-Thread clock framework, located in components/drivers/clk/, provides:

  1. Consumer API: Simple interface for device drivers to manage clocks
  2. Provider API: Framework for implementing clock drivers
  3. Clock Tree: Hierarchical parent-child relationships
  4. Device Tree Integration: Automatic configuration from FDT
  5. Reference Counting: Safe enable/disable with multiple consumers
  6. Notifier Chains: Event notification for rate changes
  7. Rate Constraints: Min/max rate management per consumer

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)                 │
└──────────────────────────────────────────────────────────┘

Kconfig Configuration

Main Configuration

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 first
  • RT_USING_ADT_REF: Reference counting support (automatic)

Default: Enabled when DM is enabled

SCMI Clock Driver

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 interface

Vendor-Specific Options

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

Device Tree Bindings

Clock Provider Properties

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

Clock Consumer Properties

Devices reference clocks using:

clocks = <&clk_provider idx>;    /* Clock phandle and index */
clock-names = "name";            /* Clock connection names */

Fixed-Rate Clock Example

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";
    };
};

Clock Controller Example

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";
};

Consumer Usage Example

/* 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>;
};

Application Layer API

Overview

The consumer API provides functions for device drivers to manage their clocks. The framework uses a two-level prepare/enable model similar to Linux:

  • prepare: May sleep, can configure PLLs
  • enable: Atomic operation, must not sleep

Getting and Releasing Clocks

rt_clk_get_by_name

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 pointer
  • name: Clock connection name (matches clock-names in device tree)

Returns:

  • Pointer to clock on success
  • NULL on failure

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

rt_clk_get_by_index

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 pointer
  • index: Clock index (0-based)

Returns:

  • Pointer to clock on success
  • NULL on failure

Example:

/* Get the first clock */
struct rt_clk *clk = rt_clk_get_by_index(dev, 0);

rt_clk_get_array

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 pointer

Returns:

  • Pointer to clock array on success
  • Error pointer on failure (check with 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];
    /* ... */
}

rt_clk_put

void rt_clk_put(struct rt_clk *clk);

Release a clock reference.

Parameters:

  • clk: Clock pointer obtained from get functions

rt_clk_array_put

void rt_clk_array_put(struct rt_clk_array *clk_arr);

Release a clock array.

Parameters:

  • clk_arr: Clock array pointer

Prepare and Enable

rt_clk_prepare

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

Returns:

  • RT_EOK on success
  • Error code on failure

Notes:

  • May sleep - don't call from atomic context
  • Must be called before rt_clk_enable()
  • Use reference counting

rt_clk_unprepare

void rt_clk_unprepare(struct rt_clk *clk);

Unprepare a previously prepared clock.

Parameters:

  • clk: Clock pointer

rt_clk_enable

rt_err_t rt_clk_enable(struct rt_clk *clk);

Enable a clock. This is an atomic operation that must not sleep.

Parameters:

  • clk: Clock pointer

Returns:

  • RT_EOK on success
  • Error code on failure

Notes:

  • Must not sleep - safe to call from atomic context
  • Must be preceded by rt_clk_prepare()
  • Uses reference counting

rt_clk_disable

void rt_clk_disable(struct rt_clk *clk);

Disable a previously enabled clock.

Parameters:

  • clk: Clock pointer

rt_clk_prepare_enable

rt_err_t rt_clk_prepare_enable(struct rt_clk *clk);

Convenience function that prepares and enables a clock.

Parameters:

  • clk: Clock pointer

Returns:

  • RT_EOK on success
  • Error code on failure

Example:

/* Typical usage */
ret = rt_clk_prepare_enable(clk);
if (ret != RT_EOK) {
    LOG_E("Failed to enable clock: %d", ret);
    return ret;
}

rt_clk_disable_unprepare

void rt_clk_disable_unprepare(struct rt_clk *clk);

Convenience function that disables and unprepares a clock.

Parameters:

  • clk: Clock pointer

Clock Array Operations

rt_clk_array_prepare_enable

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

Returns:

  • RT_EOK on success
  • Error code on failure (partial enable handled internally)

rt_clk_array_disable_unprepare

void rt_clk_array_disable_unprepare(struct rt_clk_array *clk_arr);

Disable and unprepare all clocks in an array.

Parameters:

  • clk_arr: Clock array pointer

Rate Management

rt_clk_set_rate

rt_err_t rt_clk_set_rate(struct rt_clk *clk, rt_ubase_t rate);

Set clock frequency.

Parameters:

  • clk: Clock pointer
  • rate: Desired frequency in Hz

Returns:

  • RT_EOK on success
  • Error code on failure

Notes:

  • Actual rate may differ due to hardware limitations
  • Use rt_clk_get_rate() to verify actual rate
  • May trigger notifier callbacks

Example:

/* 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_clk_get_rate

rt_ubase_t rt_clk_get_rate(struct rt_clk *clk);

Get current clock frequency.

Parameters:

  • clk: Clock pointer

Returns:

  • Current frequency in Hz
  • 0 on error

rt_clk_round_rate

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 pointer
  • rate: Desired frequency in Hz

Returns:

  • Closest supported rate
  • Negative error code on failure

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_clk_set_rate_range

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 pointer
  • min: Minimum acceptable rate in Hz
  • max: Maximum acceptable rate in Hz

Returns:

  • RT_EOK on success
  • Error code on failure

Example:

/* UART requires 48MHz ±2% */
rt_clk_set_rate_range(uart_clk, 47040000, 48960000);

Parent Management

rt_clk_set_parent

rt_err_t rt_clk_set_parent(struct rt_clk *clk, struct rt_clk *parent);

Change clock parent (for multiplexer clocks).

Parameters:

  • clk: Clock pointer
  • parent: New parent clock

Returns:

  • RT_EOK on success
  • Error code on failure

rt_clk_get_parent

struct rt_clk *rt_clk_get_parent(struct rt_clk *clk);

Get current parent clock.

Parameters:

  • clk: Clock pointer

Returns:

  • Pointer to parent clock
  • NULL if no parent or error

Phase Control

rt_clk_set_phase

rt_err_t rt_clk_set_phase(struct rt_clk *clk, int degrees);

Set clock phase in degrees.

Parameters:

  • clk: Clock pointer
  • degrees: Phase in degrees (0-359)

Returns:

  • RT_EOK on success
  • -RT_ENOSYS if not supported
  • Error code on failure

rt_clk_get_phase

rt_base_t rt_clk_get_phase(struct rt_clk *clk);

Get current clock phase.

Parameters:

  • clk: Clock pointer

Returns:

  • Phase in degrees (0-359)
  • Negative error code on failure

Notifier API

rt_clk_notifier_register

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 pointer
  • notifier: Notifier structure with callback

Returns:

  • RT_EOK on success
  • Error code on failure

Notifier 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 change
  • RT_CLK_MSG_POST_RATE_CHANGE: After successful rate change
  • RT_CLK_MSG_ABORT_RATE_CHANGE: Rate change aborted

Example:

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

Complete Application Example

Example: UART Driver with Clock Management

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

Driver Implementation Guide

Key Structures

rt_clk_cell

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 */
};

rt_clk_ops

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

Example: Fixed-Rate Clock Driver

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

Best Practices

For Consumer Drivers

  1. Always use prepare_enable/disable_unprepare: Simpler and safer
  2. Check return values: Clock operations can fail
  3. Balance enable/disable: Match every enable with a disable
  4. Order matters: Enable clocks before using hardware
  5. Handle rate changes: Use notifiers if rate changes affect operation
  6. Set rate constraints: Use rt_clk_set_rate_range() when needed

For Provider Drivers

  1. Implement only supported operations: Leave unsupported ops NULL
  2. Cache rates when possible: Avoid hardware access in get_rate()
  3. Handle reference counting: Framework manages prepare/enable counts
  4. Propagate rate changes: Update cached rates in children
  5. Support multiple parents: For multiplexer clocks
  6. Document constraints: Min/max rates, parent relationships

Common Patterns

Simple Clock Usage

/* 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);

Dynamic Frequency Scaling

/* 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;
}

Troubleshooting

Common Issues

  1. Clock not found

    • Check device tree: Ensure clocks and clock-names properties exist
    • Check compatible string: Verify clock driver is loaded
    • Check Kconfig: Enable clock framework and drivers
  2. Enable fails

    • Check parent clocks: Parents must be enabled first
    • Check prepare: Must prepare before enable
    • Check hardware: Verify clock controller is accessible
  3. Wrong frequency

    • Check parent rate: Parent must be correct frequency
    • Check dividers: Hardware may have limited divider values
    • Use rt_clk_round_rate(): Verify supported rates
  4. System instability

    • Check critical clocks: Some clocks must never be disabled
    • Check rate constraints: Don't exceed hardware limits
    • Check dependencies: Some clocks depend on others

Performance Considerations

Memory Usage

  • Each clock cell: ~80-100 bytes
  • Each consumer reference: ~40 bytes
  • Clock tree overhead: Depends on hierarchy depth

Timing

  • prepare/enable: Can be slow (ms for PLLs)
  • Rate changes: May be slow, use notifiers
  • get_rate: Usually fast (cached)

Optimization Tips

  1. Cache clock pointers: Don't call get/put repeatedly
  2. Batch operations: Use clock arrays when possible
  3. Avoid unnecessary rate changes: Check current rate first
  4. Use prepare_enable: Combines two operations

Related Modules

  • regulator: Power supply management, coordinate with clocks
  • pinctrl: Pin configuration, may need clock enable
  • reset: Reset control, coordinate with clock enable
  • pmdomain: Power domain, higher-level power management

References