前言

  1. i2c控制器获取时钟的流程分析

一、硬件流程图

示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。
简化如下:
在这里插入图片描述

二、晶振设备树描述

先来看看晶振("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)

  1. 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"

  1. 编写对应驱动
    准备好 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

Logo

更多推荐