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 = 50 MHz
如果 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 = 50 MHz
SPI0 clock = 100 MHz
TIMER clock = 25 MHz
Reset
解决:
“这个模块现在是不是处于干净、已知的初始状态?”
例如:
UART0 reset = asserted
表示 UART0 被保持在 reset。
释放:
UART0 reset = deasserted
下面用一张表格从五个维度对比 Clock 与 Reset,方便你快速抓住两者的本质区别:
| 对比维度 | Clock | Reset |
|---|---|---|
| 解决问题 | 这个模块有没有运行频率? | 这个模块现在是不是处于干净、已知的初始状态? |
| 硬件行为 | 提供时钟信号,让外设能够运行 | 将外设保持在复位状态(asserted),或释放复位使其正常工作(deasserted) |
| 寄存器操作 | 读-改-写 CLOCK_ENABLE,置位/清除对应外设的 bit | 向 RESET_ASSERT / RESET_DEASSERT 寄存器写入对应外设的 bit |
| Zephyr 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
bit 0 = UART0
bit 1 = UART1
bit 2 = SPI0
bit 3 = 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
例如:
bit 0 = UART0
bit 1 = UART1
bit 2 = SPI0
bit 3 = 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 = <&clock 0>;
resets = <&reset 0>;
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);
/* 记录当前已使能的外设 */
data->enabled_mask |= bit;
return 0;
}
/*
* 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);
/* 更新记录 */
data->enabled_mask &= ~bit;
return 0;
}
/*
* 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 固定 50 MHz,真实实现需查 PLL/Divider */
*rate = 50000000;
return 0;
}
/* 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 时钟(假设 bit 31 是 CPU) */
sys_write32(BIT(31), cfg->base + CLOCK_ENABLE_REG);
return 0;
}
/* 设备配置实例:基地址来自 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
*/
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/reset_control.h>
#include <zephyr/sys/sys_io.h>
/* 寄存器偏移(对应第五节中的 RESET_CTRL) */
#define RESET_ASSERT_REG 0x00
#define RESET_DEASSERT_REG 0x04
#define RESET_STATUS_REG 0x08
/* 各外设对应的 reset bit */
#define RST_UART0 BIT(0)
#define RST_UART1 BIT(1)
#define RST_SPI0 BIT(2)
#define RST_TIMER0 BIT(3)
/* 设备配置结构体:保存寄存器基地址 */
struct my_reset_config {
uintptr_t base;
};
/*
* my_reset_assert()
* 作用:将指定外设置于复位状态(停止工作、状态清零)。
* 调用时机:外设需要重新初始化、或进入复位流程时。
* 实现:向 RESET_ASSERT 寄存器写入对应 bit。
*/
static int my_reset_assert(const struct device *dev,
reset_control_subsys_t subsys)
{
const struct my_reset_config *cfg = dev->config;
uint32_t bit = (uint32_t)(uintptr_t)subsys;
/* 写 1 到 assert 寄存器,外设进入复位状态 */
sys_write32(bit, cfg->base + RESET_ASSERT_REG);
return 0;
}
/*
* my_reset_deassert()
* 作用:释放指定外设的复位,使其开始正常工作。
* 调用时机:外设初始化时,必须先释放复位再配置寄存器。
* 实现:向 RESET_DEASSERT 寄存器写入对应 bit。
*/
static int my_reset_deassert(const struct device *dev,
reset_control_subsys_t subsys)
{
const struct my_reset_config *cfg = dev->config;
uint32_t bit = (uint32_t)(uintptr_t)subsys;
/* 写 1 到 deassert 寄存器,外设退出复位状态 */
sys_write32(bit, cfg->base + RESET_DEASSERT_REG);
return 0;
}
/* Reset Driver API 表 */
static const struct reset_control_driver_api my_reset_api = {
.assert = my_reset_assert,
.deassert = my_reset_deassert,
};
/*
* my_reset_init()
* 作用:Reset Controller 初始化。
* 调用时机:系统启动 PRE_KERNEL_1 阶段,由 Zephyr 自动调用。
* 实现:上电后默认把所有外设置于复位状态,等待各 Driver 释放。
*/
static int my_reset_init(const struct device *dev)
{
const struct my_reset_config *cfg = dev->config;
/* 上电后先 assert 所有外设,保证已知初始状态 */
sys_write32(0xFFFFFFFF, cfg->base + RESET_ASSERT_REG);
return 0;
}
/* 设备配置实例:基地址来自 Devicetree reg 属性 */
static const struct my_reset_config my_reset_cfg = {
.base = DT_REG_ADDR(DT_NODELABEL(reset)),
};
/*
* DEVICE_DT_DEFINE()
* 作用:把上面的实现注册成 Zephyr 标准 struct device。
* 调用时机:编译期静态注册,运行前即完成。
* 注意:与 Clock 同属 PRE_KERNEL_1,但 priority 略高,
* 保证 Reset 先于 Clock 完成初始化。
*/
DEVICE_DT_DEFINE(
DT_NODELABEL(reset),
my_reset_init,
NULL,
NULL,
&my_reset_cfg,
PRE_KERNEL_1,
CONFIG_RESET_INIT_PRIORITY,
&my_reset_api
);
3. UART Driver 如何调用这两个 Driver
/*
* my_uart.c
* 展示 UART Driver 如何通过 API 使用 Clock / Reset
*/
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/reset_control.h>
/*
* my_uart_init()
* 作用:UART 外设初始化。
* 调用时机:系统启动 PRE_KERNEL_2 阶段,由 Zephyr 自动调用。
* 顺序:先释放 Reset,再开启 Clock,最后配置 UART 寄存器。
*/
static int my_uart_init(const struct device *dev)
{
/* 通过 Devicetree 获取 Clock / Reset 设备指针 */
const struct device *clock_dev =
DEVICE_DT_GET(DT_CLOCKS_CTLR(DT_NODELABEL(uart0)));
const struct device *reset_dev =
DEVICE_DT_GET(DT_RESETS_CTLR(DT_NODELABEL(uart0)));
/* 获取 UART 在 Clock / Reset 中的 subsys id(即 <&clock 0> 中的 0) */
clock_control_subsys_t clock_id =
(clock_control_subsys_t)DT_CLOCKS_CELL(DT_NODELABEL(uart0), id);
reset_control_subsys_t reset_id =
(reset_control_subsys_t)DT_RESETS_CELL(DT_NODELABEL(uart0), id);
/* 1. 检查设备是否 ready */
if (!device_is_ready(clock_dev) || !device_is_ready(reset_dev)) {
return -ENODEV;
}
/* 2. 先释放 UART 的复位(否则寄存器无法配置) */
reset_control_deassert(reset_dev, reset_id);
/* 3. 再开启 UART 的时钟(没有时钟外设不工作) */
clock_control_on(clock_dev, clock_id);
/* 4. 查询时钟频率,用于计算波特率分频 */
uint32_t uart_rate;
clock_control_get_rate(clock_dev, clock_id, &uart_rate);
/* 5. 根据 uart_rate 计算并配置波特率分频寄存器 */
/* baud_div = uart_rate / baudrate; */
/* sys_write32(baud_div, ...); */
/* 6. 配置 UART 数据格式、FIFO、中断等 */
/* ... */
return 0;
}
/* UART 设备注册:PRE_KERNEL_2,晚于 Clock / Reset */
DEVICE_DT_DEFINE(
DT_NODELABEL(uart0),
my_uart_init,
NULL,
NULL,
NULL,
PRE_KERNEL_2,
CONFIG_UART_INIT_PRIORITY,
NULL
);
4. 初始化顺序总结
PRE_KERNEL_1
│
├── Reset Driver (my_reset_init: 先 assert 所有外设)
│
├── Clock Driver (my_clock_init: 再开启 CPU 时钟)
│
└── Pinmux
│
▼
PRE_KERNEL_2
│
└── UART Driver (my_uart_init: deassert → clock_on → 配置寄存器)
│
▼
POST_KERNEL
│
▼
APPLICATION
5. 关键要点回顾
- 调用时机:
my_reset_init和my_clock_init在PRE_KERNEL_1阶段由 Zephyr 自动调用,早于所有外设;my_uart_init在PRE_KERNEL_2阶段调用,此时 Clock / Reset 已就绪。 - API 解耦:UART Driver 只调用
clock_control_on()和reset_control_deassert(),不直接操作寄存器,与具体 SoC 完全解耦。 - 初始化顺序:先
reset_control_deassert()释放复位,再clock_control_on()开启时钟,最后配置 UART 寄存器——顺序不能颠倒。 - Devicetree 驱动:所有设备指针和 subsys id 都来自 Devicetree 生成的头文件,这就是第六节中 Devicetree 描述的意义所在。
└── interrupts ──> Interrupt Controller
**七、这和前面的 Driver Dependency 完全串起来了**
我们之前讲过:
UART Driver 依赖 Clock、Pinmux、Interrupt Controller。
现在可以把它完整画出来:
```bash
┌───────────────┐
│ Clock Driver │
└───────┬───────┘
│
▼
┌──────────────┐ ┌───────────────┐
│ Reset Driver │─────────>│ UART Driver │
└──────────────┘ └───────┬───────┘
│
▼
┌────────────────┐
│ struct device │
│ uart0 │
└────────────────┘
更完整一点:
SoC BSP
│
┌──────────┼──────────┐
▼ ▼ ▼
Clock Reset Pinmux
│ │ │
└──────────┼──────────┘
▼
UART
│
▼
IRQ
八、Clock Driver 本身也是 Zephyr Driver
这是非常关键的认识。
不要认为:
Clock
Reset
是 Zephyr 的"特殊魔法"。
它们本质上也是:
struct device
例如:
struct clock_config {
uintptr_t base;
};
struct clock_data {
...
};
static const struct clock_driver_api clock_api = {
.on = my_clock_on,
.off = my_clock_off,
};
然后:
DEVICE_DT_DEFINE(
DT_NODELABEL(clock),
my_clock_init,
NULL,
&clock_data,
&clock_config,
PRE_KERNEL_1,
CONFIG_CLOCK_INIT_PRIORITY,
&clock_api
);
于是:
clock
也变成了一个:
struct device
九、那么 UART 怎么找到 Clock?
通过 Devicetree。
例如:
uart0 {
clocks = <&clock 0>;
};
Driver:
const struct device *clock =
DEVICE_DT_GET(DT_CLOCKS_CTLR(DT_NODELABEL(uart0)));
然后:
if (!device_is_ready(clock)) {
return -ENODEV;
}
这和我们之前学的:
device_is_ready()
完全是一条链。
十、Clock Controller API
Zephyr 中常见的设计思路是提供 Clock Control API。
概念上可以理解成:
clock_on(clock_device, clock_id);
clock_off(clock_device, clock_id);
例如:
clock_control_on(clock_dev, clock_subsys);
最终:
UART Driver
│
▼
clock_control_on()
│
▼
Clock Driver
│
▼
CLOCK_ENABLE register
│
▼
UART clock starts
所以 UART Driver 不应该直接写:
sys_write32(..., 0x40000000);
更好的设计是:
UART
│
▼
Clock API
│
▼
Clock Driver
│
▼
SoC register
这样 UART Driver 和具体 SoC 解耦。
十一、Reset 也是一样
UART Driver 不应该知道:
RESET_CTRL = 0x40001000
它只需要:
reset_control_deassert(...);
然后:
UART Driver
│
▼
Reset API
│
▼
Reset Driver
│
▼
RESET_DEASSERT register
这就是 BSP 分层。
十二、为什么 Clock / Reset 往往属于 PRE_KERNEL_1?
因为很多外设在初始化之前必须已经有 clock。
例如:
PRE_KERNEL_1
Clock Controller
│
▼
Reset Controller
然后:
PRE_KERNEL_2
UART
SPI
I2C
GPIO
再往后:
POST_KERNEL
Network
Storage
USB
Application drivers
于是:
PRE_KERNEL_1
│
├── Clock
├── Reset
└── Pinmux
│
▼
PRE_KERNEL_2
│
├── UART
├── SPI
├── I2C
└── GPIO
│
▼
POST_KERNEL
│
▼
APPLICATION
当然,实际初始化顺序并不单纯由这个 level 决定;同一 level 内还涉及 priority 和依赖关系。前面第 13~15 篇已经铺垫了这一点。
十三、一个真正的 SoC BSP 启动过程
现在可以把整个过程串起来。
假设 SoC 上电:
下面是完整的启动时序图,用 Mermaid 时序图展示各阶段的前后关系,并标注每个阶段对应的 Zephyr 初始化级别:
这时候才开始像一台真正的 SoC。
十四、Clock Tree 才是 SoC 真正有意思的地方
真实 SoC 往往不是:
XTAL
│
├── UART
├── SPI
└── CPU
而是:
XTAL 24MHz
│
▼
PLL
│
480 MHz
│
┌──────────┼──────────┐
▼ ▼ ▼
CPU BUS PERIPH
480M 120M 60M
│
┌─────────┼─────────┐
▼ ▼ ▼
UART SPI TIMER
60M 60M 30M
于是一个 UART 的 clock frequency 可能来自:
XTAL
↓
PLL
↓
AHB
↓
APB
↓
UART clock mux
↓
UART divider
↓
UART
十四点五、完整实战:mycompany SoC 的 Clock Tree 初始化
前面讲了 Clock Tree 的概念,现在我们把它们落成真正的寄存器操作代码。下面是一个基于 mycompany SoC 的完整 Clock Tree 初始化示例,包含 PLL 配置、分频器设置、时钟源切换(mux),并展示如何通过 clock_control_get_rate() 返回 UART0 的实际频率(48 MHz)。
1. 寄存器布局定义
/*
* my_soc_clock_tree.c
* mycompany SoC Clock Tree 初始化
*
* 对应 Clock Controller 基地址:
* CLOCK_CTRL base = 0x40000000
*/
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/sys/sys_io.h>
/* ========== 寄存器偏移 ========== */
#define CLOCK_ENABLE_REG 0x00 /* 外设时钟门控 */
#define CLOCK_DIV_REG 0x04 /* 分频器配置 */
#define CLOCK_STATUS_REG 0x08 /* 状态查询 */
#define PLL_CTRL_REG 0x0C /* PLL 控制 */
#define PLL_CFG_REG 0x10 /* PLL 倍频/分频配置 */
#define CLOCK_MUX_REG 0x14 /* 时钟源选择(mux) */
/* ========== PLL 配置位域 ========== */
#define PLL_ENABLE BIT(0) /* PLL 使能 */
#define PLL_LOCKED BIT(1) /* PLL 锁定状态(只读) */
#define PLL_BYPASS BIT(2) /* PLL 旁路(直接用 XTAL) */
/* PLL 倍频系数:N = 20,即 24MHz × 20 = 480MHz */
#define PLL_MUL_SHIFT 8
#define PLL_MUL_MASK (0x1F << PLL_MUL_SHIFT)
#define PLL_MUL_20 (20 << PLL_MUL_SHIFT)
/* PLL 分频系数:M = 2,即 480MHz / 2 = 240MHz(中间频率) */
#define PLL_DIV_SHIFT 16
#define PLL_DIV_MASK (0x0F << PLL_DIV_SHIFT)
#define PLL_DIV_2 (2 << PLL_DIV_SHIFT)
/* ========== 分频器配置位域 ========== */
#define DIV_AHB_SHIFT 0 /* AHB 分频:480M / 4 = 120M */
#define DIV_AHB_MASK (0x0F << DIV_AHB_SHIFT)
#define DIV_AHB_4 (4 << DIV_AHB_SHIFT)
#define DIV_APB_SHIFT 4 /* APB 分频:120M / 2 = 60M */
#define DIV_APB_MASK (0x0F << DIV_APB_SHIFT)
#define DIV_APB_2 (2 << DIV_APB_SHIFT)
#define DIV_UART_SHIFT 8 /* UART 分频:60M / 5 = 12M */
#define DIV_UART_MASK (0x1F << DIV_UART_SHIFT)
#define DIV_UART_5 (5 << DIV_UART_SHIFT)
/* ========== 时钟源 mux 选择 ========== */
#define MUX_UART_SRC_XTAL 0 /* UART 时钟源 = XTAL 24MHz */
#define MUX_UART_SRC_PLL 1 /* UART 时钟源 = PLL 分频后 */
#define MUX_UART_SRC_SHIFT 0
#define MUX_UART_SRC_MASK (0x03 << MUX_UART_SRC_SHIFT)
/* ========== 外设时钟门控 bit ========== */
#define CLK_UART0 BIT(0)
#define CLK_UART1 BIT(1)
#define CLK_SPI0 BIT(2)
#define CLK_TIMER0 BIT(3)
2. PLL 配置函数
/*
* my_pll_init()
* 作用:配置并启动 PLL,将 24MHz XTAL 倍频到 480MHz。
* 调用时机:Clock Driver 初始化(PRE_KERNEL_1)时最先调用。
* 实现步骤:
* 1. 先配置倍频/分频系数(此时 PLL 尚未使能);
* 2. 使能 PLL;
* 3. 轮询等待 PLL 锁定(LOCKED 位置位)。
*/
static int my_pll_init(const struct device *dev)
{
const struct my_clock_config *cfg = dev->config;
uint32_t reg;
/* 1. 配置 PLL 倍频 N=20、分频 M=2,得到 480MHz */
reg = sys_read32(cfg->base + PLL_CFG_REG);
reg &= ~(PLL_MUL_MASK | PLL_DIV_MASK);
reg |= PLL_MUL_20 | PLL_DIV_2;
sys_write32(reg, cfg->base + PLL_CFG_REG);
/* 2. 使能 PLL(清除 bypass,让 PLL 真正工作) */
reg = sys_read32(cfg->base + PLL_CTRL_REG);
reg &= ~PLL_BYPASS;
reg |= PLL_ENABLE;
sys_write32(reg, cfg->base + PLL_CTRL_REG);
/* 3. 等待 PLL 锁定,最多轮询 1000 次 */
for (int i = 0; i < 1000; i++) {
reg = sys_read32(cfg->base + PLL_CTRL_REG);
if (reg & PLL_LOCKED) {
break;
}
k_busy_wait(10); /* 每次等待 10us */
}
/* 4. 检查是否真的锁定 */
reg = sys_read32(cfg->base + PLL_CTRL_REG);
if (!(reg & PLL_LOCKED)) {
return -EIO; /* PLL 锁定失败 */
}
return 0;
}
3. 分频器配置函数
/*
* my_divider_init()
* 作用:配置 AHB / APB / UART 各级分频器。
* 调用时机:PLL 锁定之后调用。
* 实现:
* AHB = 480M / 4 = 120M
* APB = 120M / 2 = 60M
* UART = 60M / 5 = 12M(后续 mux 会切到 PLL 路径)
*/
static int my_divider_init(const struct device *dev)
{
const struct my_clock_config *cfg = dev->config;
uint32_t reg;
/* 读当前分频配置寄存器 */
reg = sys_read32(cfg->base + CLOCK_DIV_REG);
/* 清除旧的 AHB / APB / UART 分频位域 */
reg &= ~(DIV_AHB_MASK | DIV_APB_MASK | DIV_UART_MASK);
/* 写入新的分频系数 */
reg |= DIV_AHB_4; /* AHB = 480M / 4 = 120M */
reg |= DIV_APB_2; /* APB = 120M / 2 = 60M */
reg |= DIV_UART_5; /* UART = 60M / 5 = 12M */
/* 写回寄存器,分频器立即生效 */
sys_write32(reg, cfg->base + CLOCK_DIV_REG);
return 0;
}
4. 时钟源切换(mux)函数
/*
* my_clock_mux_init()
* 作用:为 UART0 选择时钟源。
* 调用时机:分频器配置完成后调用。
* 实现:把 UART0 的时钟源从默认的 XTAL 切换到 PLL 分频路径。
* 这样 UART0 才能拿到 48MHz 的高频时钟。
*/
static int my_clock_mux_init(const struct device *dev)
{
const struct my_clock_config *cfg = dev->config;
uint32_t reg;
/* 读当前 mux 配置寄存器 */
reg = sys_read32(cfg->base + CLOCK_MUX_REG);
/* 清除 UART0 时钟源选择位域 */
reg &= ~MUX_UART_SRC_MASK;
/* 选择 PLL 分频路径作为 UART0 时钟源 */
reg |= (MUX_UART_SRC_PLL << MUX_UART_SRC_SHIFT);
/* 写回寄存器,mux 切换生效 */
sys_write32(reg, cfg->base + CLOCK_MUX_REG);
return 0;
}
5. 通过 get_rate() 返回 UART0 实际频率
/*
* my_clock_get_rate()
* 作用:查询指定外设的时钟频率(Hz)。
* 调用时机:UART Driver 计算波特率分频时调用。
* 实现:根据当前 mux 选择和分频器配置,实时计算并返回频率。
* 这里 UART0 走 PLL 路径:480M / 4 / 2 / 5 = 12M,
* 再经过 UART 内部 4 分频得到 48MHz 的 UART 工作时钟。
*/
static int my_clock_get_rate(const struct device *dev,
clock_control_subsys_t subsys,
uint32_t *rate)
{
const struct my_clock_config *cfg = dev->config;
uint32_t mux_reg, div_reg;
uint32_t uart_mux, uart_div;
uint32_t freq;
/* 读取当前 mux 和分频配置 */
mux_reg = sys_read32(cfg->base + CLOCK_MUX_REG);
div_reg = sys_read32(cfg->base + CLOCK_DIV_REG);
/* 取出 UART0 的时钟源选择 */
uart_mux = (mux_reg & MUX_UART_SRC_MASK) >> MUX_UART_SRC_SHIFT;
if (uart_mux == MUX_UART_SRC_XTAL) {
/* 走 XTAL 路径:直接 24MHz */
freq = 24000000;
} else {
/* 走 PLL 路径:480M / AHB分频 / APB分频 / UART分频 */
uint32_t ahb_div = (div_reg & DIV_AHB_MASK) >> DIV_AHB_SHIFT;
uint32_t apb_div = (div_reg & DIV_APB_MASK) >> DIV_APB_SHIFT;
uint32_t uart_div_val = (div_reg & DIV_UART_MASK) >> DIV_UART_SHIFT;
/* 480M / 4 / 2 / 5 = 12M */
freq = 480000000 / ahb_div / apb_div / uart_div_val;
/* UART 内部再 4 分频,得到 48MHz 的 UART 工作时钟 */
freq = freq * 4;
}
*rate = freq;
return 0;
}
6. 完整初始化流程
/*
* my_clock_init()
* 作用:Clock Tree 总初始化入口。
* 调用时机:系统启动 PRE_KERNEL_1 阶段,由 Zephyr 自动调用。
* 顺序:PLL → 分频器 → mux → 外设门控。
*/
static int my_clock_init(const struct device *dev)
{
const struct my_clock_config *cfg = dev->config;
int ret;
/* 1. 先配置并启动 PLL(24M → 480M) */
ret = my_pll_init(dev);
if (ret < 0) {
return ret;
}
/* 2. 配置 AHB / APB / UART 分频器 */
ret = my_divider_init(dev);
if (ret < 0) {
return ret;
}
/* 3. 切换 UART0 时钟源到 PLL 路径 */
ret = my_clock_mux_init(dev);
if (ret < 0) {
return ret;
}
/* 4. 开启 CPU 时钟(假设 bit 31 是 CPU) */
sys_write32(BIT(31), cfg->base + CLOCK_ENABLE_REG);
/* 5. 开启 UART0 时钟门控 */
sys_write32(CLK_UART0, cfg->base + CLOCK_ENABLE_REG);
return 0;
}
7. 验证:UART Driver 拿到 48MHz
/*
* my_uart_init()
* 作用:UART 外设初始化。
* 调用时机:系统启动 PRE_KERNEL_2 阶段,由 Zephyr 自动调用。
* 实现:通过 clock_control_get_rate() 拿到 UART0 实际频率,
* 再据此计算波特率分频。
*/
static int my_uart_init(const struct device *dev)
{
const struct device *clock_dev =
DEVICE_DT_GET(DT_CLOCKS_CTLR(DT_NODELABEL(uart0)));
clock_control_subsys_t clock_id =
(clock_control_subsys_t)DT_CLOCKS_CELL(DT_NODELABEL(uart0), id);
uint32_t uart_rate;
/* 查询 UART0 实际时钟频率 */
clock_control_get_rate(clock_dev, clock_id, &uart_rate);
/* uart_rate 此时应为 48000000(48MHz) */
/* 计算波特率分频:baud_div = uart_rate / baudrate */
/* 例如 115200 baud:48000000 / 115200 = 416 */
uint32_t baud_div = uart_rate / 115200;
/* 配置波特率分频寄存器 */
/* sys_write32(baud_div, ...); */
/* 配置 UART 数据格式、FIFO、中断等 */
/* ... */
return 0;
}
8. 关键要点回顾
- 初始化顺序:PLL 必须先锁定,才能配置分频器;分频器生效后,才能切换 mux;最后才开启外设门控。顺序颠倒会导致外设拿到错误频率甚至没有时钟。
- 频率计算链路:
24MHz XTAL → PLL ×20 → 480MHz → AHB ÷4 → 120MHz → APB ÷2 → 60MHz → UART ÷5 → 12MHz → UART 内部 ×4 → 48MHz。 - get_rate() 是实时计算:它读取当前 mux 和分频寄存器,动态算出频率,而不是返回一个写死的常量。这样 UART Driver 拿到的永远是真实频率。
- mux 是关键:同一个外设可以选 XTAL 或 PLL 作为时钟源,切换后频率完全不同,这也是 Clock Tree 最灵活也最容易出错的地方。
这才是实际 SoC BSP 中 Clock Driver 最复杂的地方。
十五、因此 Clock Driver 通常不只是 clock_on()
可能还需要:
clock_on()
clock_off()
get_rate()
set_rate()
set_parent()
get_parent()
enable_pll()
disable_pll()
set_divider()
set_mux()
例如:
clock_control_get_rate(...)
UART Driver 可能需要:
UART clock = 48 MHz
然后计算:
baudrate divisor
例如:
UART clock = 48 MHz
baudrate = 115200
Driver 根据 clock frequency 计算:
UART_DIV
所以:
Clock Driver
│
└── get_rate()
│
▼
UART Driver
│
▼
baud divisor
十六、这里出现一个非常重要的 BSP 边界
假设:
UART0
clock = 50 MHz
那么:
SoC BSP 负责:
Clock Controller
PLL
Mux
Divider
Gate
Reset Controller
UART Driver 负责:
baudrate
FIFO
TX
RX
parity
stop bits
不要混在一起。
正确结构:
Zephyr UART Driver
│
▼
Clock API
│
▼
SoC Clock Driver
│
▼
Hardware
十七、Devicetree Binding 也开始出现
Clock Controller:
compatible: "mycompany,my-soc-clock"
include:
- name: base.yaml
properties:
"#clock-cells":
const: 1
Reset:
compatible: "mycompany,my-soc-reset"
properties:
"#reset-cells":
const: 1
UART:
properties:
clocks:
required: true
resets:
required: true
于是:
DTS
│
▼
Binding
│
▼
DT validation
│
▼
generated devicetree
│
▼
Driver
这就是前面第 16~18 篇学习的内容再次被用起来。
十八、最终形成完整 SoC 依赖图
到了这里,我们可以画出目前为止整个 BSP:
┌───────────────┐
│ ARCH │
└───────┬───────┘
│
▼
┌───────────────┐
│ CPU │
└───────┬───────┘
│
┌────────────────────────┼────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Clock Ctrl │ │ Reset Ctrl │ │ Pinmux │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
└────────────────────────┼────────────────────────┘
│
▼
┌───────────────┐
│ UART │
└───────┬───────┘
│
▼
┌───────────────┐
│ IRQ │
└───────┬───────┘
│
▼
┌───────────────┐
│ struct device │
└───────────────┘
这已经是一个相当完整的 SoC BSP 架构了。
十九、从 west build 看整个链路
最终:
west build -b my_board
背后大致形成:
my_board
│
▼
board.dts
│
├── CPU
├── clock
├── reset
├── pinctrl
├── uart
└── interrupt-controller
│
▼
Devicetree
│
▼
generated headers
│
▼
Kconfig
│
▼
CMake
│
▼
SoC drivers
│
▼
Clock / Reset / IRQ / UART
│
▼
ELF
│
▼
firmware
所以:
BSP 不是一堆 .c 文件。
BSP 实际上是:
Kconfig
+
Devicetree
+
Bindings
+
CMake
+
SoC code
+
Drivers
+
Startup
+
Clock
+
Reset
+
IRQ
共同构成的一个系统。
二十、这一篇最重要的认知
到现在,你应该开始建立这样的思维:
Zephyr
│
┌───────┴───────┐
│ │
Generic SoC-specific
│ │
│ ┌──────┼──────┐
│ │ │ │
▼ ▼ ▼ ▼
UART API Clock Reset IRQ
│ │ │ │
└────────┴──────┴──────┘
│
▼
SoC HW
Zephyr 尽量提供:
Generic API
Generic device model
Generic initialization
Generic DT mechanism
而你的 BSP 提供:
这个 SoC 的 Clock 怎么开
这个 SoC 的 Reset 怎么释放
这个 SoC 的 IRQ 怎么配置
这个 SoC 的 UART 寄存器在哪里
这个 SoC 的 Pinmux 怎么设置
这就是:
“Zephyr 是通用的,BSP 把具体 SoC 翻译给 Zephyr。”

546

被折叠的 条评论
为什么被折叠?



