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就能產生這些信號的不同形態,從而支持不同的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的顯示並不是對一副圖像快速的顯示一下,爲了持續和穩定的在LCD上顯示,就需要切換到另一幅圖上(另一幅圖可以和上一副圖一樣或者不一樣,目
的只是爲了將圖像持續的顯示在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爲顯示設備提供的一個接口,它把一些顯示設備描述成一個緩衝區,允許應用程序通過
FrameBuffer定義好的接口訪問這些圖形設備,從而不用去關心具體的硬件細節。對於幀緩衝設備而言,只要在顯示緩衝區與顯示點對應的區域寫入顏色
值,對應的顏色就會自動的在屏幕上顯示。下面來看一下在不同色位模式下緩衝區與顯示點的對應關係:
幀緩衝(FrameBuffer)設備驅動
:
幀
緩衝設備爲標準的字符型設備,在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結構體有關,該結構體記錄了幀緩衝設備的全部信息,包括設備的設置參數、狀態以及對底層硬件操作的
函數指針。在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的掛起或恢復狀態*/
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結構體又主要記錄用戶不可以修改的控制器的參數,比如屏幕緩衝區的物理地址和長度等,該結構體的定義如下:
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;
/*一行的字節數 */
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
,
//控制器IO端口開始地址jjj
.
end =
S3C24XX_PA_LCD +
S3C24XX_SZ_LCD -
1
,
//控制器IO端口結束地址
.
flags =
IORESOURCE_MEM
,
//標識爲LCD控制器IO端口,在驅動中引用這個就表示引用IO端口
}
,
[
1]
=
{
.
start =
IRQ_LCD
,
//LCD中斷
.
end =
IRQ_LCD,
.
flags =
IORESOURCE_IRQ
,
//標識爲LCD中斷
}
}
;
static
u64 s3c_device_lcd_dmamask =
0xffffffffUL;
struct
platform_device s3c_device_lcd =
{
.
name =
"s3c2410-lcd"
,
//作爲平臺設備的LCD設備名
.
id =
-
1,
.
num_resources =
ARRAY_SIZE(
s3c_lcd_resource)
,
//資源數量
.
resource =
s3c_lcd_resource
,
//引用上面定義的資源
.
dev =
{
.
dma_mask =
&
s3c_device_lcd_dmamask,
.
coherent_dma_mask =
0xffffffffUL
}
}
;
EXPORT_SYMBOL(
s3c_device_lcd)
;
//導出定義的LCD平臺設備,好在mach-smdk2440.c的smdk2440_devices[]中添加到平臺設備列表中
|
除
此之外,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 =
{
.
lcdcon5 =
S3C2410_LCDCON5_FRM565 |
S3C2410_LCDCON5_INVVLINE |
S3C2410_LCDCON5_INVVFRAME |
S3C2410_LCDCON5_PWREN |
S3C2410_LCDCON5_HWSWP,
.
type =
S3C2410_LCDCON1_TFT
,
//TFT類型
/* /* NEC 3.5'''' */
. width = 240
,
//屏幕寬度
. height = 320
,
//屏幕高度
//以下一些參數在上面的時序圖分析中講到過
. pixclock = 100000
,
//像素時鐘
. xres = 240
,
//水平可見的有效像素
. yres = 320
,
//垂直可見的有效像素
. bpp = 16
,
//色位模式
. left_margin = 19
,
//行切換,從同步到繪圖之間的延遲
. right_margin = 36
,
//行切換,從繪圖到同步之間的延遲
. hsync_len = 5
,
//水平同步的長度
. upper_margin = 1
,
//幀切換,從同步到繪圖之間的延遲
. lower_margin = 5
,
//幀切換,從繪圖到同步之間的延遲
. vsync_len = 1
,
//垂直同步的長度
} ;
*/
.lcdcon5 = S3C2410_LCDCON5_FRM565 |
S3C2410_LCDCON5_INVVLINE |
S3C2410_LCDCON5_INVVFRAME |
S3C2410_LCDCON5_PWREN |
S3C2410_LCDCON5_HWSWP,
.type = S3C2410_LCDCON1_TFT,
.width = 800,//屏幕寬度
.height = 600,//屏幕高度
//.pixclock = 100000, /* HCLK/10 */ //像素時鐘
.pixclock = 2000000, /* HCLK/10 */
//像素時鐘
.xres = 800, //水平可見的有效像素
.yres = 600, //垂直可見的有效像素
.bpp = 16, //色位模式
.left_margin = 40, //2 //行切換,從同步到繪圖之間的延遲
.right_margin = 88, //2 //行切換,從繪圖到同步之間的延遲
.hsync_len = 128, //41 //水平同步的長度
.upper_margin = 20, //2 //幀切換,從同步到繪圖之間的延遲
.lower_margin = 20, //2 //幀切換,從繪圖到同步之間的延遲
.vsync_len = 10, //10 //垂直同步的長度
};
static
struct
s3c2410fb_mach_info smdk2440_fb_info __initdata =
{
.
displays =
&
smdk2440_lcd_cfg
,
//應用上面定義的配置信息
.
num_displays =
1,
.
default_display =
0,
.
gpccon =
0xaa955699,
.
gpccon_mask =
0xffc003cc,
.
gpcup =
0x0000ffff,
.
gpcup_mask =
0xffffffff,
.
gpdcon =
0xaa95aaa1,
.
gpdcon_mask =
0xffc0fff0,
.
gpdup =
0x0000faff,
.
gpdup_mask =
0xffffffff,
.
lpcsel =
0xf82,
}
;
|
從
上面的代碼來看,要使LCD控制器支持其他的LCD屏,重要的是根據LCD的數據手冊修改以上這些參數的值。下面,我們再看一下在驅動中是如果引用到
s3c2410fb_mach_info結構體的(注意上面講的是在內核中如何使用的)。在mach-smdk2440.c中有:
//S3C2440初始化函數
static
void
__init smdk2440_machine_init(
void
)
{
//調用該函數將上面定義的LCD硬件信息保存到平臺數據中
s3c24xx_fb_set_platdata(
&
smdk2440_fb_info)
;
s3c_i2c0_set_platdata(
NULL
)
;
platform_add_devices(
smdk2440_devices,
ARRAY_SIZE(
smdk2440_devices)
)
;
smdk_machine_init(
)
; // 函數在arch/arm/plat-s3c24xx/common-smdk.c,初始化
}
|
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 data/n"
)
;
}
}
|
最後:當編譯好內核燒到目標板上時,屏閃比較厲害,通過查看屏的datasheet 得知,屏的時鐘頻率爲33MHz,通過工式計算出clkdiv 值爲0,最接近lcd 的時鐘頻率。文件是:drivers/video/s3c2440fb.c
clkdiv= 2;
if (type == S3C2410_LCDCON1_TFT) {
s3c2410fb_calculate_tft_lcd_regs(info, &fbi->regs);
--clkdiv;
if (clkdiv < 0)
clkdiv = 0;
} else {
s3c2410fb_calculate_stn_lcd_regs(info, &fbi->regs);
if (clkdiv < 2)
clkdiv = 2;
}
添加如下代碼:clkdiv = 0;
屏不閃了。
這
裏再講一個小知識:不知大家有沒有留意,在平臺設備驅動中,platform_data可以保存各自平臺設備實例的數據,但這些數據的類型都是不同的,爲
什麼都可以保存?這就要看看platform_data的定義,定義在/linux/device.h中,void
*platform_data是一個void類型的指針,在Linux中void可保存任何指針的數據類型。