设备树..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);   // 不再使用, 退出时调用
原文地址:https://www.cnblogs.com/zsy12138/p/10453253.html