NXP

dts文件分析---以ov5640为例,修改dts文件使ov5640使用第二个IPU

2019-07-12 13:48发布

ARMlinux中,每一个.dts文件都对应一个ARMmachine,这些文件都放在arch/arm/boot/dts文件夹中。同时,对于每一个SoC可能对应多个machine,这些dts文件中会包含许多共同的部分,所以就有了.dtsi文件。这个.dtsi文件类似C语言中的头文件,在其他的.dts文件中可以通过#include”xxxx.dtsi“ 来包含这些头文件。
因为我们的开发板是imx6q-sabersd,所以首先查看对应的imx6q-sabresd.dts文件,有如下内容: #include "imx6q.dtsi" #include "imx6qdl-sabresd.dtsi" / { model = "Freescale i.MX6 Quad SABRE Smart Device Board"; compatible = "fsl,imx6q-sabresd", "fsl,imx6q"; };
.dts文件的每个设备,都有一个compatible属性,compatible属性用于用户驱动和设备的绑定。compatible属性是一个字符串的列表,列表中的第一个字符串表征了结点代表的确切设备,形式为",",其后的字符串表征可兼容的其他设备。可以说前面的是特指,后面的则涵盖更广的范围。
上述.dts文件中,root结点"/"compatible属性compatible= "fsl,imx6q-sabresd","fsl,imx6q";定义了系统的名称。Linux内核透过root结点"/"compatible属性即可判断它启动的是什么machine
但是在这个文件中,只有少量的设备,对于其他cpu等设备的描述信息都没有,肯定不是全部的设备信息,这时候就能看到这个文件中包含的其他文件,cpu等信息肯定是在其他地方描述的。

每个文件中都会有一个根结点/“,在编译设备文件的时候,编译器会将这些文件都放在同一个”/”结点下面,这样就不会担心各个文件中的根结点会重复,同时,编译器也会对不同文件中相同结点下的信息进行合并处理,在后面再具体分析。由于每个文件中都有一个根结点,这样就更方便我们书写这个设备树文件。
继续查看imx6q.dtsi文件: / { cpus { #address-cells = <1>; #size-cells = <0>; cpu0: cpu@0 { compatible = "arm,cortex-a9"; device_type = "cpu"; reg = <0>; next-level-cache = <&L2>; operating-points = < /* kHz uV */ 1200000 1275000 996000 1250000 852000 1250000 792000 1175000 396000 975000 >; fsl,soc-operating-points = < /* ARM kHz SOC-PU uV */ 1200000 1275000 996000 1250000 852000 1250000 792000 1175000 396000 1175000 >; clock-latency = <61036>; /* two CLK32 periods */ clocks = <&clks IMX6QDL_CLK_ARM>, <&clks IMX6QDL_CLK_PLL2_PFD2_396M>, <&clks IMX6QDL_CLK_STEP>, <&clks IMX6QDL_CLK_PLL1_SW>, <&clks IMX6QDL_CLK_PLL1_SYS>, <&clks IMX6QDL_PLL1_BYPASS>, <&clks IMX6QDL_CLK_PLL1>, <&clks IMX6QDL_PLL1_BYPASS_SRC> ; clock-names = "arm", "pll2_pfd2_396m", "step", "pll1_sw", "pll1_sys", "pll1_bypass", "pll1", "pll1_bypass_src"; arm-supply = <®_arm>; pu-supply = <®_pu>; soc-supply = <®_soc>; }; cpu@1 { compatible = "arm,cortex-a9"; device_type = "cpu"; reg = <1>; next-level-cache = <&L2>; }; cpu@2 { compatible = "arm,cortex-a9"; device_type = "cpu"; reg = <2>; next-level-cache = <&L2>; }; cpu@3 { compatible = "arm,cortex-a9"; device_type = "cpu"; reg = <3>; next-level-cache = <&L2>; }; };
这个文件中包含1root结点"/"root结点下面含一系列子结点,本例中为"cpus";结点”cpus”下又有一个子结点”cpu0“,结点"cpu0"下又含有一系列子结点,为"cpu0","cpu1",”cpu2”和”cpu3”
各结点都有一系列属性。这些属性可能为空,可能为字符串,可能为字符串数组,也可能为Cells(由u32整数组成)。

注意cpuscpus2cpu子结点的命名,它们遵循的组织形式为:[@]<>中的内容是必选项,[]中的则为可选项。name是一个ASCII字符串,用于描述结点对应的设备类型,如3comEthernet适配器对应的结点name宜为ethernet,而不是3com509。如果一个结点描述的设备有地址,则应该给出@unit-address。多个相同类型设备结点的name可以一样,只要unit-address不同即可,如本例中含有cpu@0cpu@1以及serial@101f0000serial@101f2000这样的同名结点。设备的unit-address地址也经常在其对应结点的reg属性中给出。ePAPR标准给出了结点命名的规范。
下面来看cpus结点的信息: #address-cells = <1>; #size-cells = <0>;
首先前面的#代表是数字,对于这个cells的概念必须理解:一个cell代表一个u32整数,而这个address-cells代表子结点的address所包含的cells数目(即是由几个u32所组成的)。同样,size-cells代表子结点的size所包含的cells数目(即是由几个u32所组成的)。这两个概念与子结点的reg信息息息相关,也就是说,如果子结点中包含了reg信息的话,这个reg的表示方法是通过父结点的address-cellssize-cells来指定的。
(一)可寻址的设备使用如下信息来在DeviceTree中编码地址信息:
  • reg
  • #address-cells
  • #size-cells
其中reg的组织形式为reg= ,其中的每一组addresslength表明了设备使用的一个地址范围。address1个或多个32位的整型(即cell),而length则为cell的列表或者为空(若#size-cells= 0)。addresslength字段是可变长的,父结点的#address-cells#size-cells分别决定了子结点的reg属性的addresslength字段的长度。 以上面的信息为例: cpus { #address-cells = <1>; #size-cells = <0>; cpu@1 { compatible = "arm,cortex-a9"; device_type = "cpu"; reg = <1>; next-level-cache = <&L2>; }; cpu@2 { compatible = "arm,cortex-a9"; device_type = "cpu"; reg = <2>; next-level-cache = <&L2>; }; };
cpus结点中指定了#address-cells= <1>#size-cells= <0>,决定了4cpu子结点的address1,而length为空,于是形成了4cpureg= <0>; reg = <1>; reg = <2>;reg= <3>;
再来看两个例子 soc { #address-cells = <1>; #size-cells = <1>; timer@00a00600 { compatible = "arm,cortex-a9-twd-timer"; reg = <0x00a00600 0x20>; }; ipu2: ipu@02800000 { compatible = "fsl,imx6q-ipu"; reg = <0x02800000 0x400000>; }; };
soc结点中指定了#address-cells= <1>#size-cells= <1>,决定了下面timer子结点的address1length1
External-bus { #address-cells = <2> #size-cells = <1>; ethernet@0,0 { compatible = "smc,smc91c111"; reg = <0 0 0x1000>; interrupts = < 5 2 >; }; flash@2,0 { compatible = "samsung,k8f1315ebm", "cfi-flash"; reg = <2 0 0x4000000>; }; }; external-bus结点中指定了#address-cells= <2>#size-cells=<1>,决定了下面ethernet子结点和flash子结点的address2length1。同时需要注意的是,在这里数字的表示都是大端模式,如果address用两个cells来表示,那么想要知道这个外设的地址,就直接将两个cells里面的地址连接起来即可。
(二)DeviceTree中还可以表示中断连接信息,对于中断控制器而言,它提供如下属性:
interrupt-controller---
这个属性为空,中断控制器应该加上此属性表明自己的身份;
#interrupt-cells
#address-cells#size-cells相似,它表明连接此中断控制器的设备的interrupts属性的cell大小。
intc: interrupt-controller@00a01000 { compatible = "arm,cortex-a9-gic"; #interrupt-cells = <3>; #address-cells = <1>; #size-cells = <1>; interrupt-controller; reg = <0x00a01000 0x1000>, <0x00a00100 0x100>; }; soc { #address-cells = <1>; #size-cells = <1>; compatible = "simple-bus"; interrupt-parent = <&intc>; ipu1: ipu@02400000 { interrupts = <0 6 IRQ_TYPE_LEVEL_HIGH>, <0 5 IRQ_TYPE_LEVEL_HIGH>; }; };
在整个DeviceTree中,与中断相关的属性还包括:
interrupt-parent---
设备结点透过它来指定它所依附的中断控制器的phandle,当结点没有指定interrupt-parent时,则从父结点继承。对于本例而言,soc结点指定了interrupt-parent= <&intc>;其对应于intc:interrupt-controller@00a01000,而soc结点的子结点ipu1并未指定interrupt-parent,因此它们都继承了intc,即位于0x00a01000的中断控制器。
interrupts---
用到了中断的设备结点通过它指定中断号、触发方法等,具体这个属性含有多少个cell,由它依附的中断控制器结点的#interrupt-cells属性决定。而具体每个cell又是什么含义,一般由驱动的实现决定,而且也会在DeviceTreebinding文档中说明。

对于其它的有关dts文件的信息,查看ePAPR标准
下面是关于imx6q-sabresd开发板有关ipu信息的描述: imx6qdl.dtsi文件中: mipi_csi: mipi_csi@021dc000 { /* MIPI-CSI */ compatible = "fsl,imx6q-mipi-csi2"; reg = <0x021dc000 0x4000>; interrupts = <0 100 0x04>, <0 101 0x04>; clocks = <&clks IMX6QDL_CLK_HSI_TX>, <&clks IMX6QDL_CLK_EMI_SEL>, <&clks IMX6QDL_CLK_VIDEO_27M>; /* Note: clks 138 is hsi_tx, however, the dphy_c * hsi_tx and pll_refclk use the same clk gate. * In current clk driver, open/close clk gate do * use hsi_tx for a temporary debug purpose. */ clock-names = "dphy_clk", "pixel_clk", "cfg_clk"; status = "disabled"; }; ipu1: ipu@02400000 { compatible = "fsl,imx6q-ipu"; reg = <0x02400000 0x400000>; interrupts = <0 6 IRQ_TYPE_LEVEL_HIGH>, <0 5 IRQ_TYPE_LEVEL_HIGH>; clocks = <&clks IMX6QDL_CLK_IPU1>, <&clks IMX6QDL_CLK_IPU1_DI0>, <&clks IMX6QDL_CLK_IPU1_DI1>, <&clks IMX6QDL_CLK_IPU1_DI0_SEL>, <&clks IMX6QDL_CLK_IPU1_DI1_SEL>, <&clks IMX6QDL_CLK_LDB_DI0>, <&clks IMX6QDL_CLK_LDB_DI1>; clock-names = "bus", "di0", "di1", "di0_sel", "di1_sel", "ldb_di0", "ldb_di1"; resets = <&src 2>; bypass_reset = <0>; };
imx6q.dtsi文件中: ipu2: ipu@02800000 { compatible = "fsl,imx6q-ipu"; reg = <0x02800000 0x400000>; interrupts = <0 8 IRQ_TYPE_LEVEL_HIGH>, <0 7 IRQ_TYPE_LEVEL_HIGH>; clocks = <&clks IMX6QDL_CLK_IPU2>, <&clks IMX6QDL_CLK_IPU2_DI0>, <&clks IMX6QDL_CLK_IPU2_DI1>, <&clks IMX6QDL_CLK_IPU2_DI0_SEL>, <&clks IMX6QDL_CLK_IPU2_DI1_SEL>, <&clks IMX6QDL_CLK_LDB_DI0>, <&clks IMX6QDL_CLK_LDB_DI1>; clock-names = "bus", "di0", "di1", "di0_sel", "di1_sel", "ldb_di0", "ldb_di1"; resets = <&src 4>; bypass_reset = <0>; };
imx6qdl-sabresd.dtsi文件中: v4l2_cap_0 { compatible = "fsl,imx6q-v4l2-capture"; ipu_id = <0>; csi_id = <0>; mclk_source = <0>; status = "okay"; }; v4l2_cap_1 { compatible = "fsl,imx6q-v4l2-capture"; ipu_id = <0>; csi_id = <1>; mclk_source = <0>; status = "okay"; }; &i2c1 { clock-frequency = <100000>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_i2c1>; status = "okay"; ov564x: ov564x@3c { compatible = "ovti,ov564x"; reg = <0x3c>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_ipu1_2>; clocks = <&clks IMX6QDL_CLK_CKO>; clock-names = "csi_mclk"; DOVDD-supply = <&vgen4_reg>; /* 1.8v */ AVDD-supply = <&vgen3_reg>; /* 2.8v, on rev C board is VGEN3, on rev B board is VGEN5 */ DVDD-supply = <&vgen2_reg>; /* 1.5v*/ pwn-gpios = <&gpio1 16 1>; /* active low: SD1_DAT0 */ rst-gpios = <&gpio1 17 0>; /* active high: SD1_DAT1 */ csi_id = <0>; mclk = <24000000>; mclk_source = <0>; }; }; &i2c2 { clock-frequency = <100000>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_i2c2>; status = "okay"; ov564x_mipi: ov564x_mipi@3c { /* i2c2 driver */ compatible = "ovti,ov564x_mipi"; reg = <0x3c>; clocks = <&clks 201>; clock-names = "csi_mclk"; DOVDD-supply = <&vgen4_reg>; /* 1.8v */ AVDD-supply = <&vgen3_reg>; /* 2.8v, rev C board is VGEN3 rev B board is VGEN5 */ DVDD-supply = <&vgen2_reg>; /* 1.5v*/ pwn-gpios = <&gpio1 19 1>; /* active low: SD1_CLK */ rst-gpios = <&gpio1 20 0>; /* active high: SD1_DAT2 */ csi_id = <1>; mclk = <24000000>; mclk_source = <0>; }; }; &mipi_csi { status = "okay"; ipu_id = <0>; csi_id = <1>; v_channel = <0>; lanes = <2>; };
mxc_v4l2_capture.c文件中, static struct platform_driver mxc_v4l2_driver = { .driver = { .name = "mxc_v4l2_capture", .owner = THIS_MODULE, .of_match_table = mxc_v4l2_dt_ids, }, .id_table = imx_v4l2_devtype, .probe = mxc_v4l2_probe, .remove = mxc_v4l2_remove, .suspend = mxc_v4l2_suspend, .resume = mxc_v4l2_resume, .shutdown = NULL, };static const struct of_device_id mxc_v4l2_dt_ids[] = { { .compatible = "fsl,imx6q-v4l2-capture", .data = &imx_v4l2_devtype[IMX6_V4L2], }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, mxc_v4l2_dt_ids);
当内核中有匹配的驱动与设备的时候,就会调用mxc_v4l2_probe函数。是否匹配,在内核中是通过match函数的: xxxxxxxxxxxxxxx 这个match函数中首先匹配of设备树类型的:匹配的就是这个compatible变量。向上搜索这个"fsl,imx6q-v4l2-capture",可以发现,在dts类文件中,匹配的是v4l2_cap_0v4l2_cap_1。当这两个设备注册到内核中的时候,就会调用两次mxc_v4l2_probe函数。而在这个mxc_v4l2_probe函数中