1. 程式人生 > >[uboot] (番外篇)uboot串列埠&console&stdio裝置工作流程

[uboot] (番外篇)uboot串列埠&console&stdio裝置工作流程

轉自 https://blog.csdn.net/ooonebook/article/details/53313112

 

一、uboot serial框架

1、serial模組驅動模型
在《[uboot] (番外篇)uboot 驅動模型》中我們已經介紹uboot的驅動模型,uboot DM。 
在uboot中,serial模組也使用了對應的驅動模型。 
其框架圖如下: 

                   è¿éåå¾çæè¿°
 
我們在《[uboot] (番外篇)uboot 驅動模型》已經說明過了,這裡再簡單解釋一下:

serial core為serial模組向外提供介面,但是也是在serial-uclass中實現
serial uclass是serial裝置的集合抽象,為serial裝置提供統一的操作介面,serial uclass driver則是其對應的驅動
serial udevice是serial裝置的具體抽象,代表了一個serial裝置物件,serial driver則是其對應的驅動


2、serial DM實現
在《[uboot] (番外篇)uboot 驅動模型》中,我們已經知道了uclass和udevice由uboot動態生成,但是我們需要在dtsi中新增相應的裝置資訊,以及新增相應的uclass driver和udevice driver. 
以tiny210為例,如下:

dts中的裝置資訊

/{
    aliases {
        console = "/[email protected]";
    };

    [email protected] {
        compatible = "samsung,exynos4210-uart";
        reg = <0xe2900000 0x100>;
        interrupts = <0 51 0>;
        id = <0>;
    };
};


這裡,有些人或許會有疑問,在relocate之前就需要列印串列埠資料,就需要使用到這個節點了,為什麼不需要加上“u-boot,dm-pre-reloc”屬性? 
確實是可以加,但是不加也沒事,因為console中已經指定了串列埠節點的路徑,在relocate之前的串列埠初始化過程中,在裝置連結串列上找不到對應串列埠裝置的話,會強制繫結console指定的串列埠節點的裝置。

uclass driver 
driver/serial/serial-uclass.c

UCLASS_DRIVER(serial) = {
    .id     = UCLASS_SERIAL,
    .name       = "serial",
    .flags      = DM_UC_FLAG_SEQ_ALIAS,
    .post_probe = serial_post_probe,
    .pre_remove = serial_pre_remove,
    .per_device_auto_alloc_size = sizeof(struct serial_dev_priv),
};


udevice driver 
driver/serial/serial_s5p.c

static const struct udevice_id s5p_serial_ids[] = {
    { .compatible = "samsung,exynos4210-uart" },  // 必須和dts匹配
    { }
};

// s5p serial driver 提供瞭如下操作集 
static const struct dm_serial_ops s5p_serial_ops = {
    .putc = s5p_serial_putc,
    .pending = s5p_serial_pending,
    .getc = s5p_serial_getc,
    .setbrg = s5p_serial_setbrg,
};

U_BOOT_DRIVER(serial_s5p) = {
    .name   = "serial_s5p",
    .id = UCLASS_SERIAL,
    .of_match = s5p_serial_ids,
    .ofdata_to_platdata = s5p_serial_ofdata_to_platdata,
    .platdata_auto_alloc_size = sizeof(struct s5p_serial_platdata),
    .probe = s5p_serial_probe,
    .ops    = &s5p_serial_ops,
    .flags = DM_FLAG_PRE_RELOC,
};
1


注意,serial uclass driver的id和serial driver的id是一致的,都是UCLASS_SERIAL。 
具體也在《[uboot] (番外篇)uboot 驅動模型》分析過了,這裡也不多說了。 
關於tiny210的串列埠驅動的實現,在第四節中再學習。

3、serial core提供的介面
serial core會利用serial uclass找到對應的裝置及其操作集,向上層提供介面。 
如下: 
driver/serial/serial-uclass.c

void serial_putc(char ch) 
往gd->cur_serial_dev指定的串列埠裝置輸出一個字元。

void serial_putc(char ch)
{
    if (gd->cur_serial_dev)
        _serial_putc(gd->cur_serial_dev, ch); // 將gd->cur_serial_dev指定的udevice作為引數傳入
}

static void _serial_putc(struct udevice *dev, char ch)
{
    struct dm_serial_ops *ops = serial_get_ops(dev); // 獲取對應udevice的操作集(driver->ops)
    int err;

    if (ch == '\n')
        _serial_putc(dev, '\r');

    do {
        err = ops->putc(dev, ch); // 呼叫udevice的操作集(driver->ops)中的putc函式,這裡真正向硬體串列埠裝置進行輸出。
    } while (err == -EAGAIN);
}


void serial_puts(const char *str) 
往gd->cur_serial_dev指定的串列埠裝置輸出字串。其方法與serial_putc類似,自己參考程式碼。

int serial_getc(void) 
從gd->cur_serial_dev指定的串列埠裝置獲取一個字元。其方法與serial_putc類似,自己參考程式碼。

int serial_tstc(void) 
判斷gd->cur_serial_dev指定的串列埠裝置是否有資料在等待。其方法與serial_putc類似,自己參考程式碼。

void serial_setbrg(void) 
設定gd->cur_serial_dev指定的串列埠裝置的波特率。其方法與serial_putc類似,自己參考程式碼。

void serial_initialize(void) & int serial_init(void) 
串列埠初始化。這裡重點說明

void serial_initialize(void)
{
    serial_init();
}
// 可以看出serial_initialize和serial_init是一樣的。

int serial_init(void)
{
    serial_find_console_or_panic(); //呼叫serial_find_console_or_panic來查詢console指定的裝置
    gd->flags |= GD_FLG_SERIAL_READY;

    return 0;
}

static void serial_find_console_or_panic(void)
{
    const void *blob = gd->fdt_blob;
    struct udevice *dev;
    int node;

    if (CONFIG_IS_ENABLED(OF_CONTROL) && blob) {
        node = fdtdec_get_chosen_node(blob, "stdout-path"); // 嘗試在chosen節點中獲取"stdout-path"節點
        if (node < 0) {
            const char *str, *p, *name;

            str = fdtdec_get_chosen_prop(blob, "stdout-path"); // 嘗試在chosen節點中獲取"stdout-path"屬性
            if (str) {
                p = strchr(str, ':');
                name = fdt_get_alias_namelen(blob, str,
                        p ? p - str : strlen(str));
                if (name)
                    node = fdt_path_offset(blob, name);
            }
        }
        if (node < 0)
            node = fdt_path_offset(blob, "console"); // 上述都找不到的話,最終去獲取"console"指定的節點路徑對應的偏移node
        if (!uclass_get_device_by_of_offset(UCLASS_SERIAL, node,
                            &dev)) { 
                // 根據節點node從serial uclass的裝置連結串列中獲取對應的udevice裝置,在獲取之後,會進行probe。
                // 注意,tiny210的serial節點並沒有設定“u-boot,dm-pre-reloc”屬性,所以在relocate之前dm_init中並不會去解析這個節點,
                // 所以在relocate之前,這裡會返回失敗,也就是找不到對應的udevice
            gd->cur_serial_dev = dev;
            return;
        }

        /*
         * If the console is not marked to be bound before relocation,
         * bind it anyway.
         */
                // 如果console的串列埠裝置節點在relocate之前沒有被繫結,那麼這裡就會強制呼叫lists_bind_fdt和device_probe進行繫結和probe
                // 這裡就是為什麼可以不需要加“u-boot,dm-pre-reloc”屬性的原因。
        if (node > 0 &&
            !lists_bind_fdt(gd->dm_root, blob, node, &dev)) {
            if (!device_probe(dev)) {
                gd->cur_serial_dev = dev;
                return;
            }
        }
    }
}


在串列埠初始化完成之後,如果指定了console並且對應serial節點正常,那麼serial_putc等等API都可以正常使用了。 
同時,gd->flags中的GD_FLG_SERIAL_READY的標誌也被設定了 
後續就可以通過serial_putc、serial_puts、serial_getc向串列埠輸出資料或者獲取資料了。

4、預設波特率的設定
有兩個地方可以設定預設波特率,並且儲存到gd->baudrate中

環境變數”baudrate”
如果環境變數”baudrate”不存在,則使用CONFIG_BAUDRATE巨集
具體參考程式碼: 
common/board_f.c

static int init_baud_rate(void)
{
    gd->baudrate = getenv_ulong("baudrate", 10, CONFIG_BAUDRATE);
// 從環境變數中獲取"baudrate"的值,儲存到gd->baudrate中,如果如果環境變數"baudrate"不存在,則使用CONFIG_BAUDRATE巨集的值作為預設波特率
    return 0;
}

在include/configs/tiny210.h中配置預設波特率如下:

#define CONFIG_BAUDRATE         115200

5、serial初始化時機
在relocate之前的board_f中和relocate之後的board_r各會執行一次串列埠初始操作

在relocate之前的serial初始化 
common/board_f.c

static init_fnc_t init_sequence_f[] = {
#ifdef CONFIG_OF_CONTROL
    fdtdec_setup,
#endif
    initf_dm,                    // DM的初始化,解析帶有“u-boot,dm-pre-reloc”屬性的節點
    init_baud_rate,     // 設定預設波特率
    serial_init,        // 串列埠初始化
}

在relocate之後的serial初始化 
common/board_r.c

init_fnc_t init_sequence_r[] = {
    initr_dm,            // DM的初始化,解析所有裝置節點
    initr_serial,        // 又一次進行串列埠初始化
}

/* initr_serial實現如下 */
static int initr_serial(void)
{
    serial_initialize();
    return 0;
}

 

二、debug、printf的輸出流程


我們知道,在uboot中,可以通過debug和printf兩個函式來進行串列埠log輸出。

1、簡單流程圖

                   è¿éåå¾çæè¿°
簡單說明如下

debug會轉化為printf
printf會呼叫到console的puts介面進行輸出
console的puts的輸出主要分成如下三個階段 
在serial初始化之前,會儲存到pre console buffer中
在serial初始化之後,console完全初始化(console_init_r)之前,會直接呼叫serial的serial_puts介面進行輸出
在serial初始化之後,console完全初始化(console_init_r)之後,會使用標準輸入輸出裝置進行輸出,但最終也是會呼叫到串列埠裝置進行輸出


2、debug()流程
要使能debug()的輸出功能,需要先開啟DEBUG巨集 
include/common.h

++#define DEBUG
#ifdef DEBUG
#define _DEBUG 1
#else

debug()使用方法如下:

    debug("U-Boot code: %08lX -> %08lX  BSS: -> %08lX\n",
        text_base, bss_start, bss_end);

debug()定義如下: 
include/common.h

#define debug_cond(cond, fmt, args...)          \
    do {                        \
        if (cond)               \   // 會根據_DEBUG的值來判斷是否要進行輸出
            printf(pr_fmt(fmt), ##args);    \  //呼叫printf進行輸出    
    } while (0)

#define debug(fmt, args...)         \
    debug_cond(_DEBUG, fmt, ##args) 

可以觀察到最終也是呼叫到printf進行輸出。

3、printf()流程
printf程式碼流程如下: 

lib/vsprintf.c

int printf(const char *fmt, ...)
{
    va_list args;
    uint i;
    char printbuffer[CONFIG_SYS_PBSIZE];

    va_start(args, fmt);

    /*
     * For this to work, printbuffer must be larger than
     * anything we ever want to print.
     */
    i = vscnprintf(printbuffer, sizeof(printbuffer), fmt, args); // 對引數進行格式化
    va_end(args);

    /* Print the string */
    puts(printbuffer); // 呼叫puts進行輸出
    return i;
}

// common/console.c
void puts(const char *s)
{
 // 在serial初始化之前,會呼叫pre_console_puts將字串儲存到pre console buffer中
 // 具體參考下面第4小節 
    if (!gd->have_console)    
        return pre_console_puts(s);

    if (gd->flags & GD_FLG_DEVINIT) {
// 在serial初始化之後,console完全初始化(console_init_r)之後,
// 會使用標準輸入輸出裝置進行輸出,也就是呼叫fputs(stdout, s)進行輸出,但最終也是會呼叫到串列埠裝置進行輸出
// 具體參考下面第5小節
        /* Send to the standard output */
        fputs(stdout, s);
    } else {
        /* Send directly to the handler */
//在serial初始化之後,console完全初始化(console_init_r)之前,會直接呼叫serial的serial_puts介面進行輸出
// serial_puts我們已經在前面說過了
        pre_console_puts(s);
        serial_puts(s);
    }
}

幾個重點標誌位說明一下:

gd->have_console 
用於判斷是否有console,在relocate之前、serial串列埠初始化之後的console_init_f進行設定, 
這裡可以簡單的理解為作為console的串列埠初始化的標識

gd->flags & GD_FLG_DEVINIT 
標準輸入輸出裝置初始化完成的標識。 
在console_init_r中設定,當這個標識被設定,就表示console已經完全初始化了。

4、pre console buffer的使用
根據上面第2小節說明,在serial初始化之前,發到串列埠的資料會通過pre_console_puts儲存到pre console buffer中。 
當串列埠初始化之後,在console_init_f中會對pre console buffer裡面的內容進行輸出

需要開啟的巨集 
以tiny210為例 
include/configs/tiny210.h

#define CONFIG_PRE_CONSOLE_BUFFER // 用於使能pre console buffer的功能
#define CONFIG_PRE_CON_BUF_SZ           4096 // buffer的size
#define CONFIG_PRE_CON_BUF_ADDR         0x30000000 // 需要從記憶體中指定一塊區域給buffer用,必須小心的判斷哪部分割槽域在relocate之前是不會被使用的

儲存流程 
在puts中通過呼叫pre_console_puts將字串儲存到pre console buffer中,程式碼流程如下: 

common/console.c
#define CIRC_BUF_IDX(idx) ((idx) % (unsigned long)CONFIG_PRE_CON_BUF_SZ)
static void pre_console_putc(const char c)
{
    char *buffer = (char *)CONFIG_PRE_CON_BUF_ADDR; // 設定buffer地址為CONFIG_PRE_CON_BUF_ADDR
    buffer[CIRC_BUF_IDX(gd->precon_buf_idx++)] = c; // 以gd->precon_buf_idx為當前buffer的指標,寫入對應位置上
}

static void pre_console_puts(const char *s)
{
    while (*s)
        pre_console_putc(*s++);
}

輸出流程 
uboot會在relocate之前的串列埠初始化之後,通過呼叫console_init_f對pre console buffer的內容進行輸出。程式碼如下: 

common/console.c
int console_init_f(void)
{
    gd->have_console = 1; // 開啟這個標誌之後,printf的輸出資訊就不會在儲存到pre console buffer中了。
    print_pre_console_buffer(PRE_CONSOLE_FLUSHPOINT1_SERIAL); //  呼叫print_pre_console_buffer對pre console buffer的資料進行輸出。
    return 0;
}

static void print_pre_console_buffer(int flushpoint)
{
    unsigned long in = 0, out = 0;
    char *buf_in = (char *)CONFIG_PRE_CON_BUF_ADDR;
    char buf_out[CONFIG_PRE_CON_BUF_SZ + 1];

    if (gd->precon_buf_idx > CONFIG_PRE_CON_BUF_SZ)
        in = gd->precon_buf_idx - CONFIG_PRE_CON_BUF_SZ;

    while (in < gd->precon_buf_idx)
        buf_out[out++] = buf_in[CIRC_BUF_IDX(in++)];

    buf_out[out] = 0; // 將pre console buffer資料複製到buf_out中

    switch (flushpoint) {
    case PRE_CONSOLE_FLUSHPOINT1_SERIAL:
        puts(buf_out); // 重新呼叫puts,此時的puts不會再儲存到pre console buffer,而是會走serial_puts進行輸出。
        break;
    case PRE_CONSOLE_FLUSHPOINT2_EVERYTHING_BUT_SERIAL:
        console_puts_noserial(stdout, buf_out);
        break;
    }
}

 

 

三、標準輸入輸出(stdio)


前面也說了,當console完全初始化之後,會生成標準輸入輸出裝置。 
後續會在puts中呼叫fputs(stdout, s)對串列埠資料進行輸出。

1、標準輸入輸出裝置的結構體

include/stdio_dev.h

/* Device information */
struct stdio_dev {
    int flags;          /* Device flags: input/output/system    */
    int ext;            /* Supported extensions         */
    char    name[32];       /* Device name              */ //裝置名稱

/* GENERAL functions */
    int (*start)(struct stdio_dev *dev);    /* To start the device */ // 啟動這個裝置的方法
    int (*stop)(struct stdio_dev *dev); /* To stop the device */ // 停止這個裝置的方法

/* OUTPUT functions */
    void (*putc)(struct stdio_dev *dev, const char c);  /* To put a char */ // 輸出一個字元
    void (*puts)(struct stdio_dev *dev, const char *s);  /* To put a string (accelerator) */ // 輸出字串

/* INPUT functions */
    int (*tstc)(struct stdio_dev *dev);/* To test if a char is ready... */ // 測試是否有資料可以獲取
    int (*getc)(struct stdio_dev *dev); /* To get that char */ // 獲取一個字元

/* Other functions */
    void *priv;         /* Private extensions           */
    struct list_head list;
};


可以看到包含了標準輸入輸出裝置的一些操作集。

2、標準輸入輸出裝置的儲存位置
uboot建立了一個虛擬stdio裝置devs作為所有stdio裝置的連結串列頭,所有其他stdio裝置都會掛載到devs.list連結串列上。 
並且提供瞭如下操作連結串列的方法: 

common/stdio.c

static struct stdio_dev devs;
struct list_head* stdio_get_list(void); // 獲取stdio連結串列
struct stdio_dev* stdio_get_by_name(const char *name); // 通過name獲取stdio連結串列中的stdio裝置
struct stdio_dev* stdio_clone(struct stdio_dev *dev); // 複製一個stdio裝置
int stdio_register_dev(struct stdio_dev *dev, struct stdio_dev **devp); // 註冊一個stdio裝置,會連線到stdio連結串列中
int stdio_register(struct stdio_dev *dev); // 呼叫stdio_register_dev
int stdio_init_tables(void); // 初始化stdio連結串列

並且預設提供了stdin、stdout、stderr三個標準輸入輸出裝置

struct stdio_dev *stdio_devices[] = { NULL, NULL, NULL };
char *stdio_names[MAX_FILES] = { "stdin", "stdout", "stderr" };

3、stdio模組初始化
stdio模組的初始化是在relocate之後的board_r中實現的

init_fnc_t init_sequence_r[] = {
    stdio_init_tables,
    stdio_add_devices,
    console_init_r, 
}

stdio_init_tables用於stdio連結串列的初始化 
stdio_add_devices用於建立一些標準輸入輸出裝置。 
console_init_r用於將console和標準輸入輸出裝置關聯

4、stdio_add_devices

int stdio_add_devices(void)
{
    drv_system_init ();  // 在drv_system_init 中建立一些必須的標準輸入輸出裝置,比如串列埠的標準輸入輸出裝置。
    return 0;
}

static void drv_system_init (void)
{
    struct stdio_dev dev;

    memset (&dev, 0, sizeof (dev));
 /* 以下建立串列埠的標準輸入輸出裝置,設定其方法,並註冊到stdio連結串列中 */
    strcpy (dev.name, "serial");
    dev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT;
    dev.putc = stdio_serial_putc; //可以觀察到這幾個方法都是直接呼叫serial的介面進行輸出
    dev.puts = stdio_serial_puts;
    dev.getc = stdio_serial_getc;
    dev.tstc = stdio_serial_tstc;
    stdio_register (&dev);
}
static void stdio_serial_putc(struct stdio_dev *dev, const char c)
{
    serial_putc(c);
}

經過上述步驟之後就註冊了serial的標準輸入輸出裝置了。

5、console_init_r
console_init_r主要用將console和標準輸入輸出裝置關聯。 
其會獲取stdio連結串列中的第一個滿足條件的標準輸入輸出裝置,進行關聯。 
程式碼如下

int console_init_r(void)
{
    struct stdio_dev *inputdev = NULL, *outputdev = NULL;
    int i;
    struct list_head *list = stdio_get_list(); // 獲取stdio連結串列
    struct list_head *pos;
    struct stdio_dev *dev;

    /* Scan devices looking for input and output devices */
    list_for_each(pos, list) { //遍歷stdio連結串列
        dev = list_entry(pos, struct stdio_dev, list);

        if ((dev->flags & DEV_FLAGS_INPUT) && (inputdev == NULL)) {
            inputdev = dev; // 設定輸入裝置為第一個搜尋到的支援DEV_FLAGS_INPUT的stdio裝置
        }
        if ((dev->flags & DEV_FLAGS_OUTPUT) && (outputdev == NULL)) {
            outputdev = dev;// 設定輸出裝置為第一個搜尋到的支援DEV_FLAGS_OUTPUT的stdio裝置
        }
        if(inputdev && outputdev)
            break;
    }

// 此時,我們的stdio連結串列上只有serial一個裝置,並且既支援DEV_FLAGS_INPUT又支援DEV_FLAGS_OUTPUT,
// 所以inputdev = serail_stdio_dev, output = serail_stdio_dev

    /* Initializes output console first */
    if (outputdev != NULL) {
        console_setfile(stdout, outputdev); //設定系統的標準輸出裝置stdio_devices[0]為outputdev,對於tiny210來說就是serail_stdio_dev
        console_setfile(stderr, outputdev); //設定系統的標準錯誤裝置stdio_devices[2]為outputdev,對於tiny210來說就是serail_stdio_dev
    }

    /* Initializes input console */
    if (inputdev != NULL) {
        console_setfile(stdin, inputdev);//設定系統的標準輸入裝置stdio_devices[1]為inputdev,對於tiny210來說就是serail_stdio_dev
    }

#ifndef CONFIG_SYS_CONSOLE_INFO_QUIET
    stdio_print_current_devices();
#endif /* CONFIG_SYS_CONSOLE_INFO_QUIET */
        // 打印出三個系統標準輸入輸出裝置的名稱,在log中可以看到如下:
        // In:    [email protected]                        Out:   [email protected]                     Err:   [email protected]

    /* Setting environment variables */
    for (i = 0; i < 3; i++) {
        setenv(stdio_names[i], stdio_devices[i]->name);
    }
        // 設定環境變數stdio_names

    gd->flags |= GD_FLG_DEVINIT;    /* device initialization completed */
        // 設定GD_FLG_DEVINIT標識,表示當前系統標準輸入輸出裝置已經初始化完成了,

    print_pre_console_buffer(PRE_CONSOLE_FLUSHPOINT2_EVERYTHING_BUT_SERIAL);
    return 0;
}

後續就可以通過fputs(stdout, s)從串列埠輸出字元,或者通過fgets(string, sizeof(string), stdin);從串列埠中獲取字元。

6、fputs流程
fputs也是有console實現,較為簡單,簡單地看一下程式碼 
fputs(stdout, s)從串列埠輸出的流程如下:

void fputs(int file, const char *s)
{
    if (file < MAX_FILES)
        console_puts(file, s);
}

static inline void console_puts(int file, const char *s)
{
    stdio_devices[file]->puts(stdio_devices[file], s); 
       // 在console_init_r中stdio_devices已經和serial的標準輸入輸出綁定了
        // 所以這裡會呼叫到stdio_serial_puts函式
        // 最終就呼叫到serial_puts從串列埠輸出資料了
}

到這裡,框架層的串列埠輸出流程就分析完成了,也就是板級無關的部分。 
後面我們分析一下和板級相關的串列埠驅動的部分。

 

 

四、s5pv210 seriial driver分析


和tiny210(s5pv210)相關性較強,這裡僅僅說明一下思路。 
程式碼具體參考driver/serial/serial_s5p.c

1、先定義驅動中需要使用到的私有資料

/* Information about a serial port */
struct s5p_serial_platdata {
    struct s5p_uart *reg;  /* address of registers in physical memory */ // 和uart相關的暫存器的物理基地址
    u8 port_id;     /* uart port number */ // 串列埠id號
};

2、定義DM模型中的driver結構體

U_BOOT_DRIVER(serial_s5p) = {
    .name   = "serial_s5p",
    .id = UCLASS_SERIAL,  // 這個是固定的!!!
    .of_match = s5p_serial_ids,
    .ofdata_to_platdata = s5p_serial_ofdata_to_platdata,
    .platdata_auto_alloc_size = sizeof(struct s5p_serial_platdata), // 私有資料的大小
    .probe = s5p_serial_probe,
    .ops    = &s5p_serial_ops,
    .flags = DM_FLAG_PRE_RELOC,
};

3、設定match_id:s5p_serial_ids

static const struct udevice_id s5p_serial_ids[] = {
    { .compatible = "samsung,exynos4210-uart" },
    { }
};

4、設定dts節點的解析函式:s5p_serial_ofdata_to_platdata

static int s5p_serial_ofdata_to_platdata(struct udevice *dev)
{
    struct s5p_serial_platdata *plat = dev->platdata;
    fdt_addr_t addr;

    addr = dev_get_addr(dev); // 獲取dts節點中的暫存器地址

    plat->reg = (struct s5p_uart *)addr; // 將地址儲存在plat->reg中
    plat->port_id = fdtdec_get_int(gd->fdt_blob, dev->of_offset, "id", -1); // 獲取dtsi中的id屬性,並且存放到plat->port_id中

    return 0;
}

5、實現probe函式:s5p_serial_probe
probe就相當於是啟用這個串列埠,就需要在probe中對這個串列埠進行初始話

static int s5p_serial_probe(struct udevice *dev)
{
    struct s5p_serial_platdata *plat = dev->platdata;
    struct s5p_uart *const uart = plat->reg;
    s5p_serial_init(uart);
    return 0;
}

static void __maybe_unused s5p_serial_init(struct s5p_uart *uart)
{
    /* enable FIFOs, auto clear Rx FIFO */
    writel(0x3, &uart->ufcon);
    writel(0, &uart->umcon);
    /* 8N1 */
    writel(0x3, &uart->ulcon);
    /* No interrupts, no DMA, pure polling */
    writel(0x245, &uart->ucon);
}

6、實現操作集:s5p_serial_ops
注意,因為這個driver對應的udevice所屬的serial uclass型別,因此其操作集的型別必須定義為dm_serial_ops型別。

static const struct dm_serial_ops s5p_serial_ops = {
    .putc = s5p_serial_putc,
    .pending = s5p_serial_pending,
    .getc = s5p_serial_getc,
    .setbrg = s5p_serial_setbrg,
};

具體實現這裡不多說了。

7、在dtsi中新增裝置資訊

/{
    aliases {
        console = "/[email protected]";
    };

    [email protected] {
        compatible = "samsung,exynos4210-uart";
        reg = <0xe2900000 0x100>;
        interrupts = <0 51 0>;
        id = <0>;
    };
};