转载需声明出处:https://blog.csdn.net/chenjiebing2016/article/details/83058816

内核把驱动分为三部分,分别为为框架实现层(与具体soc平台相关)、框架层(内核自身)、驱动层(调用框架提供的api)

今天,我们将建立在这个框架思想上来分析时钟系统使用过程

以rk3399上的rk808 pmic为例

为什么要选用这个?

rk808 pmic上有两个clock输出,其中一路提供给蓝牙模块,由于没有具体的soc datasheet,只能以这个来分析。具体流程框架都是一样的,只是涉及的具体平台的操作实现内容不一样。

由于是内核4.4,插讲device tree

这里只是再分析框架的同时需要我们要理解设备树的简单知识,设备树并不是很难,我们只是学习怎么使用设备树,不必深入分析

    wireless-bluetooth {
        compatible = "bluetooth-platdata";
        clocks = <&rk808 1>;
        clock-names = "ext_clock";

        //wifi-bt-power-toggle;
        uart_rts_gpios = <&gpio2 19 GPIO_ACTIVE_LOW>; /* GPIO2_C3 */
        pinctrl-names = "default", "rts_gpio";
        pinctrl-0 = <&uart0_rts>;
        pinctrl-1 = <&uart0_gpios>;
        //BT,power_gpio  = <&gpio3 19 GPIO_ACTIVE_HIGH>; /* GPIOx_xx */
        BT,reset_gpio    = <&gpio0 9 GPIO_ACTIVE_HIGH>; /* GPIO0_B1 */
        BT,wake_gpio     = <&gpio2 26 GPIO_ACTIVE_HIGH>; /* GPIO2_D2 */
        BT,wake_host_irq = <&gpio0 4 GPIO_ACTIVE_HIGH>; /* GPIO0_A4 */
        status = "okay";
    };

我们只分析红色部分,其他部分大家可以上谷歌百度查阅资料,或者发邮件到我的个人邮箱咨询 jiebing2016@163.com

关于红色部分怎么在内核驱动里面使用

内核有一套完整的of开头api来解析,建议多看源码,加深对device tree的理解。

以下分析使用:

我们在设备驱动代码中仅使用以下两个api即打开了对应的时钟

data->ext_clk = devm_clk_get(dev, "ext_clock");
    if (IS_ERR(data->ext_clk)) {
        LOG("%s: clk_get failed!!!.\n", __func__);
    } else {
        clk_prepare_enable(data->ext_clk);
    }

我们解析来分析它的流程

"ext_clock"为设备树中 clock-names = "ext_clock";

继续追源码

dev_id为设备的名称,对应设备树节点的 wireless-bluetooth

con_id为“ext_clock”

我们继续分析__of_clk_get_by_name

of_property_match_string查找ext_clock是否在clock-names的属性中,我们从设备树中看出 clock-names的属性值为“ext_clock”

结果返回0,即index为0

index是什么意思?

它代表的是属性值的编号。

比如clock-names=“ext_clock”,"xxxx";

其中index 0为“ext_clock, index 1 为“xxxx”

继续分析__of_clk_get

这个api很重要,为什么说很重要,很多人在学习设备设备树的时候不知道

 clocks = <&rk808 1>;

这个尖括号里面代表的意思

我们一起分析,第一个参数是device_node,第二个参数是设备树中的clocks ,那#clock-cells是啥?

这个是另一节点里面的属性

 

为什么会扯到这个节点。这就是这个api的重要性,我简单解释下这个api的作用:

 clocks = <&rk808 1>;

&rk808引用的是rk808这个节点,如上图

其中#clock-cells是用来表示 clocks 这个属性参数的个数

#clock-cells=<1>;表示参数只有一个

参数值为1

那么解析出来的参数又存在哪里?

struct of_phandle_args clkspec;

struct of_phandle_args {
    struct device_node *np;   //引用到的节点
    int args_count;   //参数数量
    uint32_t args[MAX_PHANDLE_ARGS];参数
};

这个参数很重要,我们暂且记住,后面时钟用到了再说

__of_clk_get_from_provider(&clkspec, dev_id, con_id, true);

继续分析

遍历of_clk_providers链表

获得struct of_clk_provider *provider;

这结构体是用来干嘛的

重点,函数指针,获得clk结构体的函数,分析到现在,终于知道了clk是怎么来的,那么,这个函数指针又是在哪被赋值的呢?

既然有遍历,就有add链表。

我们追到了这个api,这个非常关键,它将由具体平台的实现框架进行调用。

这个api即为刚才那个函数指针

我们注意到了idx,这个就是我们提到的参数

struct clk_onecell_data {
    struct clk **clks;
    unsigned int clk_num;
};

最后返回一个clk_data->clks[idx];,这代表是什么意思?

从函数看出它应该是一个存放clk指针的数组,取参数对应的clk

函数指针通过一下api传入

注意:回调函数由内核框架提供,固定。所以,这个of_clk_add_provider的最后一个参数void*data只能为struct clk_onecell_data 类型

clk设备注册流程:

 devm_clk_register

              clk_register

                              core->ops = hw->init->ops;  //时钟操作函数赋值
                               hw->core = core;

                               clk_hw_create_clk

 

简单说下:

创建clk,并对其中成员进行赋值

其中的core里面的    const struct clk_ops    *ops;即为时钟操作函数

讲了这里,基本结束

总结框架:

devm_clk_register返回一个时钟指针

struct clk_onecell_data {
    struct clk **clks;
    unsigned int clk_num;
};
返回的clk指针存放在这个结构体,设备树的参数代表取得是哪一个clk

devm_clk_get获得对应的clk,clk_prepare_enable调用对应的硬件操作函数,

具体的代码,大家可以结合自己的平台分析,框架不变。

 

接下来将发布:linux audio驱动框架,详细分析。敬请关注!

 

 

 

 

 

Logo

更多推荐