S3C2440上LCD驅動(FramBuffer)實例開發(fā)詳解(一)
嵌入式Linux之我行,主要講述和總結了本人在學習嵌入式linux中的每個步驟。一為總結經驗,二希望能給想入門嵌入式Linux的朋友提供方便。如有錯誤之處,謝請指正。
一、開發(fā)環(huán)境
編譯器:arm-linux-gcc-4.3.2
二、背景知識
1. LCD工作的硬件需求:
要使一塊LCD正常的顯示文字或圖像,不僅需要LCD驅動器,而且還需要相應的LCD控制器。在通常情況下,生產廠商把LCD驅動器會以COF/COG的形式與LCD玻璃基板制作在一起,而LCD控制器則是由外部的電路來實現,現在很多的MCU內部都集成了LCD控制器,如S3C2410/2440等。通過LCD控制器就可以產生LCD驅動器所需要的控制信號來控制STN/TFT屏了。
2. S3C2440內部LCD控制器結構圖:
我們根據數據手冊來描述一下這個集成在S3C2440內部的LCD控制器:
a:LCD控制器由REGBANK、LCDCDMA、TIMEGEN、VIDPRCS寄存器組成;
b:REGBANK由17個可編程的寄存器組和一塊256*16的調色板內存組成,它們用來配置LCD控制器的;
c:LCDCDMA是一個專用的DMA,它能自動地把在偵內存中的視頻數據傳送到LCD驅動器,通過使用這個DMA通道,視頻數據在不需要CPU的干預的情況下顯示在LCD屏上;
d:VIDPRCS接收來自LCDCDMA的數據,將數據轉換為合適的數據格式,比如說4/8位單掃,4位雙掃顯示模式,然后通過數據端口VD[23:0]傳送視頻數據到LCD驅動器;
e:TIMEGEN由可編程的邏輯組成,他生成LCD驅動器需要的控制信號,比如VSYNC、HSYNC、VCLK和LEND等等,而這些控制信號又與REGBANK寄存器組中的LCDCON1/2/3/4/5的配置密切相關,通過不同的配置,TIMEGEN就能產生這些信號的不同形態(tài),從而支持不同的LCD驅動器(即不同的STN/TFT屏)。
3. 常見TFT屏工作時序分析:
LCD提供的外部接口信號:
VSYNC/VFRAME/STV:垂直同步信號(TFT)/幀同步信號(STN)/SEC TFT信號;
HSYNC/VLINE/CPV:水平同步信號(TFT)/行同步脈沖信號(STN)/SEC TFT信號;
VCLK/LCD_HCLK:象素時鐘信號(TFT/STN)/SEC TFT信號;
VD[23:0]:LCD像素數據輸出端口(TFT/STN/SEC TFT);
VDEN/VM/TP:數據使能信號(TFT)/LCD驅動交流偏置信號(STN)/SEC TFT 信號;
LEND/STH:行結束信號(TFT)/SEC TFT信號;
LCD_LPCOE:SEC TFT OE信號;
LCD_LPCREV:SEC TFT REV信號;
LCD_LPCREVB:SEC TFT REVB信號。
所有顯示器顯示圖像的原理都是從上到下,從左到右的。這是什么意思呢?這么說吧,一副圖像可以看做是一個矩形,由很多排列整齊的點一行一行組成,這些點稱之為像素。那么這幅圖在LCD上的顯示原理就是:
A:顯示指針從矩形左上角的第一行第一個點開始,一個點一個點的在LCD上顯示,在上面的時序圖上用時間線表示就為VCLK,我們稱之為像素時鐘信號;
B:當顯示指針一直顯示到矩形的右邊就結束這一行,那么這一行的動作在上面的時序圖中就稱之為1 Line;
C:接下來顯示指針又回到矩形的左邊從第二行開始顯示,注意,顯示指針在從第一行的右邊回到第二行的左邊是需要一定的時間的,我們稱之為行切換;
D:如此類推,顯示指針就這樣一行一行的顯示至矩形的右下角才把一副圖顯示完成。因此,這一行一行的顯示在時間線上看,就是時序圖上的HSYNC;
E:然而,LCD的顯示并不是對一副圖像快速的顯示一下,為了持續(xù)和穩(wěn)定的在LCD上顯示,就需要切換到另一幅圖上(另一幅圖可以和上一副圖一樣或者不一樣,目的只是為了將圖像持續(xù)的顯示在LCD上)。那么這一副一副的圖像就稱之為幀,在時序圖上就表示為1 Frame,因此從時序圖上可以看出1 Line只是1 Frame中的一行;
F:同樣的,在幀與幀切換之間也是需要一定的時間的,我們稱之為幀切換,那么LCD整個顯示的過程在時間線上看,就可表示為時序圖上的VSYNC。
上面時序圖上各時鐘延時參數的含義如下:(這些參數的值,LCD產生廠商會提供相應的數據手冊)
VBPD(vertical back porch):表示在一幀圖像開始時,垂直同步信號以后的無效的行數,對應驅動中的upper_margin;
VFBD(vertical front porch):表示在一幀圖像結束后,垂直同步信號以前的無效的行數,對應驅動中的lower_margin;
VSPW(vertical sync pulse width):表示垂直同步脈沖的寬度,用行數計算,對應驅動中的vsync_len;
HBPD(horizontal back porch):表示從水平同步信號開始到一行的有效數據開始之間的VCLK的個數,對應驅動中的left_margin;
HFPD(horizontal front porth):表示一行的有效數據結束到下一個水平同步信號開始之間的VCLK的個數,對應驅動中的right_margin;
HSPW(horizontal sync pulse width):表示水平同步信號的寬度,用VCLK計算,對應驅動中的hsync_len;
對于以上這些參數的值將分別保存到REGBANK寄存器組中的LCDCON1/2/3/4/5寄存器中:(對寄存器的操作請查看S3c2440數據手冊LCD部分)
LCDCON1:17 - 8位CLKVAL
6 - 5位掃描模式(對于STN屏:4位單/雙掃、8位單掃)
4 - 1位色位模式(1BPP、8BPP、16BPP等)
LCDCON2:31 - 24位VBPD
23 - 14位LINEVAL
13 - 6位VFPD
5 - 0位VSPW
LCDCON3:25 - 19位HBPD
18 - 8位HOZVAL
7 - 0位HFPD
LCDCON4: 7 - 0位HSPW
LCDCON5:
4. 幀緩沖(FrameBuffer):
幀緩沖是Linux為顯示設備提供的一個接口,它把一些顯示設備描述成一個緩沖區(qū),允許應用程序通過FrameBuffer定義好的接口訪問這些圖形設備,從而不用去關心具體的硬件細節(jié)。對于幀緩沖設備而言,只要在顯示緩沖區(qū)與顯示點對應的區(qū)域寫入顏色值,對應的顏色就會自動的在屏幕上顯示。下面來看一下在不同色位模式下緩沖區(qū)與顯示點的對應關系:[!--empirenews.page--]
幀緩沖設備為標準的字符型設備,在Linux中主設備號29,定義在/include/linux/major.h中的FB_MAJOR,次設備號定義幀緩沖的個數,最大允許有32個FrameBuffer,定義在/include/linux/fb.h中的FB_MAX,對應于文件系統下/dev/fb%d設備文件。
1. 幀緩沖設備驅動在Linux子系統中的結構如下:
我們從上面這幅圖看,幀緩沖設備在Linux中也可以看做是一個完整的子系統,大體由fbmem.c和xxxfb.c組成。向上給應用程序提供完善的設備文件操作接口(即對FrameBuffer設備進行read、write、ioctl等操作),接口在Linux提供的fbmem.c文件中實現;向下提供了硬件操作的接口,只是這些接口Linux并沒有提供實現,因為這要根據具體的LCD控制器硬件進行設置,所以這就是我們要做的事情了(即xxxfb.c部分的實現)。
2. 幀緩沖相關的重要數據結構:
從幀緩沖設備驅動程序結構看,該驅動主要跟fb_info結構體有關,該結構體記錄了幀緩沖設備的全部信息,包括設備的設置參數、狀態(tài)以及對底層硬件操作的函數指針。在Linux中,每一個幀緩沖設備都必須對應一個fb_info,fb_info在/linux/fb.h中的定義如下:(只列出重要的一些)
struct fb_info {
int node;
int flags;
struct fb_var_screeninfo var;/*LCD可變參數結構體*/
struct fb_fix_screeninfo fix;/*LCD固定參數結構體*/
struct fb_monspecs monspecs;/*LCD顯示器標準*/
struct work_struct queue;/*幀緩沖事件隊列*/
struct fb_pixmap pixmap; /*圖像硬件mapper*/
struct fb_pixmap sprite; /*光標硬件mapper*/
struct fb_cmap cmap; /*當前的顏色表*/
struct fb_videomode *mode;/*當前的顯示模式*/
#ifdef CONFIG_FB_BACKLIGHT
struct backlight_device *bl_dev
struct mutex bl_curve_mutex;
u8 bl_curve[FB_BACKLIGHT_LEVELS]
#endif
#ifdef CONFIG_FB_DEFERRED_IO
struct delayed_work deferred_work;
struct fb_deferred_io *fbdefio;
#endif
struct fb_ops *fbops
struct device *device;
struct device *dev;/*fb設備*/
int class_flag;
#ifdef CONFIG_FB_TILEBLITTING
struct fb_tile_ops *tileops; /*圖塊Blitting*/
#endif
char __iomem *screen_base;/*虛擬基地址*/
unsigned long screen_size;/*LCD IO映射的虛擬內存大小*/
void *pseudo_palette;/*偽16色顏色表*/
#define FBINFO_STATE_RUNNING 0
#define FBINFO_STATE_SUSPENDED 1
u32 state;/*LCD的掛起或恢復狀態(tài)*/
void *fbcon_par;
void *par;
};
其中,比較重要的成員有struct fb_var_screeninfo var、struct fb_fix_screeninfo fix和struct fb_ops *fbops,他們也都是結構體。下面我們一個一個的來看。
fb_var_screeninfo結構體主要記錄用戶可以修改的控制器的參數,比如屏幕的分辨率和每個像素的比特數等,該結構體定義如下:
struct fb_var_screeninfo {
__u32 xres;/*可見屏幕一行有多少個像素點*/
__u32 yres;/*可見屏幕一列有多少個像素點*/
__u32 xres_virtual;/*虛擬屏幕一行有多少個像素點*/
__u32 yres_virtual;/*虛擬屏幕一列有多少個像素點*/
__u32 xoffset;/*虛擬到可見屏幕之間的行偏移*/
__u32 yoffset;/*虛擬到可見屏幕之間的列偏移*/
__u32 bits_per_pixel;/*每個像素的位數即BPP*/
__u32 grayscale;/*非0時,指的是灰度*/
struct fb_bitfield red;/*fb緩存的R位域*/
struct fb_bitfield green;/*fb緩存的G位域*/
struct fb_bitfield blue;/*fb緩存的B位域*/
struct fb_bitfield transp;/*透明度*/
__u32 nonstd;/* != 0 非標準像素格式*/
__u32 activate;
__u32 height;/*高度*/
__u32 width;/*寬度*/
__u32 accel_flags;
/*定時:除了pixclock本身外,其他的都以像素時鐘為單位*/
__u32 pixclock;/*像素時鐘(皮秒)*/
__u32 left_margin;/*行切換,從同步到繪圖之間的延遲*/
__u32 right_margin;/*行切換,從繪圖到同步之間的延遲*/
__u32 upper_margin;/*幀切換,從同步到繪圖之間的延遲*/
__u32 lower_margin;/*幀切換,從繪圖到同步之間的延遲*/
__u32 hsync_len;/*水平同步的長度*/
__u32 vsync_len;/*垂直同步的長度*/
__u32 sync;
__u32 vmode;
__u32 rotate;
__u32 reserved[5];/*保留*/
};
而fb_fix_screeninfo結構體又主要記錄用戶不可以修改的控制器的參數,比如屏幕緩沖區(qū)的物理地址和長度等,該結構體的定義如下:
struct fb_fix_screeninfo {
char id[16];/*字符串形式的標示符 */
unsigned long smem_start;/*fb緩存的開始位置 */
__u32 smem_len;/*fb緩存的長度 */
__u32 type;/*看FB_TYPE_* */
__u32 type_aux;/*分界*/
__u32 visual;/*看FB_VISUAL_* */
__u16 xpanstep;/*如果沒有硬件panning就賦值為0 */
__u16 ypanstep;/*如果沒有硬件panning就賦值為0 */
__u16 ywrapstep;/*如果沒有硬件ywrap就賦值為0 */
__u32 line_length;/*一行的字節(jié)數 */
unsigned long mmio_start;/*內存映射IO的開始位置*/
__u32 mmio_len;/*內存映射IO的長度*/
[!--empirenews.page--]__u32 accel;
__u16 reserved[3];/*保留*/
};
其中,比較重要的成員有struct fb_var_screeninfo var、struct fb_fix_screeninfo fix和struct fb_ops *fbops,他們也都是結構體。下面我們一個一個的來看。
fb_var_screeninfo結構體主要記錄用戶可以修改的控制器的參數,比如屏幕的分辨率和每個像素的比特數等,該結構體定義如下:
struct fb_var_screeninfo {
__u32 xres;/*可見屏幕一行有多少個像素點*/
__u32 yres;/*可見屏幕一列有多少個像素點*/
__u32 xres_virtual;/*虛擬屏幕一行有多少個像素點*/
__u32 yres_virtual;/*虛擬屏幕一列有多少個像素點*/
__u32 xoffset;/*虛擬到可見屏幕之間的行偏移*/
__u32 yoffset;/*虛擬到可見屏幕之間的列偏移*/
__u32 bits_per_pixel;/*每個像素的位數即BPP*/
__u32 grayscale;/*非0時,指的是灰度*/
struct fb_bitfield red;/*fb緩存的R位域*/
struct fb_bitfield green;/*fb緩存的G位域*/
struct fb_bitfield blue;/*fb緩存的B位域*/
struct fb_bitfield transp;/*透明度*/
__u32 nonstd;/* != 0 非標準像素格式*/
__u32 activate;
__u32 height;/*高度*/
__u32 width;/*寬度*/
__u32 accel_flags;
/*定時:除了pixclock本身外,其他的都以像素時鐘為單位*/
__u32 pixclock;/*像素時鐘(皮秒)*/
__u32 left_margin;/*行切換,從同步到繪圖之間的延遲*/
__u32 right_margin;/*行切換,從繪圖到同步之間的延遲*/
__u32 upper_margin;/*幀切換,從同步到繪圖之間的延遲*/
__u32 lower_margin;/*幀切換,從繪圖到同步之間的延遲*/
__u32 hsync_len;/*水平同步的長度*/
__u32 vsync_len;/*垂直同步的長度*/
__u32 sync;
__u32 vmode;
__u32 rotate;
__u32 reserved[5];/*保留*/
};
而fb_fix_screeninfo結構體又主要記錄用戶不可以修改的控制器的參數,比如屏幕緩沖區(qū)的物理地址和長度等,該結構體的定義如下:
struct fb_fix_screeninfo {
char id[16];/*字符串形式的標示符 */
unsigned long smem_start;/*fb緩存的開始位置 */
__u32 smem_len;/*fb緩存的長度 */
__u32 type;/*看FB_TYPE_* */
__u32 type_aux;/*分界*/
__u32 visual;/*看FB_VISUAL_* */
__u16 xpanstep;/*如果沒有硬件panning就賦值為0 */
__u16 ypanstep;/*如果沒有硬件panning就賦值為0 */
__u16 ywrapstep;/*如果沒有硬件ywrap就賦值為0 */
__u32 line_length;/*一行的字節(jié)數 */
unsigned long mmio_start;/*內存映射IO的開始位置*/
__u32 mmio_len;/*內存映射IO的長度*/
__u32 accel;
__u16 reserved[3];/*保留*/
};
fb_ops結構體是對底層硬件操作的函數指針,該結構體中定義了對硬件的操作有:(這里只列出了常用的操作)
struct fb_ops {
struct module *owner;
//檢查可變參數并進行設置
int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);
//根據設置的值進行更新,使之有效
int (*fb_set_par)(struct fb_info *info);
//設置顏色寄存器
int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
unsigned blue, unsigned transp, struct fb_info *info);
//顯示空白
int (*fb_blank)(int blank, struct fb_info *info);
//矩形填充
void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
//復制數據
void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
//圖形填充
void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);
};
3. 幀緩沖設備作為平臺設備:
在S3C2440中,LCD控制器被集成在芯片的內部作為一個相對獨立的單元,所以Linux把它看做是一個平臺設備,故在內核代碼/arch/arm/plat-s3c24xx/devs.c中定義有LCD相關的平臺設備及資源,代碼如下:
/* LCD Controller */
//LCD控制器的資源信息
static struct resource s3c_lcd_resource[] = {
[0] = {
.start = S3C24XX_PA_LCD
}
};
static u64 s3c_device_lcd_dmamask = 0xffffffffUL;
struct platform_device s3c_device_lcd = {
.name = "s3c2410-lcd"
.id = -1,
.num_resources = ARRAY_SIZE(s3c_lcd_resource)
.dev = {
.dma_mask = &s3c_device_lcd_dmamask,
.coherent_dma_mask = 0xffffffffUL
}
};
EXPORT_SYMBOL(s3c_device_lcd)
除此之外,Linux還在/arch/arm/mach-s3c2410/include/mach/fb.h中為LCD平臺設備定義了一個s3c2410fb_mach_info結構體,該結構體主要是記錄LCD的硬件參數信息(比如該結構體的s3c2410fb_display成員結構中就用于記錄LCD的屏幕尺寸、屏幕信息、可變的屏幕參數、LCD配置寄存器等),這樣在寫驅動的時候就直接使用這個結構體。下面,我們來看一下內核是如果使用這個結構體的。在/arch/arm/mach-s3c2440/mach-smdk2440.c中定義有:
/* LCD driver info */
//LCD硬件的配置信息,注意這里我使用的LCD是NEC 3.5寸TFT屏,這些參數要根據具體的LCD屏進行設置
static struct s3c2410fb_display smdk2440_lcd_cfg __initdata = {
//這個地方的設置是配置LCD寄存器5,這些宏定義在regs-lcd.h中,計算后二進制為:111111111111,然后對照數據手冊上LCDCON5的各位來看,注意是從右邊開始
.lcdcon5 = S3C2410_LCDCON5_FRM565 |[!--empirenews.page--]
S3C2410_LCDCON5_INVVLINE |
S3C2410_LCDCON5_INVVFRAME |
S3C2410_LCDCON5_PWREN |
S3C2410_LCDCON5_HWSWP,
.type = S3C2410_LCDCON1_TFT
//以下一些參數在上面的時序圖分析中講到過,各參數的值請跟據具體的LCD屏數據手冊結合上面時序分析來設定
};
static struct s3c2410fb_mach_info smdk2440_fb_info __initdata = {
.displays = &smdk2440_lcd_cfg
.num_displays = 1,
.default_display = 0,
.gpccon = 0xaaaa555a
.gpccon_mask = 0xffffffff,
.gpcup = 0x0000ffff
.gpcup_mask = 0xffffffff,
.gpdcon = 0xaaaaaaaa
.gpdcon_mask = 0xffffffff,
.gpdup = 0x0000ffff
.gpdup_mask = 0xffffffff,
.lpcsel = 0x0
};
注意:可能有很多朋友不知道上面紅色部分的參數是做什么的,其值又是怎么設置的?其實它是跟你的開發(fā)板LCD控制器密切相關的,看了下面兩幅圖相信就大概知道他們是干什么用的:
上面第一幅圖是開發(fā)板原理圖的LCD控制器部分,第二幅圖是S3c2440數據手冊中IO端口C和IO端口D控制器部分。原理圖中使用了GPC8-15和GPD0-15來用做LCD控制器VD0-VD23的數據端口,又分別使用GPC0、GPC1端口用做LCD控制器的LEND和VCLK信號,對于GPC2-7則是用做STN屏或者三星專業(yè)TFT屏的相關信號。然而,S3C2440的各個IO口并不是單一的功能,都是復用端口,要使用他們首先要對他們進行配置。所以上面紅色部分的參數就是把GPC和GPD的部分端口配置成LCD控制功能模式。
從以上講述的內容來看,要使LCD控制器支持其他的LCD屏,重要的是根據LCD的數據手冊修改以上這些參數的值。下面,我們再看一下在驅動中是如果引用到s3c2410fb_mach_info結構體的(注意上面講的是在內核中如何使用的)。在mach-smdk2440.c中有:
//S3C2440初始化函數
static void __init smdk2440_machine_init(void)
{
s3c24xx_fb_set_platdata(&smdk2440_fb_info);
s3c_i2c0_set_platdata(NULL);
platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
smdk_machine_init();
}
s3c24xx_fb_set_platdata定義在plat-s3c24xx/devs.c中:
void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd)
{
struct s3c2410fb_mach_info *npd;
npd = kmalloc(sizeof(*npd), GFP_KERNEL);
if (npd) {
memcpy(npd, pd, sizeof(*npd));
//這里就是將內核中定義的s3c2410fb_mach_info結構體數據保存到LCD平臺數據中,所以在寫驅動的時候就可以直接在平臺數據中獲取s3c2410fb_mach_info結構體的數據(即LCD各種參數信息)進行操作
s3c_device_lcd.dev.platform_data = npd;
} else {
printk(KERN_ERR "no memory for LCD platform datan");
}
}
這里再講一個小知識:不知大家有沒有留意,在平臺設備驅動中,platform_data可以保存各自平臺設備實例的數據,但這些數據的類型都是不同的,為什么都可以保存?這就要看看platform_data的定義,定義在/linux/device.h中,void *platform_data是一個void類型的指針,在Linux中void可保存任何數據類型。