轻松学习Zephyr BSP: 25-BOARD、SOC、ARCH 三者到底是什么关系?

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,方便你快速抓住两者的本质区别:

对比维度ClockReset
解决问题这个模块有没有运行频率?这个模块现在是不是处于干净、已知的初始状态?
硬件行为提供时钟信号,让外设能够运行将外设保持在复位状态(asserted),或释放复位使其正常工作(deasserted)
寄存器操作读-改-写 CLOCK_ENABLE,置位/清除对应外设的 bitRESET_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_initmy_clock_initPRE_KERNEL_1 阶段由 Zephyr 自动调用,早于所有外设;my_uart_initPRE_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 初始化级别:

ApplicationUARTInterrupt ControllerPinmuxReset ControllerClock ControllerArchitecture initZephyr startupCPU Reset VectorPower OnApplicationUARTInterrupt ControllerPinmuxReset ControllerClock ControllerArchitecture initZephyr startupCPU Reset VectorPower OnPRE_KERNEL_1PRE_KERNEL_1PLL → Divider → GatePRE_KERNEL_1release peripheralsPRE_KERNEL_1PRE_KERNEL_2PRE_KERNEL_2POST_KERNEL上电复位1跳转到复位向量2启动内核3初始化时钟4释放外设复位5配置引脚复用6配置中断控制器7初始化 UART8进入应用9

这时候才开始像一台真正的 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。”

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值