当前位置:   article > 正文

捋一捋IMX6ULL的Uboot是如何初始化片上外设的——以串口为例_imx6ull如何实现外设初始化的

imx6ull如何实现外设初始化的

        传统的SoC比如S3C2440,6410或者s5pv210,它们的Uboot在lowlevel_init.S文件中做了很多片上外设的初始化工作,比如配置部分GPIO口的电气属性,配置串口,配置DDR控制器等,配置的过程很简单,简单来说就是这样:

        

  1. ldr r0,=外设寄存器地址
  2. ldr r1,=寄存器的值
  3. str r1,[r0] @r0和r1只是常用,也可以使用别的寄存器来进行这个过程

        但是NXP的IMX6ULL这款SoC的Uboot,在lowlevel_init.S这个文件中仅仅只是在片内RAM中设置了堆栈,然后划分出一部分区域用于存储struct global_data的值

  1. ENTRY(lowlevel_init)
  2. ldr sp, =CONFIG_SYS_INIT_SP_ADDR @设置sp指针到片内RAM的一块
  3. bic sp, sp, #7 @8字节对齐
  4. sub sp, sp, #GD_SIZE @留出struct global_data变量的区域
  5. bic sp, sp, #7
  6. mov r9, sp @把struct global_data变量的起始地方放到r9中
  7. push {ip, lr}
  8. bl s_init @对IMX6ULL来说,这个函数是个空函数
  9. pop {ip, pc}
  10. ENDPROC(lowlevel_init)

        之后进入到_main函数,这个函数在arch/arm/lib/crt0.S中,对IMX6ULL来说这个函数几乎干了Uboot在启动Linux系统之前的所有事情。

        1、上来重新设置以下堆栈指针,因为上述的lowlevel_init函数把sp指针指向struct global变量的起始地址去了。   

ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)

        2、连续三个函数调用。

  1. mov r0, sp @进行函数调用的参数传递,把参数放到r0中
  2. bl board_init_f_alloc_reserve @在片内SRAM留出malloc区域和新的global_data区域
  3. mov sp, r0 @获取函数返回值
  4. mov r9, r0
  5. bl board_init_f_init_reserve @把上述留出的两个区域给清零
  6. mov r0, #0
  7. bl board_init_f @调用board_init_f函数,这个函数非常重要

        boadr_init_f函数非常重要!它的作用有两个:一是初始化部分片上外设,比如串口,LCD接口,定时器等。二是初始化global_data中的所有成员变量,global_data的成员变量中有一大部分是地址信息,描述了外部DDR该如何划分,哪里是malloc区,哪里是重定位的起始地址,哪里是Uboot的终止地址等等。有了这些信息才能进行后续的Uboot代码重定位过程。

        下面来看看board_inif_f函数,该函数位于/uboot/common/board_f.c,这里只摘录函数中的重点:

  1. void board_init_f(ulong boot_flags)
  2. {
  3. gd->flags = boot_flags; //传入值为0,上述代码中有
  4. gd->have_console = 0; //表示还没控制台
  5. if (initcall_run_list(init_sequence_f))
  6. hang();
  7. ...
  8. ...
  9. }

        这个initcall_run_list函数会依次执行init_sequence_f这个函数指针数组中的所有函数,这些函数执行成功返回0,失败返回非0值,就会陷入hang()循环。

        这个函数指针数组里的重点函数如下:

  1. static init_fnc_t init_sequence_f[] = {
  2. setup_mon_len,//计算整个Uboot的长度
  3. board_early_init_f,
  4. timer_init,
  5. get_clocks,
  6. env_init,
  7. init_baud_rate,
  8. serial_init,
  9. console_init_f,
  10. display_options,
  11. display_text_info,
  12. print_cpuinfo,
  13. show_board_info,
  14. dram_init,
  15. setup_dram_config,
  16. show_dram_config,
  17. setup_dest_addr,
  18. reserve_uboot,
  19. reserve_malloc,
  20. reserve_global_data,
  21. reloc_fdt,
  22. setup_reloc,
  23. .....
  24. }

        串口初始化是在board_init_f中完成的,通过调用这个函数数组中的函数初始化串口。下面来看看Uboot是如何初始化串口的:

        1、board_early_init_f,配引脚。该函数完成了串口所要使用的引脚的复用属性和电气属性的配置,其在board/freescale/mx6ullevk/mx6ullevk.c中定义:

  1. int board_early_init_f(void)
  2. {
  3. setup_iomux_uart();//配置串口的引脚复用为uart,具体实现如下
  4. return 0;
  5. }
  6. //根据引脚配置设置复用属性
  7. static void setup_iomux_uart(void)
  8. {
  9. imx_iomux_v3_setup_multiple_pads(uart1_pads, ARRAY_SIZE(uart1_pads));
  10. }
  11. //引脚的复用属性宏和电气属性宏
  12. static iomux_v3_cfg_t const uart1_pads[] = {
  13. MX6_PAD_UART1_TX_DATA__UART1_DCE_TX | MUX_PAD_CTRL(UART_PAD_CTRL),
  14. MX6_PAD_UART1_RX_DATA__UART1_DCE_RX | MUX_PAD_CTRL(UART_PAD_CTRL),
  15. };
  16. //以下是电气属性配置的过程的分解步骤
  17. #define MUX_PAD_CTRL(x) ((iomux_v3_cfg_t)(x) << MUX_PAD_CTRL_SHIFT)
  18. #define MUX_PAD_CTRL_SHIFT 42
  19. //位于board/freescale/mx6ullevk/mx6ullevk.c中,宏的组合来达到电气属性的配置
  20. #define UART_PAD_CTRL (PAD_CTL_PKE | PAD_CTL_PUE | \
  21. PAD_CTL_PUS_100K_UP | PAD_CTL_SPEED_MED | \
  22. PAD_CTL_DSE_40ohm | PAD_CTL_SRE_FAST | PAD_CTL_HYS)
  23. //以PAD_CTL_PUS_100K_UP说明这个宏是如何配置引脚的电气属性
  24. //位于arch/arm/include/asm/imx-common/iomux-v3.h
  25. #define PAD_CTL_PUE (0x1 << 4)
  26. //位于arch/arm/include/asm/imx-common/iomux-v3.h
  27. #define PAD_CTL_PUS_100K_UP (2 << 14 | PAD_CTL_PUE)
  28. //通过上述两个操作,使得PAD_CTL_PUS_100K_UP这个宏代表了GPIO的一个电气属性的配置值,只需要将这个配置值写入GPIO的控制寄存器即可
  29. //引脚复用属性的配置也是一样的道理
  30. //位于arch/arm/include/asm/arch/mx6ul_pins.h
  31. MX6_PAD_UART1_TX_DATA__UART1_DCE_TX = IOMUX_PAD(0x0310, 0x0084, 0, 0x0000, 0, 0)
  32. //位于arch/arm/include/asm/imx-common/iomux-v3.h
  33. typedef u64 iomux_v3_cfg_t
  34. #define IOMUX_PAD(pad_ctrl_ofs, mux_ctrl_ofs, mux_mode, sel_input_ofs, \
  35. sel_input, pad_ctrl) \
  36. (((iomux_v3_cfg_t)(mux_ctrl_ofs) << MUX_CTRL_OFS_SHIFT) | \
  37. ((iomux_v3_cfg_t)(mux_mode) << MUX_MODE_SHIFT) | \
  38. ((iomux_v3_cfg_t)(pad_ctrl_ofs) << MUX_PAD_CTRL_OFS_SHIFT) | \
  39. ((iomux_v3_cfg_t)(pad_ctrl) << MUX_PAD_CTRL_SHIFT) | \
  40. ((iomux_v3_cfg_t)(sel_input_ofs) << MUX_SEL_INPUT_OFS_SHIFT)| \
  41. ((iomux_v3_cfg_t)(sel_input) << MUX_SEL_INPUT_SHIFT))
  42. //可以看出,最终出来的也是一个要写入寄存器的值。

        总结一下串口的引脚配置过程:

        首先在board/freescale/mx6ullevk/mx6ullevk.c文件中的uart1_pads[]数组中写入引脚的复用属性和电气属性宏。

        然后调用imx_iomux_v3_setup_multiple_pads(uart1_pads, ARRAY_SIZE(uart1_pads))函数把寄存器的值写入到相应的寄存器中。

        

        2、init_baud_rate,获取波特率存入global_data中。要注意的是波特率的修改是在mx6_common.h中,这个头文件被include/configs/mx6ullevk.h所包含。

  1. //位于uboot/common/board_f.c
  2. //从环境变量中读取波特率的值写入到global_data中去
  3. static int init_baud_rate(void)
  4. {
  5. gd->baudrate = getenv_ulong("baudrate", 10, CONFIG_BAUDRATE);
  6. return 0;
  7. }
  8. //位于include/configs/mx6_common.h
  9. #define CONFIG_BAUDRATE 115200

         3、serial_init函数,通过多层调用实现IMX6ULL的UART1的初始化。该函数位于drivers/serial目录下

  1. int serial_init(void)
  2. {
  3. gd->flags |= GD_FLG_SERIAL_READY;
  4. return get_current()->start();
  5. }
  6. static struct serial_device *get_current(void)
  7. {
  8. struct serial_device *dev;
  9. if (!(gd->flags & GD_FLG_RELOC))
  10. dev = default_serial_console();//此时还未重定位,所以会执行这一句,函数定义如下。
  11. else if (!serial_current)
  12. dev = default_serial_console();
  13. else
  14. dev = serial_current;
  15. /* We must have a console device */
  16. if (!dev) {
  17. #ifdef CONFIG_SPL_BUILD
  18. puts("Cannot find console\n");
  19. hang();
  20. #else
  21. panic("Cannot find console\n");
  22. #endif
  23. }
  24. return dev;
  25. }
  26. //可以看到这是一个弱定义,别处没有定义的话就用这里的,在serial.c中未定义,所以会用这个函数。
  27. __weak struct serial_device *default_serial_console(void)
  28. {
  29. return &mxc_serial_drv;
  30. }
  31. //下面是返回的这个结构体的内容
  32. static struct serial_device mxc_serial_drv = {
  33. .name = "mxc_serial",
  34. .start = mxc_serial_init,
  35. .stop = NULL,
  36. .setbrg = mxc_serial_setbrg,
  37. .putc = mxc_serial_putc,
  38. .puts = default_serial_puts,
  39. .getc = mxc_serial_getc,
  40. .tstc = mxc_serial_tstc,
  41. };
  42. //可以看出该结构体中都是函数指针,这些函数实现了IMX6ULL的串口初始化和读写函数
  43. //回到最前面,serial_init其实调用的就是mxc_serial_init函数。
  44. static int mxc_serial_init(void)
  45. {
  46. __REG(UART_PHYS + UCR1) = 0x0;
  47. __REG(UART_PHYS + UCR2) = 0x0;
  48. while (!(__REG(UART_PHYS + UCR2) & UCR2_SRST));
  49. __REG(UART_PHYS + UCR3) = 0x0704 | UCR3_ADNIMP;
  50. __REG(UART_PHYS + UCR4) = 0x8000;
  51. __REG(UART_PHYS + UESC) = 0x002b;
  52. __REG(UART_PHYS + UTIM) = 0x0;
  53. __REG(UART_PHYS + UTS) = 0x0;
  54. serial_setbrg();
  55. __REG(UART_PHYS + UCR2) = UCR2_WS | UCR2_IRTS | UCR2_RXEN | UCR2_TXEN | UCR2_SRST;
  56. __REG(UART_PHYS + UCR1) = UCR1_UARTEN;
  57. return 0;
  58. }
  59. //其中的函数展开为
  60. void serial_setbrg(void)
  61. {
  62. get_current()->setbrg();
  63. }
  64. //真正的配置IMX6ULL的串口波特率
  65. static void mxc_serial_setbrg(void)
  66. {
  67. u32 clk = imx_get_uartclk();
  68. if (!gd->baudrate)
  69. gd->baudrate = CONFIG_BAUDRATE;
  70. __REG(UART_PHYS + UFCR) = (RFDIV << UFCR_RFDIV_SHF)
  71. | (TXTL << UFCR_TXTL_SHF)
  72. | (RXTL << UFCR_RXTL_SHF);
  73. __REG(UART_PHYS + UBIR) = 0xf;
  74. __REG(UART_PHYS + UBMR) = clk / (2 * gd->baudrate);
  75. }
  76. //上面的宏定义如下
  77. #define UART_PHYS CONFIG_MXC_UART_BASE
  78. #define __REG(x) (*((volatile u32 *)(x)))
  79. //这些都是寄存器相对于基地址UART_PHYS的偏移
  80. #define URXD 0x0 /* Receiver Register */
  81. #define UTXD 0x40 /* Transmitter Register */
  82. #define UCR1 0x80 /* Control Register 1 */
  83. #define UCR2 0x84 /* Control Register 2 */
  84. #define UCR3 0x88 /* Control Register 3 */
  85. #define UCR4 0x8c /* Control Register 4 */
  86. #define UFCR 0x90 /* FIFO Control Register */
  87. #define USR1 0x94 /* Status Register 1 */
  88. #define USR2 0x98 /* Status Register 2 */
  89. #define UESC 0x9c /* Escape Character Register */
  90. #define UTIM 0xa0 /* Escape Timer Register */
  91. #define UBIR 0xa4 /* BRM Incremental Register */
  92. #define UBMR 0xa8 /* BRM Modulator Register */
  93. #define UBRC 0xac /* Baud Rate Count Register */
  94. #define UTS 0xb4 /* UART Test Register (mx31) */
  95. //位于include/configs/mx6ullevk.h,这里选择UART1作为uboot启动时候的串口,可以换成2。
  96. #define CONFIG_MXC_UART_BASE UART1_BASE

        通过以上三步,Uboot完成了IMX6ULL的串口初始化。

        通过对这个过程的了解,我们在移植Uboot过程中,对于串口,最主要的工作就是在板级文件夹下的.c文件中(uboot/board/freescale/mx6ullevk.c)中修改引脚的复用属性和电气属性,在相应的头文件中修改好波特率和要使用的串口的宏定义。

        其他的片上外设的初始化过程分析,敬请期待!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/代码探险家/article/detail/772715
推荐阅读
相关标签
  

闽ICP备14008679号