《linux設備驅動開發詳解》筆記——18 ARM linux設備樹


18.1 設備樹的起源

  linux 2.6及之前,大量板級信息被硬編碼到內核里,十分龐大,大量冗余代碼;

  linux 2.6之前,引入了設備樹;

  設備樹源於OpenFirmware,描述硬件的數據結構。由一些列節點node和屬性property組成,通常包括下列信息:

  

  本質上是畫一棵CPU、總線、設備組成的樹,Linux內核會把設備樹展開成platform_device、i2c_client、spi_device等設備,而這些設備用到的內存、中斷等資源,也會傳遞個內核,內核會將這些資源綁定給展開的相應設備。

18.2 設備樹的組成和結構

18.2.1 DTS/DTC/DTB

  dts:文本

  dtc:編譯dts的

  dtb:編譯后的二進制文件

  綁定:documentation/devicetree/bindings/***,描述各硬件的dts格式,不一定及時更新,但可作為參考。

以下為dts的結構:

描述上圖屬性結構的示例dts為:

1/ {
2     compatible = "acme,coyotes-rev
3     #address-cells = <1>;  // 描述下一級子節點的數據屬性 4     #size-cells = <1>; 5     interrupt-parent = <&intc>;
6 
7     cpus { 8       #address-cells = <1>; 9       #size-cells = <0>; 10       cpu@0 { 11         compatible = "arm,cortex-a9";   // device兼容性,用於與driver匹配 12         reg = <0>; 13       }; 14       cpu@1 { 15         compatible = "arm,cortex-a9"; 16         reg = <1>; 17       }; 18    }; 19 20    serial@101f0000 {        // 地址 21      compatible = "arm,pl011"; 22      reg = <0x101f0000 0x1000 >; 23      interrupts = < 1 0 >; 24    }; 25 26    serial@101f2000 { 27      compatible = "arm,pl011"; 28      reg = <0x101f2000 0x1000 >; 29      interrupts = < 2 0 >; 30    }; 31 32    gpio@101f3000 { 33      compatible = "arm,pl061"; 34      reg = <0x101f3000 0x1000 35      0x101f4000 0x0010>; 36      interrupts = < 3 0 >; 37    }; 38 39    intc: interrupt-controller@10140000 { 40      compatible = "arm,pl190"; 41      reg = <0x10140000 0x1000 >; 42      interrupt-controller; 43      #interrupt-cells = <2>; 44    }; 45 46    spi@10115000 { 47      compatible = "arm,pl022"; 48      reg = <0x10115000 0x1000 >; 49      interrupts = < 4 0 >;50    };
51
52    external-bus { 53      #address-cells = <2> 54      #size-cells = <1>; 55      ranges = <0 0 0x10100000 0x10000 // Chipselect 1, Ethernet 56            1 0 0x10160000 0x10000 // Chipselect 2, i2c controller 57            2 0 0x30000000 0x1000000>; // Chipselect 3, NOR Flash 58  59      ethernet@0,0 { 60        compatible = "smc,smc91c111"; 61        reg = <0 0 0x1000>; 62        interrupts = < 5 2 >; 63      }; 64 65      i2c@1,0 { 66        compatible = "acme,a1234-i2c-bus"; 67        #address-cells = <1>; 68        #size-cells = <0>; 69        reg = <1 0 0x1000>; 70        interrupts = < 6 2 >; 71        rtc@58 { 72          compatible = "maxim,ds1338"; 73          reg = <58>; 74          interrupts = < 7 3 >; 75        }; 76     }; 77 78     flash@2,0 { 79       compatible = "samsung,k8f1315ebm", "cfi-flash"; 80       reg = <2 0 0x4000000>; 81     }; 82   };// end of external-bus 83};

 

18.2.2 根節點兼容性

18.2.2.1 實現芯片型號相關初始化

根節點的兼容性,一般格式如下(以ZYNQ為例):

dts:
compatible = "xlnx,zynq-7000";  // 廠商、型號 

內核代碼:
static const char * const zynq_dt_match[] = {
    "xlnx,zynq-7000",
    NULL
};

// 匹配后,DT_MACHINE_START到MACHINE_END之間的函數會被執行,arch/arm/mach-zynq/common.c DT_MACHINE_START(XILINX_EP107,
"Xilinx Zynq Platform") .smp = smp_ops(zynq_smp_ops), .map_io = zynq_map_io, .init_irq = zynq_irq_init, .init_machine = zynq_init_machine, .init_late = zynq_init_late, .init_time = zynq_timer_init, .dt_compat = zynq_dt_match, .reserve = zynq_memory_init, .restart = zynq_system_reset, MACHINE_END #define DT_MACHINE_START(_name, _namestr) \ static const struct machine_desc __mach_desc_##_name \ __used \ __attribute__((__section__(".arch.info.init"))) = { \ .nr = ~0, \ .name = _namestr, #define MACHINE_END \ };

18.2.2.2 提倡同類型號歸一處理

  同一類型的芯片,可以使用同一的初始化接口,用OF的API判斷根節點兼容性字段,走不同分支。

/**
 * of_machine_is_compatible - Test root of device tree for a given compatible value
 * @compat: compatible string to look for in root node's compatible property.
 *
 * Returns true if the root node has the given value in its
 * compatible property.
 */
int of_machine_is_compatible(const char *compat)
{
    struct device_node *root;
    int rc = 0;

    root = of_find_node_by_path("/");
    if (root) {
        rc = of_device_is_compatible(root, compat);
        of_node_put(root);
    }
    return rc;
}
EXPORT_SYMBOL(of_machine_is_compatible);

 

  

18.2.3 設備節點兼容性

  表示device的兼容性, 用於與driver匹配。

zynq i2c匹配示例

dts: ps7_i2c_1: ps7
-i2c@e0005000 { clock-frequency = <400000>; clocks = <&clkc 39>; compatible = "cdns,i2c-r1p10"; interrupt-parent = <&ps7_scugic_0>; interrupts = <0 48 4>; reg = <0xe0005000 0x1000>; xlnx,has-interrupt = <0x0>; #address-cells = <1>; #size-cells = <0>; eeprom@52 { compatible = "at,24c512"; reg = <0x52>; }; } ; static const struct of_device_id cdns_i2c_of_match[] = { { .compatible = "cdns,i2c-r1p10", }, { /* end of table */ } }; MODULE_DEVICE_TABLE(of, cdns_i2c_of_match); static struct platform_driver cdns_i2c_drv = { .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, .of_match_table = cdns_i2c_of_match, .pm = &cdns_i2c_dev_pm_ops, }, .probe = cdns_i2c_probe, .remove = cdns_i2c_remove, };
// i2c總線負責匹配
struct bus_type i2c_bus_type = { .name = "i2c", .match = i2c_device_match, .probe = i2c_device_probe, .remove = i2c_device_remove, .shutdown = i2c_device_shutdown, .pm = &i2c_device_pm_ops, }; EXPORT_SYMBOL_GPL(i2c_bus_type); static int i2c_device_match(struct device *dev, struct device_driver *drv) { struct i2c_client *client = i2c_verify_client(dev); struct i2c_driver *driver; if (!client) return 0; /* Attempt an OF style match */ if (of_driver_match_device(dev, drv)) return 1; /* Then ACPI style match */ if (acpi_driver_match_device(dev, drv)) return 1; driver = to_i2c_driver(drv); /* match on an id table if there is one */ if (driver->id_table) return i2c_match_id(driver->id_table, client) != NULL; return 0; }

 

 

18.2.4 設備節點及label的命名

設備節點采用 <name>[@<unit-address>]的格式,[ ]為可選項。掛到內存空間的設備,unit-address一般是內存地址。

還可以為節點創建標簽,別的地方引用時可以用標簽。

ps7_gpio_0: ps7-gpio@e000a000 {    // 標簽 : 節點
            #gpio-cells = <2>;
            clocks = <&clkc 42>;
            compatible = "xlnx,zynq-gpio-1.0";
            emio-gpio-width = <64>;
            gpio-controller ;
            gpio-mask-high = <0x0>;
            gpio-mask-low = <0x5600>;
            interrupt-parent = <&ps7_scugic_0>;
            interrupts = <0 20 4>;
            reg = <0xe000a000 0x1000>;
        } ;


ps7_ethernet_0: ps7-ethernet@e000b000 {
            ...
            enet-reset = <&ps7_gpio_0 11 0>;  // 引用標簽
            ...
        } ;

18.2.5 地址編碼

1. #address-cells和#size-cells

// 父節點的address-cells和size-cells決定子節點的reg的address和lenth字段的長度,cell的單位為32bit
#address-cells  // 子節點reg的address為幾個32bit的整型數據 #size-cells    // 長度為幾個32bit整型數據,如果為0,則沒有lenth字段 node{ reg = <address1 lenth1 [address2 lenth2] [address3 lenth3]...>; }

 

    ps7_axi_interconnect_0: amba@0 {
 #address-cells = <1>;  // 下一級節點reg的address字段長度為1個32bit,下一級節點為ps7-i2c@e0005000 #size-cells = <1>;   // 下一級節點reg的len字段長度為1個32bit

        ...
        ps7_i2c_1: ps7-i2c@e0005000 {
            clock-frequency = <400000>;
            clocks = <&clkc 39>;
            compatible = "cdns,i2c-r1p10"; 
            interrupt-parent = <&ps7_scugic_0>;
            interrupts = <0 48 4>;
            reg = <0xe0005000 0x1000>;  // 地址為0xe0005000,長度為0x1000
            xlnx,has-interrupt = <0x0>;
            #address-cells = <1>;  // 下一級eeprom的reg屬性
            #size-cells = <0>;     // 沒有len字段
            eeprom@52 {
                compatible = "at,24c512";
                reg = <0x52>;    // 地址為0x52,沒有長度字段
            };
        } ;
        ...
    }

 

18.2.6 中斷連接

  • interrutt-controller,屬性為空,表明“我是中斷控制器”
  • #interrupt-cells,表明連接此中斷控制器的設備的中斷屬性的cell大小,也就是interrupt = <>屬性的cell大小
  • interrupt-parent,設備節點通過這個關鍵字指定其依附的中斷控制器phandle,如果沒有指定,則繼承父節點的interrupt-parent配置
  • interrupt,設備節點里使用,一般包含中斷號、觸發方法等。具體有多少個cell,由#interrupt-cells決定,每個cell的具體含義,一般由驅動的實決定,一般會在綁定文件里說明,下面是arm interrupt的綁定文件:3個cell,1st是種類(spi/ppi),2st是中斷號,3st是flag

 

Documentation/devicetree/bindings/arm/gic.txt

* ARM Generic Interrupt Controller ARM SMP cores are often associated with a GIC, providing per processor interrupts (PPI), shared processor interrupts (SPI) and software generated interrupts (SGI). Primary GIC is attached directly to the CPU and typically has PPIs and SGIs. Secondary GICs are cascaded into the upward interrupt controller and do not have PPIs or SGIs. Main node required properties: - compatible : should be one of: "arm,gic-400" "arm,cortex-a15-gic" "arm,cortex-a9-gic" "arm,cortex-a7-gic" "arm,arm11mp-gic" - interrupt-controller : Identifies the node as an interrupt controller - #interrupt-cells : Specifies the number of cells needed to encode an interrupt source. The type shall be a <u32> and the value shall be 3. The 1st cell is the interrupt type; 0 for SPI interrupts, 1 for PPI interrupts. The 2nd cell contains the interrupt number for the interrupt type. SPI interrupts are in the range [0-987]. PPI interrupts are in the range [0-15]. The 3rd cell is the flags, encoded as follows: bits[3:0] trigger type and level flags. 1 = low-to-high edge triggered 2 = high-to-low edge triggered 4 = active high level-sensitive 8 = active low level-sensitive bits[15:8] PPI interrupt cpu mask. Each bit corresponds to each of the 8 possible cpus attached to the GIC. A bit set to '1' indicated the interrupt is wired to that CPU. Only valid for PPI interrupts. - reg : Specifies base physical address(s) and size of the GIC registers. The first region is the GIC distributor register base and size. The 2nd region is the GIC cpu interface register base and size. Optional - interrupts : Interrupt source of the parent interrupt controller on secondary GICs, or VGIC maintenance interrupt on primary GIC (see below). - cpu-offset : per-cpu offset within the distributor and cpu interface regions, used when the GIC doesn't have banked registers. The offset is cpu-offset * cpu-nr. Example: intc: interrupt-controller@fff11000 { compatible = "arm,cortex-a9-gic"; #interrupt-cells = <3>; #address-cells = <1>; interrupt-controller; reg = <0xfff11000 0x1000>, <0xfff10100 0x100>; }; * GIC virtualization extensions (VGIC) For ARM cores that support the virtualization extensions, additional properties must be described (they only exist if the GIC is the primary interrupt controller). Required properties: - reg : Additional regions specifying the base physical address and size of the VGIC registers. The first additional region is the GIC virtual interface control register base and size. The 2nd additional region is the GIC virtual cpu interface register base and size. - interrupts : VGIC maintenance interrupt. Example: interrupt-controller@2c001000 { compatible = "arm,cortex-a15-gic"; #interrupt-cells = <3>; interrupt-controller; reg = <0x2c001000 0x1000>, <0x2c002000 0x1000>, <0x2c004000 0x2000>, <0x2c006000 0x2000>; interrupts = <1 9 0xf04>; };

 

        ps7_scugic_0: ps7-scugic@f8f01000 {
            #address-cells = <2>;    
            #interrupt-cells = <3>;   // 設備節點的interrupt屬性有3個cells
            #size-cells = <1>;
            compatible = "arm,cortex-a9-gic", "arm,gic";
            interrupt-controller ;   // 我是中斷控制器
            num_cpus = <2>;
            num_interrupts = <96>;
            reg = <0xf8f01000 0x1000>, <0xf8f00100 0x100>;
        } ;
        ps7_i2c_1: ps7-i2c@e0005000 {
            clock-frequency = <400000>; clocks = <&clkc 39>; compatible = "cdns,i2c-r1p10";  interrupt-parent = <&ps7_scugic_0>; interrupts = <0 48 4>;   // 0,spi中斷;48號中斷,4表示高電平觸發 reg = <0xe0005000 0x1000>; xlnx,has-interrupt = <0x0>; #address-cells = <1>; #size-cells = <0>; eeprom@52 { compatible = "at,24c512"; reg = <0x52>; }; } ;  

18.2.7 GPIO、時鍾、pinmux連接

 1. GPIO

  • dts的GPIO描述

  gpio-controller表示“我是GPIO控制器”;

  #gpio-cells,設置GPIO屬性的大小

  

 ps7_gpio_0: ps7-gpio@e000a000 {
            #gpio-cells = <2>;    // 2個cell,一般第一個表示用哪個GPIO,第二個數0表示高電平有效、1表示低電平有效
            clocks = <&clkc 42>;
            compatible = "xlnx,zynq-gpio-1.0";
            emio-gpio-width = <64>;
            gpio-controller ;
            gpio-mask-high = <0x0>;
            gpio-mask-low = <0x5600>;
            interrupt-parent = <&ps7_scugic_0>;
            interrupts = <0 20 4>;
            reg = <0xe000a000 0x1000>;
        } ;

        ps7_ethernet_0: ps7-ethernet@e000b000 {  
       ...  
       enet-reset = <&ps7_gpio_0 11 0>;       ... } ;
  • 驅動中用name獲取GPIO
/**
 * of_get_named_gpio() - Get a GPIO number to use with GPIO API
 * @np:        device node to get GPIO from
 * @propname:    Name of property containing gpio specifier(s)
 * @index:    index of the GPIO
 *
 * Returns GPIO number to use with Linux generic GPIO API, or one of the errno
 * value on the error condition.
 */
static inline int of_get_named_gpio(struct device_node *np, const char *propname, int index);

例如:
of_get_named_gpio(np,"enet-reset",0);

 

  • 驅動中用編號獲取GPIO
/**
 * of_get_gpio() - Get a GPIO number to use with GPIO API
 * @np:        device node to get GPIO from
 * @index:    index of the GPIO
 *
 * Returns GPIO number to use with Linux generic GPIO API, or one of the errno
 * value on the error condition.
 */
static inline int of_get_gpio(struct device_node *np, int index):

 

 2. 時鍾

        ps7_slcr_0: ps7-slcr@f8000000 {
            #address-cells = <1>;
            #size-cells = <1>;
            compatible = "xlnx,zynq-slcr", "syscon";
            ranges ;
            reg = <0xf8000000 0x1000>;
            clkc: clkc@100 {
                #clock-cells = <1>;
                clock-output-names = "armpll", "ddrpll", "iopll", "cpu_6or4x", "cpu_3or2x",
                    "cpu_2x", "cpu_1x", "ddr2x", "ddr3x", "dci",
                    "lqspi", "smc", "pcap", "gem0", "gem1",
                    "fclk0", "fclk1", "fclk2", "fclk3", "can0",
                    "can1", "sdio0", "sdio1", "uart0", "uart1",
                    "spi0", "spi1", "dma", "usb0_aper", "usb1_aper",
                    "gem0_aper", "gem1_aper", "sdio0_aper", "sdio1_aper", "spi0_aper",
                    "spi1_aper", "can0_aper", "can1_aper", "i2c0_aper", "i2c1_aper",
                    "uart0_aper", "uart1_aper", "gpio_aper", "lqspi_aper", "smc_aper",
                    "swdt", "dbg_trc", "dbg_apb";
                compatible = "xlnx,ps7-clkc";
                fclk-enable = <0xf>;
                ps-clk-frequency = <50000000>;
                reg = <0x100 0x100>;
            } ;
        } ;


        ps7_spi_1: ps7-spi@e0007000 {
            clock-names = "ref_clk", "pclk";    // 用於接口函數獲取時鍾
            clocks = <&clkc 26>, <&clkc 35>;    // 后面的數字對應上面clock-output-names數組的index
            ...
        };

 

 

static int cdns_spi_probe(struct platform_device *pdev)
{
    ...
    xspi->pclk = devm_clk_get(&pdev->dev, "pclk");    // 通過別名獲取時鍾
    if (IS_ERR(xspi->pclk)) {
        dev_err(&pdev->dev, "pclk clock not found.\n");
        ret = PTR_ERR(xspi->pclk);
        goto remove_master;
    }

    xspi->ref_clk = devm_clk_get(&pdev->dev, "ref_clk");
    if (IS_ERR(xspi->ref_clk)) {
        dev_err(&pdev->dev, "ref_clk clock not found.\n");
        ret = PTR_ERR(xspi->ref_clk);
        goto remove_master;
    }
    ...
}

 

 3. pinmux

 

18.3 由設備樹引起的BSP和驅動變更

 1. 設備自動展開,設備信息不再需要硬編碼

根節點匹配以后,會自動展開device,resouce中的信息來源與dts的reg、interrupt等字段。

static
const char * const zynq_dt_match[] = { "xlnx,zynq-7000", NULL }; DT_MACHINE_START(XILINX_EP107, "Xilinx Zynq Platform") .smp = smp_ops(zynq_smp_ops), .map_io = zynq_map_io, .init_irq = zynq_irq_init, .init_machine = zynq_init_machine, .init_late = zynq_init_late, .init_time = zynq_timer_init, .dt_compat = zynq_dt_match, .reserve = zynq_memory_init, .restart = zynq_system_reset, MACHINE_END /** * zynq_init_machine - System specific initialization, intended to be * called from board specific initialization. */ static void __init zynq_init_machine(void) { struct platform_device_info devinfo = { .name = "cpufreq-cpu0", }; of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL); platform_device_register(&zynq_cpuidle_device); platform_device_register_full(&devinfo); zynq_slcr_init(); }

18.4 常用的OF API

// 1. 尋找節點:根據兼容屬性,獲取設備節點。遍歷設備節點,匹配設備類型和兼容屬性,多數情況下,from和type都填NULL 
extern struct device_node *of_find_compatible_node(struct device_node *from,
    const char *type, const char *compat);

 

// 2.讀取屬性
// (1)讀取屬性名為propname的整型數組
extern int of_property_read_u8_array(const struct device_node *np,
            const char *propname, u8 *out_values, size_t sz);
extern int of_property_read_u16_array(const struct device_node *np,
            const char *propname, u16 *out_values, size_t sz);
extern int of_property_read_u32_array(const struct device_node *np,
                      const char *propname,
                      u32 *out_values,
                      size_t sz);
extern int of_property_read_u64(const struct device_node *np,
                const char *propname, u64 *out_value);

static void __init pl310_of_setup(const struct device_node *np,
                  u32 *aux_val, u32 *aux_mask)
{
    u32 data[3] = { 0, 0, 0 };
    u32 tag[3] = { 0, 0, 0 };
    u32 filter[2] = { 0, 0 };

    of_property_read_u32_array(np, "arm,tag-latency", tag, ARRAY_SIZE(tag));
    if (tag[0] && tag[1] && tag[2])
        writel_relaxed(
            ((tag[0] - 1) << L2X0_LATENCY_CTRL_RD_SHIFT) |
            ((tag[1] - 1) << L2X0_LATENCY_CTRL_WR_SHIFT) |
            ((tag[2] - 1) << L2X0_LATENCY_CTRL_SETUP_SHIFT),
            l2x0_base + L2X0_TAG_LATENCY_CTRL);

    of_property_read_u32_array(np, "arm,data-latency",
                   data, ARRAY_SIZE(data));
    if (data[0] && data[1] && data[2])
        writel_relaxed(
            ((data[0] - 1) << L2X0_LATENCY_CTRL_RD_SHIFT) |
            ((data[1] - 1) << L2X0_LATENCY_CTRL_WR_SHIFT) |
            ((data[2] - 1) << L2X0_LATENCY_CTRL_SETUP_SHIFT),
            l2x0_base + L2X0_DATA_LATENCY_CTRL);

    of_property_read_u32_array(np, "arm,filter-ranges",
                   filter, ARRAY_SIZE(filter));
    if (filter[1]) {
        writel_relaxed(ALIGN(filter[0] + filter[1], SZ_1M),
                   l2x0_base + L2X0_ADDR_FILTER_END);
        writel_relaxed((filter[0] & ~(SZ_1M - 1)) | L2X0_ADDR_FILTER_EN,
                   l2x0_base + L2X0_ADDR_FILTER_START);
    }
}


//dts
ps7_pl310_0: ps7-pl310@f8f02000 {
            arm,data-latency = <3 2 2>;
            arm,tag-latency = <2 2 2>;
            cache-level = <2>;
            cache-unified ;
            compatible = "arm,pl310-cache";
            interrupt-parent = <&ps7_scugic_0>;
            interrupts = <0 2 4>;
            reg = <0xf8f02000 0x1000>;
        } ;

// (2) 讀取字符串,注意指向指針的指針
extern int of_property_read_string(struct device_node *np,
                   const char *propname, const char **out_string); // 讀取屬性中第index個字符串 int of_property_read_string_index(struct device_node *np, const char *propname, int index, const char **output);
// (3) 讀取bool
/**
 * of_property_read_bool - Findfrom a property
 * @np:        device node from which the property value is to be read.
 * @propname:    name of the property to be searched.
 *
 * Search for a property in a device node.
 * Returns true if the property exist false otherwise.
 */
static inline bool of_property_read_bool(const struct device_node *np,
                     const char *propname) { struct property *prop = of_find_property(np, propname, NULL); return prop ? true : false; }

 

// 3 reg字段內存映射
/**
 * of_iomap - Maps the memory mapped IO for a given device_node,對dts的reg字段進行內存映射
 * @device:    the device whose io range will be mapped
 * @index:    index of the io range,reg可能有多段,0代表第一段
 *
 * Returns a pointer to the mapped memory
 */
void __iomem *of_iomap(struct device_node *np, int index)
{
    struct resource res;

    if (of_address_to_resource(np, index, &res))
        return NULL;

    return ioremap(res.start, resource_size(&res));
}

 

// 4 解析中斷
/*
 * irq_of_parse_and_map() is used by all OF enabled platforms; but SPARC
 * implements it differently.  However, the prototype is the same for all,
 * so declare it here regardless of the CONFIG_OF_IRQ setting.
 * 實質是從dts的interrupt字段解析出中斷號,如果有多個中斷,index指定索引號
 */
extern unsigned int irq_of_parse_and_map(struct device_node *node, int index);

 

 

// 5 獲取與節點對應的platform_device
// (1). node------->platform_device
/**
 * of_find_device_by_node - Find the platform_device associated with a node
 * @np: Pointer to device tree node
 *
 * Returns platform_device pointer, or NULL if not found
 */
struct platform_device *of_find_device_by_node(struct device_node *np)
{
    struct device *dev;

    dev = bus_find_device(&platform_bus_type, NULL, np, of_dev_node_match);
    return dev ? to_platform_device(dev) : NULL;
}

// (2). platform_device------->node
static int xxx_probe( struct platform_device * pdev )
{
    struct device_node *node = pdev->dev.op_node;
}

 

18.5 總結

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM