0%

Linux驱动之CCF子系统

概述

CCF主要用于系统clock的管理等操作。clk的种类说明:

如上图所示,时钟源大概可分为如下几种:

  • 提供基础时钟源的晶振(可分为有源晶振、无源晶振两种)
  • 用于倍频的锁相环
  • 用于分频的divider
  • 用于多路时钟源选择的mux
  • 用于时钟使能的与门电路等

而在CCF子系统的抽象中,这五种均抽象为clk,但是针对这5种类型的时钟也提供了单独的时钟注册函数(也就是对clk_register函数的封装,并针对不同的时钟类型定义了不同的结构体)。

在CCF子系统中,针对硬件时钟的操作接口,也抽象了对应的结构体struct clk_ops,包含时钟的使能接口、时钟频率的修改接口等等。而针对上述所说的不同种类的时钟源,其并不需要实现所有struct clk_ops中定义的接口。针对“时钟使能的与门电路”而言,仅需要实现enabel、disable、is_enable接口即可;针对多路时钟源选择的mux而言,则需要实现父时钟源的设置及获取的接口set_parent、get_parent等;对于倍频、分频而言,则需要实现时钟频率相关的接口set_rate、recalc_rate等。

API(consumer)

clock获取有关的API

  • 以device指针或者id字符串(可以看作name)为参数,查找clock。devm_clk_get使用了device resource management,可以自动释放
1
2
struct clk *clk_get(struct device *dev, const char *id);
struct clk *devm_clk_get(struct device *dev, const char *id);
dev和id的任意一个可以为空。如果id为空,则必须有device tree的支持才能获得device对应的clk;
根据具体的平台实现,id可以是一个简单的名称,也可以 是一个预先定义的、唯一的标识(一般在平台提供的头文件中定义,如mach/clk.h);
不可以在中断上下文调用
  • get的反向操作,一般和对应的get API成对调用
1
2
void clk_put(struct clk *clk);
void devm_clk_put(struct device *dev, struct clk *clk);
  • 使用device的name查找clock
1
struct clk *clk_get_sys(const char *dev_id, const char *con_id);
  • device tree相关的接口,直接从相应的DTS node中,以index、name等为索引,获取clk
1
2
3
struct clk *of_clk_get(struct device_node *np, int index);
struct clk *of_clk_get_by_name(struct device_node *np, const char *name);
struct clk *of_clk_get_from_provider(struct of_phandle_args *clkspec);

clock控制有关的API

  • 启动/停止clock。不会睡眠
1
2
int clk_enable(struct clk *clk);
void clk_disable(struct clk *clk);
  • 启动clock前的准备工作/停止clock后的善后工作
1
2
inline int clk_prepare(struct clk *clk)
inline void clk_unprepare(struct clk *clk)
  • clock频率的获取和设置,其中clk_set_rate可能会不成功(例如没有对应的分频比),此时会返回错误。如果要确保设置成功,则需要先调用clk_round_rate接口,得到和需要设置的rate比较接近的那个值
1
2
3
unsigned long clk_get_rate(struct clk *clk);
int clk_set_rate(struct clk *clk, unsigned long rate)
long clk_round_rate(struct clk *clk, unsigned long rate);
  • 获取/选择clock的parent clock
1
2
struct clk *clk_get_parent(struct clk *clk);
int clk_set_parent(struct clk *clk, struct clk *parent);
  • 将clk_prepare和clk_enable组合起来,一起调用
1
int clk_prepare_enable(struct clk *clk)
  • 将clk_disable和clk_unprepare组合起来,一起调用
1
void clk_disable_unprepare(struct clk *clk)

prepare/unprepare,enable/disable的说明

这两套API的本质,是把clock的启动/停止分为atomic和non-atomic两个阶段,以方便实现和调用。因此上面所说的“不会睡眠/可能会睡眠”,有两个角度的含义:一是告诉底层的clock driver,请把可能引起睡眠的操作,放到prepare/unprepare中实现,一定不能放到enable/disable中;二是提醒上层使用clock的driver,调用prepare/unprepare接口时可能会睡眠哦,千万不能在atomic上下文(例如中断处理中)调用哦,而调用enable/disable接口则可放心。

另外,clock的开关为什么需要睡眠呢?这里举个例子,例如enable PLL clk,在启动PLL后,需要等待它稳定。而PLL的稳定时间是很长的,这段时间要把CPU交出(进程睡眠),不然就会浪费CPU。

最后,为什么会有合在一起的clk_prepare_enable/clk_disable_unprepare接口呢?如果调用者能确保是在non-atomic上下文中调用,就可以顺序调用prepare/enable、disable/unprepared,为了简单,framework就帮忙封装了这两个接口。

其它接口

  • 注册/注销 clock rate改变的通知。例如某个driver关心某个clock,期望这个clock的rate改变时,通知到自己,就可以注册一个notify
    1
    2
    int clk_notifier_register(struct clk *clk, struct notifier_block *nb);
    int clk_notifier_unregister(struct clk *clk, struct notifier_block *nb);

通用API的使用说明

  • 首先,在DTS(device tree source)中,指定device需要使用的clock,如下:

    1
    2
    3
    4
    5
    /* DTS */
    device {
    clocks = <&osc 1>, <&ref 0>;
    clock-names = "baud", "register";
    };

    该DTS的含义是:

    device需要使用两个clock,“baud”和“regitser”,由clock-names关键字指定;

    baud取自“osc”的输出1,register取自“ref”的输出0,由clocks关键字指定。

  • 系统启动后,device tree会解析clock有关的关键字,并将解析后的信息放在platform_device相关的字段中

  • 具体的driver可以在probe时,以clock的名称(不提供也行)为参数,调用clk get接口,获取clock的句柄,然后利用该句柄,可直接进行enable、set rate等操作,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* driver */
int xxx_probe(struct platform_device *pdev)
{
struct clk *baud_clk;
int ret;

baud_clk = devm_clk_get(&pdev->dev, “baud”);
if (IS_ERR(clk)) {

}

ret = clk_prepare_enable(baud_clk);
if (ret) {

}
}

数据结构(provider)

CCF实现的数据结构如下:原图

  • clk_hw
    为硬件时钟源的逻辑抽象可以理解为clk provider

  • clk_core
    可以理解为clk provider的driver,包含了clk provider的访问方法、时钟频率设定、支持的时钟频率、父时钟源的个数以及父时钟源的名称等内容

  • clk
    可以理解clk consumer,包含了指向clk provider的指针、使用者的设备名称、使用者所定义的时钟别名(con_id)

  • clk_ops
    主要是clk provider的操作接口,包含prepare与unprepare操作接口(这两个接口执行时允许sleep,主要是用的互斥锁)、enable与disable接口(这两个接口执行时不允许sleep,里面使用了自旋锁),也包含了速率设置接口、重新计算速率接口、parent设置与获取接口等等,通过这些接口可实现时钟频率的修改、父时钟源的选择、时钟的使能与否等功能

  • clk_init_data
    描述该clock的静态数据,clock provider负责把系统中每个clock的静态数据准备好,然后交给clock framework的核心逻辑,剩下的事情,clock provider就不用操心了。这个过程,就是clock driver的编写过程

ccf的驱动框架也是与设备驱动模型类似的结构,clk_core用于抽象设备树驱动的方法类似于device_driver而clk则更像是一个device,在soc内部有很多控制器,这些控制器都挂载在时钟树下面,这些控制器就是consumer,他们会用到clk数据结构用于使能失能时钟,以及设置时钟频率等操作。而clk_core结构体则抽象了时钟树中的每一个节点组织成树状结构来描述整个时钟树。下面看下clk_core在内核中的组织关系结构图,如下:原图

实现

clock分类及register

根据clock的特点,clock framework将clock分为fixed rate、gate、devider、mux、fixed factor、composite六类,每一类clock都有相似的功能、相似的控制方式,因而可以使用相同的逻辑s,统一处理,这充分体现了面向对象的思想。

  • fixed rate clock
    这一类clock具有固定的频率,不能开关、不能调整频率、不能选择parent、不需要提供任何的clk_ops回调函数,是最简单的一类clock。 可以直接通过DTS配置的方式支持,clock framework core能直接从DTS中解出clock信息,并自动注册到kernel,不需要任何driver支持。 clock framework使用struct clk_fixed_rate结构抽象这一类clock,另外提供了一个接口,可以直接注册fixed rate clock,如下:
1
2
3
4
5
6
7
8
9
10
struct clk_fixed_rate {
struct clk_hw hw;
unsigned long fixed_rate;
unsigned long fixed_accuracy;
unsigned long flags;
};
...
struct clk *clk_register_fixed_rate(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
unsigned long fixed_rate);
  • gate clock
    这一类clock只可开关(会提供.enable/.disable回调),可使用下面接口注册:
1
2
3
4
struct clk *clk_register_gate(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
void __iomem *reg, u8 bit_idx,
u8 clk_gate_flags, spinlock_t *lock);
  • divider clock
    这一类clock可以设置分频值(因而会提供.recalc_rate/.set_rate/.round_rate回调),可通过下面两个接口注册:
1
2
3
4
5
#define clk_register_divider(dev, name, parent_name, flags, reg, shift, width, \
clk_divider_flags, lock) \
clk_register_divider_table((dev), (name), (parent_name), (flags), \
(reg), (shift), (width), \
(clk_divider_flags), NULL, (lock))
  • mux clock
    这一类clock可以选择多个parent,因为会实现.get_parent/.set_parent/.recalc_rate回调,可通过下面两个接口注册:
1
2
3
4
5
#define clk_register_mux(dev, name, parent_names, num_parents, flags, reg,    \
shift, width, clk_mux_flags, lock) \
clk_register_mux_table((dev), (name), (parent_names), (num_parents), \
(flags), (reg), (shift), BIT((width)) - 1, \
(clk_mux_flags), NULL, (lock))
  • fixed factor clock
    这一类clock具有固定的factor(即multiplier和divider),clock的频率是由parent clock的频率,乘以mul,除以div,多用于一些具有固定分频系数的clock。由于parent clock的频率可以改变,因而fix factor clock也可该改变频率,因此也会提供.recalc_rate/.set_rate/.round_rate等回调。
1
2
3
struct clk *clk_register_fixed_factor(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
unsigned int mult, unsigned int div);
  • composite clock
    顾名思义,就是mux、divider、gate等clock的组合,可通过下面接口注册:
1
2
3
4
5
6
struct clk *clk_register_composite(struct device *dev, const char *name,
const char * const *parent_names, int num_parents,
struct clk_hw *mux_hw, const struct clk_ops *mux_ops,
struct clk_hw *rate_hw, const struct clk_ops *rate_ops,
struct clk_hw *gate_hw, const struct clk_ops *gate_ops,
unsigned long flags);

上面这些注册接口的实现逻辑

clk注册流程如下:原图

驱动针对不同类型的clock节点提供了几种不同的注册函数,驱动开发人员一般不需要自己编写对应的驱动而是尽可能调用系统提供的接口,而对于系统而言也是尽可能的复用函数,这些注册函数都是通过调用clk_hw_register函数来实现自己的功能。

这里有一个map映射没有涉及,ccf子系统提供了clk provider的map,这种map机制可以理解为定义了clk consumer与clk_provider的映射关系,即该clk provider可以给哪些clk consumer提供时钟(如针对非设备树模式,则定义了clk consumer的设备名称、clk consumer的时钟使用名称)。

系统启动阶段对设备树的处理

clk初始化流程如下:原图

上面介绍了clk的注册流程,在上图中展示了系统启动过程中,解析设备树按照默认配置来设置系统时钟树的流程,下面给出详细说明:

  • 通常在Clock驱动中会有一个CLK_OF_DECLARE宏,该宏用于声明一个struct of_device_id数据结构,并且最终在链接的时候会将数据结构链接在一个叫做__of_clk_table的段中,这个段的定义位于vmlinux.lds.h文件中
  • 在内核启动过程中,最终会调用到of_clk_init函数,在该函数中会去访问__of_clk_table段中的内容,由于此时内核已经调用了unflatten_device_tree()接口,完成了Device Tree的解析过程,在of_clk_init函数中,会根据struct of_device_id中的内容去匹配Device Node
  • 最终调用__of_clk_table中匹配上的struct of_device_id中的函数,也就是Clock驱动的入口函数了,此时顺着这个入口函数,可以完成一系列的操作,比如clk_ops中的函数实现,注册进CCF框架等
  • 当驱动完成注册之后,Consumer Driver的Device Node和Clock Provider建立了联系,Consumer Driver便可通过CCF接口来操作时钟了

参考文献

http://www.wowotech.net/pm_subsystem/clk_overview.html
http://www.wowotech.net/pm_subsystem/clock_provider.html
http://www.wowotech.net/pm_subsystem/clock_framework_core.html
https://blog.csdn.net/lickylin/article/details/107729156?spm=1001.2014.3001.5501
https://www.qter.org/portal.php?mod=view&aid=7774