tiny4412 串口驱动分析一 --- u-boot中的串口驱动

2023-06-25  

开发板: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

74decb4129124345d22ee4db3207450b_jrEDJltAAAAAElFTkSuQmCC.png

// 设置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 n, also do r */

         if (c == 'n')

                   serial_putc('r');

}


下面是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控制器的基地址

文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。