• 设备树..ing


      .dts==>.dtb ==>device_node ==>  platform_device ==> led_dev.c  ==>匹配 led_drv.c    (设备树是对平台设备总线的一种改进)

    1.使用设备树时平台设备总线源码分析

             平台设备总线分析:https://www.cnblogs.com/zsy12138/p/10391933.html

             

    struct bus_type platform_bus_type =
    {
        .name        = "platform",
        .dev_groups    = platform_dev_groups,
        .match        = platform_match,  
        /*
         *  platform_match --> platform_match(device,device_driver) --> of_driver_match_device(dev, drv)
         *  --> of_match_device(drv->of_match_table, dev)
         *  最终用总线的 devices 和总线的 driver->of_match_table 相互比较
         *
        */
        .uevent        = platform_uevent,
        .dma_configure    = platform_dma_configure,
        .pm        = &platform_dev_pm_ops,
    };
    
    
    /* 
     *   platform_driver 分配,设置,注册file_operations ,读取platform_device 硬件资源操作硬件
     */
    struct platform_driver 
        {
            int (*probe)(struct platform_device *);
            int (*remove)(struct platform_device *);
            void (*shutdown)(struct platform_device *);
            int (*suspend)(struct platform_device *, pm_message_t state);
            int (*resume)(struct platform_device *);
            struct device_driver driver;
            const struct platform_device_id *id_table;
            bool prevent_deferred_probe;
       };
    
    /*
     *   platform_device  指定硬件资源
     */
     struct platform_device 
       {
           const char  *name;
           int       id;
           bool        id_auto;
           struct device   dev;
           u32       num_resources;
           struct resource *resource;
       
           const struct platform_device_id *id_entry;
           char *driver_override; /* Driver name to force a match */
       
           /* MFD cell pointer */
           struct mfd_cell *mfd_cell;
       
           /* arch specific additions */
           struct pdev_archdata    archdata;
       };
    
    
    
    1.编写一个drv.c和.dts
           通过比较  platform_device->device->device_node->property->name       属性名字
                    platform_driver->device_driver->of_device_id->compatible   是否相同来决定是否支持该设备
                
    
    2.编写一个drv.c和dev.c
          通过比较 platform_driver->platform_device_id->name 
                   platform_device->name 是否相同来决定是否支持该设备


    匹配过程按优先顺序罗列如下:
    a. 比较 platform_dev.driver_override 和 platform_driver.drv->name
    b. 比较 platform_dev.dev.of_node的compatible属性 和 platform_driver.drv->of_match_table
    c. 比较 platform_dev.name 和 platform_driver.id_table
    d. 比较 platform_dev.name 和 platform_driver.drv->name

    jz2440.dtb 的 led 节点:

    #define S3C2410_GPF(_nr) ((5<<16) + (_nr))

    led { compatible
    = "jz2440_led"; reg = <S3C2410_GPF(5) 1>; };

    2.如何编译使用dts文件:

     1. 将jz2440.dtbs文件传入 linux-4.19-rc3/arch/arm/boot/dts

        2. 进入内核根目录,执行 make dtbs

     3. 拷贝编译好的dtb文件到网络文件系统 cp linux-4.19-rc3/arch/arm/boot/dts/jz2440.dtb    /work/nfs_root/

     4. 重启开发板,进入u-boot 命令模式,从网络文件系统下载jz2440.dtb文件   nfs 32000000  192.168.1.123:/work/nfs_root/jz2440.dtb

        5. 擦除 device_tree 分区,写入文件  nand erase device_tree &  nand write.jiffs2 32000000 device_tree

      6. 启动内核 boot

         7. 启动后,进入 /sys/devices/platform/50005.led/of_node(open for node)  ==> compatible  ,  name  ,  reg

         cat  compatible     ==>    jz2440_led/sys/fireware/devicetree/base/led

          cat  name          ==>    led/sys/fireware/devicetree/base/led

                  hexdump -C  reg    ==>    00 05 00 05 00 00 00 01  (大字节序)     <==>   S3C2410_GPF(5)  1

     

     

    3.设备树规范

     各个驱动的规范文档目录:linux-4.19Documentationdevicetreeinding

        各个单板的设备树文件:    linux-4.19archarmootdts

        设备树官方语法: https://www.devicetree.org/specifications/

        内核设备树语法: linux-4.19-rc3Documentationdevicetreeusage-model.txt

     

    3.1 .dts 格式

     (1) 语法:
    
       Devicetree node格式:
        [label:] node-name[@unit-address] {
        [properties definitions]
        [child nodes]
        };
    
       Property格式1:
        [label:] property-name = value;
    
       Property格式2(没有值):
        [label:] property-name;
    
       Property取值只有3种: 
        arrays of cells(1个或多个32位数据, 64位数据使用2个32位数据表示), 
        string(字符串), 
        bytestring(1个或多个字节)
    
       示例: 
        a. Arrays of cells : cell就是一个32位的数据
         interrupts = <17 0xc>;
    
        b. 64bit数据使用2个cell来表示:
         clock-frequency = <0x00000001 0x00000000>;
    
        c. A null-terminated string (有结束符的字符串):
         compatible = "simple-bus";
    
        d. A bytestring(字节序列) :
         local-mac-address = [00 00 12 34 56 78]; // 每个byte使用2个16进制数来表示
         local-mac-address = [000012345678];      // 每个byte使用2个16进制数来表示
    
        e. 可以是各种值的组合, 用逗号隔开:
         compatible = "ns16550", "ns8250";
         example = <0xf00f0000 19>, "a strange property format";
    
    (2) DTS文件布局(layout):
    
          /dts-v1/;                       // 表示dts的版本
          [memory reservations]           // 表示保留的内存区域,格式为: /memreserve/ <address> <length>;
          / {                     // 根节点,设备树的起点
          [property definitions]       // 属性,用来描述硬件信息
          [child nodes]
           };
    
    (
    3) 特殊的、默认的属性:       a. 根节点:         #address-cells       // 在它的子节点的reg属性中, 使用多少个u32整数来描述地址(address)         #size-cells        // 在它的子节点的reg属性中, 使用多少个u32整数来描述大小(size)         compatible       // 定义一系列的字符串, 用来指定内核中哪个machine_desc可以支持本设备                     // 即这个板子兼容哪些平台                     // uImage : smdk2410 smdk2440 mini2440 ==> machine_desc         model     // 咱这个板子是什么                 // 比如有2款板子配置基本一致, 它们的compatible是一样的                 // 那么就通过model来分辨这2款板子       b. /memory         device_type = "memory";         reg       // 用来指定内存的地址、大小       c. /chosen         bootargs     // 内核command line参数, 跟u-boot中设置的bootargs作用一样       d. /cpus         /cpus节点下有1个或多个cpu子节点, cpu子节点中用reg属性用来标明自己是哪一个cpu           所以 /cpus 中有以下2个属性:         #address-cells     // 在它的子节点的reg属性中, 使用多少个u32整数来描述地址(address)         #size-cells      // 在它的子节点的reg属性中, 使用多少个u32整数来描述大小(size)                    // 必须设置为0       e. /cpus/cpu*         device_type = "cpu";         reg      // 表明自己是哪一个cpu

    (4) 引用其他节点:       a. phandle :   // 节点中的phandle属性, 它的取值必须是唯一的(不要跟其他的phandle值一样)         pic@10000000 {         phandle = <1>;         interrupt-controller;         };         another-device-node {         interrupt-parent = <1>;     // 使用phandle值为1来引用上述节点         };       b. label:         PIC: pic@10000000 {         interrupt-controller;         };         another-device-node {         interrupt-parent = <&PIC>;      // 使用label来引用上述节点,              // 使用lable时实际上也是使用phandle来引用,              // 在编译dts文件为dtb文件时, 编译器dtc会在dtb中插入phandle属性         }; (5)包含其他.dts文件:       #include"jz2440.dtsi"       &LED {           pin = <S3C2440_GPF(7)>;          }; (1) 语法:    

    3.2 .dtb 格式

     使用dtc编译器把 .dts  ==> .dtb

     官方文档:  https://www.devicetree.org/specifications/                         

     内核文档:Documentation/devicetree/booting-without-of.txDTB文件布局:

    base ---->
                 ------------------------------
                 |         ftd_header         |     ->头部用来表名各个部分偏移地址,整个文件的偏移大小
                 ------------------------------
                 |      (alignment gap) (*)   |   -> 填充00对齐
                 ------------------------------
                 |        memory         |     ->保留的内存起始地址和大小
                 ------------------------------
                 |      (alignment gap)       |
                 ------------------------------
                 |                            |
                 |             block          |     ->存放节点的信息
                 |                            |
                 ------------------------------
                 |      (alignment gap)       |
                 ------------------------------
                 |                            |
                 |            strings         |     ->dts文件中的属性的名字
                 |                            |
                   ------------------------------
    (base + totalsize)--> 
    
           
    struct fdt_header 
    {
        uint32_t magic;                -> d00d feed 
        uint32_t totalsize;            -> 整个文件的大小
        uint32_t off_dt_struct;        -> blockd 的偏移地址
        uint32_t off_dt_strings;       -> string 的偏移地址
        uint32_t off_mem_rsvmap;       -> memory 的偏移地址
        uint32_t version;              ->
        uint32_t last_comp_version;    ->
        uint32_t boot_cpuid_phys;      ->
        uint32_t size_dt_strings;      ->
        uint32_t size_dt_struct;       ->
    };
    
     memory reservation block ->
                                 struct fdt_reserve_entry
                                 {
                                 uint64_t address;   // 保留内存的起始地址
                                 uint64_t size;      // 保存内存的大小
                                 };
                
    strings  block            ->  
                                 struct        // 代表属性 (属性的值放在这个结构体后面)
                                 {                   
                                 uint32_t len;       // 属性值长度
                                 uint32_t nameoff;  // 属性名字在string的偏移地址
                                 }
    
    
     <jz2440.dts>
    #define S3C2410_GPF(_nr)    ((5<<16) + (_nr))
    
    /dts-v1/;
    
    / {                                     -->0x00000001 表示根节点开始
         led                                --> 
            {                               -->0x00000001 表示节点开始 + 节点名字
             compatible = "jz2440_led";     -->0x00000003 表示属性开始 +        
                                                           struct{uint32_t len;属性值有多长 uint32_t nameoff;名字在string block的偏移值 }
                                                           +属性值(len个字节)
             reg = <S3C2410_GPF(5) 1>;      -->0x00000003 表示属性开始
            };                              -->0x00000002 表示节点结束
    };                                      -->0x00000002 表示根节点结束
                                            -->0x00000009 表示整个struct block 结束
    
    
    fdt_begin_node 0x00000001
    fdt_end_node 0x00000002
    fdt_prop 0x00000003
    fdt_nop 0x00000004
    fdt_end 0x00000009
    大端:低字节放在高地址 小端:低字节放在低地址 字符串,数组,结构体 没有大小端,都是先放低地址
    .dtb为大端字节序

    4. 内核对设备树的处理

    4.1  u-boot ,head.S对设备树的处理(内核版本 linux-4.19):

       uboot如何将设备树文件传递给内核

    bootloader启动内核时,会设置r0,r1,r2三个寄存器,
    thekernel=(void (*)(int,int ,unsigned int))0x30008000;
    thekernel(0,362,0x30000100); //r0,r1,r2
    
    r0一般设置为0;
    r1一般设置为machine id (单板ID,在使用设备树时该参数没有被使用);  // u-boot设置machine_id 和kernel设置machine_desc{init , num} , 
    r2一般设置ATAGS或DTB的开始地址                               //  当machine_id 和machine_desc->num 匹配则调用相应的machine_desc->init函数
                                                              
    bootloader给内核传递的参数时有2种方法:
    ATAGS 或 DTB
    
    a. __lookup_processor_type : 使用汇编指令读取CPU ID, 根据该ID找到对应的proc_info_list结构体(里面含有这类CPU的初始化函数、信息)
    b. __vet_atags             : 判断是否存在可用的ATAGS或DTB
    c. __create_page_tables    : 创建页表, 即创建虚拟地址和物理地址的映射关系
    d. __enable_mmu            : 使能MMU, 以后就要使用虚拟地址了
    e. __mmap_switched         : 上述函数里将会调用__mmap_switched
    f. 把bootloader传入的r2参数, 保存到变量__atags_pointer中
    g. 调用C函数start_kernel
    
    head.S/head-common.S  : 
    把bootloader传来的r0值, 赋给了C变量: processor_id    
    把bootloader传来的r1值, 赋给了C变量: __machine_arch_type
    把bootloader传来的r2值, 赋给了C变量: __atags_pointer     // dtb首地址

     4.2 main.c 对设备树的处理:

      寻找与jz2440.dts的根节点compatible适应度最高的machine_desc->dt_compat(model,compatible节点)

    
    
    a. 设备树根节点的compatible属性列出了一系列的字符串,
       表示它兼容的单板名,
       从"最兼容"到次之
    
    b. 内核中有多个machine_desc,
       其中有dt_compat成员, 它指向一个字符串数组, 里面表示该machine_desc支持哪些单板
    
    c. 使用compatile属性的值, 
       跟每一个machine_desc.dt_compat 比较,
       成绩为"吻合的compatile属性值的位置",
       成绩越低越匹配, 对应的machine_desc即被选中
       
    d. 多个machine_desc 如何编译,存放,读取
    
        static const char *const smdk2440_dt_compat[] __initconst=    //一个 machine_desc->dt_compat[]有多个字符串
        {                                                             //表示一个machine_desc可以支持的单板
                "samsung,smdk2440",
                "samsung,smdk2410",
                NULL
        }
    
        MACHINE_START(S3C2440, "SMDK2440")     // 使用MACHINE_START 与 MACHINE_END 定义一个machine desc 数据段
         .atag_offset    = 0x100,
         .dt_compat = smdk2440_dt_compat,      // 每个machine_desc->dt_compat 表明支持那些单板
         .init_irq    = s3c2440_init_irq,
         .map_io        = smdk2440_map_io,
         .init_machine    = smdk2440_machine_init,
         .init_time    = smdk2440_init_time,
        MACHINE_END
       
        #define MACHINE_START(_type,_name)                            //通过GNU的attribute机制的指定段属性__section__,
        static const struct machine_desc __mach_desc_##_type          //将所有用MACHINE_START定义的machine_desc数据段存放在
        __used                                                        //链接脚本变量__arch_info_begin ---- __arch_info_end中。
        __attribute__((__section__(".arch.info.init"))) = {           //
        .nr        = MACH_TYPE_##_type,                               //
        .name        = _name,                                          //
        
        .init.arch.info :             // archarmkernelvmlinux.lds.S 内核链接脚本
        {                             
            __arch_info_begin = .;
            *(.arch.info.init)
            __arch_info_end = .;
        }
        
        arch_get_next_mach()
        machine_desc *mdesc = __arch_info_begin           //通过直接把__arch_info_begin段地址当成指针赋给mdesc来读取machine_desc
        *match = m->dt_compat                             // 返回 machine_desc->dt_compat 再与 .dts->compatible属性比较
       
    e.dts想要什么样的machine_desc
    /{
       compatible = "samsung,smdk2440","samsung,smdk2410","samsung,smdk24xx";  // 优先第一个,依次下降
     } 
     
     
    
    函数调用过程:
    start_kernel // init/main.c
        setup_arch(&command_line);  // arch/arm/kernel/setup.c    
            mdesc = setup_machine_fdt(__atags_pointer);  // arch/arm/kernel/devtree.c
                        early_init_dt_verify(phys_to_virt(dt_phys)       // 判断是否是效的dtb(通过头部是否含有amgic), drivers/of/ftd.c
                                        initial_boot_params = params;
                        mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);  // 找到最匹配的machine_desc, drivers/of/ftd.c
                                        while ((data = get_next_compat(&compat))) {
                                            score = of_flat_dt_match(dt_root, compat);
                                            if (score > 0 && score < best_score) {
                                                best_data = data;
                                                best_score = score;
                                            }
                                        }
                        
            machine_desc = mdesc;
            
    
    
    
     

    4.3 内核对于设备树配置信息的处理

    fdt.c如何将内核树信息提取出来(chosen,#address-cells,#size-cells ,memory节点)

    函数调用过程:
    start_kernel // init/main.c
        setup_arch(&command_line);  // arch/arm/kernel/setup.c
            mdesc = setup_machine_fdt(__atags_pointer);  // arch/arm/kernel/devtree.c
                        early_init_dt_scan_nodes();      // drivers/of/ftd.c
                            /* Retrieve various information from the /chosen node */
                            of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
    
                            /* Initialize {size,address}-cells info */
                            of_scan_flat_dt(early_init_dt_scan_root, NULL);
    
                            /* Setup memory, calling early_init_dt_add_memory_arch */
                            of_scan_flat_dt(early_init_dt_scan_memory, NULL);
    
    a. /chosen节点中bootargs属性的值, 存入全局变量: boot_command_line
    b. 确定根节点的这2个属性的值: #address-cells, #size-cells   存入全局变量: dt_root_addr_cells, dt_root_size_cells
    c. 解析/memory中的reg属性, 提取出"base, size", 最终调用memblock_add(base, size);添加一个内存块

    4.4  dtb转换为device_node(unflatten)

    unflatten_device_tree()如何将节点转化为树

    函数调用过程:
    start_kernel // init/main.c
        setup_arch(&command_line);  // arch/arm/kernel/setup.c
    arm_memblock_init(mdesc); early_init_fdt_reserve_self(); // 把DTB所占区域保留下来, 即调用: memblock_reserve
    early_init_dt_reserve_memory_arch(__pa(initial_boot_params), fdt_totalsize(initial_boot_params),0); early_init_fdt_scan_reserved_mem(); // 根据dtb中的memreserve信息, 调用memblock_reserve
    unflatten_device_tree();
    // 把设备树转化为一棵树 __unflatten_device_tree(initial_boot_params, NULL,
                &of_root,early_init_dt_alloc_memory_arch, false); // drivers/of/fdt.c /* First pass, scan for size */ size = unflatten_dt_nodes(blob, NULL, dad, NULL); /* Allocate memory for the expanded device tree */ mem = dt_alloc(size + 4, __alignof__(struct device_node)); /* Second pass, do actual unflattening */ unflatten_dt_nodes(blob, mem, dad, mynodes); offset = fdt_next_node(blob, offset, &depth)) { // 将节点一个一个读取出来 populate_node() // 将读取出来的节点构造为设备树 np = unflatten_dt_alloc(mem, sizeof(struct device_node) +
                                  allocl, __alignof__(struct device_node)); // 分配一个device_node空间 np->full_name = fn = ((char *)np) + sizeof(*np); // 把名字写到node最后 populate_properties // 设置device_node的属性 pp = unflatten_dt_alloc(mem, sizeof(struct property),__alignof__(struct property)); pp->name = (char *)pname; pp->length = sz; pp->value = (__be32 *)val; a. 在DTB文件中, 每一个节点都以TAG(FDT_BEGIN_NODE, 0x00000001)开始, 节点内部可以嵌套其他节点, 每一个属性都以TAG(FDT_PROP, 0x00000003)开始 b. 每一个节点都转换为一个device_node结构体: struct device_node { const char *name; // 来自节点中的name属性, 如果没有该属性, 则设为"NULL" const char *type; // 来自节点中的device_type属性, 如果没有该属性, 则设为"NULL" phandle phandle; const char *full_name; // 节点的名字, node-name[@unit-address] struct fwnode_handle fwnode; struct property *properties; // 节点的属性 struct property *deadprops; /* removed properties */ struct device_node *parent; // 节点的父亲 struct device_node *child; // 节点的孩子(子节点) struct device_node *sibling; // 节点的兄弟(同级节点) #if defined(CONFIG_OF_KOBJ) struct kobject kobj; #endif unsigned long _flags; void *data; #if defined(CONFIG_SPARC) const char *path_component_name; unsigned int unique_id; struct of_irq_controller *irq_trans; #endif }; c. device_node结构体中有properties, 用来表示该节点的属性 每一个属性对应一个property结构体: struct property { char *name; // 属性名字, 指向dtb文件中的字符串 int length; // 属性值的长度 void *value; // 属性值, 指向dtb文件中value所在位置, 数据仍以big endian存储 struct property *next; #if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC) unsigned long _flags; #endif #if defined(CONFIG_OF_PROMTREE) unsigned int unique_id; #endif #if defined(CONFIG_OF_KOBJ) struct bin_attribute attr; #endif }; d. 这些device_node构成一棵树, 根节点为: of_root

     

    4.5 设备树与device_node ,property关系图

     jz2440.dts源码

    #define S3C2410_GPF(_nr)    ((5<<16) + (_nr))
    
    /dts-v1/;
    
    / {
        model = "SMDK24440";
        compatible = "samsung,smdk2440";
        #address-cells = <1>;
        #size-cells = <1>;
            
        memory@30000000 
        {
            device_type = "memory";
            reg =  <0x30000000 0x4000000>;
        };
        
        chosen 
        {
            bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";
        };
        
        led 
        {
            compatible = "jz2440_led";
            reg = <S3C2410_GPF(5) 1>;
        };
    };

     关系图:

    4.6 device_node转换为platform_device

     platform_device->device->device_node

    a. 哪些device_node可以转换为platform_device?
    根节点下含有compatile属性的子节点
    如果一个结点的compatile属性含有这些特殊的值("simple-bus","simple-mfd","isa","arm,amba-bus")之一, 那么它的子结点(需含compatile属性)也可以转换为platform_device
    i2c, spi等总线节点下的子节点, 应该交给对应的总线驱动程序来处理, 它们不应该被转换为platform_device
    
    b. 怎么转换?
    platform_device中含有resource数组, 它来自device_node的reg, interrupts属性,从device_node转换得到;
    platform_device.dev.of_node指向device_node, 可以通过它获得其他属性
    
    struct platform_device {
        const char    *name;
        int        id;
        bool        id_auto;
        struct device    dev;         // device->of_node(device_node) 保存device_node其他属性
        u32        num_resources;       // 资源数组有多少项
        struct resource    *resource;   // 资源数组(资源种类IO/MEM/INT)
    };
    
    
    
    b.4 示例: 
        比如以下的节点, 
        /mytest会被转换为platform_device, 
        因为它兼容"simple-bus", 它的子节点/mytest/mytest@0 也会被转换为platform_device
    
        /i2c节点一般表示i2c控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver;
        /i2c/at24c02节点不会被转换为platform_device, 它被如何处理完全由父节点的platform_driver决定, 一般是被创建为一个i2c_client。
    
        类似的也有/spi节点, 它一般也是用来表示SPI控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver;
        /spi/flash@0节点不会被转换为platform_device, 它被如何处理完全由父节点的platform_driver决定, 一般是被创建为一个spi_device。
        
        / {
              mytest {
                  compatile = "mytest", "simple-bus";
                  mytest@0 {
                        compatile = "mytest_0";
                  };
              };
              
              i2c {
                  compatile = "samsung,i2c";
                  at24c02 {
                        compatile = "at24c02";                      
                  };
              };
    
              spi {
                  compatile = "samsung,spi";              
                  flash@0 {
                        compatible = "winbond,w25q32dw";
                        spi-max-frequency = <25000000>;
                        reg = <0>;
                      };
              };
          };
    
    
    函数调用过程: 
    a. of_platform_default_populate_init (drivers/of/platform.c) 被调用到过程:
    start_kernel     // init/main.c
        rest_init();
            pid = kernel_thread(kernel_init, NULL, CLONE_FS);
                        kernel_init
                            kernel_init_freeable();
                                do_basic_setup();
                                    do_initcalls();
                                        for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
                                            do_initcall_level(level);  // 比如 do_initcall_level(3)
                                                   for (fn = initcall_levels[3]; fn < initcall_levels[3+1]; fn++)
                                                        do_one_initcall(initcall_from_entry(fn));  // 就是调用"arch_initcall_sync(fn)"中定义的fn函数
                常见GNU的attribite指定section机制
                arch_initcall_sync(of_platform_default_populate_init);
                #define arch_initcall_sync(fn)        __define_initcall(fn, 3s)                                                                        
                #define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)                                                                        
                #define ___define_initcall(fn, id, __sec) static initcall_t __initcall_##fn##id __used __attribute__((__section__(#__sec ".init"))) = fn;                                                                        
                                                                                        
                                                                                        
    b. of_platform_default_populate_init  (drivers/of/platform.c) 生成platform_device的过程:
    of_platform_default_populate_init
        of_platform_default_populate(NULL, NULL, NULL);
            of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL)        // 如果节点中含有of_default_bus_match_table的值之一,就为子节点创建platform_device
                for_each_child_of_node(root, child) {                                 // 便利整个device_node树,为特定的节点生成platform_device
                    rc = of_platform_bus_create(child, matches, lookup, parent, true);// 创建总线
                                dev = of_device_alloc(np, bus_id, parent);            // 根据device_node节点的属性设置platform_device的resource
                                    of_address_to_resource(np, i, res);               // address转资源
                                    of_irq_to_resource_table(np, res, num_irq)        // 中断转资源
                    if (rc) {
                        of_node_put(child);
                        break;
                    }
                }
                
    c. of_platform_bus_create(bus, matches, ...)的调用过程(处理bus节点生成platform_devie, 并决定是否处理它的子节点):
            dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);  // 生成bus节点的platform_device结构体
            if (!dev || !of_match_node(matches, bus))  // 如果bus节点的compatile属性不吻合matches成表, 就不处理它的子节点
                return 0;
    
            for_each_child_of_node(bus, child) {    // 取出每一个子节点
                pr_debug("   create child: %pOF
    ", child);
                rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);   // 处理它的子节点, of_platform_bus_create是一个递归调用
                if (rc) {
                    of_node_put(child);
                    break;
                }
            }
            
    d. I2C总线节点的处理过程:
       /i2c节点一般表示i2c控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver;
       platform_driver的probe函数中会调用i2c_add_numbered_adapter:
       
       i2c_add_numbered_adapter   // drivers/i2c/i2c-core-base.c
            __i2c_add_numbered_adapter
                i2c_register_adapter
                    of_i2c_register_devices(adap);   // drivers/i2c/i2c-core-of.c
                        for_each_available_child_of_node(bus, node) {
                            client = of_i2c_register_device(adap, node);
                                            client = i2c_new_device(adap, &info);   // 设备树中的i2c子节点被转换为i2c_client
                        }
                        
    e. SPI总线节点的处理过程:
       /spi节点一般表示spi控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver;
       platform_driver的probe函数中会调用spi_register_master, 即spi_register_controller:
       
       spi_register_controller        // drivers/spi/spi.c
            of_register_spi_devices   // drivers/spi/spi.c
                for_each_available_child_of_node(ctlr->dev.of_node, nc) {
                    spi = of_register_spi_device(ctlr, nc);  // 设备树中的spi子节点被转换为spi_device
                                    spi = spi_alloc_device(ctlr);
                                    rc = of_spi_parse_dt(ctlr, spi, nc);
                                    rc = spi_add_device(spi);
                }

    5.1 内核中设备树的操作函数

    dtb -> device_node -> platform_device
    a. 处理DTB
    of_fdt.h           // dtb文件的相关操作函数, 我们一般用不到, 因为dtb文件在内核中已经被转换为device_node树(它更易于使用)
    
    b. 处理device_node
    of.h               // 提供设备树的一般处理函数, 比如 of_property_read_u32(读取某个属性的u32值), of_get_child_count(获取某个device_node的子节点数)
    of_address.h       // 地址相关的函数, 比如 of_get_address(获得reg属性中的addr, size值)
    of_match_device(从matches数组中取出与当前设备最匹配的一项)
    of_dma.h           // 设备树中DMA相关属性的函数
    of_gpio.h          // GPIO相关的函数
    of_graph.h         // GPU相关驱动中用到的函数, 从设备树中获得GPU信息
    of_iommu.h         // 很少用到
    of_irq.h           // 中断相关的函数
    of_mdio.h          // MDIO (Ethernet PHY) API
    of_net.h           // OF helpers for network devices. 
    of_pci.h           // PCI相关函数
    of_pdt.h           // 很少用到
    of_reserved_mem.h  // reserved_mem的相关函数
    
    c. 处理 platform_device
    of_platform.h      // 把device_node转换为platform_device时用到的函数, 
                       // 比如of_device_alloc(根据device_node分配设置platform_device), 
                       //     of_find_device_by_node (根据device_node查找到platform_device),
                       //     of_platform_bus_probe (处理device_node及它的子节点)
    of_device.h        // 设备相关的函数, 比如 of_match_device

    5.2 在根文件系统中查看设备树(有助于调试)

    a. /sys/firmware/fdt        // 原始dtb文件
    
      hexdump -C /sys/firmware/fdt
    
    b. /sys/firmware/devicetree // 以目录结构程现的dtb文件, 根节点对应base目录, 每一个节点对应一个目录, 每一个属性对应一个文件
    
    c. /sys/devices/platform    // 系统中所有的platform_device, 有来自设备树的, 也有来有.c文件中注册的
       对于来自设备树的platform_device,
       可以进入 /sys/devices/platform/<设备名>/of_node 查看它的设备树属性
    
    d.  /proc/device-tree 是链接文件, 指向 /sys/firmware/devicetree/base

    6 u-boot 可以对设备树做些什么

    6.1 uboot如何传递设备树给内核

    a. u-boot中内核启动命令:
       bootm <uImage_addr>                            // 无设备树,bootm 0x30007FC0
       bootm <uImage_addr> <initrd_addr> <dtb_addr>   // 有设备树
       
       比如 :
       nand read.jffs2 0x30007FC0 kernel;     // 读内核uImage到内存0x30007FC0
       nand read.jffs2 32000000 device_tree;  // 读dtb到内存32000000
       bootm 0x30007FC0 - 0x32000000          // 启动, 没有initrd时对应参数写为"-"
    
    b. bootm命令怎么把dtb_addr写入r2寄存器传给内核?
       ARM程序调用规则(ATPCS)
       
          c_function(p0, p1, p2) // p0 => r0, p1 => r1, p2 => r2
          
          定义函数指针 the_kernel, 指向内核的启动地址,
          然后执行: the_kernel(0, machine_id, 0x32000000);
          
    
    c. dtb_addr 可以随便选吗?
       c.1 不要破坏u-boot本身
       c.2 不要挡内核的路: 内核本身的空间不能占用, 内核要用到的内存区域也不能占用
                           内核启动时一般会在它所处位置的下边放置页表, 这块空间(一般是0x4000即16K字节)不能被占用
       
      JZ2440内存使用情况:
                         ------------------------------
      0x33f80000       ->|    u-boot                  |
                         ------------------------------
                         |    u-boot所使用的内存(栈等)|
                         ------------------------------
                         |                            |
                         |                            |
                         |        空闲区域            |
                         |                            |
                         |                            |
                         |                            |
                         |                            |
                         ------------------------------
      0x30008000       ->|      zImage                |
                         ------------------------------  uImage = 64字节的头部+zImage
      0x30007FC0       ->|      uImage头部            |
                         ------------------------------
      0x30004000       ->|      内核创建的页表        |  head.S
                         ------------------------------
                         |                            |
                         |                            |
                  -----> ------------------------------
                  |
                  |
                  --- (内存基址 0x30000000)
    
    
    命令示例:
    a. 可以启动:
    nand read.jffs2 30000000 device_tree
    nand read.jffs2 0x30007FC0 kernel
    bootm 0x30007FC0 - 30000000
    
    b. 不可以启动: 内核启动时会使用0x30004000的内存来存放页表,dtb会被破坏
    nand read.jffs2 30004000 device_tree
    nand read.jffs2 0x30007FC0 kernel
    bootm 0x30007FC0 - 30004000

    6.2 .dtb文件的修改原理

    例子1. 修改属性的值,
    假设 老值: len
         新值: newlen (假设newlen > len)
    
    a. 把原属性val所占空间从len字节扩展为newlen字节:
       把老值之后的所有内容向后移动(newlen - len)字节
    
    b. 把新值写入val所占的newlen字节空间
    
    c. 修改dtb头部信息中structure block的长度: size_dt_struct
    
    d. 修改dtb头部信息中string block的偏移值: off_dt_strings
    
    e. 修改dtb头部信息中的总长度: totalsize
    
    
    
    例子2. 添加一个全新的属性
    a. 如果在string block中没有这个属性的名字,
       就在string block尾部添加一个新字符串: 属性的名
       并且修改dtb头部信息中string block的长度: size_dt_strings
       修改dtb头部信息中的总长度: totalsize
    
    b. 找到属性所在节点, 在节点尾部扩展一块空间, 内容及长度为: 
       TAG      // 4字节, 对应0x00000003
       len      // 4字节, 表示属性的val的长度
       nameoff  // 4字节, 表示属性名的offset
       val      // len字节, 用来存放val
    
    c. 修改dtb头部信息中structure block的长度: size_dt_struct
    
    d. 修改dtb头部信息中string block的偏移值: off_dt_strings
    
    e. 修改dtb头部信息中的总长度: totalsize
    
    
    可以从u-boot官网源码下载一个比较新的u-boot, 查看它的cmd/fdt.c
    ftp://ftp.denx.de/pub/u-boot/
    
    fdt命令集合:

    "fdt move <fdt> <newaddr> <length>   - Copy the fdt to <addr> and make it active "
    "fdt resize [<extrasize>]        - Resize fdt to size + padding to 4k addr + some optional <extrasize> if needed "
    "fdt print <path> [<prop>]        - Recursive print starting at <path> "
    "fdt list <path> [<prop>]            - Print one level starting at <path> "
    "fdt get value <var> <path> <prop>   - Get <property> and store in <var> "
    "fdt get name <var> <path> <index>   - Get name of node <index> and store in <var> "
    "fdt get addr <var> <path> <prop>    - Get start address of <property> and store in <var> "
    "fdt get size <var> <path> [<prop>]  - Get size of [<property>] or num nodes and store in <var> "
    "fdt set <path> <prop> [<val>]       - Set <property> [to <val>] "
    "fdt mknode <path> <node>            - Create a new node after <path> "
    "fdt rm <path> [<prop>]              - Delete the node or <property> "
    "fdt header                          - Display header info "
    "fdt bootcpu <id>                    - Set boot cpuid "
    "fdt memory <addr> <size>            - Add/Update memory node "
    "fdt rsvmem print                    - Show current mem reserves "
    "fdt rsvmem add <addr> <size>        - Add a mem reserve "
    "fdt rsvmem delete <index>           - Delete a mem reserves "
    "fdt chosen [<start> <end>]          - Add/update the /chosen branch in the tree "

    
    
    fdt命令调用过程:
        fdt set    <path> <prop> [<val>] 
    a. 根据path找到节点
    b. 根据val确定新值长度newlen, 并把val转换为字节流
    c. fdt_setprop
            c.1 fdt_setprop_placeholder       // 为新值在DTB中腾出位置
                     fdt_get_property_w     // 得到老值的长度 oldlen
                     fdt_splice_struct_       // 腾空间
                            fdt_splice_       // 使用memmove移动DTB数据, 移动(newlen-oldlen)
                            fdt_set_size_dt_struct  // 修改DTB头部, size_dt_struct
                            fdt_set_off_dt_strings  // 修改DTB头部, off_dt_strings
                            
            c.2 memcpy(prop_data, val, len);        // 在DTB中存入新值

    6.3 fdt命令的移植

    我们仍然使用u-boot 1.1.6,需要在里面添加fdc命令命令, 这个命令可以用来查看、修改dtb
    从u-boot官网下载最新的源码, 把里面的 cmd/fdt.c移植过来.
    
    u-boot官网源码:
    ftp://ftp.denx.de/pub/u-boot/
    
      最终的补丁存放在如下目录: doc_and_sources_for_device_treesource_and_imagesu-bootu-boot-1.1.6_device_tree_for_jz2440_add_fdt_20181022.patch
      补丁使用方法:
      export  PATH=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/work/system/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabi/bin
      tar xjf u-boot-1.1.6.tar.bz2                                                // 解压
      cd u-boot-1.1.6                  
      patch -p1 < ../u-boot-1.1.6_device_tree_for_jz2440_add_fdt_20181022.patch   // 打补丁
      make 100ask24x0_config                                                      // 配置
      make                                                                        // 编译, 可以得到u-boot.bin
    
      
    a. 移植fdt命令
    a.1 先把代码移过去, 修改Makefile来编译
    u-boot-2018.11-rc2liblibfdt   主要用这个目录, 
                                    它里面的大部分文件是直接包含scriptsdtclibfdt中的同名文件
                                    只有2个文件是自己的版本
    u-boot-2018.11-rc2scriptsdtclibfdt
    
    
    把新u-boot中cmd/fdt.c重命名为cmd_fdt.c , 和 lib/libfdt   // 一起复制到老u-boot的common/fdt目录
    修改 老u-boot/Makefile, 添加一行: LIBS += common/fdt/libfdt.a
    修改 老u-boot/common/fdt/Makefile, 仿照 drivers/nand/Makefile来修改
    
    
    a.2 根据编译的错误信息修改源码
    
    移植时常见问题:
    i. No such file or directory:
       要注意, 
       #include "xxx.h"  // 是在当前目录下查找xxx.h
       #include <xxx.h>  // 是在指定目录下查找xxx.h, 哪些指定目录呢?
                         // 编译文件时可以用"-I"选项指定头文件目录, 
                         // 比如: arm-linux-gcc -I <dir> -c -o ....
                         // 对于u-boot来说, 一般就是源码的 include目录
       
       解决方法: 
       确定头文件在哪, 把它移到include目录或是源码的当前目录
    
    ii. xxx undeclared :
       宏, 变量, 函数未声明/未定义
       
       对于宏, 去定义它;
       对于变量, 去定义它或是声明为外部变量;
       对于函数, 去实现它或是声明为外部函数;
       
    
    iii. 上述2个错误是编译时出现的,
       当一切都没问题时, 最后就是链接程序, 这时常出现: undefined reference to `xxx'
       这表示代码里用到了xxx函数, 但是这个函数没有实现
       
       解决方法: 去实现它, 或是找到它所在文件, 把这文件加入工程
       
    
    b. fdt命令使用示例
    nand read.jffs2 32000000 device_tree  // 从flash读出dtb文件到内存(0x32000000)
    fdt addr 32000000                     // 告诉fdt, dtb文件在哪
    fdt print /led pin                    // 打印/led节点的pin属性
    fdt get value XXX /led pin            // 读取/led节点的pin属性, 并且赋给环境变量XXX
    print XXX                             // 打印环境变量XXX的值
    fdt set /led pin <0x00050005>         // 设置/led节点的pin属性
    fdt print /led pin                    // 打印/led节点的pin属性
    nand erase device_tree                // 擦除flash分区
    nand write.jffs2 32000000 device_tree // 把修改后的dtb文件写入flash分区
    7.1 Linux对中断处理的框架及代码流程简述
    a. 异常向量入口: archarmkernelentry-armv.S
    
        .section .vectors, "ax", %progbits
    .L__vectors_start:
        W(b)    vector_rst
        W(b)    vector_und
        W(ldr)  pc, .L__vectors_start + 0x1000
        W(b)    vector_pabt
        W(b)    vector_dabt
        W(b)    vector_addrexcptn
        W(b)    vector_irq
        W(b)    vector_fiq
    
    b. 中断向量: vector_irq
    /*
     * Interrupt dispatcher
     */
        vector_stub irq, IRQ_MODE, 4   // 相当于 vector_irq: ..., 
                                       // 它会根据SPSR寄存器的值,
                                       // 判断被中断时CPU是处于USR状态还是SVC状态, 
                                       // 然后调用下面的__irq_usr或__irq_svc
    
        .long   __irq_usr               @  0  (USR_26 / USR_32)
        .long   __irq_invalid           @  1  (FIQ_26 / FIQ_32)
        .long   __irq_invalid           @  2  (IRQ_26 / IRQ_32)
        .long   __irq_svc               @  3  (SVC_26 / SVC_32)
        .long   __irq_invalid           @  4
        .long   __irq_invalid           @  5
        .long   __irq_invalid           @  6
        .long   __irq_invalid           @  7
        .long   __irq_invalid           @  8
        .long   __irq_invalid           @  9
        .long   __irq_invalid           @  a
        .long   __irq_invalid           @  b
        .long   __irq_invalid           @  c
        .long   __irq_invalid           @  d
        .long   __irq_invalid           @  e
        .long   __irq_invalid           @  f
    
    c. __irq_usr/__irq_svc
    
        __irq_usr:
        usr_entry                  // 保存现场
        kuser_cmpxchg_check
        irq_handler                  // 调用 irq_handler
        get_thread_info tsk
        mov    why, #0
        b    ret_to_user_from_irq   // 恢复现场
    
       
    d. irq_handler: 将会调用C函数 handle_arch_irq
    
        .macro  irq_handler
    #ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
        ldr r1, =handle_arch_irq
        mov r0, sp
        badr    lr, 9997f
        ldr pc, [r1]
    #else
        arch_irq_handler_default
    #endif
    9997:
        .endm
        
    e. handle.c
       set_handle_irq( pointer_to_fun() )
            handle_arch_irq = pointer_to_fun()
            
    

    f. handle_arch_irq的处理过程: (读取中断控制器得到硬件中断号,然后再找到中断控制器对应的域,在域里面从硬件中断号得到虚拟中断号,找到irq_desc[virq])
       读取寄存器获得中断信息: hwirq
       把hwirq转换为virq                  // hwirq为硬件中断号,virq为虚拟中断号 (两者间有偏移值)
                               // #define S3C2410_CPUIRQ_OFFSET (16) #define S3C2410_IRQ(x) ((x) + S3C2410_CPUIRQ_OFFSET)
       调用 1.irq_desc[virq].handle_irq        //处理中断
    2.irq_desc[virq].irq_data.irq_chip.fun()   // 清中断

       对于S3C2440,irq_s3c24xx.c是入口源文件, s3c24xx_handle_irq 是用于处理中断的C语言入口函数
       set_handle_irq(s3c24xx_handle_irq);

    
        
     注:
        irq_desc[nr_irqs]                  // 包含有多个irq_desc结构体,每个对应不同的中断
        
        struct irq_desc 
        {
        struct irq_data        irq_data;     // 带有具体处理中断函数
        irq_flow_handler_t    handle_irq;   // 1.调用action链表中的handler(也就是具体的处理函数) 2.再清中断(使用irq_data->chip的函数)
        struct irqaction    *action;      // 指向irqaction链表
        }
    
        struct irqaction 
        {
        irq_handler_t        handler;      // 用户设置的中断的具体处理函数
        void                *dev_id;      // request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
    struct irqaction *next;
        } 
    
        struct irq_data 
        {
        u32            mask;
        unsigned int        irq;
        unsigned long        hwirq;
        struct irq_common_data    *common;
        struct irq_chip        *chip;        // 很多中断操作函数
        struct irq_domain    *domain;
        void            *chip_data;
        };
        
        
        struct irq_chip 
        {
        void    (*irq_enable)(struct irq_data *data);   // 使能中断函数
        void    (*irq_disable)(struct irq_data *data);  // 去使能中断函数
        void    (*irq_ack)(struct irq_data *data);
        void    (*irq_mask)(struct irq_data *data);        //屏蔽中断函数
        void    (*irq_unmask)(struct irq_data *data);
        }


    中断处理流程:
    假设中断结构如下:
    sub int controller ---> int controller ---> cpu

    
    

    发生中断时,
    cpu跳到"vector_irq", 保存现场, 调用C函数handle_arch_irq

    
    

    handle_arch_irq:
    a. 读 int controller, 得到hwirq
    b. 根据hwirq得到virq
    c. 调用 irq_desc[virq].handle_irq

    
    

    如果该中断没有子中断, irq_desc[virq].handle_irq的操作:
    a. 取出irq_desc[virq].action链表中的每一个handler, 执行它
    b. 使用irq_desc[virq].irq_data.chip的函数清中断

    
    

    如果该中断是由子中断产生, irq_desc[virq].handle_irq的操作:
    a. 读 sub int controller, 得到hwirq'
    b. 根据hwirq'得到virq
    c. 调用 irq_desc[virq].handle_irq

    调用过程:

    s3c24xx_handle_intc
      pnd = readl_relaxed(intc->reg_intpnd);
      handle_domain_irq(intc->domain, intc_offset + offset, regs);
        __handle_domain_irq(domain, hwirq, true, regs);
          irq = irq_find_mapping(domain, hwirq);
          generic_handle_irq(irq);
            struct irq_desc *desc = irq_to_desc(irq);
            generic_handle_irq_desc(desc);
                desc->handle_irq(desc);

     

    7.2 中断号与domain域

    不同的中断控制器对应不同的域,各个域的转换公式 不一样,防止不同硬件中断对应同一虚拟中断号(hwirq-->virq)


    老中断体系,怎么使用中断
    以前, 对于每一个硬件中断(hwirq)都预先确定它的中断号(virq), 这些中断号一般都写在一个头文件里, 比如archarmmach
    -s3c24xxincludemachirqs.h 使用时, a. 执行 request_irq(virq, my_handler) : 内核根据virq可以知道对应的硬件中断, 然后去设置、使能中断等 b. 发生硬件中断时, 内核读取硬件信息, 确定hwirq, 反算出virq, 然后调用 irq_desc[virq].handle_irq, 最终会用到my_handler 怎么根据hwirq计算出virq? 硬件上有多个intc(中断控制器), 对于同一个hwirq数值, 会对应不同的virq 所以在讲hwirq时,应该强调"是哪一个intc的hwirq", 在描述hwirq转换为virq时, 引入一个概念: irq_domain, 域, 在这个域里hwirq转换为某一个virq 当中断控制器越来越多、当中断越来越多,上述方法(virq和hwirq固定绑定)有缺陷: a. 增加工作量, 你需要给每一个中断确定它的中断号, 写出对应的宏, 可能有成百上千个 b. 你要确保每一个硬件中断对应的中断号互不重复 有什么方法改进? a. hwirq跟virq之间不再绑定 b. 要使用某个hwirq时, 先在irq_desc数组中找到一个空闲项, 它的位置就是virq 再在irq_desc[virq]中放置处理函数 新中断体系中, 怎么使用中断: a.以前是request_irq发起, 现在是先在设备树文件中声明想使用哪一个中断(哪一个中断控制器下的哪一个中断) b. 内核解析设备树时, 会根据"中断控制器"确定irq_domain, 根据"哪一个中断"确定hwirq, 然后在irq_desc数组中找出一个空闲项, 它的位置就是virq 并且把virq和hwirq的关系保存在irq_domain中: irq_domain.linear_revmap[hwirq] = virq; c. 驱动程序 request_irq(virq, my_handler) d. 发生硬件中断时, 内核读取硬件信息, 确定hwirq, 确定中断控制器的域,确定 virq = irq_domain.linear_revmap[hwirq]; 然后调用 irq_desc[virq].handle_irq, 最终会用到my_handler 假设要使用子中断控制器(subintc)的n号中断, 它发生时会导致父中断控制器(intc)的m号中断: a. 设备树表明要使用<subintc n> subintc表示要使用<intc m> b. 解析设备树时, 会为<subintc n>找到空闲项 irq_desc[virq'], sub irq_domain.linear_revmap[n] = virq'; 会为<intc m> 找到空闲项 irq_desc[virq], irq_domain.linear_revmap[m] = virq; 并且设置它的handle_irq为某个分析函数demux_func c. 驱动程序 request_irq(virq', my_handler) d. 发生硬件中断时, 内核读取intc硬件信息, 确定hwirq = m, 确定 virq = irq_domain.linear_revmap[m]; 然后调用 irq_desc[m].handle_irq, 即demux_func e. demux_func: 读取sub intc硬件信息, 确定hwirq = n, 确定 virq' = sub irq_domain.linear_revmap[n]; 然后调用 irq_desc[n].handle_irq, 即my_handler

    在设备树中设置中断控制器和硬件中断号,内核才会生成虚拟中断号。<intcxx,hwirqxx> --> virq
    .xlate (解析设备树,得到hwirq,irq_type)
    .map (hwirq <--> virq)建立联系

     7.3 设备树如何描述中断

      make uImage   // 生成 arch/arm/boot/uImage
      make dtbs     // 生成 arch/arm/boot/dts/jz2440_irq.dtb
    
    老内核:
    / # cat /proc/interrupts
               CPU0
     29:      17593       s3c  13 Edge      samsung_time_irq
     42:          0       s3c  26 Edge      ohci_hcd:usb1
     43:          0       s3c  27 Edge      s3c2440-i2c.0
     74:         86  s3c-level   0 Edge      s3c2440-uart
     75:        561  s3c-level   1 Edge      s3c2440-uart
     83:          0  s3c-level   9 Edge      ts_pen
     84:          0  s3c-level  10 Edge      adc
     87:          0  s3c-level  13 Edge      s3c2410-wdt
    
    新内核:
    nfs 30000000 192.168.1.124:/work/nfs_root/uImage; nfs 32000000 192.168.1.124:/work/nfs_root/jz2440_irq.dtb; bootm 30000000 - 32000000
     
    / # cat /proc/interrupts
               CPU0
      8:          0       s3c   8 Edge      s3c2410-rtc tick
     13:        936       s3c  13 Edge      samsung_time_irq
     30:          0       s3c  30 Edge      s3c2410-rtc alarm
     32:         15  s3c-level  32 Level     50000000.serial
     33:         60  s3c-level  33 Level     50000000.serial
     59:          0  s3c-level  59 Edge      53000000.watchdog
    
    
    a. 某个设备要使用中断, 需要在设备树中描述中断, 如何?
       它要用哪一个中断? 这个中断连接到哪一个中断控制器去?
       即: 使用哪一个中断控制器的哪一个中断?
       
       至少有有2个属性:
       interrupts        // 表示要使用哪一个中断, 中断的触发类型等等
       interrupt-parent  // 这个中断要接到哪一个设备去? 即父中断控制器是谁
    
    b. 上述的interrupts属性用多少个u32来表示?
       这应该由它的父中断控制器来描述,
       在父中断控制器中, 至少有2个属性:
       interrupt-controller;   // 表示自己是一个中断控制器
        #interrupt-cells       // 表示自己的子设备里应该有几个U32的数据来描述中断
    
    c.  如何用设备树描述一个中断                        --> (ethernet@20000000)
        1.表明这个中断属于哪个中断控制器                 --> interrupt_parent = intc
        2.表明这个中断属于中断控制器的哪个中断            --> interrupts = < intc_num  [trigger_type] >
                                                            具体含义,用多少个U32描述,由中断控制器解释
    
    d.  如何用设备树描述二级中断控制器                   -->(gpg)(gpf)
        1.表明这是一个中断控制器                        --> interrup_controller;
        2.表明控制器下一级中断要用多少U32描述下级中断      -->#interrupt-cells = <0x4>  
        3.表明这个中断控制器的上一级中断控制器            --> phandle = <0x6>
        
    e.  根节点下如何描述中断                            -->(/{)        
        interrupt-parent = <0x1>
                                        
    f.  如何用设备树描述一级中断控制器                   -->(interrupt-controller@4a000000)
        1.表明控制器下一级中断要用多少U32描述下级中断      -->#interrupt-cells = <0x4>
        2.表明这是一个中断控制器                        --> interrup_controller;
        3.表明他没有父节点                             --> phandle = <0x1>

     jz2440_irq_all.dts 源码

    /dts-v1/;
    
    / {
        compatible = "samsung,s3c2440", "samsung,smdk2440";
        interrupt-parent = <0x1>;
        #address-cells = <0x1>;
        #size-cells = <0x1>;
        model = "JZ2440";
    
        aliases {
            pinctrl0 = "/pinctrl@56000000";
            serial0 = "/serial@50000000";
            serial1 = "/serial@50004000";
            serial2 = "/serial@50008000";
            i2c1 = "/i2c-gpio-1";
        };
    
        interrupt-controller@4a000000 {
            compatible = "samsung,s3c2410-irq";
            reg = <0x4a000000 0x100>;
            interrupt-controller;
            #interrupt-cells = <0x4>;
            phandle = <0x1>;
        };
    
        pinctrl@56000000 {
            reg = <0x56000000 0x1000>;
            compatible = "samsung,s3c2440-pinctrl";
    
            wakeup-interrupt-controller {
                compatible = "samsung,s3c2410-wakeup-eint";
                interrupts = <0x0 0x0 0x0 0x3 0x0 0x0 0x1 0x3 0x0 0x0 0x2 0x3 0x0 0x0 0x3 0x3 0x0 0x0 0x4 0x4 0x0 0x0 0x5 0x4>;
            };
    
            gpa {
                gpio-controller;
                #gpio-cells = <0x2>;
            };
    
            gpb {
                gpio-controller;
                #gpio-cells = <0x2>;
                phandle = <0xd>;
            };
    
            gpc {
                gpio-controller;
                #gpio-cells = <0x2>;
            };
    
            gpd {
                gpio-controller;
                #gpio-cells = <0x2>;
            };
    
            gpe {
                gpio-controller;
                #gpio-cells = <0x2>;
                phandle = <0x7>;
            };
    
            gpf {
                gpio-controller;
                #gpio-cells = <0x2>;
                interrupt-controller;
                #interrupt-cells = <0x2>;
                phandle = <0x6>;
            };
    
            gpg {
                gpio-controller;
                #gpio-cells = <0x2>;
                interrupt-controller;
                #interrupt-cells = <0x2>;
            };
    
            gph {
                gpio-controller;
                #gpio-cells = <0x2>;
            };
    
            gpj {
                gpio-controller;
                #gpio-cells = <0x2>;
            };
    
            uart0-data {
                samsung,pins = "gph-0", "gph-1";
                samsung,pin-function = <0x2>;
                phandle = <0x3>;
            };
    
            i2c0-bus {
                samsung,pins = "gpe-14", "gpe-15";
                samsung,pin-function = <0x2>;
                phandle = <0x4>;
            };
    
            nand_pinctrl {
                samsung,pins = "gpa-17", "gpa-18", "gpa-19", "gpa-20", "gpa-22";
                samsung,pin-function = <0x1>;
                phandle = <0x5>;
            };
    
            lcd_pinctrl {
                samsung,pins = "gpc-8", "gpc-9", "gpc-10", "gpc-11", "gpc-12", "gpc-13", "gpc-14", "gpc-15", "gpd-0", "gpd-1", "gpd-2", "gpd-3", "gpd-4", "gpd-5", "gpd-6", "gpd-7", "gpd-8", "gpd-9", "gpd-10", "gpd-11", "gpd-12", "gpd-13", "gpd-14", "gpd-15", "gpc-1", "gpc-2", "gpc-3", "gpc-4";
                samsung,pin-function = <0x2>;
                phandle = <0x8>;
            };
    
            lcd_backlight {
                samsung,pins = "gpg-4";
                samsung,pin-function = <0x3>;
                phandle = <0x9>;
            };
    
            uda1340_codec_pinctrl {
                samsung,pins = "gpb-4", "gpb-3", "gpb-2";
                samsung,pin-function = <0x1>;
                phandle = <0xc>;
            };
    
            s3c2440_iis_pinctrl {
                samsung,pins = "gpe-0", "gpe-1", "gpe-2", "gpe-3", "gpe-4";
                samsung,pin-function = <0x2>;
                phandle = <0xa>;
            };
        };
    
        timer@51000000 {
            compatible = "samsung,s3c2410-pwm";
            reg = <0x51000000 0x1000>;
            interrupts = <0x0 0x0 0xa 0x3 0x0 0x0 0xb 0x3 0x0 0x0 0xc 0x3 0x0 0x0 0xd 0x3 0x0 0x0 0xe 0x3>;
            #pwm-cells = <0x4>;
            clock-names = "timers";
            clocks = <0x2 0x19>;
        };
    
        serial@50000000 {
            compatible = "samsung,s3c2440-uart";
            reg = <0x50000000 0x4000>;
            interrupts = <0x1 0x1c 0x0 0x4 0x1 0x1c 0x1 0x4>;
            status = "okay";
            clock-names = "uart";
            clocks = <0x2 0x10>;
            pinctrl-names = "default";
            pinctrl-0 = <0x3>;
        };
    
        serial@50004000 {
            compatible = "samsung,s3c2410-uart";
            reg = <0x50004000 0x4000>;
            interrupts = <0x1 0x17 0x3 0x4 0x1 0x17 0x4 0x4>;
            status = "disabled";
        };
    
        serial@50008000 {
            compatible = "samsung,s3c2410-uart";
            reg = <0x50008000 0x4000>;
            interrupts = <0x1 0xf 0x6 0x4 0x1 0xf 0x7 0x4>;
            status = "disabled";
        };
    
        watchdog@53000000 {
            compatible = "samsung,s3c2410-wdt";
            reg = <0x53000000 0x100>;
            interrupts = <0x1 0x9 0x1b 0x3>;
            status = "okay";
            clocks = <0x2 0x6>;
            clock-names = "watchdog";
        };
    
        rtc@57000000 {
            compatible = "samsung,s3c2410-rtc";
            reg = <0x57000000 0x100>;
            interrupts = <0x0 0x0 0x1e 0x3 0x0 0x0 0x8 0x3>;
            status = "okay";
            clocks = <0x2 0x1a>;
            clock-names = "rtc";
        };
    
        i2c@54000000 {
            compatible = "samsung,s3c2440-i2c";
            reg = <0x54000000 0x100>;
            interrupts = <0x0 0x0 0x1b 0x3>;
            #address-cells = <0x1>;
            #size-cells = <0x0>;
            status = "disabled";
            clocks = <0x2 0x13>;
            clock-names = "i2c";
            pinctrl-names = "default";
            pinctrl-0 = <0x4>;
        };
    
        cpus {
            #address-cells = <0x1>;
            #size-cells = <0x0>;
    
            cpu {
                compatible = "arm,arm920t";
            };
        };
    
        xti_clock {
            compatible = "fixed-clock";
            clock-frequency = <0xb71b00>;
            clock-output-names = "xti";
            #clock-cells = <0x0>;
        };
    
        clock-controller@4c000000 {
            compatible = "samsung,s3c2440-clock";
            reg = <0x4c000000 0x20>;
            #clock-cells = <0x1>;
            phandle = <0x2>;
        };
    
        nand@4e000000 {
            compatible = "samsung,s3c2440-nand";
            reg = <0x4e000000 0x40>;
            interrupts = <0x0 0x0 0x18 0x3>;
            clocks = <0x2 0x23>;
            clock-names = "nand";
            pinctrl-names = "default";
            pinctrl-0 = <0x5>;
            status = "okay";
            nand,tacls = <0xa>;
            nand,twrph0 = <0x19>;
            nand,twrph1 = <0xa>;
            #address-cells = <0x1>;
            #size-cells = <0x1>;
    
            partitions {
                #address-cells = <0x1>;
                #size-cells = <0x1>;
                nr-chips = <0x1>;
                set-name = "jz2440-0";
    
                partition@0 {
                    label = "bootloader";
                    reg = <0x0 0x40000>;
                    read-only;
                };
    
                partition@40000 {
                    label = "device_tree";
                    reg = <0x40000 0x20000>;
                    read-only;
                };
    
                partition@60000 {
                    label = "params";
                    reg = <0x60000 0x20000>;
                    read-only;
                };
    
                partition@80000 {
                    label = "kernel";
                    reg = <0x80000 0x400000>;
                    read-only;
                };
    
                partition@480000 {
                    label = "rootfs";
                    reg = <0x480000 0x0>;
                };
            };
        };
    
        usb_ohci@49000000 {
            compatible = "samsung,s3c2440-ohci";
            reg = <0x49000000 0x60>;
            interrupts = <0x0 0x0 0x1a 0x3>;
            clocks = <0x2 0x21 0x2 0x7>;
            clock-names = "usb-host", "usb-bus-host";
            status = "okay";
        };
    
        memory {
            device_type = "memory";
            reg = <0x30000000 0x4000000>;
        };
    
        chosen {
            bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";
        };
    
        srom-cs4@20000000 {
            compatible = "simple-bus";
            #address-cells = <0x1>;
            #size-cells = <0x1>;
            reg = <0x20000000 0x8000000>;
            ranges;
    
            ethernet@20000000 {
                compatible = "davicom,dm9000";
                reg = <0x20000000 0x2 0x20000004 0x2>;
                interrupt-parent = <0x6>;
                interrupts = <0x7 0x1>;
                local-mac-address = [00 00 de ad be ef];
                davicom,no-eeprom;
            };
        };
    
        i2c-gpio-1 {
            compatible = "i2c-gpio";
            #address-cells = <0x1>;
            #size-cells = <0x0>;
            gpios = <0x7 0xf 0x0 0x7 0xe 0x0>;
            i2c-gpio,delay-us = <0x5>;
            status = "disabled";
    
            eeprom@50 {
                compatible = "24c02";
                reg = <0x50>;
                pagesize = <0x20>;
                status = "okay";
            };
        };
    
        fb@4d000000 {
            compatible = "jz2440,lcd";
            reg = <0x4d000000 0x60>;
            interrupts = <0x0 0x0 0x10 0x3>;
            clocks = <0x2 0x20>;
            clock-names = "lcd";
            pinctrl-names = "default";
            pinctrl-0 = <0x8 0x9>;
            status = "okay";
            lcdcon5 = <0xb09>;
            type = <0x60>;
            width = [01 e0];
            height = [01 10];
            pixclock = <0x186a0>;
            xres = [01 e0];
            yres = [01 10];
            bpp = [00 10];
            left_margin = [00 02];
            right_margin = [00 02];
            hsync_len = [00 29];
            upper_margin = [00 02];
            lower_margin = [00 02];
            vsync_len = [00 0a];
        };
    
        jz2440ts@5800000 {
            compatible = "jz2440,ts";
            reg = <0x58000000 0x100>;
            reg-names = "adc_ts_physical";
            interrupts = <0x1 0x1f 0x9 0x3 0x1 0x1f 0xa 0x3>;
            interrupt-names = "int_ts", "int_adc_s";
            clocks = <0x2 0x16>;
            clock-names = "adc";
        };
    
        s3c2410-dma@4B000000 {
            compatible = "s3c2440-dma";
            reg = <0x4b000000 0x1000>;
            interrupts = <0x0 0x0 0x11 0x3 0x0 0x0 0x12 0x3 0x0 0x0 0x13 0x3 0x0 0x0 0x14 0x3>;
            #dma-cells = <0x1>;
            phandle = <0xb>;
        };
    
        s3c2440_iis@55000000 {
            compatible = "s3c24xx-iis";
            reg = <0x55000000 0x100>;
            clocks = <0x2 0x18>;
            clock-names = "iis";
            pinctrl-names = "default";
            pinctrl-0 = <0xa>;
            dmas = <0xb 0x9 0xb 0xa>;
            dma-names = "rx", "tx";
        };
    
        s3c24xx_uda134x {
            compatible = "s3c24xx_uda134x";
            clocks = <0x2 0x2 0x2 0x18>;
            clock-names = "mpll", "iis";
        };
    
        uda134x-codec {
            compatible = "uda134x-codec";
            pinctrl-names = "default";
            pinctrl-0 = <0xc>;
            uda,clk_gpio = <0xd 0x4 0x1>;
            uda,data_gpio = <0xd 0x3 0x1>;
            uda,mode_gpio = <0xd 0x2 0x1>;
            uda,use_gpios;
            uda,data_hold;
            uda,data_setup;
            uda,clock_high;
            uda,mode_hold;
            uda,mode;
            uda,mode_setup;
            uda,model = <0x2>;
        };
    };
    7.4 按键中断设备树节点:
    
        buttons 
        {
            compatible = "jz2440_button";
            eint-pins  = <&gpf 0 0>, <&gpf 2 0>, <&gpg 3 0>, <&gpg 11 0>;
            interrupts-extended = <&intc 0 0 0 3>,<&intc 0 0 2 3>, <&gpg 3 3>, <&gpg 11 3>;
        };
    
    1.  interrupts-extended (扩展中断属性):
        见:devicetree-specifications-v0.2 - 2.4.1节
        interrupt-extend = <&intc 0 0 0 3>  --> &intc表示中断控制器  0 0 0 3 表示描述的是哪个中断
        0 0 0 3意义见:kernel/../samsung,s3c24xx-irq.txt:
                            <ctrl_num parent_irq ctrl_irq type>
                            val_1:ctrl_num contains the controller to use:(代表中断信号发给主还是子中断控制器)
                                    - 0 ... main controller
                                    - 1 ... sub controller
                                    - 2 ... second main controller on s3c2416 and s3c2450
                            val_2: parent_irq contains the parent bit in the main controller and 
                                        will be ignored in main controllers (代表子中断控制器是主中断控制器的中断)
                            val_3: ctrl_irq contains the interrupt bit of the controller(代表哪个中断)EINT0 EINT2
                            val_4: type contains the trigger type to use(中断的触发方式)
                                        trigger type见kernel..samsung-pinctrl.txt                   
                                            - 1 = rising edge triggered
                                            - 2 = falling edge triggered
                                            - 3 = rising and falling edge triggered
                                            - 4 = high level triggered
                                            - 8 = low level triggered
    
        <&gpg 3 3> 见:kernel..samsung-pinctrl.txt    
                            - First Cell: represents the external gpio interrupt number local to the (代表哪个中断)EINT3 EINT11
                                          external gpio interrupt space of the controller.
                            - Second Cell: flags to identify the type of the interrupt(代表中断触发方式)
                                            - 1 = rising edge triggered
                                            - 2 = falling edge triggered
                                            - 3 = rising and falling edge triggered
                                            - 4 = high level triggered
                                            - 8 = low level triggered 
    7.5内核对设备树中断信息的处理过程
    从硬件结构上看, 处理过程分上下两个层面: 中断控制器, 使用中断的设备
    从软件结构上看, 处理过程分左右两个部分: 在设备树中描述信息, 在驱动中处理设备树
    
    中断分为三级
    (1) 一级中断控制器   -->root_intc
    这又分为root irq controller
    a. root irq controller
    a.1 在设备树中的描述
    a.2 在内核中的驱动 
    
    (2)二级中断控制器    -->pinctrl
    b. gpf/gpg irq controller
    b.1 在设备树中的描述(在pinctrl节点里)
    b.2 在内核中的驱动 (在pinctrl驱动中)
    
    (3) 三级 设备的中断  -->按键中断
    a.1 在设备节点中描述(表明使用"哪一个中断控制器里的哪一个中断, 及中断触发方式")
    a.2 在内核中的驱动 (在platform_driver.probe中获得IRQ资源, 即中断号)
    
    
    irq_domain是核心: 
            {
               .ops.map // 为硬件中断号 虚拟中断号创建联系
                        1.对于直达root_irq_control的中断,设置irq_desc[virq].handler_irq = handle_edeg_irq
                        2.对于先到达gpf_irq_control(virq_m),再到达root_irq_control(virq_n)的中断, 设置
                              先:irq_desc[virq_m].handler_irq = irq_demux(分发函数,需要读寄存器确定是哪个子中断产生,然后调用对应的handle_irq)
                              再:irq_desc[virq_n].handler_irq = handle_edeg_irq
               .ops.xlate //解析设备树的中断信息,生成platform_device
               .linear_revmap[hwirq]=virq // 将硬件中断号转化为虚拟中断号
            } a. 每一个中断控制器都有一个irq_domain b. 对设备中断信息的解析, b.
    1 需要调用 irq_domain->ops->xlate (即从设备树中获得hwirq, type) b.2 获取未使用的virq, 保存: irq_domain->linear_revmap[hwirq] = virq; b.3 在hwirq和virq之间建立联系: 要调用 irq_domain->ops->map, 比如根据hwirq的属性设置virq的中断处理函数(是一个分发函数还是可以直接处理中断) irq_desc[virq].handle_irq = 常规函数; 如果这个hwirq有上一级中断, 假设它的中断号为virq', 还要设置: irq_desc[virq'].handle_irq = 中断分发函数; s3c2440设备树中断相关代码调用关系: (1) 上述处理过程如何触发? a. 内核启动时初始化中断的入口: start_kernel // init/main.c init_IRQ(); if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq) irqchip_init(); // 一般使用它 else machine_desc->init_irq(); b. 设备树中的中断控制器的处理入口: irqchip_init // drivers/irqchip/irqchip.c of_irq_init(__irqchip_of_table); // 对设备树文件中每一个中断控制器节点, 调用对应的处理函数 为每一个符合的"interrupt-controller"节点, 分配一个of_intc_desc结构体, desc->irq_init_cb = match->data; // = IRQCHIP_DECLARE中传入的函数 并调用处理函数 (先调用root irq controller对应的函数, 再调用子控制器的函数, 再调用更下一级控制器的函数...) (2) root irq controller的中断控制器初始化过程: a. 为root irq controller定义处理函数: IRQCHIP_DECLARE(s3c2410_irq, "samsung,s3c2410-irq", s3c2410_init_intc_of); //drivers/irqchip/irq-s3c24xx.c 其中: #define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn) #define OF_DECLARE_2(table, name, compat, fn) _OF_DECLARE(table, name, compat, fn, of_init_fn_2) #define _OF_DECLARE(table, name, compat, fn, fn_type) static const struct of_device_id __of_table_##name __used __section(__##table##_of_table) = { .compatible = compat, .data = (fn == (fn_type)NULL) ? fn : fn } 展开为: static const struct of_device_id __of_table_s3c2410_irq __used __section("__irqchip_of_table") = { .compatible = "samsung,s3c2410-irq", .data = s3c2410_init_intc_of } 它定义了一个of_device_id结构体, 段属性为"__irqchip_of_table", 在编译内核时这些段被放在__irqchip_of_table地址处。 即__irqchip_of_table起始地址处, 放置了一个或多个 of_device_id, 它含有compatible成员; 设备树中的设备节点含有compatible属性, 如果双方的compatible相同, 并且设备节点含有"interrupt-controller"属性, 则调用of_device_id中的函数来处理该设备节点。 所以: IRQCHIP_DECLARE 是用来声明设备树中的中断控制器的处理函数。 b. root irq controller处理函数的执行过程: s3c2410_init_intc_of // drivers/irqchip/irq-s3c24xx.c // 初始化中断控制器: intc, subintc s3c_init_intc_of(np, interrupt_parent, s3c2410_ctrl, ARRAY_SIZE(s3c2410_ctrl)); // 为中断控制器创建irq_domain domain = irq_domain_add_linear(np, num_ctrl * 32, &s3c24xx_irq_ops_of, NULL); intc->domain = domain; // 设置handle_arch_irq, 即中断处理的C语言总入口函数 set_handle_irq(s3c24xx_handle_irq); (3) pinctrl系统中gpf/gpg irq controller的驱动调用过程: a. pinctrl系统的中断控制器初始化过程: a.1 源代码: drivers/pinctrl/samsung/pinctrl-samsung.c static struct platform_driver samsung_pinctrl_driver = { .probe = samsung_pinctrl_probe, .driver = { .name = "samsung-pinctrl", .of_match_table = samsung_pinctrl_dt_match, // 含有 { .compatible = "samsung,s3c2440-pinctrl", .data = &s3c2440_of_data }, .suppress_bind_attrs = true, .pm = &samsung_pinctrl_pm_ops, }, }; a.2 设备树中: pinctrl@56000000 { reg = <0x56000000 0x1000>; compatible = "samsung,s3c2440-pinctrl"; // 据此找到驱动 a.3 驱动中的操作: samsung_pinctrl_probe // drivers/pinctrl/samsung/pinctrl-samsung.c 最终会调用到 s3c24xx_eint_init // drivers/pinctrl/samsung/pinctrl-s3c24xx.c // eint0,1,2,3的处理函数在处理root irq controller时已经设置; // 设置eint4_7, eint8_23的处理函数(它们是分发函数) for (i = 0; i < NUM_EINT_IRQ; ++i) { unsigned int irq; if (handlers[i]) /* add by weidongshan@qq.com, 不再设置eint0,1,2,3的处理函数 */ { irq = irq_of_parse_and_map(eint_np, i); if (!irq) { dev_err(dev, "failed to get wakeup EINT IRQ %d ", i); return -ENXIO; } eint_data->parents[i] = irq; irq_set_chained_handler_and_data(irq, handlers[i], eint_data); } } // 为GPF、GPG设置irq_domain for (i = 0; i < d->nr_banks; ++i, ++bank) { ops = (bank->eint_offset == 0) ? &s3c24xx_gpf_irq_ops : &s3c24xx_gpg_irq_ops; bank->irq_domain = irq_domain_add_linear(bank->of_node, bank->nr_pins, ops, ddata); } (4) 使用中断的驱动初始化过程: a. 在设备节点中描述(表明使用"哪一个中断控制器里的哪一个中断, 及中断触发方式") 比如: buttons { compatible = "jz2440_button"; eint-pins = <&gpf 0 0>, <&gpf 2 0>, <&gpg 3 0>, <&gpg 11 0>; interrupts-extended = <&intc 0 0 0 3>, <&intc 0 0 2 3>, <&gpg 3 3>, <&gpg 11 3>; }; b. 设备节点会被转换为 platform_device, "中断的硬件信息" 会转换为"中断号", 保存在platform_device的"中断资源"里 第3课第05节_device_node转换为platform_device, 讲解了设备树中设备节点转换为 platform_device 的过程; 我们只关心里面对中断信息的处理: of_device_alloc (drivers/of/platform.c) dev = platform_device_alloc("", PLATFORM_DEVID_NONE); // 分配 platform_device num_irq = of_irq_count(np); // 计算中断数 of_irq_to_resource_table(np, res, num_irq) // drivers/of/irq.c, 根据设备节点中的中断信息, 构造中断资源 of_irq_to_resource int irq = of_irq_get(dev, index); // 获得virq, 中断号 rc = of_irq_parse_one(dev, index, &oirq); // drivers/of/irq.c, 解析设备树中的中断信息, 保存在of_phandle_args结构体中 domain = irq_find_host(oirq.np); // 查找irq_domain, 每一个中断控制器都对应一个irq_domain irq_create_of_mapping(&oirq); // kernel/irq/irqdomain.c, 创建virq和中断信息的映射 irq_create_fwspec_mapping(&fwspec); irq_create_fwspec_mapping(&fwspec); irq_domain_translate(domain, fwspec, &hwirq, &type) // 调用irq_domain->ops->xlate, 把设备节点里的中断信息解析为hwirq, type virq = irq_find_mapping(domain, hwirq); // 看看这个hwirq是否已经映射, 如果virq非0就直接返回 virq = irq_create_mapping(domain, hwirq); // 否则创建映射 virq = irq_domain_alloc_descs(-1, 1, hwirq, of_node_to_nid(of_node), NULL); // 返回未占用的virq irq_domain_associate(domain, virq, hwirq) // 调用irq_domain->ops->map(domain, virq, hwirq), 做必要的硬件设置 c. 驱动程序从platform_device的"中断资源"取出中断号, 就可以request_irq()
     

     8.3设备树的clock

    文档:
    内核 Documentation/devicetree/bindings/clock/clock-bindings.txt
    内核 Documentation/devicetree/bindings/clock/samsung,s3c2410-clock.txt
    
    s3c2440时钟:
                                                            FCLK    (cpu内核时钟)
    INPUT_CLOCK->MPLL->CLOCK_DIVN->CLOCK_CON->CLOCK_SLOW->  HCLK    (AHB总线时钟) DMA NAND SDRAM
                                                            PCKL     (PHB总线时钟) PWM ADC GPIO UART
    
    a.     设备树中一级时钟(MPLL),在文档中称之为"Clock providers", 比如:
        xti_clock 
        {
            compatible = "fixed-clock";      //根据compatible,找到对应函数设置时钟为0xb71b00
            clock-frequency = <0xb71b00>;
            clock-output-names = "xti";
            #clock-cells = <0x0>;
        };
    
    
    b. 设备树中二级时钟(FCLK,HCLK,PCLK), 在文档中称之为"Clock providers", 比如:
        clocks: clock-controller@4c000000 
        {
            compatible = "samsung,s3c2440-clock"; //根据ccompatible,找到函数,通过reg寄存器设置时钟,并为每个子设备分配一个ID
            reg = <0x4c000000 0x20>;
            #clock-cells = <1>;      // 想使用这个clocks时要表明设备是这个时钟的哪个子设备(ID)用32位来表示, 比如这个clocks中发出的LCD时钟、PWM时钟,NAND时钟
        };
    
    c. 设备树中三级时钟(AHB/PHB总线上的设备),它是"Clock consumers", 它描述了使用哪一个"Clock providers"中的哪一个时钟(id), 比如:
        fb0: fb@4d000000
        {
            compatible = "jz2440,lcd";
            reg = <0x4D000000 0x60>;
            interrupts = <0 0 16 3>;
            clocks = <&clocks HCLK_LCD>;  // 使用clocks即时钟提供者,HCLK_LCD为时钟提供者的哪个子设备(ID)        
        };
    
    d. 驱动中获得/使能时钟:
    
        // 确定时钟个数
        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);

    8.2设备树中的pinctrl

    
    文档:
    内核 Documentation/devicetree/bindings/pinctrl/samsung-pinctrl.txt
    
    几个概念:
    
    Bank: 以引脚名为依据, 这些引脚分为若干组, 每组称为一个Bank
          比如s3c2440里有GPA、GPB、GPC等Bank,
          每个Bank中有若干个引脚, 比如GPA0,GPA1, ..., GPC0, GPC1,...等引脚
    
    Group: 以功能为依据, 具有相同功能的引脚称为一个Group
           比如s3c2440中串口0的TxD、RxD引脚使用 GPH2,GPH3, 那这2个引脚可以列为一组
           比如s3c2440中串口0的流量控制引脚使用 GPH0,GPH1, 那这2个引脚也可以列为一组
    
    State: 设备的某种状态, 比如内核自己定义的"default","init","idel","sleep"状态;
           也可以是其他自己定义的状态, 比如串口的"flow_ctrl"状态(使用流量控制)
           
           设备处于某种状态时, 它可以使用若干个Group引脚
    
    a. 设备树中pinctrl节点:
    a.1 它定义了各种 pin bank, 比如s3c2440有GPA,GPB,GPC,...,GPB各种BANK, 每个BANK中有若干引脚:
        pinctrl_0: pinctrl@56000000 {
            reg = <0x56000000 0x1000>;
    
            gpa: gpa {
                gpio-controller;
                #gpio-cells = <2>;  /* 以后想使用gpa bank中的引脚时, 需要2个u32来指定引脚 */
            };
    
            gpb: gpb {
                gpio-controller;
                #gpio-cells = <2>;
            };
    
            gpc: gpc {
                gpio-controller;
                #gpio-cells = <2>;
            };
    
            gpd: gpd {
                gpio-controller;
                #gpio-cells = <2>;
            };
        };
    
    a.2 它还定义了各种group(组合), 某种功能所涉及的引脚称为group,
        比如串口0要用到2个引脚: gph0, gph1:
    
        uart0_data: uart0-data {
            samsung,pins = "gph-0", "gph-0";
            samsung,pin-function = <2>;   /* 在GPHCON寄存器中gph0,gph1可以设置以下值:
                                                 0 --- 输入功能
                                                 1 --- 输出功能
                                                 2 --- 串口功能
                                              我们要使用串口功能,  
                                              samsung,pin-function 设置为2
                                           */
        };
    
        uart0_sleep: uart0_sleep {
            samsung,pins = "gph-0", "gph-1";
            samsung,pin-function = <0>;   /* 在GPHCON寄存器中gph0,gph1可以设置以下值:
                                                 0 --- 输入功能
                                                 1 --- 输出功能
                                                 2 --- 串口功能
                                              我们要使用输入功能,  
                                              samsung,pin-function 设置为0
                                           */
        };
    
        
    b. 设备节点中要使用某一个 pin group:
        serial@50000000 {
            ......
            pinctrl-names = "default", "sleep";  /* 既是名字, 也称为state(状态) */
            pinctrl-0 = <&uart0_data>;
            pinctrl-1 = <&uart0_sleep>;
        };
        
        pinctrl-names中定义了2种state: default 和 sleep,
        default 对应的引脚是: pinctrl-0, 它指定了使用哪些pin group: uart0_data
        sleep   对应的引脚是: pinctrl-1, 它指定了使用哪些pin group: uart0_sleep
    
    c. platform_device, platform_driver匹配时:
    
    "第3课第06节_platform_device跟platform_driver的匹配" 中讲解了platform_device和platform_driver的匹配过程,
    最终都会调用到 really_probe (drivers/base/dd.c)
    
    really_probe:
        /* If using pinctrl, bind pins now before probing */
        ret = pinctrl_bind_pins(dev);
                    dev->pins->default_state = pinctrl_lookup_state(dev->pins->p,
                                    PINCTRL_STATE_DEFAULT);  /* 获得"default"状态的pinctrl */
                    dev->pins->init_state = pinctrl_lookup_state(dev->pins->p,
                                    PINCTRL_STATE_INIT);    /* 获得"init"状态的pinctrl */
    
                    ret = pinctrl_select_state(dev->pins->p, dev->pins->init_state);    /* 优先设置"init"状态的引脚 */
                    ret = pinctrl_select_state(dev->pins->p, dev->pins->default_state); /* 如果没有init状态, 则设置"default"状态的引脚 */
                                    
        ......
        ret = drv->probe(dev);
    
    所以: 如果设备节点中指定了pinctrl, 在对应的probe函数被调用之前, 先"bind pins", 即先绑定、设置引脚
    
    d. 驱动中想选择、设置某个状态的引脚:
       devm_pinctrl_get_select_default(struct device *dev);      // 使用"default"状态的引脚
       pinctrl_get_select(struct device *dev, const char *name); // 根据name选择某种状态的引脚
       
       pinctrl_put(struct pinctrl *p);   // 不再使用, 退出时调用
  • 相关阅读:
    [转]char、varchar、nchar、nvarchar的区别
    【转】Asp.net 2.0中页的生存周期(Lifecycle)和动态控件 [.Net]
    git免登录sshkey
    ios8,xcode6 周边
    iOS 推送证书
    Lazarus中TreeView导出XML以及XML导入TreeView
    flac文件提取专辑封面手记
    Lazarus解决含中文文件名或路径的使用问题
    使用PowerShell管理Windows8应用
    thbgm拆包【in progress】
  • 原文地址:https://www.cnblogs.com/zsy12138/p/10453253.html
Copyright © 2020-2023  润新知