
Clock Reset SoC BSP摘要:本文讲解 Zephyr SoC BSP 中 Clock 与 Reset 两大核心硬件模块。文章从整体架构出发,说明 Clock 解决"模块有没有运行频率"、Reset 解决"模块是否处于已知初始状态"这两个不同问题;随后以寄存器级示例展示 Clock/Reset Controller 的原始操作,并引出 Devicetree 如何描述这些硬件。接着梳理 Clock/Reset Driver 作为 Zephyr 标准struct device的定位、UART 通过 Devicetree 查找并调用 Clock/Reset API 的完整链路,以及它们为何属于PRE_KERNEL_1初始化阶段。最后串起 SoC 上电启动流程、Clock Tree 与频率计算、BSP 分层边界,并给出从west build到固件生成的完整视角,帮助读者建立"Zephyr 通用、BSP 翻译具体 SoC"的核心认知。Clock / Reset:SoC BSP 真正开始"像 SoC"前面我们已经把:BOARD → SOC → ARCH → IRQ → Driver → Device这一整条链路串起来了。但到这里还缺了两个真正属于 SoC 硬件本身的东西:Clock Reset因为一个真实 SoC 不是"CPU + 一堆寄存器"这么简单。CPU、UART、SPI、I2C、Timer、GPIO……这些外设通常都需要:Clock:给模块提供时钟 Reset:让模块进入已知初始状态所以从这一篇开始,你写的 BSP 才真正开始具有:“这个 SoC 自己的硬件特征”。一、先建立整体认识假设我们自己设计了一个 SoC:┌─────────────────────┐ │ SoC │ │ │ XTAL ──PLL ─────│──CPU │ │ │ │──Clock Controller │ │ │ │ │ ├── UART0 │ │ ├── SPI0 │ │ ├── GPIO │ │ └── TIMER │ │ │ │──Reset Controller │ │ │ │ │ ├── UART0 │ │ ├── SPI0 │ │ ├── GPIO │ │ └── TIMER │ └─────────────────────┘Zephyr BSP 要解决的不是:“Clock 是什么?”而是:Zephyr 中的 Driver 如何控制这个 SoC 的 Clock / Reset 硬件?二、Clock 到底解决什么问题?例如 UART0:UART0 │ └── clock=50MHz如果 UART0 没有 clock:CPU ──write──UART register │ X 没有时钟UART 根本不会工作。所以典型启动过程实际上是:Power On │ ▼ Reset │ ▼ Clock 初始化 │ ▼ Peripheral Reset Release │ ▼ UART Driver Init │ ▼ UART Ready三、Reset 和 Clock 是两个不同的问题这是 BSP 初学者非常容易混淆的地方。Clock解决:“这个模块有没有运行频率?”例如:UART0 clock=50MHz SPI0 clock=100MHz TIMER clock=25MHzReset解决:“这个模块现在是不是处于干净、已知的初始状态?”例如:UART0 reset=asserted表示 UART0 被保持在 reset。释放:UART0 reset=deasserted下面用一张表格从五个维度对比 Clock 与 Reset,方便你快速抓住两者的本质区别:对比维度ClockReset解决问题这个模块有没有运行频率?这个模块现在是不是处于干净、已知的初始状态?硬件行为提供时钟信号,让外设能够运行将外设保持在复位状态(asserted),或释放复位使其正常工作(deasserted)寄存器操作读-改-写CLOCK_ENABLE,置位/清除对应外设的 bit向RESET_ASSERT/RESET_DEASSERT寄存器写入对应外设的 bitZephyr API 示例clock_control_on(clock_dev, clock_id)/clock_control_off(clock_dev, clock_id)reset_control_assert(reset_dev, reset_id)/reset_control_deassert(reset_dev, reset_id)初始化阶段PRE_KERNEL_1(早于所有外设)PRE_KERNEL_1(早于所有外设,且 priority 略高于 Clock)之后 UART0 才开始正常工作。四、一个真实 SoC 的 Clock Controller假设我们设计:CLOCK_CTRL base=0x40000000寄存器:0x00 CLOCK_ENABLE 0x04 CLOCK_DIV 0x08 CLOCK_STATUS其中:CLOCK_ENABLE bit0=UART0 bit1=UART1 bit2=SPI0 bit3=TIMER0那么:# define CLK_UART0 BIT(0)# define CLK_UART1 BIT(1)# define CLK_SPI0 BIT(2)# define CLK_TIMER0 BIT(3)开启 UART0:reg=sys_read32(CLOCK_ENABLE);reg|=CLK_UART0;sys_write32(reg, CLOCK_ENABLE);这就是最原始的 Clock Controller。五、Reset Controller 也类似假设:RESET_CTRL base=0x40001000寄存器:0x00 RESET_ASSERT 0x04 RESET_DEASSERT 0x08 RESET_STATUS例如:bit0=UART0 bit1=UART1 bit2=SPI0 bit3=TIMER0初始化 UART:reset_deassert(UART0);最终可能就是:sys_write32(BIT(0), RESET_DEASSERT);六、那么 Zephyr 怎么知道这些东西?答案仍然是:Devicetree这就是前面学习 Devicetree 的真正意义。例如:clock: clock-controller@40000000{compatible="mycompany,my-soc-clock";reg=0x40000000 0x1000;#clock-cells = 1;};reset: reset-controller@40001000{compatible="mycompany,my-soc-reset";reg=0x40001000 0x1000;#reset-cells = 1;};然后 UART:uart0: serial@40010000{compatible="mycompany,my-uart";reg=0x40010000 0x1000;clocks=clock0;resets=reset0;interrupts=5;status="okay";};这里已经出现了一条非常重要的关系:UART0 │ ├── clocks ──Clock Controller │ ├── resets ──Reset Controller │ **六点五、完整实战:Clock / Reset Driver 代码示例** 前面已经讲清楚了概念和 Devicetree 描述方式,现在我们把它们落成真正的 Zephyr Driver 代码。下面是一个完整的、可编译的 Clock / Reset Driver 示例,包含`clock_control_on/off`和`reset_control_deassert/assert`的具体实现。 **1. Clock Driver 完整实现** ```c /* * my_soc_clock.c * SoC Clock Controller Driver * * 对应 Devicetree 节点: * clock: clock-controller@40000000 */#include zephyr/kernel.h#include zephyr/device.h#include zephyr/drivers/clock_control.h#include zephyr/sys/sys_io.h/* 寄存器偏移(对应第四节中的 CLOCK_CTRL) */#define CLOCK_ENABLE_REG 0x00#define CLOCK_DIV_REG 0x04#define CLOCK_STATUS_REG 0x08/* 各外设对应的 clock bit(对应第四节中的定义) */#define CLK_UART0 BIT(0)#define CLK_UART1 BIT(1)#define CLK_SPI0 BIT(2)#define CLK_TIMER0 BIT(3)/* 设备配置结构体:保存寄存器基地址 */ struct my_clock_config{uintptr_t base;};/* 设备运行时数据:可保存当前状态 */ struct my_clock_data{uint32_t enabled_mask;};/* * my_clock_on()* 作用:开启指定外设的时钟。 * 调用时机:外设 Driver 初始化时,或运行时需要恢复时钟时。 * 实现:读-改-写 CLOCK_ENABLE 寄存器,置位对应 bit。 */ static int my_clock_on(const struct device *dev, clock_control_subsys_t subsys){const struct my_clock_config *cfg=dev-config;struct my_clock_data *data=dev-data;uint32_t bit=(uint32_t)(uintptr_t)subsys;uint32_t reg;/* 读当前使能寄存器 */ reg=sys_read32(cfg-base + CLOCK_ENABLE_REG);/* 置位对应外设的 clock bit */ reg|=bit;/* 写回寄存器,时钟真正开始供给 */ sys_write32(reg, cfg-base + CLOCK_ENABLE_REG);/* 记录当前已使能的外设 */ >enabled_mask|=bit;return0;}/* * my_clock_off()* 作用:关闭指定外设的时钟,用于省电或外设休眠。 * 调用时机:外设进入低功耗模式,或 Driver 卸载时。 * 实现:读-改-写 CLOCK_ENABLE 寄存器,清除对应 bit。 */ static int my_clock_off(const struct device *dev, clock_control_subsys_t subsys){const struct my_clock_config *cfg=dev-config;struct my_clock_data *data=dev-data;uint32_t bit=(uint32_t)(uintptr_t)subsys;uint32_t reg;/* 读当前使能寄存器 */ reg=sys_read32(cfg-base + CLOCK_ENABLE_REG);/* 清除对应外设的 clock bit */ reg=~bit;/* 写回寄存器,时钟停止供给 */ sys_write32(reg, cfg-base + CLOCK_ENABLE_REG);/* 更新记录 */ >enabled_mask=~bit;return0;}/* * my_clock_get_rate()* 作用:查询指定外设的时钟频率(Hz)。 * 调用时机:UART Driver 计算波特率分频时调用。 * 实现:这里简化为固定频率,真实 SoC 需要遍历 Clock Tree。 */ static int my_clock_get_rate(const struct device *dev, clock_control_subsys_t subsys, uint32_t *rate){/* 简化:UART0 固定50MHz,真实实现需查 PLL/Divider */ *rate=50000000;return0;}/* Clock Driver API 表:Zephyr 通过这张表调用我们的实现 */ static const struct clock_control_driver_api my_clock_api={.on=my_clock_on, .off=my_clock_off, .get_rate=my_clock_get_rate,};/* * my_clock_init()* 作用:Clock Controller 初始化。 * 调用时机:系统启动 PRE_KERNEL_1 阶段,由 Zephyr 自动调用。 * 实现:先复位所有外设时钟,再开启 CPU 必需的时钟。 */ static int my_clock_init(const struct device *dev){const struct my_clock_config *cfg=dev-config;/* 上电后先关闭所有外设时钟,保证已知初始状态 */ sys_write32(0x0, cfg-base + CLOCK_ENABLE_REG);/* 开启 CPU 时钟(假设 bit31是 CPU) */ sys_write32(BIT(31), cfg-base + CLOCK_ENABLE_REG);return0;}/* 设备配置实例:基地址来自 Devicetree reg 属性 */ static const struct my_clock_config my_clock_cfg={.base=DT_REG_ADDR(DT_NODELABEL(clock)),};/* 设备运行时数据实例 */ static struct my_clock_data my_clock_data;/* * DEVICE_DT_DEFINE()* 作用:把上面的实现注册成 Zephyr 标准 struct device。 * 调用时机:编译期静态注册,运行前即完成。 * 注意:PRE_KERNEL_1 + CONFIG_CLOCK_INIT_PRIORITY * 保证 Clock 先于所有外设初始化。 */ DEVICE_DT_DEFINE(DT_NODELABEL(clock), my_clock_init, NULL,my_clock_data,my_clock_cfg, PRE_KERNEL_1, CONFIG_CLOCK_INIT_PRIORITY,my_clock_api);2. Reset Driver 完整实现/* * my_soc_reset.c * SoC Reset Controller Driver * * 对应 Devicetree 节点: * reset: reset-controller@40001000 */#includezephyr/kernel.h#includezephyr/device.h#includezephyr/drivers/reset_control.h#includezephyr/sys/sys_io.h/* 寄存器偏移(对应第五节中的 RESET_CTRL) */#defineRESET_ASSERT_REG0x00#defineRESET_DEASSERT_REG0x04#defineRESET_STATUS_REG0x08/* 各外设对应的 reset bit */#defineRST_UART0BIT(0)#defineRST_UART1BIT(1)#defineRST_SPI0BIT(2)#defineRST_TIMER0BIT(3)/* 设备配置结构体:保存寄存器基地址 */structmy_reset_config{uintptr_tbase;};/* * my_reset_assert() * 作用:将指定外设置于复位状态(停止工作、状态清零)。 * 调用时机:外设需要重新初始化、或进入复位流程时。 * 实现:向 RESET_ASSERT 寄存器写入对应 bit。 */staticintmy_reset_assert(conststructdevice*dev,reset_control_subsys_tsubsys){conststructmy_reset_config*cfg=dev-config;uint32_tbit=(uint32_t)(uintptr_t)subsys;/* 写 1 到 assert 寄存器,外设进入复位状态 */sys_write32(bit,cfg-base+RESET_ASSERT_REG);return0;