zoukankan      html  css  js  c++  java
  • tiny4412 串口驱动分析一 --- u-boot中的串口驱动

    作者:彭东林

    邮箱:pengdonglin137@163.com

     

    开发板:tiny4412ADK+S700 4GB Flash

    主机:Wind7 64位

    虚拟机:Vmware+Ubuntu12_04

    u-boot:U-Boot 2010.12

    Linux内核版本:linux-3.0.31

    Android版本:android-4.1.2

    我们以tiny4412为例分析串口驱动,下面我们从u-boot开始分析,然后再分析到Linux。

    串口初始化

    关于这部分代码流程参考件:tiny4412 u-boot 启动.pdf,这里主要分析函数:uart_asm_init

    在初始化串口驱动之前已经进行了系统时钟以及内存的初始化。下面的代码取自board/samsung/tiny4412/lowlevel_init.S:

    lowlevel_init:
    
              ……
    
             /* init system clock */
    
             bl      system_clock_init
    
             /* Memory initialize */
    
             bl      mem_ctrl_asm_init
    
             /* init uart for debug */
    
             bl      uart_asm_init

    通过函数system_clock_init,得到如下结果:

    APLL = 1400MHz, MPLL = 800MHz

    通过函数uart_asm_init,将uart的波特率设置为了115200bps,下面是uart_asm_init的实现:

    /*
    
     * uart_asm_init: Initialize UART in asm mode, 115200bps fixed.
    
     * void uart_asm_init(void)
    
     */
    
             .globl uart_asm_init
    
    uart_asm_init:
    
             /* set GPIO to enable UART */
    
             @ GPIO setting for UART for UART0/1/2/3
    
             ldr    r0, =0x11400000
             ldr    r1, =0x22222222
             str    r1, [r0]
             ldr    r0, =0x11400020
             ldr    r1, =0x222222
             str    r1, [r0]
    
    // tiny4412有4组uart
    
     
    
    // 设置uart0~4的时钟源为SCLKMPLL_USER_T,为800MHz
             ldr    r0, =S5PV310_CLOCK_BASE
             ldr    r1, =CLK_SRC_PERIL0_VAL
             ldr    r2, =CLK_SRC_PERIL0_OFFSET
             str    r1, [r0, r2]
    
    // 设置uart的分频系数为7,经计算得到SCLK UART=800M/(7+1)=100M
    
             ldr    r1, =CLK_DIV_PERIL0_VAL
             ldr    r2, =CLK_DIV_PERIL0_OFFSET
             str    r1, [r0, r2]
    
    // 在tiny4412.h中定义了CONFIG_SERIAL0,即使用uart0作为默认的串口输出,所以S5PV310_UART_CONSOLE_BASE的值就是uart0控制器的基地址,为0x13800000,设置这个寄存的目的是启动并设置uart的FIFO功能,结果:启动uart0的FIFO功能,uart0的Rx FIFO Trigger Level是64B,Tx FIFO Trigger Level是32B
    
             ldr    r0, =S5PV310_UART_CONSOLE_BASE
             ldr    r1, =0x111
             str    r1, [r0, #UFCON_OFFSET]
    
    // 设置uart0发送或者接受数据包每帧大小,这里设置为了8bit,1bit停止位,无奇偶校验,normal mode(除此之外还有一种叫做info-red的模式,用于红外发送和接受)
    
             mov r1, #0x3
             str    r1, [r0, #ULCON_OFFSET]
    
    // 设置uart0的读取接收缓冲区和写输出缓冲区的方式为中断或者轮询(除此之外还有DMA方式等);中断触发类型为电平触发
    
             ldr    r1, =0x3c5
             str    r1, [r0, #UCON_OFFSET]
    
    /* SCLK_UART0=100MHz, 波特率设置为115200
    
    * 寄存器的值如下计算:
    
    * DIV_VAL = 100,000,000 / (115200 * 16) - 1 = 53.25
    
    * UBRDIVn0 = 整数部分 = 53
    
    * UFRACVAL0 = 小数部分 x 16 = 0.25 * 16 = 4
    
    */
    
             ldr    r1, =UART_UBRDIV_VAL   // 0x35
             str    r1, [r0, #UBRDIV_OFFSET]
             ldr    r1, =UART_UDIVSLOT_VAL  // 0x4
             str    r1, [r0, #UDIVSLOT_OFFSET]
    
    // UTXH_OFFSET是输出缓冲区,这里是向uart0上打印 ‘O’
    
             ldr    r1, =0x4f4f4f4f
             str    r1, [r0, #UTXH_OFFSET]            @'O'
             mov pc, lr

    上面完成了串口底层的初始化,接下来就可以使用了。下面以printf为例分析

    u-boot中printf的实现

    下面是u-boot中printf的源码

    common/console.c

    int printf(const char *fmt, ...)
    {
             va_list args;
             uint i;
             char printbuffer[CONFIG_SYS_PBSIZE]; // CONFIG_SYS_PBSIZE的值是256
             va_start(args, fmt);
             /* For this to work, printbuffer must be larger than
              * anything we ever want to print.
              */
    
             i = vsprintf(printbuffer, fmt, args); // 将要打印的内容写到printbuffer中
             va_end(args);
             /* Print the string */
             puts(printbuffer);  // 将printbuffer中的内容从串口输出
             return i;
    }
    
    void puts(const char *s)
    {
           if (gd->flags & GD_FLG_DEVINIT) {
                    /* Send to the standard output */
                    fputs(stdout, s);
           } else {
                    /* Send directly to the handler */
                    serial_puts(s);
           }
    }

    common/serial.c

    void serial_puts (const char *s)
    {
             if (!(gd->flags & GD_FLG_RELOC) || !serial_current) {
                       struct serial_device *dev = default_serial_console ();
                       dev->puts (s);
                       return;
             }
             serial_current->puts (s);
    }

    struct serial_device *default_serial_console(void) __attribute__((weak, alias("__default_serial_console")));

    上面的意思是如果没有定义default_serial_console,那么就使用__default_serial_console

    struct serial_device *__default_serial_console (void)
    {
    ……
    #if defined(CONFIG_SERIAL0)
             return &s5p_serial0_device;
    #elif defined(CONFIG_SERIAL1)
             return &s5p_serial1_device;
    #elif defined(CONFIG_SERIAL2)
             return &s5p_serial2_device;
    #elif defined(CONFIG_SERIAL3)
             return &s5p_serial3_device;
    #else
    #error "CONFIG_SERIAL? missing."
    #endif
    ……
    }

    由于我们使用的是uart0作为调试串口,并且定义了宏CONFIG_SERIAL0,所以__default_serial_console的返回值就是s5p_serial0_device的地址,下面我们看一下s5p_serial0_device

    drivers/serial/serial_s5p.c

    #define DECLARE_S5P_SERIAL_FUNCTIONS(port) 
    
    int s5p_serial##port##_init(void) { return serial_init_dev(port); } 
    void s5p_serial##port##_setbrg(void) { serial_setbrg_dev(port); } 
    int s5p_serial##port##_getc(void) { return serial_getc_dev(port); } 
    int s5p_serial##port##_tstc(void) { return serial_tstc_dev(port); } 
    void s5p_serial##port##_putc(const char c) { serial_putc_dev(c, port); } 
    void s5p_serial##port##_puts(const char *s) { serial_puts_dev(s, port); }
    
    #define INIT_S5P_SERIAL_STRUCTURE(port, name, bus) { 
             name, 
             bus, 
             s5p_serial##port##_init, 
             NULL, 
             s5p_serial##port##_setbrg, 
             s5p_serial##port##_getc, 
             s5p_serial##port##_tstc, 
             s5p_serial##port##_putc, 
             s5p_serial##port##_puts, }
    
    DECLARE_S5P_SERIAL_FUNCTIONS(0);
    
    struct serial_device s5p_serial0_device =
             INIT_S5P_SERIAL_STRUCTURE(0, "s5pser0", "S5PUART0");

    综上,这里s5p_serial0_device的定义如下:

    int s5p_serial0_init(void) { return serial_init_dev(0); }
    void s5p_serial0_setbrg(void) { serial_setbrg_dev(0); }
    int s5p_serial0_getc(void) { return serial_getc_dev(0); }
    int s5p_serial0_tstc(void) { return serial_tstc_dev(0); }
    void s5p_serial0_putc(const char c) { serial_putc_dev(c, 0); }
    void s5p_serial0_puts(const char *s) { serial_puts_dev(s, 0); }
    
    struct serial_device s5p_serial0_device =
    {
             "s5pser0",
             "S5PUART0",
             s5p_serial0_init,
             NULL,
             s5p_serial0_setbrg,
             s5p_serial0_getc,
             s5p_serial0_tstc,
             s5p_serial0_putc,
             s5p_serial0_puts,
    };

    然后我们看一下它的puts函数指针:s5p_serial0_puts

    void s5p_serial0_puts(const char *s) { serial_puts_dev(s, 0); }

    然后分析serial_puts_dev(s, 0)

    drivers/serial/serial_s5p.c:

    void serial_puts_dev(const char *s, const int dev_index)
    {
             while (*s)
                       serial_putc_dev(*s++, dev_index);
    }
    
    /*
     * Output a single byte to the serial port.
     */
    
    void serial_putc_dev(const char c, const int dev_index)
    {
    // 获得uart0的控制器基地址
             struct s5p_uart *const uart = s5p_get_base_uart(dev_index);
    // 读取发送状态寄存器,看是否有空余空间
             /* wait for room in the tx FIFO */
             while (!(readl(&uart->utrstat) & 0x2)) {
                       if (serial_err_check(dev_index, 1))
                                return;
             }
    
    // 将c中存放的字符写到发送缓冲区
             writeb(c, &uart->utxh);
             /* If 
    , also do 
     */
             if (c == '
    ')
                       serial_putc('
    ');
    }

    下面是s5p_get_base_uart的实现

    static inline struct s5p_uart *s5p_get_base_uart(int dev_index)
    {
             u32 offset = dev_index * sizeof(struct s5p_uart);
             return (struct s5p_uart *)samsung_get_base_uart(); // 获得uart0控制器的基地址
    }

    include/asm/arch-exynos/cpu.h

    #define SAMSUNG_BASE(device, base)                              
    static inline unsigned int samsung_get_base_##device(void) 
    {                                                                         
                       return S5PV310_##base;                                      
    }
    SAMSUNG_BASE(uart, UART_CONSOLE_BASE)
    #define S5PV310_UART_CONSOLE_BASE (S5PV310_UART_BASE + S5PV310_UART0_OFFSET)

    同理可以分析其他使用向串口读取或者写入数据的函数

  • 相关阅读:
    .net core 获取程序中文件路径
    关于autofac的一些具体的用法
    Ztree节点增加删除修改和Icheck的用法
    C# 多线程之Task(任务
    C#开启异步 线程的四种方式
    c# thread数线程的创建
    图片转二进制 ,
    循环中如果有某一次循环抛出异常了整个循环就停止的解决办法
    JAVA 8 Optional的使用
    react 获取指定表单对象中的对象的值
  • 原文地址:https://www.cnblogs.com/pengdonglin137/p/4320879.html
Copyright © 2011-2022 走看看