Linux - Device Tree

Table of contents

1. The origin of Linux device tree

2. The purpose of Linux device tree

        ​ ​ 1.Platform identification

2. Real-time configuration

3. Device implantation

3. Use of Linux device tree

1.Basic data format

2. Device tree instance analysis

4. LED driver using device tree

5. Exercises



1. The origin of Linux device tree


        Before the ARM architecture of the Linux kernel source code introduced the device tree, the related BSP code was filled with a large number of platform device (Platform Device) codes, and most of these codes were repetitive and messy. A large part of the previous kernel transplantation work was to copy a BSP code and modify the platform device information related to specific hardware in the BSP code and the target board. This leaves ARM architecture code maintainers and kernel maintainers with a lot of work to do within a period of time when a new version of the kernel is released. So much so that Linus Torvalds declared "Gaah.Guys, this whole ARM thing is a f*cking pain in the ass" on the ARM Linux mailing list on March 17, 2011. This forced the entire ARM community to reconsider this issue carefully, so Device Tree (DT) was adopted by the ARM community.
        But it should be noted that in Linux, PowerPC and SPARC architectures have used device trees for a long time. This is not a concept that was proposed recently. The device tree was originally used by Open Firmware as part of the communication method used to transfer data to a client program (usually an operating system). At runtime, the client program discovers the device topology through the device tree, eliminating the need to hardcode hardware information into the program.


2. The purpose of Linux device tree


        The device tree is a data structure that describes hardware. There is nothing magical about it, and it cannot solve all hardware configuration problems. It simply provides a language to extract hardware configuration from the Linux kernel source code. The device tree enables target boards and devices to become data-driven; they must be initialized based on the data passed to the kernel, rather than in the hard-coded manner as before. In theory, this approach can lead to less code duplication and enable a single kernel image to support many hardware platforms.
        Linux uses device trees for the following three main reasons.


        ​ ​ 1.Platform identification


        First and most importantly, the kernel uses data in the device tree to identify a specific machine (the target board, called a machine in the kernel). Ideally, the kernel should be agnostic to a specific hardware platform, since all hardware platform details are described by the device tree. However, hardware platforms are not perfect, so the kernel must identify the machine in the early initialization phase so that the kernel has a chance to run the initialization sequence relevant to the specific machine.
        In most cases, machine identification is independent of the device tree, and the kernel selects initialization code through the machine's CPU or SOC. Taking the ARM platform as an example, setup_arch will call setup_machine_fdt, which traverses the machine_desc linked list and selects the machine_desc structure that best matches the device tree data. This is done by looking up the compatible attribute of the root node of the device tree and comparing it with the entries in the dt_compat list in machine_desc to determine which machine_desc structure is the most suitable.
        The compatible attribute contains an ordered list of strings, starting with the exact machine name, followed by an optional list of boards, from best match to other match types. Taking Samsung's Exynos4x12 series SoC chips as an example, the dt_compat list in the arch/arm/mach-exynos/mach-exynos4-dt.c file is defined as follows.
 

static char const *exynos4_dt_compat[] _initdata = {
    "samsung,exynos4210"
    "samsung,exynos4212"
    "samsung,exynos4412",
    NULl
};

        The compatible attribute specified in the exynos4412.dtsi file contained in the device tree source file arch/arm/boot/dts/exynos4412-origen.dts of the origin target board is as follows.

compatible = "samsung,exynos4412";


        In this way, during the kernel startup process, the machine_desc structure corresponding to the matching machine can be found through the passed device tree data. If not found, NULL will be returned. In this way, one machine_desc can be used to support multiple machines, thereby reducing code duplication rate. Of course, the initialization process of machines with special requirements for initialization should be different, which can be solved through other attributes or some hook functions.


2. Real-time configuration


        In most cases, the device tree is the only way for data communication between firmware and the kernel, so it is also used to pass real-time or configuration data to the kernel, such as kernel parameters, the address of the initrd image, etc. Most of this data is contained in the /chosen node of the device tree, looking like this:

chosen { bootargs = "console=ttys0,115200 1oglevel=8";
    initrd-start  = <0xc8000000>;
    initrd-end    = <0xc8200000>;
};


        The bootargs attribute contains kernel parameters, and the initrd-* attribute defines the first address and size of the initrd file. The chosen
node may also contain any number of properties describing platform-specific configurations.

        In the early initialization phase, before the page table is established, the code related to the architecture initialization will use different auxiliary callback functions multiple times to call of_scan_flat_dt to decode the device data. of_scan_flat_dt traverses the device tree and uses helper functions to extract the required information. Typically, the early_init_dt_scan_chosen helper function is used to parse the chosen node including kernel parameters: the early_init_dt_scan_root helper function is used to initialize the address space model of the device tree; the early_init_dt_scan_memory helper function is used to determine the size and address of available memory.

        On the ARM platform, the setup_machine_fdt function is responsible for early device tree traversal after selecting the correct machine_desc structure.


3. Device implantation

        After board identification and early configuration data parsing, the kernel is further initialized. During this time the unflatten_device_tree function is called to convert the device tree data into a more efficient real-time form. At the same time, the machine's special startup hook function will also be called, such as the init_early function, init_irq function, init_machine function, etc. in machine_desc. From the name, we can guess that the init_early function will be executed during early initialization, and the init_irg function is used to initialize interrupt processing. Utilizing the device tree does not change the behavior or functionality of these functions. If the device tree is provided, either the init_early function or the init_irg function can call any device tree lookup function to obtain additional platform information. However, the init_machine function requires more attention. In the arch/armmach-exynos/mach-exynos4-dtc file, the init machine function has the following statement:
 

of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);

        The function of_platform_populate is to traverse the nodes in the device tree, convert the matching nodes into platform devices, and then register them into the kernel.


3. Use of Linux device tree

1.Basic data format

        In Linux, the types of device tree files are dts, .dtsi and dtb. Among them, dtsi is the included device tree source file, similar to the header file in C language; .dts is the device tree source file, which can contain other .dtsi files and is compiled by dtc to generate a .dtb file.
        The device tree is a simple tree structure containing nodes and attributes. Attributes are key-value pairs, and nodes can contain both attributes and child nodes. Here is a simple device tree in .dts format:

/ {
    node1 {
        a-string-property      = "A string";
        a-string-list-property = "first string","second string";
        a-byte-data-property   = [0x01 0x23 0x34 0x56];
        child-nodel {
            first-child-property;
            second-child-property = <1>;
            a-string-property     = "Hello,world";
            };
            child-node2 {
            };
};
    node2 {
        an-empty-property;
        a-cell-property = <1>;/* each number (cell) is uint32 */
        child-node1 {
        };
    };
};


The device tree contains the following content

  • A single root node:/.
  • Two child nodes: node1 and node2.
  • Two child nodes of node: child-node and child-node2
  • A bunch of properties scattered across the device tree.

        ​​​​Among them, attributes are simple key-value pairs, and their values ​​can be empty or contain an arbitrary byte stream. There are the following basic data representation forms in the device tree source file.

  • Text string (no terminator): can be expressed in double quotes, such as a-string-property="A string"
  • cells: 32-bit unsigned integer, qualified with angle brackets, such as second-child-property =<l>.
  • Binary data: qualified with square brackets, such as a-byte-data-property =[0x010x230x340x56].
  • Mixed representation: Use commas to connect them together, such as mixed-property ="astring”,[0x010x230x450x67],

<0x12345678>。

  • String list: Use commas to connect them together, such as string-list="red fish","blue fish".

2. Device tree instance analysis


        The following is the content extracted from the arch/arm/boot/dts/exynos4.dtsi device tree source file:

/*
 * Samsung's Exynos4 SoC series common device tree source
 *
 * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd.
 *		http://www.samsung.com
 * Copyright (c) 2010-2011 Linaro Ltd.
 *		www.linaro.org
 *
 * Samsung's Exynos4 SoC series device nodes are listed in this file.  Particular
 * SoCs from Exynos4 series can include this file and provide values for SoCs
 * specfic bindings.
 *
 * Note: This file does not include device nodes for all the controllers in
 * Exynos4 SoCs. As device tree coverage for Exynos4 increases, additional
 * nodes can be added to this file.
 *
 * 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.
 */

#include "skeleton.dtsi"

/ {
	interrupt-parent = <&gic>;

	aliases {
		spi0 = &spi_0;
		spi1 = &spi_1;
		spi2 = &spi_2;
		i2c0 = &i2c_0;
		i2c1 = &i2c_1;
		i2c2 = &i2c_2;
		i2c3 = &i2c_3;
		i2c4 = &i2c_4;
		i2c5 = &i2c_5;
		i2c6 = &i2c_6;
		i2c7 = &i2c_7;
		csis0 = &csis_0;
		csis1 = &csis_1;
		fimc0 = &fimc_0;
		fimc1 = &fimc_1;
		fimc2 = &fimc_2;
		fimc3 = &fimc_3;
	};

	chipid@10000000 {
		compatible = "samsung,exynos4210-chipid";
		reg = <0x10000000 0x100>;
	};

	mipi_phy: video-phy@10020710 {
		compatible = "samsung,s5pv210-mipi-video-phy";
		reg = <0x10020710 8>;
		#phy-cells = <1>;
	};

	pd_mfc: mfc-power-domain@10023C40 {
		compatible = "samsung,exynos4210-pd";
		reg = <0x10023C40 0x20>;
	};

	pd_g3d: g3d-power-domain@10023C60 {
		compatible = "samsung,exynos4210-pd";
		reg = <0x10023C60 0x20>;
	};

	pd_lcd0: lcd0-power-domain@10023C80 {
		compatible = "samsung,exynos4210-pd";
		reg = <0x10023C80 0x20>;
	};

	pd_tv: tv-power-domain@10023C20 {
		compatible = "samsung,exynos4210-pd";
		reg = <0x10023C20 0x20>;
	};

	pd_cam: cam-power-domain@10023C00 {
		compatible = "samsung,exynos4210-pd";
		reg = <0x10023C00 0x20>;
	};

	pd_gps: gps-power-domain@10023CE0 {
		compatible = "samsung,exynos4210-pd";
		reg = <0x10023CE0 0x20>;
	};

	gic: interrupt-controller@10490000 {
		compatible = "arm,cortex-a9-gic";
		#interrupt-cells = <3>;
		interrupt-controller;
		reg = <0x10490000 0x1000>, <0x10480000 0x100>;
	};

	combiner: interrupt-controller@10440000 {
		compatible = "samsung,exynos4210-combiner";
		#interrupt-cells = <2>;
		interrupt-controller;
		reg = <0x10440000 0x1000>;
	};

	sys_reg: syscon@10010000 {
		compatible = "samsung,exynos4-sysreg", "syscon";
		reg = <0x10010000 0x400>;
	};

	camera {
		compatible = "samsung,fimc", "simple-bus";
		status = "disabled";
		#address-cells = <1>;
		#size-cells = <1>;
		ranges;

		clock_cam: clock-controller {
			 #clock-cells = <1>;
		};

		fimc_0: fimc@11800000 {
			compatible = "samsung,exynos4210-fimc";
			reg = <0x11800000 0x1000>;
			interrupts = <0 84 0>;
			clocks = <&clock 256>, <&clock 128>;
			clock-names = "fimc", "sclk_fimc";
			samsung,power-domain = <&pd_cam>;
			samsung,sysreg = <&sys_reg>;
			status = "disabled";
		};

		fimc_1: fimc@11810000 {
			compatible = "samsung,exynos4210-fimc";
			reg = <0x11810000 0x1000>;
			interrupts = <0 85 0>;
			clocks = <&clock 257>, <&clock 129>;
			clock-names = "fimc", "sclk_fimc";
			samsung,power-domain = <&pd_cam>;
			samsung,sysreg = <&sys_reg>;
			status = "disabled";
		};

		fimc_2: fimc@11820000 {
			compatible = "samsung,exynos4210-fimc";
			reg = <0x11820000 0x1000>;
			interrupts = <0 86 0>;
			clocks = <&clock 258>, <&clock 130>;
			clock-names = "fimc", "sclk_fimc";
			samsung,power-domain = <&pd_cam>;
			samsung,sysreg = <&sys_reg>;
			status = "disabled";
		};

		fimc_3: fimc@11830000 {
			compatible = "samsung,exynos4210-fimc";
			reg = <0x11830000 0x1000>;
			interrupts = <0 87 0>;
			clocks = <&clock 259>, <&clock 131>;
			clock-names = "fimc", "sclk_fimc";
			samsung,power-domain = <&pd_cam>;
			samsung,sysreg = <&sys_reg>;
			status = "disabled";
		};

		csis_0: csis@11880000 {
			compatible = "samsung,exynos4210-csis";
			reg = <0x11880000 0x4000>;
			interrupts = <0 78 0>;
			clocks = <&clock 260>, <&clock 134>;
			clock-names = "csis", "sclk_csis";
			bus-width = <4>;
			samsung,power-domain = <&pd_cam>;
			phys = <&mipi_phy 0>;
			phy-names = "csis";
			status = "disabled";
			#address-cells = <1>;
			#size-cells = <0>;
		};

		csis_1: csis@11890000 {
			compatible = "samsung,exynos4210-csis";
			reg = <0x11890000 0x4000>;
			interrupts = <0 80 0>;
			clocks = <&clock 261>, <&clock 135>;
			clock-names = "csis", "sclk_csis";
			bus-width = <2>;
			samsung,power-domain = <&pd_cam>;
			phys = <&mipi_phy 2>;
			phy-names = "csis";
			status = "disabled";
			#address-cells = <1>;
			#size-cells = <0>;
		};
	};

	watchdog@10060000 {
		compatible = "samsung,s3c2410-wdt";
		reg = <0x10060000 0x100>;
		interrupts = <0 43 0>;
		clocks = <&clock 345>;
		clock-names = "watchdog";
		status = "disabled";
	};

	rtc@10070000 {
		compatible = "samsung,s3c6410-rtc";
		reg = <0x10070000 0x100>;
		interrupts = <0 44 0>, <0 45 0>;
		clocks = <&clock 346>;
		clock-names = "rtc";
		status = "disabled";
	};

	keypad@100A0000 {
		compatible = "samsung,s5pv210-keypad";
		reg = <0x100A0000 0x100>;
		interrupts = <0 109 0>;
		clocks = <&clock 347>;
		clock-names = "keypad";
		status = "disabled";
	};

	sdhci@12510000 {
		compatible = "samsung,exynos4210-sdhci";
		reg = <0x12510000 0x100>;
		interrupts = <0 73 0>;
		clocks = <&clock 297>, <&clock 145>;
		clock-names = "hsmmc", "mmc_busclk.2";
		status = "disabled";
	};

	sdhci@12520000 {
		compatible = "samsung,exynos4210-sdhci";
		reg = <0x12520000 0x100>;
		interrupts = <0 74 0>;
		clocks = <&clock 298>, <&clock 146>;
		clock-names = "hsmmc", "mmc_busclk.2";
		status = "disabled";
	};

	sdhci@12530000 {
		compatible = "samsung,exynos4210-sdhci";
		reg = <0x12530000 0x100>;
		interrupts = <0 75 0>;
		clocks = <&clock 299>, <&clock 147>;
		clock-names = "hsmmc", "mmc_busclk.2";
		status = "disabled";
	};

	sdhci@12540000 {
		compatible = "samsung,exynos4210-sdhci";
		reg = <0x12540000 0x100>;
		interrupts = <0 76 0>;
		clocks = <&clock 300>, <&clock 148>;
		clock-names = "hsmmc", "mmc_busclk.2";
		status = "disabled";
	};

	ehci@12580000 {
		compatible = "samsung,exynos4210-ehci";
		reg = <0x12580000 0x100>;
		interrupts = <0 70 0>;
		clocks = <&clock 304>;
		clock-names = "usbhost";
		status = "disabled";
	};

	ohci@12590000 {
		compatible = "samsung,exynos4210-ohci";
		reg = <0x12590000 0x100>;
		interrupts = <0 70 0>;
		clocks = <&clock 304>;
		clock-names = "usbhost";
		status = "disabled";
	};

	mfc: codec@13400000 {
		compatible = "samsung,mfc-v5";
		reg = <0x13400000 0x10000>;
		interrupts = <0 94 0>;
		samsung,power-domain = <&pd_mfc>;
		clocks = <&clock 273>;
		clock-names = "mfc";
		status = "disabled";
	};

	serial@13800000 {
		compatible = "samsung,exynos4210-uart";
		reg = <0x13800000 0x100>;
		interrupts = <0 52 0>;
		clocks = <&clock 312>, <&clock 151>;
		clock-names = "uart", "clk_uart_baud0";
		status = "disabled";
	};

	serial@13810000 {
		compatible = "samsung,exynos4210-uart";
		reg = <0x13810000 0x100>;
		interrupts = <0 53 0>;
		clocks = <&clock 313>, <&clock 152>;
		clock-names = "uart", "clk_uart_baud0";
		status = "disabled";
	};

	serial@13820000 {
		compatible = "samsung,exynos4210-uart";
		reg = <0x13820000 0x100>;
		interrupts = <0 54 0>;
		clocks = <&clock 314>, <&clock 153>;
		clock-names = "uart", "clk_uart_baud0";
		status = "disabled";
	};

	serial@13830000 {
		compatible = "samsung,exynos4210-uart";
		reg = <0x13830000 0x100>;
		interrupts = <0 55 0>;
		clocks = <&clock 315>, <&clock 154>;
		clock-names = "uart", "clk_uart_baud0";
		status = "disabled";
	};

	i2c_0: i2c@13860000 {
		#address-cells = <1>;
		#size-cells = <0>;
		compatible = "samsung,s3c2440-i2c";
		reg = <0x13860000 0x100>;
		interrupts = <0 58 0>;
		clocks = <&clock 317>;
		clock-names = "i2c";
		pinctrl-names = "default";
		pinctrl-0 = <&i2c0_bus>;
		status = "disabled";
	};

	i2c_1: i2c@13870000 {
		#address-cells = <1>;
		#size-cells = <0>;
		compatible = "samsung,s3c2440-i2c";
		reg = <0x13870000 0x100>;
		interrupts = <0 59 0>;
		clocks = <&clock 318>;
		clock-names = "i2c";
		pinctrl-names = "default";
		pinctrl-0 = <&i2c1_bus>;
		status = "disabled";
	};

	i2c_2: i2c@13880000 {
		#address-cells = <1>;
		#size-cells = <0>;
		compatible = "samsung,s3c2440-i2c";
		reg = <0x13880000 0x100>;
		interrupts = <0 60 0>;
		clocks = <&clock 319>;
		clock-names = "i2c";
		status = "disabled";
	};

	i2c_3: i2c@13890000 {
		#address-cells = <1>;
		#size-cells = <0>;
		compatible = "samsung,s3c2440-i2c";
		reg = <0x13890000 0x100>;
		interrupts = <0 61 0>;
		clocks = <&clock 320>;
		clock-names = "i2c";
		status = "disabled";
	};

	i2c_4: i2c@138A0000 {
		#address-cells = <1>;
		#size-cells = <0>;
		compatible = "samsung,s3c2440-i2c";
		reg = <0x138A0000 0x100>;
		interrupts = <0 62 0>;
		clocks = <&clock 321>;
		clock-names = "i2c";
		status = "disabled";
	};

	i2c_5: i2c@138B0000 {
		#address-cells = <1>;
		#size-cells = <0>;
		compatible = "samsung,s3c2440-i2c";
		reg = <0x138B0000 0x100>;
		interrupts = <0 63 0>;
		clocks = <&clock 322>;
		clock-names = "i2c";
		status = "disabled";
	};

	i2c_6: i2c@138C0000 {
		#address-cells = <1>;
		#size-cells = <0>;
		compatible = "samsung,s3c2440-i2c";
		reg = <0x138C0000 0x100>;
		interrupts = <0 64 0>;
		clocks = <&clock 323>;
		clock-names = "i2c";
		status = "disabled";
	};

	i2c_7: i2c@138D0000 {
		#address-cells = <1>;
		#size-cells = <0>;
		compatible = "samsung,s3c2440-i2c";
		reg = <0x138D0000 0x100>;
		interrupts = <0 65 0>;
		clocks = <&clock 324>;
		clock-names = "i2c";
		status = "disabled";
	};

	spi_0: spi@13920000 {
		compatible = "samsung,exynos4210-spi";
		reg = <0x13920000 0x100>;
		interrupts = <0 66 0>;
		dmas = <&pdma0 7>, <&pdma0 6>;
		dma-names = "tx", "rx";
		#address-cells = <1>;
		#size-cells = <0>;
		clocks = <&clock 327>, <&clock 159>;
		clock-names = "spi", "spi_busclk0";
		pinctrl-names = "default";
		pinctrl-0 = <&spi0_bus>;
		status = "disabled";
	};

	spi_1: spi@13930000 {
		compatible = "samsung,exynos4210-spi";
		reg = <0x13930000 0x100>;
		interrupts = <0 67 0>;
		dmas = <&pdma1 7>, <&pdma1 6>;
		dma-names = "tx", "rx";
		#address-cells = <1>;
		#size-cells = <0>;
		clocks = <&clock 328>, <&clock 160>;
		clock-names = "spi", "spi_busclk0";
		pinctrl-names = "default";
		pinctrl-0 = <&spi1_bus>;
		status = "disabled";
	};

	spi_2: spi@13940000 {
		compatible = "samsung,exynos4210-spi";
		reg = <0x13940000 0x100>;
		interrupts = <0 68 0>;
		dmas = <&pdma0 9>, <&pdma0 8>;
		dma-names = "tx", "rx";
		#address-cells = <1>;
		#size-cells = <0>;
		clocks = <&clock 329>, <&clock 161>;
		clock-names = "spi", "spi_busclk0";
		pinctrl-names = "default";
		pinctrl-0 = <&spi2_bus>;
		status = "disabled";
	};

	pwm@139D0000 {
		compatible = "samsung,exynos4210-pwm";
		reg = <0x139D0000 0x1000>;
		interrupts = <0 37 0>, <0 38 0>, <0 39 0>, <0 40 0>, <0 41 0>;
		clocks = <&clock 336>;
		clock-names = "timers";
		#pwm-cells = <2>;
		status = "disabled";
	};

	amba {
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "arm,amba-bus";
		interrupt-parent = <&gic>;
		ranges;

		pdma0: pdma@12680000 {
			compatible = "arm,pl330", "arm,primecell";
			reg = <0x12680000 0x1000>;
			interrupts = <0 35 0>;
			clocks = <&clock 292>;
			clock-names = "apb_pclk";
			#dma-cells = <1>;
			#dma-channels = <8>;
			#dma-requests = <32>;
		};

		pdma1: pdma@12690000 {
			compatible = "arm,pl330", "arm,primecell";
			reg = <0x12690000 0x1000>;
			interrupts = <0 36 0>;
			clocks = <&clock 293>;
			clock-names = "apb_pclk";
			#dma-cells = <1>;
			#dma-channels = <8>;
			#dma-requests = <32>;
		};

		mdma1: mdma@12850000 {
			compatible = "arm,pl330", "arm,primecell";
			reg = <0x12850000 0x1000>;
			interrupts = <0 34 0>;
			clocks = <&clock 279>;
			clock-names = "apb_pclk";
			#dma-cells = <1>;
			#dma-channels = <8>;
			#dma-requests = <1>;
		};
	};

	fimd: fimd@11c00000 {
		compatible = "samsung,exynos4210-fimd";
		interrupt-parent = <&combiner>;
		reg = <0x11c00000 0x20000>;
		interrupt-names = "fifo", "vsync", "lcd_sys";
		interrupts = <11 0>, <11 1>, <11 2>;
		clocks = <&clock 140>, <&clock 283>;
		clock-names = "sclk_fimd", "fimd";
		samsung,power-domain = <&pd_lcd0>;
		status = "disabled";
	};
};


        (1) Contains other ".dtsi" files, such as:        

#include "skeleton.dtsi"。


        (2) The node name is a name in the form of "<Name>[@<Device Address>]". Contents in square brackets are not required. "name" is a simple ascii string of no more than 31 bits that should be named after the device it embodies. If the device described by the node has an address, the unit address should be added. Typically, the device address is the main address used to access the device, and this address is also listed in the node's reg attribute. The reg attribute will be described later. The naming of sibling nodes must be unique, but multiple nodes can use the same common name as long as the addresses are different. Examples of node names are as follows:

serial@13800000
serial@13810000


        (3) Each device in the system is represented as a device tree node, and each device tree node has a compatible attribute.
        (4) The compatible attribute is a key factor used by the operating system to determine which device driver to use to bind to a device. compatible is a list of strings. The first string specifies the exact device represented by this node. The format of the string is: "<Manufacturer>,<Model>", and the remaining characters The string represents other compatible devices

For example:

compatible = "arm,p1330","arm,primecell";


        (5) Addressable devices use the following attributes to encode address information into the device tree:

reg
#address-cells
#size-cells


        Each addressable device has a reg, which is a tuple table in the form: reg =<address 1 length 1[address 2 length 2][address 3 length 3]...>. Each tuple represents an address range used by this device. Each address value is a list of one or more 32-bit integers called cells. Likewise, the length value can be a list of cells or empty. Since the address and length fields are both variable-sized variables, the #address-cells and #size-cells attributes of the parent node are used to declare the number of cells in each field. In other words, correct interpretation of a reg attribute requires the values ​​of the parent node's #address-cells and #size-cells. For example, the corresponding description of the I2C device in the arch/arm/boot/dts/exynos4412-origen.dts file:

        12c@13860000 {
            #address-cells = <1>;
            #size-cells=<0>;
            samsung,12c-sda-delay = <100>;
            samsung,12c-max-bus-freq=<20000>;
            pinctr1-0 = <&i2c0_bus>;
            pinctrl-names = "default";
            status = "okay";

            s5m8767_pmic@66 {
                compatible = "samsung,s5m8767-pmic";
                reg=<0x66>;
......


        Where, the I2C host controller is a parent node, the length of the address is a 32-bit integer, and the address length is 0. s5m8767_pmic is a sub-node under the 12C host controller, and its address is 0x66. By convention, if a node has a reg attribute, the node's name must contain the device address, which is the first address value in the reg attribute.
        Regarding the device address, the following three aspects need to be discussed:

  1. Memory mapped device.

        Memory-mapped devices should have address ranges. For 32-bit addresses, one cell can be used to specify the address value and one cell can be used to specify the range. For 64-bit addresses, two cells should be used to specify the address value. There is also an address representation method for memory mapped devices, which is base address, offset and length. In this way, the address is also represented by two cells.

        ​ ​ 2. Non-memory mapped device.

        Some devices are not mapped to the CPU's memory bus. Although these devices can have an address range, they are not directly accessed by the CPU. Instead, the parent device's driver performs indirect access on behalf of the CPU. Typical examples of this type of device include the I2C device mentioned above, and NAND Flash also belongs to this type of device
        3. Range (address translation).

        The address space of the root node is described from the perspective of the CPU. The direct child nodes of the root node also use this address domain, such as chipid@10000000. However, the direct child nodes of the non-root node do not use this address domain, so this address needs to be converted. The ranges attribute is used for this purpose. For example, there is the following description in the arch/arm/boot/dts/hi3620.dtsi file.


 

        sysctrl: system-controller@802000 {
            compatible = "hisilicon,sysctrl";
            #address-cells = <1>;
            #size-cells = <1>;
            ranges = <0 0x802000 0x1000>;
            reg = <0x802000 0x1000>;
            smp-offset = <0x31c>;
            resume-offset = <0x308>;
            reboot-offset = <0x4>;

            clock:clock@0 {
                compatible = "hisilicon,hi3620-clock";
                reg = <0 0x10000>;
                #clock-cells = <1>;
        };
};


        "sysctrl: system-controller@802000" This node is the parent node of "clock: clock@0". An address range is defined in the parent node. This address range is determined by "<child address parent address child address space area size >" is described by such a tuple. So "<0 0x802000 0x1000>" means that sub-address 0 is mapped at 0x802000-0x802FFF of the parent address. And the sub-node "clock: clok@0" happens to use This address. Sometimes, this mapping is also one-to-one, that is, the child node uses the same address field as the parent node. This can be achieved through an empty ranges attribute. For example:

amba {
    #address-cells = <1>;
    #size-cells= <1>;
    compatible="arm,amba-bus";
    interrupt-parent = <&gic>;
    ranges;

    pdma0:pdma@12680000 {
    compatible = "arm,pl330","arm,primecell";
    reg = <0x12680000 0x1000>;
    interrupts = <0 35 0>;
    clocks=<&clock 292>;
    clock-names = "apb_pclk";
    #dma-cells = <1>;
    #dma-channels = <8>;
    #dma-requests = <32>;
};


The "pdma0: pdma@12680000" child node uses the same address domain as the "amba" parent node.

        (6) Four attributes are required to describe the interruption of the connection.
        Interrupt-controller: An empty attribute used to define that the node is a device that receives interrupts, that is, an interrupt controller.
        #interrupt-cells: An attribute of an interrupt controller node, which declares the number of cells in the interrupt indicator of the interrupt controller, similar to #address-cells.
        Interrupt-parent: An attribute of a device node, pointing to the interrupt controller to which the device is connected. If this device node does not have this attribute, then this node inherits this attribute from the parent node.
        interrupts: An attribute of a device node, containing a list of interrupt indicators, corresponding to each interrupt output signal on the device

gic:interrupt-controller@10490000 { 
    compatible = "arm,cortex-a9-gic"
    #interrupt-cells = <3>;
    interrupt-controller;
    reg=<0x104900000x1000>,<0x1080000 0x100>;
};


        The node above represents an interrupt controller, which is used to receive interrupts. The interrupt indicator occupies 3 cells.

amba {
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "arm,amba-bus";
    interrupt-parent = <&gic>;
    ranges;

    pdma0: pdma@12680000{
        compatible = "arm,p1330”,"arm,primecell";
        reg = <0x12680000 0x1000>;
        interrupts = <0 35 0>;
        clocks = <&clock 292>;
        clock-names = "apb_pclk";
        #dma-cells=<1>;
        #dma-channels = <8>;
        #dma-requests = <32>;
};


        "amba" node is an interrupt device, and the generated interrupt is connected to the "gic" interrupt controller. "pdma0:pdma@12680000" is a child node of "amba" and inherits the interrupt-parent of the parent node. Attribute, that is, the interrupt generated by this device is also connected to the "gic" interrupt controller. The interrupt indicator occupies 3 cells. The interrupt indicator of the "pdma0:pdma@12680000" node is "<0 35 0>", which means to view the corresponding document in the kernel. Because GIC is an interrupt controller developed by ARM. Looking at the Documentation/devicetree/bindingsarm/gic.txt kernel document, we can see that the first cell is the interrupt type, 0 is SPI, a shared peripheral interrupt, that is, this interrupt is generated by an external device. If generated, it can be connected to multiple ARM cores in an SoC; 1 is PPI, a private peripheral interrupt, that is, this interrupt is generated by a peripheral, but can only be connected to a specific ARM core in an SoC. The second cell is the interrupt number. The third cell is the trigger type of the interrupt, 0 means don't care.
        (7)The aliases node is used to specify the alias of the node. Because you need to use the full path to reference a node, when the child node is far away from the root node, the node name will appear longer, so it is more convenient to define an alias. Next, the spi 0 node is defined as an alias "spi0".
 

aliases {
    spi0 = &spi0;
...

        (8) The chosen node does not represent a real device, but is just a place to pass data for firmware and operating system, such as boot parameters. The data in the chosen node does not represent the hardware either. For example, the chosen node in the arch/arm/boot/dts/exynos4412-origen.dts file is defined as follows:

chosen {
    bootargs = "console=ttySAC2,115200";
};       


        (9) Device-specific data, used to define some attributes specific to a specific device. These properties are free to define, but new device-specific property names should use manufacturer prefixes to avoid conflicts with existing standard property names. Additionally, the meaning of attributes and subnodes must be documented in a binding document so that device driver programmers know how to interpret this data. The Documentation/devicetree/bindings/ directory of the kernel source code contains a large number of binding documents. When you find that some attributes in the device tree cannot be understood, you can find the answer by viewing the corresponding documents in this directory.


4. LED driver using device tree


        The use of device trees is an inevitable trend in the kernel. Currently, except for the earlier target boards that use platform devices, almost all new target boards in the kernel use device trees. In this case, the first thing we need to do to transform the previous LED driver is to add the corresponding LED device tree node to the device tree source file, modify arch/armboot/dts/exynos4412-fs4412.dts, and add the following code.


fsled2@11000C40{
    compatible = "fs4412,fsled";
    reg = <0x11000C40 0x8>;
    id  = <2>;
    pin = <7>;
};

fsled3@11000C20 {
    compatible = "fs4412,fsled";
    reg = <0x11000C20 0x8>;
    id  = <3>;
    pin = <0>;
};

fsled4@114001E0 {
    compatible = "fs4412,fsled";
    reg = <0x114001E0 0x8>;
    id  = <4>;
    pin = <4>;
};

fsled5@114001E0 {
    compatible = "fs4412,fsled";
    reg = <0x114001E0 0x8>;
    id  = <5>;
    pin = <5>;
};


The above code adds 4 LED device tree nodes, and the compatibles are all "fs4412,fsled"; the reg attribute is the respective I/O memory; the id attribute is a custom attribute, indicating the d number of the device; The pin attribute is also a custom attribute indicating the GPIO pin used.
        After modifying the code, use the following command to recompile the device tree file, and copy the compiled results to the directory specified by the TFTP server.

# make ARCH=arm dtbs
# cp arch/arm/boot/dts/exynos4412-fs4412.dtb ~/tftpboot/

The next step is to modify the driver. The main code is as follows

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/fs.h>
#include <linux/cdev.h>

#include <linux/slab.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>

#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>

#include <linux/of.h>

#include "fsled.h"

#define FSLED_MAJOR	256
#define FSLED_DEV_NAME	"fsled"

struct fsled_dev {
	unsigned int __iomem *con;
	unsigned int __iomem *dat;
	unsigned int pin;
	atomic_t available;
	struct cdev cdev;
};

static int fsled_open(struct inode *inode, struct file *filp)
{
	struct fsled_dev *fsled = container_of(inode->i_cdev, struct fsled_dev, cdev);

	filp->private_data = fsled;
	if (atomic_dec_and_test(&fsled->available))
		return 0;
	else {
		atomic_inc(&fsled->available);
		return -EBUSY;
	}
}

static int fsled_release(struct inode *inode, struct file *filp)
{
	struct fsled_dev *fsled = filp->private_data;

	writel(readl(fsled->dat) & ~(0x1 << fsled->pin), fsled->dat);

	atomic_inc(&fsled->available);
	return 0;
}

static long fsled_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	struct fsled_dev *fsled = filp->private_data;

	if (_IOC_TYPE(cmd) != FSLED_MAGIC)
		return -ENOTTY;

	switch (cmd) {
	case FSLED_ON:
		writel(readl(fsled->dat) | (0x1 << fsled->pin), fsled->dat);
		break;
	case FSLED_OFF:
		writel(readl(fsled->dat) & ~(0x1 << fsled->pin), fsled->dat);
		break;
	default:
		return -ENOTTY;
	}

	return 0;
}

static struct file_operations fsled_ops = {
	.owner = THIS_MODULE,
	.open = fsled_open,
	.release = fsled_release,
	.unlocked_ioctl = fsled_ioctl,
};

static int fsled_probe(struct platform_device *pdev)
{
	int ret;
	dev_t dev;
	struct fsled_dev *fsled;
	struct resource *res;

	ret = of_property_read_u32(pdev->dev.of_node, "id", &pdev->id);
	if (ret)
		goto id_err;

	dev = MKDEV(FSLED_MAJOR, pdev->id);
	ret = register_chrdev_region(dev, 1, FSLED_DEV_NAME);
	if (ret)
		goto reg_err;

	fsled = kzalloc(sizeof(struct fsled_dev), GFP_KERNEL);
	if (!fsled) {
		ret = -ENOMEM;
		goto mem_err;
	}

	cdev_init(&fsled->cdev, &fsled_ops);
	fsled->cdev.owner = THIS_MODULE;
	ret = cdev_add(&fsled->cdev, dev, 1);
	if (ret)
		goto add_err;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		ret = -ENOENT;
		goto res_err;
	}

	fsled->con = ioremap(res->start, resource_size(res));
	if (!fsled->con) {
		ret = -EBUSY;
		goto map_err;
	}
	fsled->dat = fsled->con + 1;

	ret = of_property_read_u32(pdev->dev.of_node, "pin", &fsled->pin);
	if (ret)
		goto pin_err;

	atomic_set(&fsled->available, 1);
	writel((readl(fsled->con) & ~(0xF  << 4 * fsled->pin)) | (0x1  << 4 * fsled->pin), fsled->con);
	writel(readl(fsled->dat) & ~(0x1 << fsled->pin), fsled->dat);
	platform_set_drvdata(pdev, fsled);

	return 0;

pin_err:
	iounmap(fsled->con);
map_err:
res_err:
	cdev_del(&fsled->cdev);
add_err:
	kfree(fsled);
mem_err:
	unregister_chrdev_region(dev, 1);
reg_err:
id_err:
	return ret;
}

static int fsled_remove(struct platform_device *pdev)
{
	dev_t dev;
	struct fsled_dev *fsled = platform_get_drvdata(pdev);

	dev = MKDEV(FSLED_MAJOR, pdev->id);

	iounmap(fsled->con);
	cdev_del(&fsled->cdev);
	kfree(fsled);
	unregister_chrdev_region(dev, 1);

	return 0;
}

static const struct of_device_id fsled_of_matches[] = {
	{ .compatible = "fs4412,fsled", },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, fsled_of_matches);

struct platform_driver pdrv = { 
	.driver = { 
		.name    = "fsled",
		.owner   = THIS_MODULE,
		.of_match_table = of_match_ptr(fsled_of_matches),
	},  
	.probe   = fsled_probe,
	.remove  = fsled_remove,
};

module_platform_driver(pdrv);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <e-mail>");
MODULE_DESCRIPTION("A simple character device driver for LEDs on FS4412 board");


        Lines 163 to 167 of the code add a fsled_of_matches array, which is used to match the nodes of the device tree, and of_match_table is assigned the corresponding value in the platform driver structure.
        The method of obtaining IO memory resources is the same as before, but id and pin are custom attributes. To obtain the values ​​
of these two attributes, use of_property_read_u32 function, the prototype is as follows.

int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value);


        np: device node object address
        propname: the name of the attribute.
        out_value: Returned attribute value.
        The function returns 0 for success, and non-0 for failure.

        The test method is similar to the previous one, except that there is no need to load the module for registering platform devices. The device tree and the kernel and modules are compiled separately, so if the hardware changes, you only need to modify the device tree and recompile. Neither the kernel nor the driver module need to be recompiled. This is a significant advantage of the device tree.

 

 


5. Exercises


1. The resource member of the platform device is used for recording ().
[A] Resource information of platform equipment [B] Status information of platform equipment
2.udev is a program that works in ( ).
[A]User space
[B]Kernel space
3.mdev will scan the files in the ( ) directory to automatically Create device nodes.
[A]/dev
[B] /sys/block
[C] /sys/class
4.The purpose of the Linux device tree is ().
[A] Platform identification
[B] Real-time configuration
[C] Device implantation
5. Common basic data types in device tree source files are ().
[A]Text string
[B] cells
[C] Binary data
[D] Mix of text strings and binary data
[E] String list
6. The command to compile the device tree is (< a i=20> A] make ulmage [B] make dtbs


 

Guess you like

Origin blog.csdn.net/qq_52479948/article/details/132127885