首页 > 其他 > 详细

3、init_sequence_f[]中驱动模型和外设相关初始化

时间:2020-07-17 14:56:23      阅读:48      评论:0      收藏:0      [点我收藏+]

 

本章所涉及的函数如下:

 1 static init_fnc_t init_sequence_f[] = {
 2     arch_cpu_init,             /* CPU相关初始化,若没有,则返回0即可 */
 3     initf_dm,                  /* 初始化驱动模型 */
 4     mark_bootstage,            /* need timer, go after init dm */
 5     board_early_init_f,        /* 初始化UART_IOMUX */
 6     timer_init,                /* 初始化定时器 */
 7     board_postclk_init,        /* Set VDDSOC to 1.175V */
 8     serial_init,               /* 启动UART */
 9     init_func_i2c,             /* 初始化I2C */
10 
11     setup_reloc,               /* 代码重定位 */
12     NULL,
13 };

 

 

一、arch_cpu_init()

arch_cpu_init()
  -> init_aips()                                /* 初始化Arm IP Bus,设置主机访问权限和优先级 */
    -> writel(0x77777777, &aips1->mprot0);                /* *0x0207C000 = 0x77777777,AIPSTZ1_MPR指定16个4bit,共8byte */
    -> writel(0x77777777, &aips1->mprot1);                /* *0x0207C004 = 0x77777777,上一行代码只写了4byte */
    -> writel(0x00000000, &aips1->opacr0);                /* *0x0207C040 = 0x00000000,AIPSTZ1_OPACR */
  -> clear_mmdc_ch_mask()                       /* 清除MMDC通道屏蔽 */
    -> reg = readl(&mxc_ccm->ccdr) & ~(1 << 16);
    -> writel(reg, &mxc_ccm->ccdr);                       /*  0x020C4004的bit[16] = 0,CCM_CCDR->MMDC_CH1_MASK */
  -> init_bandgap()                             /* 让使用带隙的输出获得最佳的模拟块噪声性能 */
    -> struct anatop_regs *anatop = 0x020C8000;
    -> struct fuse_bank1_regs *fuse = bank->fuse_regs;    /*  0x021BC480 */
    -> writel(0x8, &anatop->ana_misc0_set);               /* *0x020C8154 = 0x8,CCM_ANALOG_MISC0_SET->REFTOP_SELFBIASOFF */
    -> val = (readl(&fuse->mem0) >> 8) & 0x7;             /*  0x021BC480的bit[10:8] */
    -> writel(val << 4, &anatop->ana_misc0_set);          /*  0x020C8154的bit[ 6:4],CCM_ANALOG_MISC0_SET->REFTOP_VBGADJ */
  -> writel(readl(0x020B0000) | 0x3, 0x020B0000);         /*  0x020B0000的bit[ 1:0] = 0b11 */
  -> imx_set_wdog_powerdown(false);             /* 关闭看门狗 */
    -> writew(0, &wdog1->wmcr);                           /* *0x020BC008 = 0,WDOG1_WMCR */
    -> writew(0, &wdog2->wmcr);                           /* *0x020C0008 = 0 */
    -> writew(0, &wdog3->wmcr);                           /* *0x021E4008 = 0 */
  -> init_src()                                 /* 强制热复位源产生冷复位,以实现更可靠的重启 */
    -> val = readl(&src_regs->scr) & ~(1 << 0);
    -> writel(val, &src_regs->scr);                       /*  0x020D8000的bit[0] = 0,SRC_SCR->warm_reset_enable */

 

 

二、initf_dm()

在介绍此函数之前,我们需要先了解u-boot的驱动模型

驱动模型是u-boot为了提供统一框架和提升代码通用性所推出的模型,其架构如下图所示:

技术分享图片

图中的root是一个虚拟设备,主要为其他设备提供挂载点,它会在initf_dm()函数中完成初始化

 

模型中主要涉及以下四个结构体

1. udevice:用于描述一个设备,由u-boot动态生成,类似于内核中的device

2. driver:设备对应的驱动,需要静态定义,类似于内核中的driver

3. uclass:共用同一个驱动的设备群组,由u-boot动态生成,并为上层调用提供统一接口

4. uclass_driver:uclass对应的驱动,需要静态定义,主要用于绑定uclass和udevice

 

上述四个结构体所构成的结构如下图所示:

技术分享图片

 

root的driver和uclass_driver的静态定义如下:

 1 /* This is the root driver - all drivers are children of this */
 2 struct driver _u_boot_list_2_driver_2_root_driver __aligned(4) 
 3     __attribute__((unused, section(".u_boot_list_2_driver_2_root_driver"))) = 
 4 {
 5     .name    = "root_driver",
 6     .id    = UCLASS_ROOT,
 7     .priv_auto_alloc_size = sizeof(struct root_priv),
 8 }
 9 
10 /* This is the root uclass */
11 struct uclass_driver _u_boot_list_2_uclass_2_root __aligned(4) 
12     __attribute__((unused,    section(".u_boot_list_2_uclass_2_root"))) = 
13 {
14     .name    = "root",
15     .id    = UCLASS_ROOT,
16 }

 

initf_dm()函数的执行过程可分为如下四部分:

1. 准备操作,如设置uclass和udevice存放的地址

initf_dm()
  -> ret = dm_init_and_scan(true);
    -> ret = dm_init();
      -> INIT_LIST_HEAD(&(((gd_t *)gd)->uclass_root));               /* uclass链表存放在gd->uclass_root中 */
      -> device_bind_by_name(NULL, 0, &info, &((gd_t*)gd->dm_root)); /* root的udevice存放在gd->dm_root中 */
        -> struct driver *drv = lists_driver_lookup_name("root_driver");   /* 查找名为"root_driver"的driver */
          -> struct driver *drv = ll_entry_start(struct driver, driver);   /* driver起始地址:.u_boot_list_2_driver_1 */
          -> ll_entry_end(struct driver, driver);                          /* driver结束地址:.u_boot_list_2_driver_3 */
          -> return entry;                                           /* 匹配"root_driver"和driver->name,成功则返回driver */

2. 设置root uclass

device_bind_by_name(NULL, 0, &info, &((gd_t*)gd->dm_root));
  -> return device_bind(NULL, drv, "root_driver" (void *)info->platdata, -1, (gd_t *)gd->dm_root);
    -> ret = uclass_get(drv->id, &uc);
      -> return uclass_add(drv->id, uc);                            /* 构造一个新的uclass */
        -> struct uclass_driver *uc_drv = lists_uclass_lookup(drv->id);
          -> uclass = ll_entry_start(struct uclass_driver, uclass);       /* uclass_driver起始地址:.u_boot_list_2_uclass_1 */
          -> return entry;                                          /* 匹配driver->uclass_id和uclass_driver->uclass_id */
        -> uc->uc_drv = uc_drv;                                     /* uclass的uc_drv指向uclass_driver */
        -> INIT_LIST_HEAD(&uc->sibling_node);
        -> INIT_LIST_HEAD(&uc->dev_head);
        -> list_add(&uc->sibling_node, &((gd_t *)gd->uclass_root));       /* 将uclass的sibling_node存放到gd->uclass_root链表中 */
        -> ret = uc_drv->init(uc);                                        /* 调用uclass_driver的init函数,此处为空 */

3. 设置root udevice

device_bind_by_name(NULL, 0, &info, &((gd_t*)gd->dm_root));
  -> return device_bind(NULL, drv, "root_driver" (void *)info->platdata, -1, (gd_t *)gd->dm_root);
    -> dev = calloc(1, sizeof(struct udevice));             /* 构造一个新的udevice */
    -> INIT_LIST_HEAD(&dev->sibling_node);
    -> INIT_LIST_HEAD(&dev->uclass_node);
    -> INIT_LIST_HEAD(&dev->devres_head);
    -> dev->name = name;                                          /* "root_driver" */
    -> dev->driver = drv;                                   /* 设置udevice的driver */
    -> dev->uclass = uc;                                    /* 设置udevuce所属的uclass */
    -> ret = uclass_bind_device(dev);
      -> list_add_tail(&dev->uclass_node, &uc->dev_head);   /* 将udevice的uclass_node存放到uclass的dev_head链表中 */
    -> *((gd_t *)gd->dm_root) = dev;                        /* 将udevice的数据存放至gd->dm_root中 */

设置完成后的关系如下图:

技术分享图片

4. 设置其他外设的uclass和udevice

ret = dm_init_and_scan(true);
  -> ret = dm_init();
    -> ret = device_probe((gd_t *)gd->dm_root);
      -> drv = dev->driver;
      -> ret = uclass_pre_probe_device(dev);
        -> struct uclass_driver *uc_drv = dev->uclass->uc_drv;
        -> ret = uc_drv->pre_probe(dev);                       /* uclass_driver->pre_probe(dev) */
      -> ret = drv->probe(dev);                                /* driver->probe(dev) */
      -> ret = uclass_post_probe_device(dev);
        -> uc_drv->post_probe(dev);                            /* uclass_driver->post_probe(dev) */
  -> ret = dm_scan_platdata(true);
    -> ret = lists_bind_drivers((gd_t *)gd->dm_root, true);
      -> ret = device_bind_by_name(NULL, true, &info, &dev);   /* 通过driver_info->name设置其他uclass和udevice */
  -> ret = dm_scan_other(true);                                /* 空函数 */

根据以上四部分代码,可以确定initf_dm()函数所做的事情有:

1. 根据设备名查到到对应的driver

2. 匹配位于.u_boot_list_2_driver_[1-3]段中的driver id和位于.u_boot_list_2_uclass_[1-3]段中的uclass_driver id

3. 初始化uclass链表,root uclass为链表头

4. 创建设备对应的udevice,root udevice并存放在gd->dm_root中

5. 调用uclass_driver和driver的probe()函数

6. 调用device_bind_by_name()函数重复第1-4点(没有第5点)

 

u-boot.map中 .u_boot_list_2_driver_[1-3]段 和 .u_boot_list_2_uclass_[1-3]段 定义如下:

 1  .u_boot_list_2_driver_1
 2                 0x87860c54        0x0 drivers/built-in.o
 3  .u_boot_list_2_driver_2_imx_thermal
 4                 0x87860c54       0x44 drivers/built-in.o
 5                 0x87860c54                _u_boot_list_2_driver_2_imx_thermal
 6  .u_boot_list_2_driver_2_root_driver
 7                 0x87860c98       0x44 drivers/built-in.o
 8                 0x87860c98                _u_boot_list_2_driver_2_root_driver
 9  .u_boot_list_2_driver_3
10                 0x87860cdc        0x0 drivers/built-in.o
11 
12  .u_boot_list_2_uclass_1
13                 0x87860da4        0x0 drivers/built-in.o
14  .u_boot_list_2_uclass_2_disk
15                 0x87860da4       0x48 drivers/built-in.o
16                 0x87860da4                _u_boot_list_2_uclass_2_disk
17  .u_boot_list_2_uclass_2_root
18                 0x87860dec       0x48 drivers/built-in.o
19                 0x87860dec                _u_boot_list_2_uclass_2_root
20  .u_boot_list_2_uclass_2_thermal
21                 0x87860e34       0x48 drivers/built-in.o
22                 0x87860e34                _u_boot_list_2_uclass_2_thermal
23  .u_boot_list_2_uclass_3
24                 0x87860e7c        0x0 drivers/built-in.o
25                 0x87860e7c                . = ALIGN (0x4)

其中,imx_thermal的driver和uclass_driver定义如下:

 1 UCLASS_DRIVER(thermal) = {
 2     .id        = UCLASS_THERMAL,
 3     .name        = "thermal",
 4 };
 5 
 6 U_BOOT_DRIVER(imx_thermal) = {
 7     .name    = "imx_thermal",
 8     .id    = UCLASS_THERMAL,
 9     .ops    = &imx_thermal_ops,
10     .probe    = imx_thermal_probe,
11     .priv_auto_alloc_size = sizeof(struct thermal_data),
12     .flags  = DM_FLAG_PRE_RELOC,
13 };

 

 

三、mark_bootstage()

mark_bootstage()
  -> bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_F, "board_init_f");
    -> bootstage_add_record(id, name, 0, timer_get_boot_us());
      -> struct bootstage_record *rec = &record[id];                /* 1 */
      -> rec->time_us = timer_get_boot_us();
      -> rec->name = "board_init_f";
      -> rec->flags = 0;
      -> rec->id = BOOTSTAGE_ID_START_UBOOT_F;
      -> show_boot_progress(flags & BOOTSTAGEF_ERROR ? -id : id);  /* 空函数 */

 

 

四、board_early_init_f()

board_early_init_f()                                /* 一般用于初始化时钟树并设置GPIO功能 */
  -> setup_iomux_uart();
    -> imx_iomux_v3_setup_multiple_pads(uart1_pads, ARRAY_SIZE(uart1_pads));
      -> imx_iomux_v3_setup_pad(*p);
        -> u32 mux_ctrl_ofs = (pad & 0xfff);        /* mux_ctrl_ofs及其其他变量都可以与IOMUX_PAD()对应 */
        -> u32 lpsr = (pad & MUX_MODE_LPSR) >> MUX_MODE_SHIFT;                /* lpsr = 0 */
        -> __raw_writel(mux_mode, base + mux_ctrl_ofs);                       /* base = 0x020E0000 */
        -> __raw_writel(sel_input, base + sel_input_ofs);
        -> __raw_writel(pad_ctrl, base + pad_ctrl_ofs);

其中主要变量定义或结果如下:

 1 static iomux_v3_cfg_t const uart1_pads[] = {
 2     /* IOMUX_PAD(0x0310, 0x0084, 0, 0x0000, 0, 0) = 0x0084 | (0x0310 << 12) = 0x00310084 */
 3     /* UART_PAD_CTRL = (1 << 12 | 1 << 13  | 2 << 14 | 2 << 6 | 6 << 3 | 1 << 0 | 1 << 16) = 0x0001b0b1
 4      * MUX_PAD_CTRL(UART_PAD_CTRL) = 0x0001b0b1 << 42
 5      */
 6     /* 0x00310084 | (0x0001b0b1 << 42) */
 7     MX6_PAD_UART1_TX_DATA__UART1_DCE_TX | MUX_PAD_CTRL(UART_PAD_CTRL),
 8     MX6_PAD_UART1_RX_DATA__UART1_DCE_RX | MUX_PAD_CTRL(UART_PAD_CTRL),
 9 };
10 
11 /* MX6_PAD_UART1_TX_DATA */
12 /* IOMUX_PAD(pad_ctrl_ofs, mux_ctrl_ofs, mux_mode, sel_input_ofs, sel_input, pad_ctrl)    
13  * IOMUX_PAD(0x0310,       0x0084,       0,        0x0000,        0,         0)
14  *         = 0x0084 | (0x0310 << 12) = 0x00310084
15  */
16 /* mux_ctrl_ofs  = pad & 0xfff = 0x084
17  * mux_mode      = (pad & (0x3f << 36)) >> 36 = 0
18  * sel_input_ofs = (pad & (0xfff << 24)) >> 24 = 0
19  * sel_input     = (pad & (0xf << 60)) >> 60 = 0
20  * pad_ctrl_ofs  = (pad & (0xfff << 12)) >> 12 = 0x310
21  * pad_ctrl      = (pad & (0x3ffff << 42)) >> 42 = 0
22  */
23 u32 mux_ctrl_ofs = (pad & MUX_CTRL_OFS_MASK) >> MUX_CTRL_OFS_SHIFT;
24 u32 mux_mode = (pad & MUX_MODE_MASK) >> MUX_MODE_SHIFT;
25 u32 sel_input_ofs = (pad & MUX_SEL_INPUT_OFS_MASK) >> MUX_SEL_INPUT_OFS_SHIFT;
26 u32 sel_input = (pad & MUX_SEL_INPUT_MASK) >> MUX_SEL_INPUT_SHIFT;
27 u32 pad_ctrl_ofs = (pad & MUX_PAD_CTRL_OFS_MASK) >> MUX_PAD_CTRL_OFS_SHIFT;
28 u32 pad_ctrl = (pad & MUX_PAD_CTRL_MASK) >> MUX_PAD_CTRL_SHIFT;
29 
30 /* lpsr  = (pad & (0x20 << 36)) >> 36 = 0,取第41位后右移36位 */
31 u32 lpsr = (pad & MUX_MODE_LPSR) >> MUX_MODE_SHIFT;

 

 

五、timer_init()

timer_init()
  -> static struct mxc_gpt *cur_gpt = (struct mxc_gpt *)GPT1_BASE_ADDR;     /* GPT1_BASE_ADDR = 0x02098000 */
  -> __raw_writel(GPTCR_SWR, &cur_gpt->control);                            /* *0x02098000 = 1 << 15 */
  -> __raw_writel(0, &cur_gpt->control);                                    /* 循环100次写0 */
  -> i = __raw_readl(&cur_gpt->control);
  -> i &= ~GPTCR_CLKSOURCE_MASK;                                            /* 7 << 6 */
  -> i |= GPTCR_CLKSOURCE_OSC | GPTCR_TEN | GPTCR_24MEN;                    /* 5 << 6 | 1 << 0 | 1 << 10 */
  -> __raw_writel((7 << GPTPR_PRESCALER24M_SHIFT), &cur_gpt->prescaler);    /* *0x02098004 = 7 << 12 */
  -> __raw_writel(i, &cur_gpt->control);

 

 

六、serial_init()

serial_init()
  -> return get_current()->start();
    -> struct serial_device *dev = default_serial_console();
      -> return &mxc_serial_drv;
    -> mxc_serial_drv.start = mxc_serial_init,
      -> UART寄存器设置

UART寄存器设置如下:

 1 static int mxc_serial_init(void)
 2 {
 3     __REG(UART_PHYS + UCR1) = 0x0;       /* *0x02020080 = 0 */
 4     __REG(UART_PHYS + UCR2) = 0x0;       /* *0x02020084 = 0 */
 5 
 6     while (!(__REG(UART_PHYS + UCR2) & UCR2_SRST));   /* !(*0x02020084 & 1) */
 7 
 8     __REG(UART_PHYS + UCR3) = 0x0704 | UCR3_ADNIMP;   /* *0x02020088 = 0x784 */
 9     __REG(UART_PHYS + UCR4) = 0x8000;    /* *0x02020084 = 0x8000 */
10     __REG(UART_PHYS + UESC) = 0x002b;    /* *0x0202008c = 0x002b */
11     __REG(UART_PHYS + UTIM) = 0x0;       /* *0x020200a0 = 0 */
12 
13     __REG(UART_PHYS + UTS) = 0x0;        /* *0x020200b4 = 0 */
14 
15     serial_setbrg();    /* 1. 获取uart时钟 */
16                         /* 2. *0x02020090 = (4 << 7) | (2 << 10) | 1 */
17                         /* 3. *0x020200a4 = 0xf */
18                         /* 4. *0x020200a8 = clk / (2 * 115200); */
19     /* *0x02020084 = (1 << 5) | (1 << 14) | (1 << 1) | (1 << 2) | 1 */
20     __REG(UART_PHYS + UCR2) = UCR2_WS | UCR2_IRTS | UCR2_RXEN | UCR2_TXEN | UCR2_SRST;
21     /* *0x02020080 = 1 */
22     __REG(UART_PHYS + UCR1) = UCR1_UARTEN;
23 
24     return 0;
25 }

 

 

七、init_func_i2c()

init_func_i2c()
  -> i2c_init_all();
    -> i2c_init_board();                                            /* 空函数 */
    -> i2c_set_bus_num(CONFIG_SYS_SPD_BUS_NUM);                     /* CONFIG_SYS_SPD_BUS_NUM = 0 */
      -> max = ll_entry_count(struct i2c_adapter, i2c);             /* .u_boot_list_2_i2c_1到.u_boot_list_2_i2c_3 */
      -> gd->cur_i2c_bus = bus;                                     /* gd->cur_i2c_bus = 0 */
      -> i2c_init_bus(bus, I2C_ADAP->speed, I2C_ADAP->slaveaddr);   /* I2C_ADAP = i2c_get_adapter(0):后面的_u_boot_list_2_i2c_mxc0 */
        -> I2C_ADAP->init(I2C_ADAP, speed, slaveaddr);              /* drivers/i2c/mxc_i2c.c */
          -> mxc_i2c_init(_u_boot_list_2_i2c_mxc0, CONFIG_SYS_MXC_I2C1_SPEED, CONFIG_SYS_MXC_I2C1_SLAVE)
            -> bus_i2c_init(adap->hwadapnr, speed, slaveaddr, NULL, NULL);   /* 0, 100000, 0,  */
              -> ret = enable_i2c_clk(1, 0);
                -> mask = 3 << (6 + (0 << 1));
                -> reg = __raw_readl(&imx_ccm->CCGR2);              /* reg = 0xffffffff */
                -> reg |= mask;                                     /* CCM->CCGR2 |= 3 << 6 */
                -> __raw_writel(reg, &imx_ccm->CCGR2);
              -> bus_i2c_set_bus_speed(&mxc_i2c_buses[0], 100000);
                -> u8 clk_idx = i2c_imx_get_clk(i2c_bus, 100000);
                  -> i2c_clk_rate = mxc_get_clock(MXC_I2C_CLK);     /* i2c_clk_rate = 66000000 */
                  -> div = (i2c_clk_rate + 100000 - 1) / 100000;    /* div = 660 */
                  -> for (clk_div = 0; i2c_clk_div[clk_div][0] < div; clk_div++) ; /* clk_div = 36 */
                -> u8 idx = i2c_clk_div[clk_idx][1];                /* { 768, 0x39 } */
                -> writeb(idx, base + (IFDR << 2));                 /* *(0x21a0000 + (1 << 2)) = 0x39 */
                -> writeb(I2CR_IDIS, base + (I2CR << 2));           /* *(0x21a0000 + (2 << 2)) = (0 << 7) */
                -> writeb(0, base + (I2SR << 2));                   /* *(0x21a0000 + (3 << 2)) = 0 */

在读者跟随上面的调用过程自己探索时,可能会发现u-boot并没有直接定义struct i2c_adapter _u_boot_list_2_i2c_mxc0

它在u-boot中是通过宏定义间接定义的,如下:

 1 include/i2c.h
 2 #define U_BOOT_I2C_ADAP_COMPLETE(_name, _init, _probe, _read, _write,  3             _set_speed, _speed, _slaveaddr, _hwadapnr)  4     ll_entry_declare(struct i2c_adapter, _name, i2c) =  5     U_BOOT_I2C_MKENT_COMPLETE(_init, _probe, _read, _write,  6          _set_speed, _speed, _slaveaddr, _hwadapnr, _name);
 7 
 8 include/linker_lists.h
 9 #define ll_entry_declare(_type, _name, _list)                10     _type _u_boot_list_2_##_list##_2_##_name __aligned(4)        11             __attribute__((unused,                12             section(".u_boot_list_2_"#_list"_2_"#_name)))
13 
14 include/i2c.h
15 #define U_BOOT_I2C_MKENT_COMPLETE(_init, _probe, _read, _write, 16         _set_speed, _speed, _slaveaddr, _hwadapnr, _name) 17     { 18         .init          =    _init, 19         .probe         =    _probe, 20         .read          =    _read, 21         .write         =    _write, 22         .set_bus_speed =    _set_speed, 23         .speed         =    _speed, 24         .slaveaddr     =    _slaveaddr, 25         .init_done     =    0, 26         .hwadapnr      =    _hwadapnr, 27         .name          =    #_name 28 };
29 
30 drivers/i2c/mxc_i2c.c
31 U_BOOT_I2C_ADAP_COMPLETE(mxc0, mxc_i2c_init, mxc_i2c_probe,
32              mxc_i2c_read, mxc_i2c_write,
33              mxc_i2c_set_bus_speed,
34              CONFIG_SYS_MXC_I2C1_SPEED/* 100000 */,
35              CONFIG_SYS_MXC_I2C1_SLAVE/* 0 */, 0)
36 
37 变成
38 struct i2c_adapter _u_boot_list_2_i2c_mxc0 __aligned(4) 39     __attribute__((unused, section(".u_boot_list_2_i2c_2_mxc0))) = 
40 {
41     .init          =    mxc_i2c_init,
42     .probe         =    mxc_i2c_probe,
43     .read          =    mxc_i2c_read,
44     .write         =    mxc_i2c_write,
45     .set_bus_speed =    mxc_i2c_set_bus_speed,
46     .speed         =    CONFIG_SYS_MXC_I2C1_SPEED,    /* 100000 */
47     .slaveaddr     =    CONFIG_SYS_MXC_I2C1_SLAVE,    /* 0 */
48     .init_done     =    0,
49     .hwadapnr      =    0,
50     .name          =    "mxc0"
51 };

 

 

八、setup_reloc()

setup_reloc()         /* 拷贝gd结构体至new_gd地址 */
  -> memcpy(gd->new_gd, (char *)gd, sizeof(gd_t));    

 

3、init_sequence_f[]中驱动模型和外设相关初始化

原文:https://www.cnblogs.com/Lioker/p/13218865.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!