[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>;
};
};