camera驱动开发 之 DTS

DTS/DTSI/DTC/DTB

dts

设备树源文件,描述某个 具体的设备板子 所用的硬件资源(如 SoC 型号、挂载的摄像头、LCD、I2C 地址等);类似“主函数”,最终会被编译成 .dtb 供内核使用
设备树的公共部分

dtsi

一个SoC可能对应多个machine,如果每个machine的设备树都写成一个完全独立的.dts文件,那么势必相当一些.dts文件有重复的部分,为了解决这个问题,Linux设备树目录把一个SoC公用的部分或者多个machine共同的部分提炼为相应的.dtsi文件。这样每个.dts就只有自己差异的部分,公有的部分只需要"include"相应的.dtsi文件, 这样就是整个设备树的管理更加有序。类似 C 的 .h 头文件,可以被多个 .dts 文件 #include。

dtc

编译器工具,负责将 .dts/.dtsi 编译成 .dtb;

dtc -I dts -O dtb -o rk3568-board.dtb rk3568-board.dts // -I dts:输入是设备树源文件;-O dtb:输出为设备树 blob(二进制);-o 指定输出文件。

dtb

编译生成的二进制文件,供内核在启动时读取;bootloader(如 U-Boot)会将 .dtb 加载到内存中;Linux 内核启动时读取 .dtb,根据描述来初始化硬件;.dtb 是最终给设备使用的配置文件。

    +-------------+       +-------------+
    |  .dtsi      |<----->|   .dts      |
    | (通用部分)  |        | (板级配置)  |
    +-------------+       +-------------+
           ↓                    ↓
              ======合并=======
                       ↓
               +---------------+ 
               |  dtc 编译器    |
               +---------------+
                       ↓
               +---------------+
               |    .dtb 文件   |
               +---------------+
                       ↓
               +---------------+
               | bootloader 加载|
               +---------------+
                       ↓
             +-------------------+
             | 内核解析 device tree|
             +-------------------+
                       ↓
          +-------------------------+
          |驱动匹配(of_match_table)|
          +-------------------------+

dts 语法结构

设备树是一个由节点和属性组成的简单树形结构。属性是键值对,节点可能同时包含属性和子节点。例如,下面是一个 .dts 格式的简单树形结构;

dts 示例注释

点击查看代码
/dts-v1/;

/ {

//compatible 指定系统名称。
//它包含一个形式为“<manufacturer>,<model>”的字符串;
//制造商:acme,型号:coyotes-revenge

    compatible = "acme,coyotes-revenge"; 
    #address-cells = <1>;
    #size-cells = <1>;

//父节点:cpus,子节点:cpu@0,cpu@1;
//制造商:arm,型号:cortex-a9;说明是 ARM 的双核 Cortex A9 系统。
//在 cpus 节点中,#address-cells 设置为 1,#size-cells 设置为 0,
//这意味着子设备注册值是表示地址的单一 uint32,没有大小字段。
//在这种情况下,两个 CPU 的地址分别为 0 和 1。cpu 节点的 #size-cells 值为 0,
//因为每个 cpu 只分配一个地址。

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;
        interrupt-parent = <&intc>;
        cpu@0 {
            compatible = "arm,cortex-a9";
            reg = <0>;
        };
        cpu@1 {
            compatible = "arm,cortex-a9";
            reg = <1>;
        };
    }; 

//按照惯例,如果一个节点有一个 reg 属性,那么节点名称中就必须包含单元地址,
//也就是 reg 属性中的第一个地址值;serial@101F0000 reg = <0x101f0000 0x1000>;
//根节点的子节点已经在使用 CPU 的地址域,因此不需要任何明确的映射。
//例如,serial@101f0000 设备直接被分配了 0x101f0000 地址。
    serial@101F0000 {
        compatible = "arm,pl011";
        reg = <0x101f0000 0x1000 >;
        interrupts = < 1 0 >;
    };

    serial@101F2000 {
        compatible = "arm,pl011";
        reg = <0x101f2000 0x1000 >;
        interrupts = < 1 0 >;
    };

    gpio@101F3000 {
        compatible = "arm,pl061";
        reg = <0x101f3000 0x1000
               0x101f4000 0x0010>;
        interrupts = < 3 0 >;
    };

//interrupt controller 中断控制器;标签 “intc: ”已被添加到中断控制器节点中;
//每个设备都使用中断属性来指定不同的中断输入线。

    intc: interrupt-controller@10140000 {
        compatible = "arm,pl190";
        reg = <0x10140000 0x1000 >;
        interrupt-controller;
        #interrupt-cells = <2>;
    };

    spi@10115000 {
        compatible = "arm,pl022";
        reg = <0x10115000 0x1000 >;
        interrupts = < 4 0 >;
    };

//external-bus 使用两个单元格来表示地址值;一个用于表示片选编号,另一个用于表示从片选基数开始的偏移量。
//长度字段#size-cells保留为一个单元格,因为只有地址的偏移部分需要有一个范围。
//因此,在本示例中,每个注册表reg 项包含 3 个单元格:片选编号、偏移量和长度。
//reg = <0 0 0x1000>; 片选 0 偏移量 0 长度0x1000;
//非根节点的直接子节点不使用 CPU 的地址域。为了获得内存映射地址,设备树必须指定如何将地址从一个域转换到另一个域
//range属性是一个地址转换列表某些总线(如外部芯片片选、多路桥接)地址不能直接被 CPU 访问;
//需要告知 Linux:子节点地址如何映射到系统地址空间;
//Linux 驱动在 probe 阶段用 of_translate_address() 解析 reg 时会使用 ranges;

    external-bus {
        #address-cells = <2>;
        #size-cells = <1>;
        ranges = <0 0  0x10100000   0x10000     // Chipselect 1, Ethernet 映射到0x10100000 
                  1 0  0x10100000   0x10000     // Chipselect 2, i2c controller 映射到0x10100000   
                  2 0  0x30000000   0x1000000>; // Chipselect 3, NOR Flash 映射到0x30000000   
        ethernet@0,0 {
            compatible = "smc,smc91c111";
            reg = <0 0 0x1000>;
            interrupts = < 5 2 >;
        };

//Non Memory Mapped Devices I2C 
//子设备rtc不能被 CPU 直接访问。由父设备i2c的驱动程序将代表 CPU 执行间接访问。

        i2c@1,0 {
            compatible = "acme,a1234-i2c-bus";
            #address-cells = <1>;
            #size-cells = <0>;
            reg = <1 0 0x1000>;
            interrupts = < 6 2 >;
            rtc@58 {
                compatible = "maxim,ds1338";
                reg = <58>;
            interrupts = < 7 3 >;
            };
        };

        flash@2,0 {
            compatible = "samsung,k8f1315ebm", "cfi-flash";
            reg = <2 0 0x4000000>;
        };
    };
};

dts中断映射补充

与遵循设备树自然结构的地址范围转换不同,中断信号可以从机器中的任何设备发出并终止。与在设备树中自然表达的设备寻址不同,中断信号表达为独立于设备树的节点之间的连接。有四种属性可用于描述中断连接:

  • interrupt-controller - 一个空属性,将节点声明为接收中断信号的设备;
  • interrupt-cells - 是中断控制器节点的属性,用来标识这个控制器需要几个单位做中断描述符,用来描述子节点中"interrupts"属性使用了父节点中的interrupts属性的具体的哪个值。一般,如果父节点的该属性的值是3,则子节点的interrupts一个cell的三个32bits整数值分别为:<中断域 中断 触发方式>,如果父节点的该属性是2,则是<中断 触发方式>

  • interrupt-parent - 设备节点的一个属性,包含该节点所连接的中断控制器的指针。没有中断父节点属性的节点也可从其父节点继承该属性;
  • interrupts -中断指定符,设备上的每个中断输出信号都有一个指定符;
点击查看代码
//interrupt-controller表示这个节点是一个中断控制器,需要注意的是,一个SoC中可能有不止一个中断控制器;
//#interrupt-cells的值是4,
//第0位:中断控制器编号:0表主中断控制器,1表示子中断控制器;
//第1位:表示子中断所属的主中断控制器的硬件中断号:对于子中断,这个参数才有意义;
//第2位:硬件中断编号:表示位于当前中断控制器的硬件中断号,比如EINT0~EINT3都是直接连接到主中断控制器的,
//所以其对应的中断控制器编号为0,硬件中断编号为0~3;
//第3位:中断类型:1为上升沿触发、2位下降沿触发、3位双边沿触发、4位高电平触发、8位低电平触发;

intc:interrupt-controller@4a000000 {
     compatible = "samsung,s3c2410-irq";
     reg = <0x4a000000 0x100>;
     interrupt-controller;
     #interrupt-cells = <4>;
};
//interrupts = <1 28 0 4> 描述的是带有子中断控制器,主中断硬件中断号为28,子中断硬件中断编号为0,高定平触发;
uart0: serial@50000000 {
                compatible = "samsung,s3c2410-uart";
                reg = <0x50000000 0x4000>;
                interrupts = <1 28 0 4>, <1 28 1 4>;
                status = "disabled";
        };

camera部分示例

DTSI

点击查看代码
...
	csi2_0: csi2_0@0xff118123 {
		compatible = "TEST,csi2-0";
		reg = <0xff118123 0x68>,
		      <0xff119123 0x290>,
		      <0xff11a123 0xd8>;
		      ...
		reg-names = "name1", "name2", "name3"...;
		status = "disabled";
		ports {
			#address-cells = <1>;
			#size-cells = <0>;
			port@0 {
				reg = <0>;
			};
			port@1 {
				reg = <1>;
				csi2_0_out: endpoint {
					remote-endpoint = <&adap0_in>;
				};
			};
		};
	};

	adap0: adap0@ff11b123 {
		compatible = "TEST,adap0";
		reg = <0xff11b123 0xe4>;
		reg-names = "adap";
		status = "disabled";
		ports {
			#address-cells = <1>;
			#size-cells = <0>;
			port@0 {
				reg = <0>;
				adap0_in: endpoint {
					remote-endpoint = <&csi2_0_out>;
				};
			};
			port@1 {
				reg = <1>;
				adap0_out: endpoint {
					remote-endpoint = <&isp_virt0_in>;
				};
			};
		};
	};

	isp: isp@ff100000 {
		compatible = "TEST,isp";
		reg = <0xff100000 0x715c>, 
		      <0xff110000 0x8a4>, 
		      <0xff111800 0x19c>, 
		      <0xff111c00 0x180>,
		      ...
		reg-names = "ispname1", "isp2name", "isp3name",
			    "ispname4",..
		//interrupt-parent = <&intc>;
		interrupts = <0 159 1>,
		 <0 158 1>,
		 <0 157 1>,
		 <0 156 1>;

		status = "disabled";
	};

	isp_virt0: isp-virt0 {
		compatible = "TETS,isp-virt0";
		isp = <&isp>;
		// power-domains = <>;
		memory-region = <&isp_reserved>;
		status = "disabled";

		port {
			isp_virt0_in: endpoint {
				remote-endpoint = <&adap0_out>;
			};
		};
	};

...

DTS

点击查看代码
/ {
	compatible = "TEST,a";
	model = "TEST Meson a";
	aliases {
		serial0 = &uart_B;
		i2c0 = &i2c0;
		spi0 = &spifc;
		tsensor0 = &tsensor;
	};

	memory@0 {
		device_type = "memory";
		linux,usable-memory = <0x0 0x80000000>;
	};
...
}
...
&i2c0 {
	status = "okay";
	clock-frequency = <100000>;
	imx290: sensor@1a {
		compatible = "sony,imx290";
		reg = <0x1a>;
		clock-frequency = <37125000>;
		port {
			imx290_out: endpoint {
				data-lanes = <0 1 2 3>;
				link-frequencies = /bits/ 64 <222750000 148500000>;
				remote-endpoint = <&csi2_0_in>;
			};
		};
	};
};

&csi2_0 {
	status = "okay";
	ports {
		port@0 {
			csi2_0_in: endpoint {
				remote-endpoint = <&imx290_out>;
				clock-lanes = <4>;
				data-lanes = <0 1 2 3>;
				clock-noncontinuous;
			};
		};
	};
};

&adap0 {
	status = "okay";
};

&isp {
	status = "okay";
};

&isp_virt0 {
	status = "okay";
};
...

参考文档:
https://www.cnblogs.com/zyly/p/17266960.html
https://blog.csdn.net/qq_39400113/article/details/127939224
https://elinux.org/Device_Tree_Usage#Basic_Data_Format
https://www.cnblogs.com/xiaojiang1025/p/6131381.html
https://www.cnblogs.com/xinghuo123/p/12968942.html
https://blog.csdn.net/u012489236/article/details/97137007

点击查看代码

posted @ 2025-05-09 13:53  chivalrySun  阅读(107)  评论(0)    收藏  举报