diff --git a/documentation/6.components/device-driver/device_driver_model/clk/README_zh.md b/documentation/6.components/device-driver/device_driver_model/clk/README_zh.md new file mode 100644 index 0000000000..e8c209b572 --- /dev/null +++ b/documentation/6.components/device-driver/device_driver_model/clk/README_zh.md @@ -0,0 +1,869 @@ +# 时钟框架 + +## 简介 + +RT-Thread 的时钟框架为嵌入式系统中的硬件时钟管理提供了全面的基础设施。它实现了层次化时钟树模型,支持时钟门控、频率调节、父时钟切换和相位调整——这些都是电源管理和系统性能调优的关键。 + +### 概述 + +时钟管理对嵌入式系统至关重要: + +- **电源管理**:关闭未使用的时钟以节省功耗 +- **性能调优**:调整时钟频率以获得最佳性能 +- **设备同步**:确保正确的时序关系 +- **动态电压频率调节(DVFS)**:协调电压与频率 +- **时钟域管理**:管理多个时钟源及其关系 + +常见时钟类型包括: +- **固定频率时钟**:晶振、固定 PLL +- **门控时钟**:启用/禁用控制 +- **分频器**:频率分频 +- **多路复用器**:父时钟选择 +- **PLL**:锁相环,用于频率倍增 +- **复合时钟**:上述类型的组合 + +### RT-Thread 实现 + +RT-Thread 时钟框架位于 `components/drivers/clk/`,提供: + +1. **消费者 API**:设备驱动程序管理时钟的简单接口 +2. **提供者 API**:实现时钟驱动程序的框架 +3. **时钟树**:层次化的父子关系 +4. **设备树集成**:从 FDT 自动配置 +5. **引用计数**:多消费者安全启用/禁用 +6. **通知链**:频率变化的事件通知 +7. **频率约束**:每个消费者的最小/最大频率管理 + +## Kconfig 配置 + +### 主配置 + +```kconfig +menuconfig RT_USING_CLK + bool "Using Common Clock Framework (CLK)" + depends on RT_USING_DM + select RT_USING_ADT_REF + default y +``` + +**在 menuconfig 中的位置**: +``` +RT-Thread Components → Device Drivers → Using Common Clock Framework (CLK) +``` + +**依赖项**: +- `RT_USING_DM`:必须首先启用 +- `RT_USING_ADT_REF`:引用计数支持(自动) + +**默认值**:启用 DM 时默认启用 + +### SCMI 时钟驱动 + +```kconfig +config RT_CLK_SCMI + bool "Clock driver controlled via SCMI interface" + depends on RT_USING_CLK + depends on RT_FIRMWARE_ARM_SCMI + default n +``` + +支持通过 ARM 系统控制和管理接口(SCMI)控制的时钟。 + +## 设备树绑定 + +### 时钟提供者属性 + +时钟提供者使用这些属性导出时钟: + +```dts +#clock-cells = ; /* 时钟说明符中的单元数 */ +clock-output-names = "name1", "name2"; /* 输出时钟名称 */ +``` + +### 时钟消费者属性 + +设备使用以下方式引用时钟: + +```dts +clocks = <&clk_provider idx>; /* 时钟 phandle 和索引 */ +clock-names = "name"; /* 时钟连接名称 */ +``` + +### 固定频率时钟示例 + +```dts +clocks { + /* 简单固定频率振荡器 */ + osc24M: oscillator-24M { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <24000000>; + clock-output-names = "osc24M"; + }; + + /* 带精度规范的固定频率 */ + osc32k: oscillator-32k { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <32768>; + clock-accuracy = <50>; /* ±50 PPM */ + clock-output-names = "osc32k"; + }; +}; +``` + +### 时钟控制器示例 + +```dts +ccu: clock-controller@1c20000 { + compatible = "vendor,clock-controller"; + reg = <0x1c20000 0x400>; + #clock-cells = <1>; + + /* 父时钟 */ + clocks = <&osc24M>, <&osc32k>; + clock-names = "hosc", "losc"; + + /* 输出时钟名称(可选)*/ + clock-output-names = "pll-cpu", "pll-ddr", "ahb1", "apb1"; +}; +``` + +### 消费者使用示例 + +```dts +/* 单时钟消费者 */ +uart0: serial@1c28000 { + compatible = "vendor,uart"; + reg = <0x1c28000 0x400>; + interrupts = <0 0 4>; + + clocks = <&ccu 64>; /* 时钟索引 64 */ + clock-names = "baudclk"; + + status = "okay"; +}; + +/* 多时钟消费者 */ +mmc0: mmc@1c0f000 { + compatible = "vendor,mmc"; + reg = <0x1c0f000 0x1000>; + + clocks = <&ccu 8>, <&ccu 9>; + clock-names = "ahb", "mmc"; + + status = "okay"; +}; +``` + +## 应用层 API + +### 概述 + +消费者 API 为设备驱动程序提供管理时钟的函数。框架使用类似 Linux 的两级 prepare/enable 模型: +- **prepare**:可能休眠,可以配置 PLL +- **enable**:原子操作,不能休眠 + +### 获取和释放时钟 + +#### rt_clk_get_by_name + +```c +struct rt_clk *rt_clk_get_by_name(struct rt_device *dev, const char *name); +``` + +通过连接名称获取时钟。 + +**参数**: +- `dev`:设备结构指针 +- `name`:时钟连接名称(匹配设备树中的 `clock-names`) + +**返回值**: +- 成功时返回时钟指针 +- 失败时返回 NULL + +**示例**: +```c +struct rt_clk *clk = rt_clk_get_by_name(dev, "baudclk"); +if (!clk) { + LOG_E("获取 baudclk 失败"); + return -RT_ERROR; +} +``` + +#### rt_clk_get_by_index + +```c +struct rt_clk *rt_clk_get_by_index(struct rt_device *dev, int index); +``` + +通过 `clocks` 属性中的索引获取时钟。 + +**参数**: +- `dev`:设备结构指针 +- `index`:时钟索引(从 0 开始) + +#### rt_clk_get_array + +```c +struct rt_clk_array *rt_clk_get_array(struct rt_device *dev); +``` + +获取设备的所有时钟作为数组。 + +**参数**: +- `dev`:设备结构指针 + +**返回值**: +- 成功时返回时钟数组指针 +- 失败时返回错误指针(使用 `rt_is_err()` 检查) + +#### rt_clk_put + +```c +void rt_clk_put(struct rt_clk *clk); +``` + +释放时钟引用。 + +### Prepare 和 Enable + +#### rt_clk_prepare + +```c +rt_err_t rt_clk_prepare(struct rt_clk *clk); +``` + +为启用准备时钟。这可能会休眠,可以配置 PLL 或执行其他不能原子完成的操作。 + +**参数**: +- `clk`:时钟指针 + +**返回值**: +- 成功时返回 `RT_EOK` +- 失败时返回错误代码 + +**注意**: +- 可能休眠——不要从原子上下文调用 +- 必须在 `rt_clk_enable()` 之前调用 +- 使用引用计数 + +#### rt_clk_unprepare + +```c +void rt_clk_unprepare(struct rt_clk *clk); +``` + +取消准备先前已准备的时钟。 + +#### rt_clk_enable + +```c +rt_err_t rt_clk_enable(struct rt_clk *clk); +``` + +启用时钟。这是一个不能休眠的原子操作。 + +**参数**: +- `clk`:时钟指针 + +**返回值**: +- 成功时返回 `RT_EOK` +- 失败时返回错误代码 + +**注意**: +- 不能休眠——可以从原子上下文安全调用 +- 必须在 `rt_clk_prepare()` 之后 +- 使用引用计数 + +#### rt_clk_disable + +```c +void rt_clk_disable(struct rt_clk *clk); +``` + +禁用先前启用的时钟。 + +#### rt_clk_prepare_enable + +```c +rt_err_t rt_clk_prepare_enable(struct rt_clk *clk); +``` + +便捷函数,准备并启用时钟。 + +**示例**: +```c +/* 典型用法 */ +ret = rt_clk_prepare_enable(clk); +if (ret != RT_EOK) { + LOG_E("启用时钟失败: %d", ret); + return ret; +} +``` + +#### rt_clk_disable_unprepare + +```c +void rt_clk_disable_unprepare(struct rt_clk *clk); +``` + +便捷函数,禁用并取消准备时钟。 + +### 频率管理 + +#### rt_clk_set_rate + +```c +rt_err_t rt_clk_set_rate(struct rt_clk *clk, rt_ubase_t rate); +``` + +设置时钟频率。 + +**参数**: +- `clk`:时钟指针 +- `rate`:期望频率(Hz) + +**返回值**: +- 成功时返回 `RT_EOK` +- 失败时返回错误代码 + +**注意**: +- 由于硬件限制,实际频率可能不同 +- 使用 `rt_clk_get_rate()` 验证实际频率 +- 可能触发通知器回调 + +**示例**: +```c +/* 将 UART 时钟设置为 48MHz */ +ret = rt_clk_set_rate(uart_clk, 48000000); +if (ret != RT_EOK) { + LOG_E("设置时钟频率失败: %d", ret); +} + +/* 验证实际频率 */ +rt_ubase_t actual_rate = rt_clk_get_rate(uart_clk); +LOG_I("时钟频率: %u Hz", actual_rate); +``` + +#### rt_clk_get_rate + +```c +rt_ubase_t rt_clk_get_rate(struct rt_clk *clk); +``` + +获取当前时钟频率。 + +**参数**: +- `clk`:时钟指针 + +**返回值**: +- 当前频率(Hz) +- 错误时返回 0 + +#### rt_clk_round_rate + +```c +rt_base_t rt_clk_round_rate(struct rt_clk *clk, rt_ubase_t rate); +``` + +获取最接近的支持频率,但不更改时钟。 + +**参数**: +- `clk`:时钟指针 +- `rate`:期望频率(Hz) + +**返回值**: +- 最接近的支持频率 +- 失败时返回负错误代码 + +#### rt_clk_set_rate_range + +```c +rt_err_t rt_clk_set_rate_range(struct rt_clk *clk, rt_ubase_t min, rt_ubase_t max); +``` + +为此消费者设置可接受的频率范围。 + +**参数**: +- `clk`:时钟指针 +- `min`:最小可接受频率(Hz) +- `max`:最大可接受频率(Hz) + +**示例**: +```c +/* UART 需要 48MHz ±2% */ +rt_clk_set_rate_range(uart_clk, 47040000, 48960000); +``` + +### 父时钟管理 + +#### rt_clk_set_parent + +```c +rt_err_t rt_clk_set_parent(struct rt_clk *clk, struct rt_clk *parent); +``` + +更改时钟父时钟(用于多路复用器时钟)。 + +**参数**: +- `clk`:时钟指针 +- `parent`:新的父时钟 + +#### rt_clk_get_parent + +```c +struct rt_clk *rt_clk_get_parent(struct rt_clk *clk); +``` + +获取当前父时钟。 + +### 相位控制 + +#### rt_clk_set_phase + +```c +rt_err_t rt_clk_set_phase(struct rt_clk *clk, int degrees); +``` + +以度为单位设置时钟相位。 + +**参数**: +- `clk`:时钟指针 +- `degrees`:相位(度,0-359) + +**返回值**: +- 成功时返回 `RT_EOK` +- 不支持时返回 `-RT_ENOSYS` + +#### rt_clk_get_phase + +```c +rt_base_t rt_clk_get_phase(struct rt_clk *clk); +``` + +获取当前时钟相位。 + +### 通知器 API + +#### rt_clk_notifier_register + +```c +rt_err_t rt_clk_notifier_register(struct rt_clk *clk, + struct rt_clk_notifier *notifier); +``` + +为时钟事件注册通知器。 + +**通知器结构**: +```c +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); +``` + +**事件消息**: +- `RT_CLK_MSG_PRE_RATE_CHANGE`:频率变化前 +- `RT_CLK_MSG_POST_RATE_CHANGE`:频率成功变化后 +- `RT_CLK_MSG_ABORT_RATE_CHANGE`:频率变化中止 + +**示例**: +```c +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("时钟频率变化: %u -> %u Hz", old_rate, new_rate); + /* 为频率变化做准备 */ + } + return RT_EOK; +} + +struct rt_clk_notifier my_notifier = { + .callback = clk_notifier_cb, +}; + +rt_clk_notifier_register(clk, &my_notifier); +``` + +## 完整应用示例 + +### 示例:带时钟管理的 UART 驱动 + +```c +#include +#include +#include + +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; + + /* 获取时钟频率 */ + clk_rate = rt_clk_get_rate(uart->clk); + if (clk_rate == 0) { + LOG_E("无效的时钟频率"); + return -RT_ERROR; + } + + /* 计算波特率分频器 */ + divisor = clk_rate / (16 * cfg->baud_rate); + + /* 配置硬件 */ + writel(divisor & 0xFF, uart->base + UART_DLL); + writel((divisor >> 8) & 0xFF, uart->base + UART_DLH); + + 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; + + /* 分配设备结构 */ + uart = rt_calloc(1, sizeof(*uart)); + if (!uart) + return -RT_ENOMEM; + + /* 映射 MMIO 区域 */ + uart->base = rt_dm_dev_iomap(dev, 0); + if (!uart->base) { + ret = -RT_ERROR; + goto err_free; + } + + /* 获取 IRQ */ + uart->irq = rt_dm_dev_get_irq(dev, 0); + + /* 获取时钟 */ + uart->clk = rt_clk_get_by_name(dev, "baudclk"); + if (!uart->clk) { + LOG_E("获取 baudclk 失败"); + ret = -RT_ERROR; + goto err_unmap; + } + + /* 准备并启用时钟 */ + ret = rt_clk_prepare_enable(uart->clk); + if (ret != RT_EOK) { + LOG_E("启用时钟失败: %d", ret); + goto err_put_clk; + } + + /* 注册串口设备 */ + ret = rt_hw_serial_register(&uart->serial, + rt_dm_dev_get_name(dev), + RT_DEVICE_FLAG_RDWR, + uart); + if (ret != RT_EOK) { + goto err_disable_clk; + } + + pdev->priv = uart; + 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; + + /* 取消注册串口设备 */ + rt_device_unregister(&uart->serial.parent); + + /* 禁用并释放时钟 */ + rt_clk_disable_unprepare(uart->clk); + rt_clk_put(uart->clk); + + /* 释放资源 */ + 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); +``` + +## 驱动实现指南 + +### 核心结构 + +#### rt_clk_ops + +```c +struct rt_clk_ops { + /* Prepare/unprepare (可能休眠) */ + 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 (原子操作) */ + 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); + + /* 频率控制 */ + 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); + + /* 父时钟控制 */ + rt_err_t (*set_parent)(struct rt_clk_cell *cell, rt_uint8_t idx); + rt_uint8_t (*get_parent)(struct rt_clk_cell *cell); +}; +``` + +### 示例:固定频率时钟驱动 + +```c +#include +#include +#include + +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; + + /* 分配驱动结构 */ + cf = rt_calloc(1, sizeof(*cf)); + if (!cf) + return -RT_ENOMEM; + + /* 从设备树读取时钟频率 */ + if ((err = rt_dm_dev_prop_read_u32(dev, "clock-frequency", &val))) { + LOG_E("缺少 clock-frequency 属性"); + goto _fail; + } + cf->fcell.fixed_rate = val; + + /* 读取可选的精度 */ + val = 0; + rt_dm_dev_prop_read_u32(dev, "clock-accuracy", &val); + cf->fcell.fixed_accuracy = val; + + /* 读取可选的时钟名称 */ + rt_dm_dev_prop_read_string(dev, "clock-output-names", + &cf->fcell.cell.name); + + /* 初始化时钟节点 */ + 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; + + /* 向框架注册 */ + if ((err = rt_clk_register(&cf->parent))) { + LOG_E("注册时钟失败: %d", err); + goto _fail; + } + + LOG_I("固定时钟 '%s' 已注册: %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); +``` + +## 最佳实践 + +### 对于消费者驱动程序 + +1. **始终使用 prepare_enable/disable_unprepare**:更简单、更安全 +2. **检查返回值**:时钟操作可能失败 +3. **平衡 enable/disable**:每次启用都要匹配一次禁用 +4. **顺序很重要**:在使用硬件之前启用时钟 +5. **处理频率变化**:如果频率变化影响操作,使用通知器 +6. **设置频率约束**:需要时使用 `rt_clk_set_rate_range()` + +### 对于提供者驱动程序 + +1. **仅实现支持的操作**:不支持的操作留 NULL +2. **尽可能缓存频率**:避免在 `get_rate()` 中访问硬件 +3. **处理引用计数**:框架管理 prepare/enable 计数 +4. **传播频率变化**:更新子时钟的缓存频率 +5. **支持多个父时钟**:用于多路复用器时钟 + +### 常见模式 + +#### 简单时钟使用 + +```c +/* 获取并启用时钟 */ +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; +} + +/* 使用硬件 */ + +/* 清理 */ +rt_clk_disable_unprepare(clk); +rt_clk_put(clk); +``` + +#### 动态频率调节 + +```c +/* 根据工作负载更改频率 */ +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; +} +``` + +## 故障排除 + +### 常见问题 + +1. **找不到时钟** + - 检查设备树:确保 `clocks` 和 `clock-names` 属性存在 + - 检查 compatible 字符串:验证时钟驱动已加载 + - 检查 Kconfig:启用时钟框架和驱动程序 + +2. **启用失败** + - 检查父时钟:父时钟必须先启用 + - 检查 prepare:必须在 enable 之前 prepare + - 检查硬件:验证时钟控制器是否可访问 + +3. **频率错误** + - 检查父频率:父时钟必须是正确的频率 + - 检查分频器:硬件可能有有限的分频器值 + - 使用 `rt_clk_round_rate()`:验证支持的频率 + +## 性能考虑 + +### 内存使用 + +- 每个时钟单元:约 80-100 字节 +- 每个消费者引用:约 40 字节 +- 时钟树开销:取决于层次深度 + +### 时序 + +- prepare/enable:可能很慢(PLL 需要 ms) +- 频率变化:可能很慢,使用通知器 +- get_rate:通常很快(已缓存) + +### 优化技巧 + +1. **缓存时钟指针**:不要重复调用 get/put +2. **批量操作**:尽可能使用时钟数组 +3. **避免不必要的频率变化**:先检查当前频率 +4. **使用 prepare_enable**:组合两个操作 + +## 相关模块 + +- **regulator**:电源管理,与时钟协调 +- **pinctrl**:引脚配置,可能需要启用时钟 +- **reset**:复位控制,与时钟启用协调 +- **pmdomain**:电源域,更高级别的电源管理 + +## 参考资料 + +- RT-Thread 源代码:`components/drivers/clk/` +- 头文件:`components/drivers/include/drivers/clk.h` +- 设备树绑定:[Linux Clock Bindings](https://www.kernel.org/doc/Documentation/devicetree/bindings/clock/) +- [RT-Thread DM 文档](../README_zh.md)