1. 程式人生 > >《Linux驅動》iTop4412開發板LCD驅動 詳細分析 (三)

《Linux驅動》iTop4412開發板LCD驅動 詳細分析 (三)

接下來我們來詳解介紹probe中的函式:

第一個函式: s3cfb_set_lcd_info(fbdev[i]);

1.該函式原始碼如下:

/*該函式在s3cfb_wa101s.c 中*/
/* name should be fixed as 's3cfb_set_lcd_info' */
void s3cfb_set_lcd_info(struct s3cfb_global *ctrl)
{
    s3cfb_setup_lcd(); //由硬體選擇裝置,初始化相應引數
    wa101.init_ldi = NULL;
    ctrl->lcd = &wa101; //讓全域性結構體指向該裝置
}

在介紹 這個函式之前,我們先來看看wa101是什麼

#include "s3cfb.h"
static struct s3cfb_lcd wa101 = {
#if 0//smdk
//  .width  = 1366,
    .width  = 1360,
    .height = 768,
    .bpp    = 24,
    .freq   = 60,

    .timing = {
        .h_fp   = 48,
        .h_bp   = 80,
    ......
}
//該結構體如下:
struct s3cfb_lcd {
      int   width;//裝置寬
int height;//裝置高 int bpp;//裝置的bpp int freq;//重新整理頻度 struct s3cfb_lcd_timing timing; //與硬體時序引數 struct s3cfb_lcd_polarity polarity; void (*init_ldi)(void); void (*deinit_ldi)(void); }; //由此可以看出wa101 就是一個描述lcd硬體裝置的結構體。

好先看 s3cfb_setup_lcd();的作用

void s3cfb_setup_lcd()
{
#if 1
int type = get_lcd_type(); //獲得type來選擇什麼樣的硬體初始化 //printk("************** type = %d\n", type); if(0x0 == type) //9.7 { wa101.width = 1024; wa101.height = 768; wa101.bpp = 24; 。。。。。。。。 } else if(0x1 == type) //7.0 { 。。。。。。。。 } else if(0x2 == type) //4.3 { wa101.width = 480; wa101.height = 272; wa101.bpp = 24; wa101.freq = 60; 。。。。。。。。。。。。。。。 }。。。。。。。。。 #endif }

好來看看type如何獲得:get_lcd_type 在board檔案中

int get_lcd_type()
{
    int value1, value2, type = 0;
    int flags = 0;
    value1 = gpio_get_value(EXYNOS4_GPC0(3));
    value2 = gpio_get_value(EXYNOS4_GPX0(6));
    type = (value1<<1)|value2;
    printk("value1 = %d, value2 = %d, type = 0x%x\n", value1, value2, type);
    return type;
}
EXPORT_SYMBOL(get_lcd_type);

由此可以看出type 由硬體 gpc0(3) gpx0(6) 兩個硬體決定,來我們看看原理圖:
外圍板
這裡寫圖片描述

這裡寫圖片描述

這裡寫圖片描述

這就是撥碼開關自適應屏的原理。這裡我用的 4.3 小屏 type 為0x2

第二個函式: pdata->cfg_gpio(pdev);

/* platform_data*/ 
pdata = to_fb_plat(&pdev->dev); 
if (pdata->cfg_gpio) 
pdata->cfg_gpio(pdev); /初始化io

//初始化io的函式,在device  s3cfb_set_platdata函式中指定的
s3cfb_get_clk_name(npd->clk_name); //獲取時鐘
npd->cfg_gpio = s3cfb_cfg_gpio; //獲取引腳操作函式
//以上是device描述的

 //該函式在setup-fb-s5p.c 中
 void s3cfb_cfg_gpio(struct platform_device *pdev)
{   ......
    s3cfb_gpio_setup_24bpp(EXYNOS4_GPF0(0), 8, S3C_GPIO_SFN(2), S5P_GPIO_DRVSTR_LV4);
    s3cfb_gpio_setup_24bpp(EXYNOS4_GPF1(0), 8, S3C_GPIO_SFN(2), S5P_GPIO_DRVSTR_LV4);
    s3cfb_gpio_setup_24bpp(EXYNOS4_GPF2(0), 8, S3C_GPIO_SFN(2), S5P_GPIO_DRVSTR_LV4);
    s3cfb_gpio_setup_24bpp(EXYNOS4_GPF3(0), 4, S3C_GPIO_SFN(2), S5P_GPIO_DRVSTR_LV4);
    ......
    //設定引腳的函式主要是把GFP0(0-7) GPF1(0-7) GPF2(0-7) GPF3(0-3) 設定成lcd模式: 
    //好原理圖和晶片手冊如下:
}  

這裡寫圖片描述

這裡寫圖片描述

第三個函式

    //該函式也是在device中
       3cfb_get_clk_name(npd->clk_name); //獲取時鐘
        npd->lcd_off = s3cfb_lcd_off;//關閉lcd 裝置
        npd->clk_on = s3cfb_clk_on; // 時鐘開
    //時鐘的名字有第一節就知道了:sclk_fimd  

    if (pdata->clk_on)
            pdata->clk_on(pdev, &fbdev[i]->clock);
 //s3cfb_clk_on  函式在setup-fb-s5p.c 中,函式原型如下:
 //該函式主要使能lcd_clk  fimd_sclk
int s3cfb_clk_on(struct platform_device *pdev, struct clk **s3cfb_clk)
{
    struct clk *sclk = NULL;
    struct clk *mout_mpll = NULL;
    struct clk *lcd_clk = NULL;

    u32 rate = 0;
    int ret = 0;

    lcd_clk = clk_get(&pdev->dev, "lcd");
    if (IS_ERR(lcd_clk)) {
        dev_err(&pdev->dev, "failed to get operation clk for fimd\n");
        goto err_clk0;
    }

    ret = clk_enable(lcd_clk);
    if (ret < 0) {
        dev_err(&pdev->dev, "failed to clk_enable of lcd clk for fimd\n");
        goto err_clk0;
    }
    clk_put(lcd_clk);

    sclk = clk_get(&pdev->dev, "sclk_fimd");
    if (IS_ERR(sclk)) {
        dev_err(&pdev->dev, "failed to get sclk for fimd\n");
        goto err_clk1;
    }

    if (soc_is_exynos4210())
        mout_mpll = clk_get(&pdev->dev, "mout_mpll");
    else
        mout_mpll = clk_get(&pdev->dev, "mout_mpll_user");

    if (IS_ERR(mout_mpll)) {
        dev_err(&pdev->dev, "failed to get mout_mpll for fimd\n");
        goto err_clk2;
    }

    ret = clk_set_parent(sclk, mout_mpll);
    if (ret < 0) {
        dev_err(&pdev->dev, "failed to clk_set_parent for fimd\n");
        goto err_clk2;
    }

    ret = clk_set_rate(sclk, 800000000);
    if (ret < 0) {
        dev_err(&pdev->dev, "failed to clk_set_rate of sclk for fimd\n");
        goto err_clk2;
    }
    dev_dbg(&pdev->dev, "set fimd sclk rate to %d\n", rate);

    clk_put(mout_mpll);

    ret = clk_enable(sclk);
    if (ret < 0) {
        dev_err(&pdev->dev, "failed to clk_enable of sclk for fimd\n");
        goto err_clk2;
    }

    *s3cfb_clk = sclk;

    return 0;

err_clk2:
    clk_put(mout_mpll);
err_clk1:
    clk_put(sclk);
err_clk0:
    clk_put(lcd_clk);

    return -EINVAL;
}

第四個函式

對硬體的初始化,裡面主要是對exynos4412的暫存器設定所有會調到 s3cfb_fimd6x.c 中的暫存器操作,將在下面詳細介紹

int s3cfb_init_global(struct s3cfb_global *fbdev)
{
    fbdev->output = OUTPUT_RGB; //指定了輸出格式
    fbdev->rgb_mode = MODE_RGB_P;//指定了rgb模式

    fbdev->wq_count = 0; //等待佇列技術清零
    init_waitqueue_head(&fbdev->wq);//初始化等到佇列
    mutex_init(&fbdev->lock);//初始化鎖

    s3cfb_set_output(fbdev); //設定輸出格式
    s3cfb_set_display_mode(fbdev);//設定模式
    s3cfb_set_polarity(fbdev);//設定引腳極性
    s3cfb_set_timing(fbdev);//設定時序
    s3cfb_set_lcd_size(fbdev);//設定lcd大小

    return 0;
}

指定了輸出的格式(這裡設定輸出的是RGB資料),並未寫入暫存器( 此功能由s3cfb_set_output來完成 ),支援格式如下:
enum s3cfb_output_t {
OUTPUT_RGB,
OUTPUT_ITU,
OUTPUT_I80LDI0,
OUTPUT_I80LDI1,
OUTPUT_WB_RGB,
OUTPUT_WB_I80LDI0,
OUTPUT_WB_I80LDI1,
};
輸出格式請參考如下手冊:Exynos4412 User Manual (Public) version 1.0

這裡寫圖片描述

RGB模式

enum s3cfb_rgb_mode_t {
    MODE_RGB_P = 0,
    MODE_BGR_P = 1,
    MODE_RGB_S = 2,
    MODE_BGR_S = 3,
};

模式參考資料手冊:
這裡寫圖片描述
這裡寫圖片描述

來看看格式設定函式:s3cfb_set_output(fbdev); //設定輸出格式

int s3cfb_set_output(struct s3cfb_global *ctrl)
{
    u32 cfg;

    cfg = readl(ctrl->regs + S3C_VIDCON0);
    cfg &= ~S3C_VIDCON0_VIDOUT_MASK; 
        //清楚vidcon0暫存器26-28 位資料--也就是上面手冊上的
        //VOUT                              

    if (ctrl->output == OUTPUT_RGB)
        cfg |= S3C_VIDCON0_VIDOUT_RGB; //我們的選擇 
            //巨集定義如下#define S3C_VIDCON0_VIDOUT_RGB           (0 << 26)
    else if (ctrl->output == OUTPUT_ITU)
        cfg |= S3C_VIDCON0_VIDOUT_ITU;
    else if (ctrl->output == OUTPUT_I80LDI0)
        cfg |= S3C_VIDCON0_VIDOUT_I80LDI0;
    else if (ctrl->output == OUTPUT_I80LDI1)
        cfg |= S3C_VIDCON0_VIDOUT_I80LDI1;
    else if (ctrl->output == OUTPUT_WB_RGB)
        cfg |= S3C_VIDCON0_VIDOUT_WB_RGB;
    else if (ctrl->output == OUTPUT_WB_I80LDI0)
        cfg |= S3C_VIDCON0_VIDOUT_WB_I80LDI0;
    else if (ctrl->output == OUTPUT_WB_I80LDI1)
        cfg |= S3C_VIDCON0_VIDOUT_WB_I80LDI1;
    else {
        dev_err(ctrl->dev, "invalid output type: %d\n", ctrl->output);
        return -EINVAL;
    }

    writel(cfg, ctrl->regs + S3C_VIDCON0);//寫入的exynos VIDCON0 暫存器

    cfg = readl(ctrl->regs + S3C_VIDCON2);
    cfg &= ~(S3C_VIDCON2_WB_MASK | S3C_VIDCON2_TVFORMATSEL_MASK | \
                    S3C_VIDCON2_TVFORMATSEL_YUV_MASK); //清楚掩碼

    if (ctrl->output == OUTPUT_RGB)
        cfg |= S3C_VIDCON2_WB_DISABLE; 
        //#define S3C_VIDCON2_WB_DISABLE            (0 << 15)
        //在這裡是相容介面,在這裡無用
    else if (ctrl->output == OUTPUT_ITU)
        cfg |= S3C_VIDCON2_WB_DISABLE;
    else if (ctrl->output == OUTPUT_I80LDI0)
        cfg |= S3C_VIDCON2_WB_DISABLE;
    else if (ctrl->output == OUTPUT_I80LDI1)
        cfg |= S3C_VIDCON2_WB_DISABLE;
    else if (ctrl->output == OUTPUT_WB_RGB)
        cfg |= (S3C_VIDCON2_WB_ENABLE | S3C_VIDCON2_TVFORMATSEL_SW | \
                    S3C_VIDCON2_TVFORMATSEL_YUV444);
    else if (ctrl->output == OUTPUT_WB_I80LDI0)
        cfg |= (S3C_VIDCON2_WB_ENABLE | S3C_VIDCON2_TVFORMATSEL_SW | \
                    S3C_VIDCON2_TVFORMATSEL_YUV444);
    else if (ctrl->output == OUTPUT_WB_I80LDI1)
        cfg |= (S3C_VIDCON2_WB_ENABLE | S3C_VIDCON2_TVFORMATSEL_SW | \
                    S3C_VIDCON2_TVFORMATSEL_YUV444);
    else {
        dev_err(ctrl->dev, "invalid output type: %d\n", ctrl->output);
        return -EINVAL;
    }

    writel(cfg, ctrl->regs + S3C_VIDCON2);
    return 0;
}

2) .s3cfb_set_display_mode(fbdev);//設定模式
函式原型如下:

int s3cfb_set_display_mode(struct s3cfb_global *ctrl)
{
    u32 cfg;

    cfg = readl(ctrl->regs + S3C_VIDCON0);
    cfg &= ~S3C_VIDCON0_PNRMODE_MASK; //對VIDCON0 的17,18 清空,可以看到上的暫存器圖
    cfg |= (ctrl->rgb_mode << S3C_VIDCON0_PNRMODE_SHIFT);
    //前面fbdev->rgb_mode = MODE_RGB_P ;也就是0
    writel(cfg, ctrl->regs + S3C_VIDCON0);
    //所以把VIDCON0 的17 ,18 為都設定成了0,也就rgb並行口,正常模式
    return 0;
}

3) s3cfb_set_polarity(fbdev);//設定引腳極性
原始碼如下:

int s3cfb_set_polarity(struct s3cfb_global *ctrl)
{
    struct s3cfb_lcd_polarity *pol;
    u32 cfg;

    pol = &ctrl->lcd->polarity;
    cfg = 0;

    /* Set VCLK hold scheme */
    cfg &= S3C_VIDCON1_FIXVCLK_MASK;
    cfg |= S3C_VIDCON1_FIXVCLK_VCLK_RUN;

    if (pol->rise_vclk)
        cfg |= S3C_VIDCON1_IVCLK_RISING_EDGE;

    if (pol->inv_hsync)
        cfg |= S3C_VIDCON1_IHSYNC_INVERT;

    if (pol->inv_vsync)
        cfg |= S3C_VIDCON1_IVSYNC_INVERT;

    if (pol->inv_vden)
        cfg |= S3C_VIDCON1_IVDEN_INVERT;

    writel(cfg, ctrl->regs + S3C_VIDCON1);

    return 0;
}

主要是對 vden vsynsc hsync vclk 使能訊號,垂直同步訊號,行同步訊號,vclk觸發方式。
要知道這些功能,首先了解 lcd 顯示原理,和這幾個訊號的做用。
要設定的話 必須根據原理圖 和兩個資料手冊:exynos4412 和 lcd控制晶片
Exynos4412 User Manual (Public) version 1.0
WXCAT43-TG6#001_V1.0
exynos 關聯位 4-vden 5-vsync 6-hsync 7-vclk

這裡寫圖片描述
這裡寫圖片描述

在wa101中 vden vsynsc hsync vclk 都是0 ,這些資料的由來,得分別看exynos 和lcd 的工作時序圖:
exynos4412
這裡寫圖片描述
exynos中 DE 是高電平觸發,clk 觸發方式(需要指定),hsync 是高脈衝觸發 ,vsynsc 是高脈衝觸發

WXCAT43:
這裡寫圖片描述

兩個都看了,lcd 硬體中 DE 是高電平觸發,clk 是下降沿觸發,hsync 是低脈衝觸發 ,vsynsc 是低脈衝觸發

綜合:要驅動lcd裝置:
exynos 要設定成lcd觸發有效的相應模式
de—-高電平觸發–不反轉
clk—下降沿觸發 —-相應位設定成0
hsync—是低脈衝觸發—-反轉
vsyns —是低脈衝觸發—反轉

4 s3cfb_set_timing(fbdev);//設定時序

程式碼原型如下:

int s3cfb_set_timing(struct s3cfb_global *ctrl)
{
    struct s3cfb_lcd_timing *time;
    u32 cfg;

    time = &ctrl->lcd->timing;
    cfg = 0;

    cfg |= S3C_VIDTCON0_VBPDE(time->v_bpe - 1);
    cfg |= S3C_VIDTCON0_VBPD(time->v_bp - 1);
    cfg |= S3C_VIDTCON0_VFPD(time->v_fp - 1);
    cfg |= S3C_VIDTCON0_VSPW(time->v_sw - 1);

    writel(cfg, ctrl->regs + S3C_VIDTCON0);

    cfg = 0;

    cfg |= S3C_VIDTCON1_VFPDE(time->v_fpe - 1);
    cfg |= S3C_VIDTCON1_HBPD(time->h_bp - 1);
    cfg |= S3C_VIDTCON1_HFPD(time->h_fp - 1);
    cfg |= S3C_VIDTCON1_HSPW(time->h_sw - 1);

    writel(cfg, ctrl->regs + S3C_VIDTCON1);

    return 0;
}

這個函式主要供能是設定各種時序和脈衝寬度。
VIDTCON0
這裡寫圖片描述

螢幕手冊如下:
這裡寫圖片描述

               VSPW            -------------(Vertical pulse width);
                VFPD            -----------------(Vertical front porch)
                VBPD            -------------------------(Vertical back porch)
                VBPDE  ------是yuv才用的著,這裡可以不用設定
                行的引數類似這裡不做介紹。由於該值有一定的範圍,一般我們是按推薦typ  設定

這裡可以看到,在itop4412提供的原始碼裡跟標準的還有是有一些不同。只要在範圍內就好了。

5 。s3cfb_set_lcd_size(fbdev);//設定lcd大小

函式原型如下:

int s3cfb_set_lcd_size(struct s3cfb_global *ctrl)
{
    u32 cfg = 0;

#ifdef CONFIG_FB_S5P_WA101S
    cfg |= S3C_VIDTCON2_HOZVAL(ctrl->lcd->width - 1);
#else
    cfg |= S3C_VIDTCON2_HOZVAL(ctrl->lcd->width - 1);
#endif

    cfg |= S3C_VIDTCON2_LINEVAL(ctrl->lcd->height - 1);

    writel(cfg, ctrl->regs + S3C_VIDTCON2);

    return 0;
}

這裡寫圖片描述
這裡寫圖片描述

至此: global init 到此結束:
這裡寫圖片描述

該函式完成主要設定:
output—–指定輸出格式為RGB
display —-指定輸出的rgb格式為normal ,且是並行輸出
polartiy —-設定觸發極性
timing —– 設定時間引數
lcd_size —–設定大小引數