Linux clock tree 的实现

1. Linux 里已经预定义了几种 clock

  • clk-fixed-rate: basic fixed-rate clock that cannot gate, 比如固定频率的 oscillator
  • clk-fixed-factor: basic fixed multiplier and divider clock that cannot gate,比如从上一级按固定比例分频
  • clk-divider: basic adjustable divider clock that cannot gate,用于分频
  • clk-composite,
  • clk-gate: basic gatable clock which can gate and ungate it's ouput
  • clk-mux: basic adjustable multiplexer clock that cannot gate,用于多路选择

2. 实现一个 clock 的驱动

对于厂商自己的 clock,上面这些预定义的就没法直接用了,需要添加驱动来支持。实现过程大概是这样的:

比如,添加 drivers/clk/clk-<vendor>.c 实现:

// 定义自己的 clock 结构,比如
struct my_clk {
    struct clk_hw hw;

    // 寄存器以及其他实现相关的内容
    ...
}

<vendor>_clk_init()
{
    // 初始化 clk_init_data,包括 name、parent_names、clk_ops 等等
    struct clk_init_data init;
    ...

    // 注册 clock
    clk_hw_register(..., &my_clk->hw);
}

其中,clock 的 name 以及 parent_names,决定了在 clock tree 里的具体位置。

而在 clk_ops 回调里,需要实现:

// 查询硬件寄存器的信息,计算 clock 的频率
unsigned long (*recalc_rate)(struct clk_hw *hw, unsigned long parent_rate);

// 返回能支持的最接近的频率
long (*round_rate)(struct clk_hw *hw, unsigned long rate, unsigned long *parent_rate);

// 把频率设置到硬件寄存器
int (*set_rate)(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate);

这样就完成了 clock 的封装。对于依赖这个 clock 的设备的驱动来说,只需要通过 Linux 里标准的 clk_xxx API 来访问,而不需要关心 clock 的实现细节,比如 clk_get_rate() 获取时钟频率,等等。

3. 使用 clock

在使用的时候,有多种方式,比如:

(1) 在 dts 里定义,比如:

osc: oscillator {
    compatible = "fixed-clock";
    clock-frequency  = <24000000>;
    ...
};

mmc_clk: mmc_clk@12340000 {
    compatible = "vendor,mmc-clock";
    reg = <0x12340000 0x100>;
    clocks = <&osc>;
    ...
};

这里定义了两个 clock。一个是 24MHz 固定频率的 osc,另一个是 mmc_clk,由刚才实现的 clock 代码来驱动,并且是以 osc 为 parent input。

这样,在具体设备的驱动里,就可以引用这个 clock 了,比如:

mmc0: mmc@56780000 {
    clocks = <&mmc_clk>;
    ...
};

(2) 定义在驱动实现里 :

对应的 drivers/clk/clk-<vendor>.c 文件里,除了实现 clock 的驱动,另外也要定义 clock tree:

<vendor>_clk_init()
{
    clk_hw *hws;

    hws[xx] = clk_hw_register_fixed_rate(NULL, "osc", NULL, 0, 24000000);
    hws[yy] = vendor_clk_hw_register("mmc_clk", ..., "osc");

    ...

    // 可选,如果导出,可以从 clk_get_sys(..., "mmc_clk") 查询到
    clk_hw_register_clkdev(..., "mmc_clk", ...);
}

其中, xx、yy 定义了对应 clock 的 dt-bindings,通常在 include/dt-bindings/clock/ 目录下。

这时,对应的 dts 是这样引用:

clks: clock-controller {
    #clock-cells = <1>;
    compatible = "vender,xxx";
    ...
};

mmc0: mmc@56780000 {
    clocks = <&clks yy>;
    ...
};

参考资料

Read More: