Linux clock子系统【3】-i2c控制器打开时钟的流程分析(devm_clk_get)(consumer侧)
Linux clock子系统【3】-i2c控制器获取时钟的流程分析
文章目录
前言
- i2c控制器获取时钟的流程分析
一、硬件流程图
简化如下:
二、晶振设备树描述
先来看看晶振("Clock providers"
)
osc: clock@1 {
compatible = "fixed-clock";
reg = <1>;
#clock-cells = <0>;
clock-frequency = <24000000>;
clock-output-names = "osc";
};
/*根据compatible可以找到对应的驱动,驱动程序将晶振的频率记录下来,以后作为计算的基准。*/
/*然后再是PLL的设备节点*/
clks: ccm@020c4000 {
compatible = "fsl,imx6ul-ccm";
reg = <0x020c4000 0x4000>;
interrupts = <GIC_SPI 87 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 88 IRQ_TYPE_LEVEL_HIGH>;
#clock-cells = <1>;
clocks = <&ckil>, <&osc>, <&ipp_di0>, <&ipp_di1>;
clock-names = "ckil", "osc", "ipp_di0", "ipp_di1";
};
/*设备节点本身非常简单,复杂的是它对应的驱动程序。在驱动程序里面,肯定会根据reg获得寄存器的地址,然后设置各种内容*/
/*我们可以为它们配上一个ID。在设备树中的#clock-cells = <1>;表示 用多少个u32位来描述消费者。在本例中使用一个u32来描述。*/
大部分的芯片为了省电,它的外部模块时钟平时都是关闭的,只有在使用某个模块时,才设置相应的寄存器开启对应的时钟。
这些使用者各有不同,要怎么描述这些使用者呢?
我们可以为它们配上一个ID。在设备树中的#clock-cells = <1>
;表示 用多少个u32
位来描述消费者。在本例中使用一个u32
来描述。
这些ID值由谁提供的?
是由驱动程序提供的,该节点会对应一个驱动程序,驱动程序给硬件(消费者)都分配了一个ID,所以说复杂的操作都留给驱动程序来做。
三、 I2CX时钟设备树描述
消费者(“Clock consumers
”)想使用时钟时,首先要找到时钟的直接提供者,向它发出申请。以I2C
控制器为例:
i2c1: i2c@021a0000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
reg = <0x021a0000 0x4000>;
interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_I2C1>;
status = "disabled";
};
在clock
属性里,首先要确定向谁发出时钟申请,这里是向clocks
发出申请,然后确定想要时钟提供者提供哪一路时钟,这里是IMX6UL_CLK_I2C1
,在驱动程序里定义了该宏,每种宏对应了一个时钟ID
。
因此,我们只需要在设备节点定义clocks
这个属性,这个属性确定时钟提供者,然后确定时钟ID
,也就是向时钟提供者申请哪一路时钟。
那么我这个设备驱动程序,怎么去使用这些时钟呢? 以前的驱动程序:clk_get(NULL, "name")
;clk_prepare_enable(clk)
; 现在的驱动程序:of_clk_get(node, 0)
; clk_prepare_enable(clk)
;
四、驱动中获得/使能时钟
4.1 流程源码分析
4.1.1 devm_clk_get(struct device *dev, const char *id)
我们在设备驱动代码中仅使用以下两个api即打开了对应的时钟
/* Get I2C clock */
i2c_imx->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(i2c_imx->clk)) {
dev_err(&pdev->dev, "can't get I2C clock\n");
return PTR_ERR(i2c_imx->clk);
}
ret = clk_prepare_enable(i2c_imx->clk);
if (ret) {
dev_err(&pdev->dev, "can't enable I2C clock\n");
return ret;
}
drivers/clk/clk-devres.c(
struct clk *devm_clk_get(struct device *dev, const char *id)
{
struct clk **ptr, *clk;
ptr = devres_alloc(devm_clk_release, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return ERR_PTR(-ENOMEM);
clk = clk_get(dev, id);
if (!IS_ERR(clk)) {
*ptr = clk;
devres_add(dev, ptr);
} else {
devres_free(ptr);
}
return clk;
}
)
struct clk *clk_get(struct device *dev, const char *con_id)
{
const char *dev_id = dev ? dev_name(dev) : NULL;
struct clk *clk;
/*dev_id为设备的名称,对应设备树节点的 21a0000.i2c(dev.of_node->name)*/
/*con_id为“clock-names”*/
pr_info("[%s]_%s\n\n",__FUNCTION__,dev_id);/*[clk_get]_21a0000.i2c*/
if (dev) {
clk = __of_clk_get_by_name(dev->of_node, dev_id, con_id);
if (!IS_ERR(clk) || PTR_ERR(clk) == -EPROBE_DEFER)
return clk;
}
return clk_get_sys(dev_id, con_id);
}
static struct clk *__of_clk_get_by_name(struct device_node *np,
const char *dev_id,
const char *name)
{
struct clk *clk = ERR_PTR(-ENOENT);
/* Walk up the tree of devices looking for a clock that matches */
while (np) {
int index = 0;
/*
* For named clocks, first look up the name in the
* "clock-names" property. If it cannot be found, then
* index will be an error code, and of_clk_get() will fail.
*/
if (name)
index = of_property_match_string(np, "clock-names", name);
clk = __of_clk_get(np, index, dev_id, name);
if (!IS_ERR(clk)) {
break;
} else if (name && index >= 0) {
if (PTR_ERR(clk) != -EPROBE_DEFER)
pr_err("ERROR: could not get clock %s:%s(%i)\n",
np->full_name, name ? name : "", index);
return clk;
}
/*
* No matching clock found on this node. If the parent node
* has a "clock-ranges" property, then we can try one of its
* clocks.
*/
np = np->parent;
if (np && !of_get_property(np, "clock-ranges", NULL))
break;
}
return clk;
}
/*of_property_match_string查找ext_clock是否在clock-names的属性中,我们从设备树中看出 clock-names的属性值为“ext_clock”,结果返回0,即index为0*/
/*它代表的是属性值的编号。
比如clock-names=“ext_clock”,"xxxx";
其中index 0为“ext_clock, index 1 为“xxxx”*/
static struct clk *__of_clk_get(struct device_node *np, int index,
const char *dev_id, const char *con_id)
{
struct of_phandle_args clkspec;
struct clk *clk;
int rc;
if (index < 0)
return ERR_PTR(-EINVAL);
/*
struct of_phandle_args {
struct device_node *np; //引用到的节点
int args_count; //参数数量
uint32_t args[MAX_PHANDLE_ARGS];参数
};*/
/*clocks = <&clks IMX6UL_CLK_I2C1>;*/
/*clkspec->np=(ccm)(&clks),clkspec->args_count=1,clkspec->args=IMX6UL_CLK_I2C1*/
rc = of_parse_phandle_with_args(np, "clocks", "#clock-cells", index,
&clkspec);
if (rc)
return ERR_PTR(rc);
clk = __of_clk_get_from_provider(&clkspec, dev_id, con_id);
of_node_put(clkspec.np);
return clk;
}
/*这个 of_parse_phandle_with_args很重要,为什么说很重要,很多人在学习设备设备树的时候不知道clocks = <&clks 156>;这个尖括号里面代表的意思*/
/*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, "21a0000.i2c", NULL, true);*/
__of_clk_get_from_provider(&clkspec, dev_id, con_id, true);
drivers/clk/clk.c(
struct clk *__of_clk_get_from_provider(struct of_phandle_args *clkspec,
const char *dev_id, const char *con_id){
struct of_clk_provider *provider;
struct clk *clk = ERR_PTR(-EPROBE_DEFER);
if (!clkspec)
return ERR_PTR(-EINVAL);
/* Check if we have such a provider in our array */
mutex_lock(&of_clk_mutex);
list_for_each_entry(provider, &of_clk_providers, link) {
if (provider->node == clkspec->np)/*ccm@020c4000*/
/*clks[IMX6UL_CLK_I2C1] = imx_clk_gate2("i2c1", "perclk", base + 0x70, 6);/*以i2c为例*/*/
/*struct clk_hw *__clk_get_hw(struct clk *clk)*/
clk = provider->get(clkspec, provider->data);(IMX6UL_CLK_I2C1,clks[])(获取时钟提供者)
if (!IS_ERR(clk)) {
/*从clk[]获取消费者时钟*/
/*Linux clock子系统【5】-从imx_clk_mux解析多路复用时钟时钟驱动(provider侧)分析(__clk_create_clk)*/
clk = __clk_create_clk(__clk_get_hw(clk), dev_id,
con_id);
if (!IS_ERR(clk) && !__clk_get(clk)) {
__clk_free_clk(clk);
clk = ERR_PTR(-ENOENT);
}
break;
}
}
mutex_unlock(&of_clk_mutex);
return clk;
}
)
/*遍历of_clk_providers链表,获得struct of_clk_provider *provider;这结构体是用来干嘛的*/
/**
* struct of_clk_provider - Clock provider registration structure
* @link: Entry in global list of clock providers
* @node: Pointer to device tree node of clock provider
* @get: Get clock callback. Returns NULL or a struct clk for the
* given clock specifier
* @data: context pointer to be passed into @get callback
*/
struct of_clk_provider {
struct list_head link;
struct device_node *node;
struct clk *(*get)(struct of_phandle_args *clkspec, void *data);
void *data;
};
/*重点,函数指针,获得clk结构体的函数,分析到现在,终于知道了clk是怎么来的*/
struct clk_hw *__clk_get_hw(struct clk *clk)
{
return !clk ? NULL : clk->core->hw;(这个是怎么来的有什么作用)
}
struct clk *__clk_create_clk(struct clk_hw *hw, const char *dev_id,
const char *con_id)
{
struct clk *clk;
/* This is to allow this function to be chained to others */
if (!hw || IS_ERR(hw))
return (struct clk *) hw;
clk = kzalloc(sizeof(*clk), GFP_KERNEL);
if (!clk)
return ERR_PTR(-ENOMEM);
clk->core = hw->core;
clk->dev_id = dev_id;
clk->con_id = con_id;
clk->max_rate = ULONG_MAX;
clk_prepare_lock();
hlist_add_head(&clk->clks_node, &hw->core->clks);
clk_prepare_unlock();
return clk;
}
4.1.2 of_clk_add_provider(注册 of_clock_provider)
Linux clock子系统【4】-从CLK_OF_DECLARE 解析时钟驱动
4.1.3 of_parse_phandle_with_args函数详解
4.1.3.1 源码分析
of_parse_phandle_with_args
__of_parse_phandle_with_args
of_find_node_by_phandle
of_property_read_u32_array
of_find_property_value_of_size
of_find_property
__of_find_property
[clk_get]_21a0000.i2c
list_name=clocks
size=8, sizeof(*list)=4
phandle=1_cur_index=0_index=0
ccm_kobj.name=ccm@020c4000
count=1
[i2c_imx_probe]_end
of_parse_phandle_with_args
函数作用:获得节点 phandle 列表中的某个节点
/* struct of_phandle_args *rc = of_parse_phandle_with_args(np, "clocks", "#clock-cells", 0,
&clkspec);*/
/*参数 np 指向当前节点;list_name 指向节点中 phandle 列表的属性名; cells_name 参数指明 phandle 指向的节点所含的 cells 个数;index 表示 phandle 列 表的索引,0 代表第一个 phandle,1 代表第二个 phandle;out_args 参数用于存储 phandle 中的参数。*/
/*函数首先检查 index 的值,小于 0 直接返回错误。检查通过之后直接调用 __of_parse_phandle_with_args() 函数,然后返回*/
int of_parse_phandle_with_args(const struct device_node *np, const char *list_name,
const char *cells_name, int index,
struct of_phandle_args *out_args)
{
if (index < 0)
return -EINVAL;
return __of_parse_phandle_with_args(np, list_name, cells_name, index, out_args);
}
EXPORT_SYMBOL(of_parse_phandle_with_args);
/**
* of_parse_phandle_with_args() - Find a node pointed by phandle in a list
* @np: pointer to a device tree node containing a list
* @list_name: property name that contains a list
* @cells_name: property name that specifies phandles' arguments count
* @index: index of a phandle to parse out
* @out_args: optional pointer to output arguments structure (will be filled)
*
* This function is useful to parse lists of phandles and their arguments.
* Returns 0 on success and fills out_args, on error returns appropriate
* errno value.
*
* Caller is responsible to call of_node_put() on the returned out_args->node
* pointer.
*
* Example:
*
* phandle1: node1 {
* #list-cells = <2>;
* }
*
* phandle2: node2 {
* #list-cells = <1>;
* }
*
* node3 {
* list = <&phandle1 1 2 &phandle2 3>;
* }
*
* To get a device_node of the `node2' node you may call this:
* of_parse_phandle_with_args(node3, "list", "#list-cells", 1, &args);
*/
static int __of_parse_phandle_with_args(const struct device_node *np,
const char *list_name,
const char *cells_name, int index,
struct of_phandle_args *out_args)
{
const __be32 *list, *list_end;
int rc = 0, size, cur_index = 0;
uint32_t count = 0;
struct device_node *node = NULL;
phandle phandle;
/* Retrieve the phandle list property */
/*检索pHandle (读取整数) 链表 属性*/
pr_info("list_name=%s\n",list_name);/*list_name=clocks*/
list = of_get_property(np, list_name, &size);
if (!list)
return -ENOENT;
list_end = list + size / sizeof(*list);
pr_info("size=%d, sizeof(*list)=%d\n",size,sizeof(*list));/*size=8, sizeof(*list)=4*/
/*用函数 of_get_property() 函数获得当前节点的 phandle list 属性的值,存储到 list 变量,然后计算 phandle list 属性结束的值。*/
/* Loop over the phandles until all the requested entry is found */
/*然后遍历节点 phandle list 里面的 cells,每遍历依次,只要 phandle 有效,就调用 of_find_node_by_phandle() 函数获得 phandle 对应的节点,然后读取该节点中 cells_name 名字对应的属性值,存储在 count 变量中。如果 list + count 的值越界, 那么判定位越界。*/
while (list < list_end) {
rc = -EINVAL;
count = 0;
/*
* If phandle is 0, then it is an empty entry with no
* arguments. Skip forward to the next entry.
*/
phandle = be32_to_cpup(list++);
pr_info("phandle=%d_cur_index=%d_index=%d\n",phandle,cur_index,index);/*phandle=1_cur_index=0_index=0*/
if (phandle) {
/*
* Find the provider node and parse the #*-cells
* property to determine the argument length
*/
node = of_find_node_by_phandle(phandle);
pr_info("%s_kobj.name=%s\n",node->name,node->kobj.name);/*ccm_kobj.name=ccm@020c4000*/
if (!node) {
pr_err("%s: could not find phandle\n",
np->full_name);
goto err;
}
if (of_property_read_u32(node, cells_name, &count)) {
pr_err("%s: could not get %s for %s\n",
np->full_name, cells_name,
node->full_name);
goto err;
}
pr_info("count=%d\n\n",count);/*#clocks-cell=<1>;conut=1*/
/*
* Make sure that the arguments actually fit in the
* remaining property data length
*/
if (list + count > list_end) {
pr_err("%s: arguments longer than property\n",
np->full_name);
goto err;
}
/*cur_index 和 index 的比较确保了正在读取指定的 phandle。如果 out_args 存在,那 么函数将 phandle 对应的参数都存储在 out_args 的 args 数组里,然后返回;否则调 用 of_node_put() 函数,释放节点的使用权;如果不是需要找的 phandle,那么继续遍 历下一个*/
/*
* All of the error cases above bail out of the loop, so at
* this point, the parsing is successful. If the requested
* index matches, then fill the out_args structure and return,
* or return -ENOENT for an empty entry.
*/
rc = -ENOENT;
if (cur_index == index) {
if (!phandle)
goto err;
if (out_args) {
int i;
if (WARN_ON(count > MAX_PHANDLE_ARGS))
count = MAX_PHANDLE_ARGS;
out_args->np = node;
out_args->args_count = count;
for (i = 0; i < count; i++)
out_args->args[i] = be32_to_cpup(list++);
} else {
of_node_put(node);
}
/* Found it! return success */
return 0;
}
of_node_put(node);
node = NULL;
list += count;
cur_index++;
}
/*
* Unlock node before returning result; will be one of:
* -ENOENT : index is for empty phandle
* -EINVAL : parsing error on data
* [1..n] : Number of phandle (count mode; when index = -1)
*/
rc = index < 0 ? cur_index : -ENOENT;
err:
if (node)
of_node_put(node);
return rc;
}
/*参数 handle 指向节点中 phandle 的属性值。
函数首先调用 raw_spin_lock_irqsave() 函数加锁。由于 DTB 将所有节点都存放在 of_allnodes 为表头的单链表里,然后调用 for 循环遍历所有节点。每次遍历一个节点, 如果节点 device_node 的 phandle 成员和遍历到的节点一致,那么就找到 phandle 对 应的节点。接着停止 for 循环,调用 of_node_get() 函数添加节点引用数。最后返回 device_node 之前调用 raw_spin_unlock_irqrestore() 函数释放锁。*/
/**
* of_find_node_by_phandle - Find a node given a phandle
* @handle: phandle of the node to find
*
* Returns a node pointer with refcount incremented, use
* of_node_put() on it when done.
*/
struct device_node *of_find_node_by_phandle(phandle handle)
{
struct device_node *np;
unsigned long flags;
raw_spin_lock_irqsave(&devtree_lock, flags);
for (np = of_allnodes; np; np = np->allnext)
if (np->phandle == handle)
break;
of_node_get(np);
raw_spin_unlock_irqrestore(&devtree_lock, flags);
return np;
}
EXPORT_SYMBOL(of_find_node_by_phandle);
4.1.3.2 驱动demo
实践目的是在 DTS 文件中构建三个私有节点,第一个私有节点通过 phandle 的方式引用 了第二个和第三个节点,节点二和节点三都存储在第一个节点的 phandle list 属性中, 然后通过 of_parse_phandle_with_args() 函数分贝读取两个节点,函数定义如下:
/*这个函数经常用用于从节点的 phandle list 中读取 phandle 对应的节点*/
int of_parse_phandle_with_args(const struct device_node *np, const char *list_name,
const char *cells_name, int index,
struct of_phandle_args *out_args)
- DTS 文件
由于使用的平台是 ARM32,所以在源码 /arch/arm/boot/dts 目录下创建一个 DTSI 文件,在 root 节点之下创建一个名为 DTS_demo 的子节点。节点默认打开。再创建两个节点,节点的 cells 分别是 3 和 2,具体内容如下:
/*
* DTS Demo Code
*
* (C) 2019.01.06 <buddy.zhang@aliyun.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
/ {
DTS_demo {
compatible = "DTS_demo, BiscuitOS";
status = "okay";
phy-handle = <&phy0 1 2 3 &phy1 4 5>;
};
phy0: phy@0 {
#phy-cells = <3>;
compatible = "PHY0, BiscuitOS";
};
phy1: phy@1 {
#phy-cells = <2>;
compatible = "PHY1, BiscuitOS";
};
};
创建完毕之后,将其保存并命名为 DTS_demo.dtsi。然后开发者在 Linux 4.20.8 的源 码中,找到 arch/arm/boot/dts/vexpress-v2p-ca9.dts 文件,然后在文件开始地方添 加如下内容:
#include "DTS_demo.dtsi"
- 编写对应驱动
准备好 DTSI 文件之后,开发者编写一个简单的驱动,这个驱动作为 DTS_demo 的设备驱 动,在 DTS 机制遍历时会调用匹配成功的驱动,最终运行驱动里面的代码。在驱动的 probe 函数中,首先获得驱动所对应的节点,通过 platform_device 的 of_node 成员传 递。获得驱动对应的节点之后,通过调用 of_parse_phandle_with_args() 函数获得指定 的节点。驱动编写如下:
/*
* DTS: of_parse_phandle_with_args
*
* int of_parse_phandle_with_args(const struct device_node *np,
* const char *list_name, const char *cells_name,
* int index, struct of_phandle_args *out_args)
*
* int of_device_is_available(const struct device_node *device)
*
* (C) 2019.01.11 BuddyZhang1 <buddy.zhang@aliyun.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
/*
* Private DTS file: DTS_demo.dtsi
*
* / {
* DTS_demo {
* compatible = "DTS_demo, BiscuitOS";
* status = "okay";
* phy-handle = <&phy0 1 2 3 &phy1 4 5>;
* };
*
* phy0: phy@0 {
* #phy-cells = <3>;
* compatible = "PHY0, BiscuitOS";
* };
*
* phy1: phy@1 {
* #phy-cells = <2>;
* compatible = "PHY1, BiscuitOS";
* };
* };
*
* On Core dtsi:
*
* include "DTS_demo.dtsi"
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/of_platform.h>
/* define name for device and driver */
#define DEV_NAME "DTS_demo"
/* probe platform driver */
static int DTS_demo_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct of_phandle_args args;
int rc, index = 0;
const u32 *comp;
printk("DTS demo probe entence.\n");
/* Read first phandle argument */
rc = of_parse_phandle_with_args(np, "phy-handle", "#phy-cells",
0, &args);
if (rc < 0) {
printk("Unable to parse phandle.\n");
return -1;
}
comp = of_get_property(args.np, "compatible", NULL);
if (comp)
printk("%s compatible: %s\n", args.np->name, comp);
for (index = 0; index < args.args_count; index++)
printk("Args %d: %#x\n", index, args.args[index]);
/* Read second phandle argument */
rc = of_parse_phandle_with_args(np, "phy-handle", "#phy-cells",
1, &args);
if (rc < 0) {
printk("Unable to parse phandle.\n");
return -1;
}
comp = of_get_property(args.np, "compatible", NULL);
if (comp)
printk("%s compatible: %s\n", args.np->name, comp);
for (index = 0; index < args.args_count; index++)
printk("Args %d: %#x\n", index, args.args[index]);
return 0;
}
/* remove platform driver */
static int DTS_demo_remove(struct platform_device *pdev)
{
return 0;
}
static const struct of_device_id DTS_demo_of_match[] = {
{ .compatible = "DTS_demo, BiscuitOS", },
{ },
};
MODULE_DEVICE_TABLE(of, DTS_demo_of_match);
/* platform driver information */
static struct platform_driver DTS_demo_driver = {
.probe = DTS_demo_probe,
.remove = DTS_demo_remove,
.driver = {
.owner = THIS_MODULE,
.name = DEV_NAME, /* Same as device name */
.of_match_table = DTS_demo_of_match,
},
};
module_platform_driver(DTS_demo_driver);
启动内核,在启动阶段就会运行驱动的 probe 函数,并打印如下信息:
[ 3.534323] DTS demo probe entence.
[ 3.534359] phy compatible: PHY0, BiscuitOS
[ 3.534364] Args 0: 0x1
[ 3.534369] Args 1: 0x2
[ 3.534372] Args 2: 0x3
[ 3.534379] phy compatible: PHY1, BiscuitOS
[ 3.534383] Args 0: 0x4
[ 3.534387] Args 1: 0x5
// 确定时钟个数
int nr_pclks = of_count_phandle_with_args(dev->of_node, "clocks",
"#clock-cells");
// 获得时钟
for (i = 0; i < nr_pclks; i++) {
struct clk *clk = of_clk_get(dev->of_node, i);
}
// 使能时钟
clk_prepare_enable(clk);
// 禁止时钟
clk_disable_unprepare(clk);
4.2 clk_prepare_enable 函数详解
/* clk_prepare_enable helps cases using clk_enable in non-atomic context. */
linux/clk.h(
static inline int clk_prepare_enable(struct clk *clk)
{
int ret;
ret = clk_prepare(clk);
if (ret)
return ret;
ret = clk_enable(clk);
if (ret)
clk_unprepare(clk);
return ret;
}
static inline int clk_prepare(struct clk *clk)
{
/*might_sleep(): 指示当前函数可以睡眠。如果它所在的函数处于原子上下文(atomic context)中(如,spinlock, irq-handler…),将打印出堆栈的回溯信息。这个函数主要用来做调试工作,在你不确定不期望睡眠的地方是否真的不会睡眠时,就把这个宏加进去。*/
might_sleep();
return 0;
}
)
drivers/clk/clk.c(
/**
* clk_enable - ungate a clock
* @clk: the clk being ungated
*
* clk_enable must not sleep, which differentiates it from clk_prepare. In a
* simple case, clk_enable can be used instead of clk_prepare to ungate a clk
* if the operation will never sleep. One example is a SoC-internal clk which
* is controlled via simple register writes. In the complex case a clk ungate
* operation may require a fast and a slow part. It is this reason that
* clk_enable and clk_prepare are not mutually exclusive. In fact clk_prepare
* must be called before clk_enable. Returns 0 on success, -EERROR
* otherwise.
*/
int clk_enable(struct clk *clk)
{
unsigned long flags;
int ret;
flags = clk_enable_lock();
ret = __clk_enable(clk);
clk_enable_unlock(flags);
return ret;
}
static int __clk_enable(struct clk *clk)
{
if (!clk)
return 0;
return clk_core_enable(clk->core);
}
static int clk_core_enable(struct clk_core *clk)
{
ret = clk_core_enable(clk->parent);
clk->ops->enable(clk->hw);
}
)
4.2.1 enable = clk_gate2_enable
arch/arm/mach-imx/clk-gate2.c(
static struct clk_ops clk_gate2_ops = {
.enable = clk_gate2_enable,
.disable = clk_gate2_disable,
.disable_unused = clk_gate2_disable_unused,
.is_enabled = clk_gate2_is_enabled,
};
static int clk_gate2_enable(struct clk_hw *hw)
{
clk_gate2_do_shared_clks(hw, true);
return 0;
}
static void clk_gate2_disable(struct clk_hw *hw)
{
clk_gate2_do_shared_clks(hw, false);
}
static void clk_gate2_do_shared_clks(struct clk_hw *hw, bool enable)
{
struct clk_gate2 *gate = to_clk_gate2(hw);
clk_gate2_do_hardware(gate, enable);
}
/*struct clk *clks[IMX6UL_CLK_I2C1] = imx_clk_gate2("i2c1", "perclk", base + 0x70, 6);/*以i2c为例*/*/
#define CCM_CCGR_FULL_ENABLE 0x3
static void clk_gate2_do_hardware(struct clk_gate2 *gate, bool enable)
{
u32 reg; (ccm_reg=(0x020c4000+0x70 0x4000)
reg = readl(gate->reg);
if (enable)
reg |= CCM_CCGR_FULL_ENABLE << gate->bit_idx;
else
reg &= ~(CCM_CCGR_FULL_ENABLE << gate->bit_idx);
writel(reg, gate->reg);
}
)
参考
of_parse_phandle_with_args函数详解
转载:Linux CCF框架简要分析和API调用
https://www.likecs.com/show-204547770.html
更多推荐
所有评论(0)