• Linux clock子系统【3】-i2c控制器打开时钟的流程分析



    前言

    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来描述。*/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    大部分的芯片为了省电,它的外部模块时钟平时都是关闭的,只有在使用某个模块时,才设置相应的寄存器开启对应的时钟。
    这些使用者各有不同,要怎么描述这些使用者呢?

    我们可以为它们配上一个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";
    	};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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 流程源码分析

    我们在设备驱动代码中仅使用以下两个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);
    
    	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*/
    			clk = provider->get(clkspec, provider->data);
    		if (!IS_ERR(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是怎么来的*/
    /*那么,这个函数指针又是在哪被赋值的呢?*/
    /*既然有遍历,就有add链表。*/
    arch/arm/mach-imx/clk-imx6ul.c(
    	/*ccm_node=clks: ccm@020c4000*/
    	CLK_OF_DECLARE(imx6ul, "fsl,imx6ul-ccm", imx6ul_clocks_init);
    	static void __init imx6ul_clocks_init(struct device_node *ccm_node)
    	{
    		clk_data.clks = clks;
    		clk_data.clk_num = ARRAY_SIZE(clks);
    		of_clk_add_provider(np, of_clk_src_onecell_get, &clk_data);
    	}
    )
    
    drivers/clk/clk.c(
    		/**
    	 * of_clk_add_provider() - Register a clock provider for a node
    	 * @np: Device node pointer associated with clock provider
    	 * @clk_src_get: callback for decoding clock
    	 * @data: context pointer for @clk_src_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;
    		};
    		static LIST_HEAD(of_clk_providers);
    	int of_clk_add_provider(struct device_node *np,
    				struct clk *(*clk_src_get)(struct of_phandle_args *clkspec,void *data),
    				void *data)
    	{
    		struct of_clk_provider *cp;
    		int ret;
    	
    		cp = kzalloc(sizeof(struct of_clk_provider), GFP_KERNEL);/*创建一个全局 时钟链表头*/
    		if (!cp)
    			return -ENOMEM;
    	
    		cp->node = of_node_get(np);/*ccm@020c4000*/
    		cp->data = data;
    		cp->get = clk_src_get;
    	
    		mutex_lock(&of_clk_mutex);
    		list_add(&cp->link, &of_clk_providers);
    		mutex_unlock(&of_clk_mutex);
    		pr_debug("Added clock from %s\n", np->full_name);
    	
    		ret = of_clk_set_defaults(np, true);
    		if (ret < 0)
    			of_clk_del_provider(np);
    	
    		return ret;
    	}
    	/*我们追到了这个api,这个非常关键,它将由具体平台的实现框架进行调用*/
    	struct clk *of_clk_src_onecell_get(struct of_phandle_args *clkspec, void *data)
    	{
    		struct clk_onecell_data *clk_data = data;
    		unsigned int idx = clkspec->args[0];
    	
    		if (idx >= clk_data->clk_num) {
    			pr_err("%s: invalid clock index %d\n", __func__, idx);
    			return ERR_PTR(-EINVAL);
    		}
    	
    		return clk_data->clks[idx];
    	}
    )
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237

    4.1 of_parse_phandle_with_args函数详解

    4.1.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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    [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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178

    4.1.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
    • 2
    • 3
    • 4
    • 5
    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";
        };
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    创建完毕之后,将其保存并命名为 DTS_demo.dtsi。然后开发者在 Linux 4.20.8 的源 码中,找到 arch/arm/boot/dts/vexpress-v2p-ca9.dts 文件,然后在文件开始地方添 加如下内容:

    #include "DTS_demo.dtsi"
    
    
    • 1
    • 2
    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 
     *
     * 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 
    #include 
    #include 
    
    /* 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);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116

    启动内核,在启动阶段就会运行驱动的 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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
       // 确定时钟个数
        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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    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);
    	}
    )
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    参考

    of_parse_phandle_with_args函数详解
    转载:Linux CCF框架简要分析和API调用
    https://www.likecs.com/show-204547770.html

  • 相关阅读:
    Spring-Bean的生命周期概述
    论文解读(BGRL)《Large-Scale Representation Learning on Graphs via Bootstrapping》
    python学习第四天之分支结构
    为金融而生的区块链Injective(INJ)
    QT day4
    CBAM: 卷积块注意模块
    学生成绩管理系统?
    集群容器部署和管理(Docker&K8S)
    深入解读[面向对象五大设计原则]
    ue4文档接口类学习
  • 原文地址:https://blog.csdn.net/m0_46535940/article/details/126328569