1. 程式人生 > >u-boot與Linux核心視訊顯示介面引數配置及傳遞方案

u-boot與Linux核心視訊顯示介面引數配置及傳遞方案

http://blog.chinaunix.net/uid-20543672-id-3244213.html
分類: LINUX2012-06-15 11:48:54
一、一般視訊顯示介面初始化所需要的引數
眾所周知,顯示器顯示的是二維的,處理器將視訊資料通過顯示介面行、地傳送到顯示器,每行中的每bit資料通過pclk(畫素時鐘)同步,每一行通過hsync(行同步時鐘)來告訴顯示器發完一行。當發完了一幀資料,通過vsync(場同步時鐘)告訴顯示器已經發完一幀。這些波形時序可以通過以前我寫過的一篇《VGA視訊訊號詳解》中的示波器的截圖來體會。這些也是寫視訊顯示和採集驅動的基礎知識,你必須瞭解CPU與視訊介面間的是資料格式。
由於早期CRT顯示器在顯示完一行或者一幀時都需要有一個消隱期來給電子束回到下一行起點或影象左上角起點的時間並避免影象的重影,數字視訊的資料格式也繼承了這個特性。但是數字視訊資料可以利用了這段消隱時間傳送其他的輔助資料(例如包含場行同步資料,可以省去場行同步訊號線;如果是多路視訊取樣的晶片,還利用這段空閒的資料區包含一些視訊通道號、行號的資料;如果是電視訊號,可以包含字幕資訊)。正是由於場行消隱期的存在和額外的場行同步時間,真正傳送到顯示器的資料如下圖所示:
在這裡插入圖片描述

大家從圖中可以看出,有效的視訊資料變成二維的時候是處在所有傳送出的視訊資料的中間,而到所有視訊資料四邊的距離,也就是有效視訊資料矩形和所有視訊資料矩形邊框的一段距離就是消隱期。而這四邊的距離以及場行同步時間是因各LCD、顯示器的規格而異的,CPU發出的資料必須和顯示器的指定資料格式匹配,否則顯示的影象就會出現偏離等問題。

對於嵌入式Linux系統來說,初始化視訊顯示裝置時一般需要根據不同的顯示器規格配置上述引數。但是如果將這些引數寫死在程式中,每次更換不同的顯示器,就需要根據這個顯示器的引數重新編譯程式碼,這樣肯定是非常麻煩的。但是現在的u-boot和Linux早就為此寫好了函式介面,讓程式設計師可以在燒入程式後動態的配置這些引數,不用重新編譯燒寫了。下面介紹下uboot與核心共同使用uboot的環境變數ENV來傳遞並獲取引數的方法。

二、u-boot獲取引數的方式
u-boot下本身具備了通過env(環境變數)配置液晶屏引數的介面API,其程式碼在:
drivers/video/videomodes.c

點選(此處)摺疊或開啟

/*
 * (C) Copyright 2004
 * Pierre Aubert, Staubli Faverges , <[email protected]>
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.     See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

/************************************************************************
  為視訊模式獲取引數:
  預設視訊模式可以通過 CONFIG_SYS_DEFAULT_VIDEO_MODE 定義。
  如果沒有定義,預設視訊模式為 0x301。
  引數可以通過環境變數"videomode"設定,
  有兩種不同的方法:
  "videomode=301" - 301 是一個16進位制數,他定義了 VESA 模式。
         以下模式是已經實現的:

     顏色        640x480 800x600 1024x768 1152x864 1280x1024
     --------+---------------------------------------------
     8 bits |    0x301    0x303     0x305     0x161     0x307
     15 bits |    0x310    0x313     0x316     0x162     0x319
     16 bits |    0x311    0x314     0x317     0x163     0x31A
     24 bits |    0x312    0x315     0x318     ?     0x31B
     --------+---------------------------------------------

  "videomode=bootargs"
         - 從bootargs環境變數中解析引數。
         格式為"NAME:VALUE,NAME:VALUE" 等等。
         例如:
         "bootargs=video=ctfb:x:800,y:600,depth:16,pclk:25000"
         以上列表中不包含的引數從預設模式中獲取,
         也就是以下模式之一:
         mode:0 640x480x24
         mode:1 800x600x16
         mode:2 1024x768x8
         mode:3 960x720x24
         mode:4 1152x864x16
         mode:5 1280x1024x8

 如果 "mode" 沒有在引數列表中提供,
 則假定為 mode:0 。
 此方法支援以下引數:
 x     xres = 可見(有效)水平解析度
 y     yres = 可見(有效)垂直解析度
 pclk 每微秒的畫素時鐘個數
 le 從行同步到影象左邊沿的畫素時鐘數
 ri 從行同步到影象右邊沿的畫素時鐘數
 up 從場同步到影象上邊沿的行數
 lo 從場同步到影象下邊沿的行數
 hs 行同步時間長度(畫素時鐘數)
 vs 場同步時間長度(行數)
 sync see FB_SYNC_*
 vmode see FB_VMODE_*
 depth 每個畫素的色深(單位:位)
 存在於bootargs中的所有其他的引數都將被忽略。
 也可以直接在變數"videomode"中直接設定引數,
 或者在其他的引數中(例如"myvideo")設定並設定變數為
 "videomode=myvideo"。

****************************************************************************/
#include <common.h>
#include "videomodes.h"

const struct ctfb_vesa_modes vesa_modes[VESA_MODES_COUNT] = {
    {0x301, RES_MODE_640x480, 8},
    {0x310, RES_MODE_640x480, 15},
    {0x311, RES_MODE_640x480, 16},
    {0x312, RES_MODE_640x480, 24},
    {0x303, RES_MODE_800x600, 8},
    {0x313, RES_MODE_800x600, 15},
    {0x314, RES_MODE_800x600, 16},
    {0x315, RES_MODE_800x600, 24},
    {0x305, RES_MODE_1024x768, 8},
    {0x316, RES_MODE_1024x768, 15},
    {0x317, RES_MODE_1024x768, 16},
    {0x318, RES_MODE_1024x768, 24},
    {0x161, RES_MODE_1152x864, 8},
    {0x162, RES_MODE_1152x864, 15},
    {0x163, RES_MODE_1152x864, 16},
    {0x307, RES_MODE_1280x1024, 8},
    {0x319, RES_MODE_1280x1024, 15},
    {0x31A, RES_MODE_1280x1024, 16},
    {0x31B, RES_MODE_1280x1024, 24},
};
const struct ctfb_res_modes res_mode_init[RES_MODES_COUNT] = {
    /* x     y pixclk le    ri up    lo hs vs s vmode */
    {640, 480, 39721, 40, 24, 32, 11, 96, 2, 0, FB_VMODE_NONINTERLACED},
    {800, 600, 27778, 64, 24, 22, 1, 72, 2, 0, FB_VMODE_NONINTERLACED},
    {1024, 768, 15384, 168, 8, 29, 3, 144, 4, 0, FB_VMODE_NONINTERLACED},
    {960, 720, 13100, 160, 40, 32, 8, 80, 4, 0, FB_VMODE_NONINTERLACED},
    {1152, 864, 12004, 200, 64, 32, 16, 80, 4, 0, FB_VMODE_NONINTERLACED},
    {1280, 1024, 9090, 200, 48, 26, 1, 184, 3, 0, FB_VMODE_NONINTERLACED},
};

/************************************************************************
 * 為視訊模式獲取引數:
 */
/*********************************************************************
 * (*start:引數起始指標,sep:分割符)返回下一個引數的長度
 */
static int
video_get_param_len (char *start, char sep)
{
    int i = 0;
    while ((*start != 0) && (*start != sep)) {
        start++;
        i++;
    }
    return i;
}
/* 視訊引數名的字串對比 */
static int
video_search_param (char *start, char *param)
{
    int len, totallen, i;
    char *p = start;
    len = strlen (param);
    totallen = len + strlen (start);
    for (i = 0; i < totallen; i++) {
        if (strncmp (p++, param, len) == 0)
            return (i);
    }
    return -1;
}

/***************************************************************
 * 通過環境變數獲取引數,Linux核心已經實現
 * 例如:
 * video=ctfb:x:800,xv:1280,y:600,yv:1024,depth:16,mode:0,pclk:25000,
 *     le:56,ri:48,up:26,lo:5,hs:152,vs:2,sync:0,vmode:0,accel:0
 *
 * penv是一個指向環境變數的指標,包含字串或其他環境變數名。
 * 它甚至可以是"bootargs"。
 */

#define GET_OPTION(name,var)                \
    if(strncmp(p,name,strlen(name))==0) {        \
        val_s=p+strlen(name);            \
        var=simple_strtoul(val_s, NULL, 10);    \
    }

int video_get_params (struct ctfb_res_modes *pPar, char *penv)
{
    char *p, *s, *val_s;
    int i = 0, t;
    int bpp;
    int mode;
    /* 優先搜尋包含在環境變數中的字串引數 */
    s = penv;
    if ((p = getenv (s)) != NULL) {
        s = p;
    }
    /* bootargs引數的情況,我們必須從
     * "video=ctfb:"開始。
     */
    i = video_search_param (s, "video=ctfb:");
    if (i >= 0) {
        s += i;
        s += strlen ("video=ctfb:");
    }
    /* 首先搜尋“模式”資訊,作為預設值 */
    p = s;
    t = 0;
    mode = 0;        /* 預設值為mode0 */
    while ((i = video_get_param_len (p, ',')) != 0) {
        GET_OPTION ("mode:", mode)
            p += i;
        if (*p != 0)
            p++;    /* 跳過 ',' */
    }
    if (mode >= RES_MODES_COUNT)
        mode = 0;
    *pPar = res_mode_init[mode];    /* 拷貝預設值 */
    bpp = 24 - ((mode % 3) * 8);
    p = s;            /* 從新開始 */
    while ((i = video_get_param_len (p, ',')) != 0) {
        GET_OPTION ("x:", pPar->xres)
            GET_OPTION ("y:", pPar->yres)
            GET_OPTION ("le:", pPar->left_margin)
            GET_OPTION ("ri:", pPar->right_margin)
            GET_OPTION ("up:", pPar->upper_margin)
            GET_OPTION ("lo:", pPar->lower_margin)
            GET_OPTION ("hs:", pPar->hsync_len)
            GET_OPTION ("vs:", pPar->vsync_len)
            GET_OPTION ("sync:", pPar->sync)
            GET_OPTION ("vmode:", pPar->vmode)
            GET_OPTION ("pclk:", pPar->pixclock)
            GET_OPTION ("depth:", bpp)
            p += i;
        if (*p != 0)
            p++;    /* 跳過 ',' */
    }
    return bpp;
}

重要資料結構:
點選(此處)摺疊或開啟

/******************************************************************
 * 解析結構體
 ******************************************************************/
struct ctfb_res_modes {
    int xres;        /* 可見解析度        */
    int yres;
    /* 時序: 所有值都以畫素時鐘為單位(當然除了畫素時鐘本身) */
    int pixclock;        /* 畫素時鐘(單位:微秒) */
    int left_margin;    /*  從行同步到影象左邊沿的畫素時鐘數   */
    int right_margin;    /* 從行同步到影象右邊沿的畫素時鐘數 */
    int upper_margin;    /* 從場同步到影象上邊沿的行數    */
    int lower_margin;    /* 從場同步到影象下邊沿的行數    */
    int hsync_len;        /* 行同步時間長度(畫素時鐘數)    */
    int vsync_len;        /* 場同步時間長度(行數)    */
    int sync;        /* see FB_SYNC_*        */
    int vmode;        /* see FB_VMODE_*        */
};

/******************************************************************
 * Vesa 模式結構體
 ******************************************************************/
struct ctfb_vesa_modes {
    int vesanr;        /* Vesa 號(在LILO中定義) (VESA Nr + 0x200} */
    int resindex;        /* 解析結構體的索引 */
    int bits_per_pixel;    /* bpp */
};

從這些程式碼中我們可以看出,我們不僅可以通過預定義在videomodes.h中的res_mode_init獲取引數,
也可以通過環境變數中的bootargs獲取視訊引數。當然,通過bootargs獲取引數比較靈活,
這樣只需改寫uboot的環境變數就可以讓uboot重新適應新的顯示器,並且可以將這個引數通過bootargs傳遞給核心cmdline,
核心也可以通過解析cmdline獲取這些引數。
這些介面函式中最重要的就是:

int video_get_params (struct ctfb_res_modes *pPar, char *penv)
pPar:是接收解析好的視訊引數的結構體
penv:是需要解析的字串指標,也可以是包含這個字串的uboot環境變數名。

如果uboot需要實現LCD等的顯示驅動,就可以通過這個API函式解析出需要的引數,
並儲存到一個struct ctfb_res_modes結構體中,在LCD初始化的時候使用。
實現視訊顯示引數的串列埠配置。例如我在S3C6410處理器的U-boot中就實現了引數的獲取。

使用舉例:

在u-boot的ENV引數中定義:

dvo2_config=x:800,y:480,le:40,ri:88,up:13,lo:32,hs:48,vs:3,pclk:30000

在LCD初始化程式碼中可以通過以下程式碼中獲取引數,儲存到一個struct ctfb_res_modes結構體中。

struct ctfb_res_modes temp;
int bpp;
bpp = video_get_params (&temp, "dvo2_config");

這些儲存到struct ctfb_res_modes temp中的資料可以用於初始化LCD控制器等顯示裝置。

三、與核心視訊引數傳遞方式

要將資料傳給Linux核心,一個標準的方法就是使用cmdline。
將 $dvo2_config 放入uboot的bootargs環境變數中,這樣就可以將引數傳給核心解析,格式應該為:

video=dvo2:x:800,y:480,le:40,ri:88,up:13,lo:32,hs:48,vs:3,pclk:30000

如果你需要配置多個顯示器,那麼必須在多新增一個,例如:

video=hdmi:pclk:148500,x:1920,le:40,ri:152,hs:44,y:1080,up:4,lo:36,vs:5

不可以放到同一個“video=”中,目的是方便核心解析,後面可以看出。

所以當你配置uboot的bootargs的時候,可以這樣:

setenv bootargs '...... video=dvo2:$dvo2_config video=hdmi:$hdmi_config'

四、linux通過cmdline獲取引數
Linux核心也為視訊引數的獲取做好了函式,程式設計師可以通過bootloader傳遞過來的cmdline,輕鬆獲得視訊相關的引數(video=),處理函式位於drivers/video/fbmem.c的最後。video_setup函式在系統啟動的時候解析cmdline中的“video=”引數,並將多個通過(video=)設定的引數字串指標分別放到drivers/video/fbmem.c中全域性的一個字串指標陣列中:

static char *video_options[FB_MAX] __read_mostly;

如果你有多個“video=”引數,每個可以包含不同顯器的配置引數。之後在顯示控制器初始化程式碼中,你可以利用fbmem.c中匯出的fb_get_options函式獲取你要處理的那個顯示器的引數video_options[i]。如使你在cmdline中存在以下字串:

video=dvo2:x:800,y:480,le:40,ri:88,up:13,lo:32,hs:48,vs:3,pclk:30000

你就可以使用以下程式碼獲取引數字串的指標。

int ret = 0;
char *dvo2_option;
if ((ret =fb_get_options("dvo2", &dvo2_option)) != 0) {
pr_debug("fb_get_options failed:%d\n", ret);
}

//處理結果為dvo2_option = “x:800,y:480,le:40,ri:88,up:13,lo:32,hs:48,vs:3,pclk:30000”
接下來就是解析這些引數的問題了。由於在現有的核心中我沒有找到相關的解析函式(雖然uboot說核心已經實現),所以我就借用了U-boot的video_get_params函式,修改出一個解析函式:
video_param.rar

void video_get_params (struct fb_videomode *pPar, char *penv)
但是替換第一個引數的結構體為struct fb_videomode,這個結構體是Linux核心獲取視訊引數的標準結構體,和原來uboot中的struct ctfb_res_modes類似。

在通過上面的程式碼獲取了dvo2_option之後,就可以通過以下程式碼解析引數,並儲存在struct fb_videomode中了:

struct fb_videomode video_port_option = {NULL, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1};
video_get_params (&video_port_option, dvo2_option);

在獲取了引數之後,有效資料(非-1)就可以用於初始化LCD控制器等顯示裝置。

但是由於這些顯示引數的使用(無論在uboot還是Linux核心中)都是在顯示裝置的初始化程式碼中的,所以無法實現引數解析的模組化,只能寫儘量通用的函式,放在各初始化程式碼中使用。

五、在Linux下配置視訊引數

在進入了Linux系統之後,如果想要配置這些視訊引數,就必須可以方便的配置uboot的ENV引數。這一點uboot早就幫我們做好了。請閱讀:《Linux下訪問u-boot環境變數簡介》。
如果需要配置和檢視視訊引數,你需要通過類似如下指令即可:

# fw_setenv dvo2_config  "x:800,y:480,le:40,ri:88,up:13,lo:32,hs:48,vs:3,pclk:30000"
# fw_printenv -n dvo2_config