LCD是Liquid Crystal Display的簡稱,也就是經常所說的液晶顯示器
LCD能夠支持彩色圖像的顯示和視頻的播放,是一種非常重要的輸出設備
Framebuffer 是Linux系統為顯示設備提供的一個接口,它將顯示緩沖區抽象,屏蔽圖像硬件的底層差異,允許上層應用程序在圖形模式下直接對顯示緩沖區進行操作
Framebuffer又叫幀緩沖,是Linux為操作顯示設備提供的一個用戶接口。用戶應用程序可以通過Framebuffer透明地訪問不同類型的顯示設備。
從這個方面來說,Framebuffer是硬件設備顯示緩沖區的抽象。Linux抽象出Framebuffer這個幀緩沖區可以供用戶應用程序直接讀寫,通過更改Framebuffer中的內容,就可以立刻顯示在LCD顯示屏上
Framebuffer是顯卡硬件的抽象:
Framebuffer機制模仿顯卡的功能,將顯卡硬件結構抽象為一系列的數據結構,可以通過Framebuffer的讀寫直接對顯存進行操作。用戶可以將Framebuffer看成是顯示內存的一個映像,將其映射到進程地址空間之后,就可以直接進行讀寫操作,而寫操作可以立即反映在屏幕上。這些操作是由抽象的,統一的。用戶不必關心物理顯存的位置、換頁機制等具體細節。這些都是由Framebuffer設備驅動完成的
對於Framebuffer而言,只要在幀緩沖區中與顯示點對應的區域寫入顏色值,對應的顏色會自動在LCD屏幕上顯示出來
Frambuffer 是標准的字符設備:
Framebuffer 是一個標准的字符設備,主設備號是29,次設備號根據緩沖區 的數目而定。
Framebuffer 對應 /dev/fb%d 設備文件。根據顯卡的多少,設備文件可能是/dev/fb0、/dev/sb1等。緩沖區設備也是一種普通的內存設備,可以直接對其進行讀寫。
例如:對屏幕進行抓屏,可以使用下面的命令:
cp /dev/fb0 myfile.png
一個系統上有多個顯示設備。例如一個系統上,又有一個獨立的顯卡,那么就有兩個緩沖區設備文件/dev/fb1 和 /dev/fb2,應用程序利用它們來工作,向其中寫入數據,就能夠在屏幕上立刻看到顯示的變化
Framebuffer與應用程序的交互:
在Linux中,Framebuffer是一種能夠提取圖形的硬件設備,是用戶進入圖形界面的很好接口。
Framebuffer是顯存抽象后的一種設備,它允許上層應用程序在圖形模式下直接對顯示緩沖區進行讀寫操作。
這種操作是抽象的、統一的。
用戶不必關系物理顯存的位置、換頁機制等具體細節
這些都由Framebuffer設備驅動程序來完成的
有了Framebuffer,用戶程序不需要對底層的驅動深入了解就能夠作出很好的圖形
對用戶程序而言,它和 /dev 下面的其他設備沒有什么區別,用戶可以把FrameBuffer看成一塊內存,既可以寫,又可以讀。顯示器將根據內存數據顯示對應的圖像界面。這一切都由LCD控制器和響應的驅動程序來完成
Framebuffer的顯示緩沖區位於Linux的內核態地址空間。而在Linux中,每個應用程序都有自己的虛擬地址空間,在應用程序中是不能直接訪問物理緩沖區的。為此,Linux在文件操作file_operations結構中提供了mmap()函數,可將文件的內容映射到用戶空間。對應幀緩沖設備,則可以通過映射操作,將屏幕緩沖區(Framebuffer)的物理地址映射到用戶空間的一段虛擬地址中,之后用戶就可以通過讀寫這段虛擬地址訪問屏幕緩沖區,在屏幕上繪圖。
Framebuffer與應用程序的交互如下圖:
Framebuffer顯示原理:
通過Framebuffer,應用程序用mmap()把顯存映射到應用程序虛擬地址空間。幀緩沖設備主設備號29,次設備號從0到31.分別對應/dev/fd0 ~ /dev/fd31
簡單地說:Framebuffer驅動的功能就是分配一塊內存作為顯存,然后LCD控制器的寄存器做一些設置。LCD顯示器會不斷地從顯存中獲取數據,並將其顯示在LCD顯示器上。LCD顯示器可以顯示顯存中的一個區域或者整個區域。framebuffer驅動程序提供了操作顯存的功能,例如復制顯存、向顯存中寫入數據(畫園,畫方型等)
具體來說:實現這些操作的方法是,填充一個fbinfo結構,用register_framebuffer(fbinfo *)將fbinfo結構注冊到內核,對於fbinfo結構,最主要的它的fs_ops成員,需要針對具體設備實現fs_ops中的接口
Framebuffer是LCD驅動中最重要的一部分,通過Framebuffer使Linux內核可以使用大多數顯示設備的功能
/include/linux/fb.h
/drivers/video/fbmem.c
Framebuffer驅動主要位於上面兩個文件中,它們處於驅動體系結構的中間層,為上層用戶程序提供系統調用,也為底層特定硬件驅動提供了接口
fb.h中定義了一些主要的數據結構,Frameuffer設備在很大程度上依靠了下面的3個數據結構。
struct fb_var_screeninfo、struct fb_fix_screeninfo 和 struct fb_info
第一個結構體用來描述圖形卡的特性,通常是被用戶設置的
第二個結構體定義了圖形卡的硬件特性,是不能改變的,用戶選定了LCD控制器和顯示器后,那么硬件特性也就定下來了
第三個結構體定義了當前圖形卡Framebuffer設備的獨立狀態,一個圖形卡可能有兩個Framebuffer,在這種情況下,就需要兩個fb_info結構。這個結構是唯一內核空間可見的
其他數據結構:
1,struct fb_cmap結構體:用來定義幀緩存區設備的顏色表(colormap)信息,可以通過ioctl()函數的FBIOGETCMAP和FBIOPUTCMAP命令來設置colormap
2,struct fb_info 結構體:包含當前顯示卡的狀態信息,struct fb_info結構體只對內核可見
3,struct fb_ops結構體:應用程序使用這些函數操作底層的LCD硬件,fb_ops結構中定義的方法用於支持這些操作。這些操作需要驅動開發人員來實現
4,struct fb_fix_screeninfo結構體:定義了顯卡信息,如framebuffer內存的起始地址,地址的長度
5,struct fb_var_screeninfo結構體:描述了一種顯示模式的所有信息,如寬、高、顏色深度等,不同顯示模式對應不同的信息
在Framebuffer設備驅動程序中,這些結構是互相關聯,互相配合使用的。只有每一個結構體起到自己的作用,才能使整個Framebuffer設備驅動程序正常工作。
它們有如下工作:
其中struct fb_info結構體包含了其他4個結構體的指針
Framebuffer驅動實現:
一般來說,應用程序通過內核對Framebuffer的控制,主要有以下3種方式:
1,讀/寫 /dev/fb相當於讀/寫屏幕緩沖區
2,通過映射操作,可將屏幕緩沖區的物理地址映射到用戶空間的一段虛擬地址中,之后用戶就可以通過讀寫這段虛擬地址訪問屏幕緩沖區,在屏幕上繪圖
3,I/O控制對於幀緩沖設備,設備文件的ioctl()函數可讀取/設置顯示設備及屏幕的參數,如分辨率、顯示顏色數、屏幕大小等。ioctl()函數由底層的驅動程序完成
因此,Framebuffer驅動要完成的工作已經很少了,只需分配顯存的大小、初始化LCD控制寄存器、設置修改硬件設備相應的struct fb_fix_screeninfo信息和 struct fb_var_screeninfo信息。這些信息是與具體的顯示設備相關的。
幀緩沖設備屬於字符設備,采用“文件層——驅動層”的接口方式,在文件層次上,Linux定義了fb_fops
static const struct file_operations fb_fops = {
.owner = THIS_MODULE,
.read = fb_read, //讀操作
.write = fb_write, //寫操作
.unlocked_ioctl = fb_ioctl, //控制操作
#ifdef CONFIG_COMPAT
.compat_ioctl = fb_compat_ioctl,
#endif
.mmap = fb_mmap, //映射操作
.open = fb_open, //打開操作
.release = fb_release, //關閉操作
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
.get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO
.fsync = fb_deferred_io_fsync,
#endif
};
在Linux中,由於幀緩沖設備是字符設備,應用程序需按文件的方式打開一個幀緩沖設備。
如果打開成功,則可對幀緩沖設備進行讀、寫等操作。
讀、寫幀緩沖設備最主要的任務就是獲取幀緩沖設備在內存中的物理地址空間及相應LCD的一些特性。應用程序通過寫幀緩沖設備來顯示圖形的全過程如下:
編寫幀緩沖驅動的工作:
1,編寫初始化函數:初始化函數首先初始化LCD控制器,通過寫寄存器設置顯示模式和顯示顏色數,然后分配LCD顯示緩沖區。在Linux中可通過kmalloc()函數分配一片連續的空間。例如:采用的LCD顯示方式為320X240,256位灰度。需要分配的顯示緩沖區為:240X320 = 750K字節,緩沖區通常分配在大容量的片外SDRAM中,其實地址保存在LCD控制器寄存器中。最后是初始化一個fb_info 結構,填充其中的成員變量,並調用register_framebuffer(&fb_info),將fb_info登記入內核。
2,編寫結構struct fb_info中函數指針fb_ops對應的成員函數,對於嵌入式系統的簡單實現,只需要實現少量的函數
fb_info是幀緩沖Framebuffer設備驅動中一個非常重要的結構體。它包含了驅動實現的底層函數和記錄設備狀態的數據。一個幀緩沖區對應一個fb_info結構體:
struct fb_info {
int node;
int flags;
struct mutex lock; /* Lock for open/release/ioctl funcs */ //為 open/release/ioctl 等函數准備的互斥鎖
struct fb_var_screeninfo var; /* Current var */ //當前緩沖區的可變參數,也就是當前視頻信息
struct fb_fix_screeninfo fix; /* Current fix */ //當前緩沖區的固定參數
struct fb_monspecs monspecs; /* Current Monitor specs */ //當前顯示器的標志
struct work_struct queue; /* Framebuffer event queue */ //幀緩沖的事件隊列
struct fb_pixmap pixmap; /* Image hardware mapper */ //圖像硬件mapper
struct fb_pixmap sprite; /* Cursor hardware mapper */ //光標硬件mapper
struct fb_cmap cmap; /* Current cmap */ //當前的顏色板,也叫調色板
struct list_head modelist; /* mode list */ //模塊列表t
struct fb_videomode *mode; /* current mode */ //當前視頻模式
#ifdef CONFIG_FB_BACKLIGHT //如果配置了LCD支持背光燈
/* assigned backlight device */
/* set before framebuffer registration,
remove after unregister */
struct backlight_device *bl_dev; //對應的背光燈設備,設置bl_dev這個變量,應該在注冊Framebuffer之前
/* Backlight level curve */
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; /* This is the parent */ //指向父設備結構體
struct device *dev; /* This is this fb device */ //內嵌的Framebuffer設備自身
int class_flag; /* private sysfs flags */ //私有sysfs標志
#ifdef CONFIG_FB_TILEBLITTING
struct fb_tile_ops *tileops; /* Tile Blitting */ //圖塊Blitting
#endif
char __iomem *screen_base; /* Virtual address */ //虛擬基地址
unsigned long screen_size; /* Amount of ioremapped VRAM or 0 */ //ioremap的虛擬內存大小
void *pseudo_palette; /* Fake palette of 16 colors */ //偽16位調色板
#define FBINFO_STATE_RUNNING 0 // 運行狀態
#define FBINFO_STATE_SUSPENDED 1 // 掛起狀態
u32 state; /* Hardware state i.e suspend */ //硬件的狀態
void *fbcon_par; /* fbcon use-only private area */ //fbcon使用的私有數據
/* From here on everything is device dependent */
void *par;
};
struct fb_videomode { //視頻模式
const char *name; /* optional */
u32 refresh; /* optional */
u32 xres;
u32 yres;
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 flag;
};
struct fb_pixmap {
u8 *addr; /* pointer to memory */
u32 size; /* size of buffer in bytes */
u32 offset; /* current offset to buffer */
u32 buf_align; /* byte alignment of each bitmap */
u32 scan_align; /* alignment per scanline */
u32 access_align; /* alignment per read/write (bits) */
u32 flags; /* see FB_PIXMAP_* */
u32 blit_x; /* supported bit block dimensions (1-32)*/
u32 blit_y; /* Format: blit_x = 1 << (width - 1) */
/* blit_y = 1 << (height - 1) */
/* if 0, will be set to 0xffffffff (all)*/
/* access methods */
void (*writeio)(struct fb_info *info, void __iomem *dst, void *src, unsigned int size);
void (*readio) (struct fb_info *info, void *dst, void __iomem *src, unsigned int size);
};
struct fb_monspecs {
struct fb_chroma chroma;
struct fb_videomode *modedb; /* mode database */
__u8 manufacturer[4]; /* Manufacturer */
__u8 monitor[14]; /* Monitor String */
__u8 serial_no[14]; /* Serial Number */
__u8 ascii[14]; /* ? */
__u32 modedb_len; /* mode database length */
__u32 model; /* Monitor Model */
__u32 serial; /* Serial Number - Integer */
__u32 year; /* Year manufactured */
__u32 week; /* Week Manufactured */
__u32 hfmin; /* hfreq lower limit (Hz) */
__u32 hfmax; /* hfreq upper limit (Hz) */
__u32 dclkmin; /* pixelclock lower limit (Hz) */
__u32 dclkmax; /* pixelclock upper limit (Hz) */
__u16 input; /* display type - see FB_DISP_* */
__u16 dpms; /* DPMS support - see FB_DPMS_ */
__u16 signal; /* Signal Type - see FB_SIGNAL_* */
__u16 vfmin; /* vfreq lower limit (Hz) */
__u16 vfmax; /* vfreq upper limit (Hz) */
__u16 gamma; /* Gamma - in fractions of 100 */
__u16 gtf : 1; /* supports GTF */
__u16 misc; /* Misc flags - see FB_MISC_* */
__u8 version; /* EDID version... */
__u8 revision; /* ...and revision */
__u8 max_x; /* Maximum horizontal size (cm) */
__u8 max_y; /* Maximum vertical size (cm) */
};
struct fb_chroma {
__u32 redx; /* in fraction of 1024 */
__u32 greenx;
__u32 bluex;
__u32 whitex;
__u32 redy;
__u32 greeny;
__u32 bluey;
__u32 whitey;
};
struct fb_cmap_user {
__u32 start; /* First entry */
__u32 len; /* Number of entries */
__u16 __user *red; /* Red values */
__u16 __user *green;
__u16 __user *blue;
__u16 __user *transp; /* transparency, can be NULL */
};
struct fb_image_user {
__u32 dx; /* Where to place image */
__u32 dy;
__u32 width; /* Size of image */
__u32 height;
__u32 fg_color; /* Only used when a mono bitmap */
__u32 bg_color;
__u8 depth; /* Depth of the image */
const char __user *data; /* Pointer to image data */
struct fb_cmap_user cmap; /* color map info */
};
struct fb_cursor_user {
__u16 set; /* what to set */
__u16 enable; /* cursor on/off */
__u16 rop; /* bitop operation */
const char __user *mask; /* cursor mask bits */
struct fbcurpos hot; /* cursor hot spot */
struct fb_image_user image; /* Cursor image */
};
fb_ops結構體用來實現對幀緩沖設備的操作。用戶可以使用ioctl()函數來操作設備:
struct fb_ops {
/* open/release and usage marking */
struct module *owner; //模塊計數指針
int (*fb_open)(struct fb_info *info, int user); //打開設備指針
int (*fb_release)(struct fb_info *info, int user); //釋放設備指針
/* For framebuffers with strange non linear layouts or that do not
* work with normal memory mapped access
*/ //下面兩個函數對於非線性布局的常規內存無法工作的幀緩沖區設備有效
ssize_t (*fb_read)(struct fb_info *info, char __user *buf,
size_t count, loff_t *ppos);
ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,
size_t count, loff_t *ppos);
/* checks var and eventually tweaks it to something supported,
* DO NOT MODIFY PAR */
int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info); //檢查可變參數,並調整到支持設備。用來檢查和改變幀緩存設備的可變參數,當幀緩沖設備中的參數不滿足驅動程序要求時,會調用這個函數
/* set the video mode according to info->var */
int (*fb_set_par)(struct fb_info *info); //設置視頻模式。根據info->var變量設備視頻模式
/* set color register */
int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
unsigned blue, unsigned transp, struct fb_info *info); //設置color寄存器。用來設置幀緩沖區設備的顏色相關寄存器的值
/* set color registers in batch */
int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info); //批量的設置顏色寄存器的值,即設置顏色表
/* blank display */
int (*fb_blank)(int blank, struct fb_info *info); //顯示空白。用於緩沖區設備的開關操作
/* pan display */
int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info); //顯示pan。用於設置幀緩沖設備的開關設備
/* Draws a rectangle */
void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect); //填充一個矩形
/* Copy data from area to another */
void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region); //復制數據從一個區域到另一個區域
/* Draws a image to the display */
void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image); //畫一個圖像到屏幕上
/* Draws cursor */
int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor); //繪制光標
/* Rotates the display */
void (*fb_rotate)(struct fb_info *info, int angle); //旋轉屏幕並且顯示出來 。旋轉顯示緩沖區
/* wait for blit idle, optional */
int (*fb_sync)(struct fb_info *info); //等待blit空閑。用於數據發送
/* perform fb specific ioctl (optional) */
int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
unsigned long arg); //實現fb特定的ioctl操作。用於執行ioctl()操作,類似字符設備的ioctl()函數
/* Handle 32bit compat ioctl (optional) */
int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd,
unsigned long arg); //處理32位的兼容ioctl操作
/* perform fb specific mmap */
int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma); //實現fb特定的mmap操作。執行幀緩沖設備具體的內存映射
/* save current hardware state */
void (*fb_save_state)(struct fb_info *info); //保存當前的硬件狀態。用來保存當前硬件的寄存器的值
/* restore saved state */
void (*fb_restore_state)(struct fb_info *info); //恢復被保存的硬件狀態
/* get capability given var */
void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps,
struct fb_var_screeninfo *var); //通過fb_info獲得Framebuffer的能力。用來獲得Framebuffer設備的能力。外部應用程序可以通過這個函數快速的得到設備的信息
};
fb_cmap結構體記錄了一個顏色信息,也可以叫做調色板。用戶可以使用ioctl()函數的FBIOGETCMAP和FBIOPUTCMAP命令讀取和設置顏色表的值:
struct fb_cmap {
__u32 start; /* First entry */ //表示顏色板的第一個元素入口位置
__u32 len; /* Number of entries */ //len表示元素的個數
__u16 *red; /* Red values */ //紅色分量的值
__u16 *green; //綠色分量的值
__u16 *blue; //藍色分量的值
__u16 *transp; /* transparency, can be NULL */ //透明度分量的值
};
fb_cmap結構體對應的存儲結構體如下圖:
fb_var_screeninfo結構體存儲了用戶可以修改的顯示器控制器的參數。例如屏幕分辨率、每個像素的比特數、透明度等:
struct fb_var_screeninfo {
__u32 xres; /* visible resolution */ //xres和yres表示可見解析度,即分辨率。xres表示一行有多少個像素點,yres表示屏幕一列有多少個像素點
__u32 yres;
__u32 xres_virtual; /* virtual resolution */ //這兩行表示虛擬解析度
__u32 yres_virtual;
__u32 xoffset; /* offset from virtual to visible */ //不可見部分與可見部分的偏移地址
__u32 yoffset; /* resolution */
__u32 bits_per_pixel; /* guess what */ //每一個像素所在的位數。每個 像素點占用多少字節
__u32 grayscale; /* != 0 Graylevels instead of colors */ //非0時的灰度值
struct fb_bitfield red; /* bitfield in fb mem if true color, */ //下面是fb緩存的R、G、B位域
struct fb_bitfield green; /* else only length is significant */
struct fb_bitfield blue;
struct fb_bitfield transp; /* transparency */
__u32 nonstd; /* != 0 Non standard pixel format */ //非標准像素格式
__u32 activate; /* see FB_ACTIVATE_* */
__u32 height; /* height of picture in mm */ //屏幕的高度
__u32 width; /* width of picture in mm */ //屏幕的寬度
__u32 accel_flags; /* (OBSOLETE) see fb_info.flags */ //fb_info的標志
/* Timing: All values in pixclocks, except pixclock (of course) */
__u32 pixclock; /* pixel clock in ps (pico seconds) */ //像素時鍾
__u32 left_margin; /* time from sync to picture */ //行切換的時間,從同步到繪圖之間的延遲
__u32 right_margin; /* time from picture to sync */ //行切換的時間,從同步到繪圖之間的延遲
__u32 upper_margin; /* time from sync to picture */ //幀切換的時間,從同步到繪圖之間的延遲
__u32 lower_margin; //幀切換的時間,從同步到繪圖之間的延遲
__u32 hsync_len; /* length of horizontal sync */ //水平同步的長度
__u32 vsync_len; /* length of vertical sync */ //垂直同步的長度
__u32 sync; /* see FB_SYNC_* */
__u32 vmode; /* see FB_VMODE_* */
__u32 rotate; /* angle we rotate counter clockwise */ //順時針旋轉的角度
__u32 reserved[5]; /* Reserved for future compatibility */ //保留
};
struct fb_bitfield {
__u32 offset; /* beginning of bitfield */
__u32 length; /* length of bitfield */
__u32 msb_right; /* != 0 : Most significant bit is */
/* right */
};
fb_fix_screeninfo結構體,記錄了用戶不能修改的固定顯示器控制參數。這些固定參數如緩沖區的物理地址、緩沖區的長度、顯示色彩模式、內存映射的開始位置等。這個結構體的成員都需要在驅動程序初始化時設置:
struct fb_fix_screeninfo {
char id[16]; /* identification string eg "TT Builtin" */ //字符串形式的標志符
unsigned long smem_start; /* Start of frame buffer mem */ //fb緩沖區的開始位置
/* (physical address) */
__u32 smem_len; /* Length of frame buffer mem */ //fb緩沖區的長度
__u32 type; /* see FB_TYPE_* */ //FB_TYPE_*類型
__u32 type_aux; /* Interleave for interleaved Planes */ //分界
__u32 visual; /* see FB_VISUAL_* */ //屏幕使用色彩模式
__u16 xpanstep; /* zero if no hardware panning */ //如果沒有硬件panning,賦值0
__u16 ypanstep; /* zero if no hardware panning */ //如果沒有硬件panning,賦值0
__u16 ywrapstep; /* zero if no hardware ywrap */ //如果沒有硬件panning,賦值0
__u32 line_length; /* length of a line in bytes */ //一行的字節數
unsigned long mmio_start; /* Start of Memory Mapped I/O */ //內存映射I/O的開始位置
/* (physical address) */
__u32 mmio_len; /* Length of Memory Mapped I/O */ //內存映射I/O的長度
__u32 accel; /* Indicate to driver which */ //特定的芯片
/* specific chip/card we have */
__u16 reserved[3]; /* Reserved for future compatibility */ //保留
};
平台驅動的加載和卸載:
int __init s3c2410fb_init(void)
{
int ret = platform_driver_register(&s3c2410fb_driver); //注冊平台驅動,將平台驅動添加到虛擬的總線上
if (ret == 0)
ret = platform_driver_register(&s3c2412fb_driver);; //防止驅動注冊成功,但是探測函數探測LCD控制器設備失敗
return ret;
}
static void __exit s3c2410fb_cleanup(void)
{
platform_driver_unregister(&s3c2410fb_driver);
platform_driver_unregister(&s3c2412fb_driver);
}
平台驅動:s3c2410_i2c_driver
static struct platform_driver s3c2410fb_driver = {
.probe = s3c2410fb_probe,
.remove = s3c2410fb_remove,
.suspend = s3c2410fb_suspend,
.resume = s3c2410fb_resume,
.driver = {
.name = "s3c2410-lcd",
.owner = THIS_MODULE,
},
};
LCD驅動程序的平台數據:
為了方便管理,Linux內核將LCD驅動程序歸入平台設備的范疇。這樣可以使用平台設備的方法操作LCD設備。LCD驅動程序的平台設備定義為s3c_devcie_lcd:
struct platform_device s3c_device_lcd =
{
.name = "s3c2410-lcd",
.id = -1 , //ID表示LCD設備的編號,-1表示只有這樣一個設備
.num_resource = ARRAY_SIZE(s3c_lcd_resource), //資源數量
.resource = s3c_lcd_resource, //資源指針
.dev =
{
.dam_mask = &s3c_device_lcd_dmamask,
.coherent_dma_mask = 0xffffffffUL
}
};
enum s3c_drv_type {
DRV_S3C2410,
DRV_S3C2412,
};
struct s3c2410fb_info {
struct device *dev;
struct clk *clk;
struct resource *mem;
void __iomem *io;
void __iomem *irq_base;
enum s3c_drv_type drv_type;
struct s3c2410fb_hw regs;
unsigned int palette_ready;
/* keep these registers in case we need to re-write palette */
u32 palette_buffer[256];
u32 pseudo_pal[16];
};
static struct resource s3c_lcd_resource[]
{
[0] = {
//LCD的I/O內存開始位置,地址被定義為0x4D000000
.start = S3C24XX_PA_LCD,
.end = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1, //1M的地址空間
.flags = IORESOURCE_MEM, //I/O內存資源
},
[1] = {
.start = IRQ_LCD, //LCD開始中斷號
.end = IRQ_LCD, //LCD結束中斷號
.flags = IRQRESOURCE_IRQ, //中斷的IRQ資源
}
};
s3c2410_mach_info表示LCD顯示的平台信息:
struct s3c2410fb_mach_info {
struct s3c2410fb_display *displays; /* attached diplays info */ //存儲相似信息
unsigned num_displays; /* number of defined displays */ //顯示緩沖的數量
unsigned default_display;
/* GPIOs */ //GPIO引腳
unsigned long gpcup;
unsigned long gpcup_mask;
unsigned long gpccon;
unsigned long gpccon_mask;
unsigned long gpdup;
unsigned long gpdup_mask;
unsigned long gpdcon;
unsigned long gpdcon_mask;
/* lpc3600 control register */ //lpc3600控制寄存器
unsigned long lpcsel;
};
/* LCD description */
struct s3c2410fb_display {
/* LCD type */
unsigned type; //LCD顯示屏的類型
/* Screen size */
unsigned short width; //屏的大小,寬度
unsigned short height; //屏高
/* Screen info */ //下面3行存儲的是屏幕信息
unsigned short xres;
unsigned short yres;
unsigned short bpp;
unsigned pixclock; /* pixclock in picoseconds */
unsigned short left_margin; /* value in pixels (TFT) or HCLKs (STN) */
unsigned short right_margin; /* value in pixels (TFT) or HCLKs (STN) */
unsigned short hsync_len; /* value in pixels (TFT) or HCLKs (STN) */
unsigned short upper_margin; /* value in lines (TFT) or 0 (STN) */
unsigned short lower_margin; /* value in lines (TFT) or 0 (STN) */
unsigned short vsync_len; /* value in lines (TFT) or 0 (STN) */
/* lcd configuration registers */
unsigned long lcdcon5; //LCD配置寄存器
};
struct s3c2410fb_hw { //5個LCD配置寄存器
unsigned long lcdcon1;
unsigned long lcdcon2;
unsigned long lcdcon3;
unsigned long lcdcon4;
unsigned long lcdcon5;
};
LCD模塊探測函數:
s3c2412fb_probe()函數中調用了s3c24xxfb_probe(),第二個參數是處理器的類型:
static int __init s3c2412fb_probe(struct platform_device *pdev)
{
return s3c24xxfb_probe(pdev, DRV_S3C2412);
}
static int __init s3c24xxfb_probe(struct platform_device *pdev,
enum s3c_drv_type drv_type)
{
struct s3c2410fb_info *info;
struct s3c2410fb_display *display;
struct fb_info *fbinfo;
struct s3c2410fb_mach_info *mach_info;
struct resource *res;
int ret;
int irq;
int i;
int size;
u32 lcdcon1;
mach_info = pdev->dev.platform_data; //得到s3c2410fb_mach_info類型結構體變量,保存在內核中的平台設備數據
if (mach_info == NULL) { //沒有相關的平台設備,LCD驅動程序提前退出
dev_err(&pdev->dev,
"no platform data for lcd, cannot attach\n");
return -EINVAL;
}
if (mach_info->default_display >= mach_info->num_displays) {
dev_err(&pdev->dev, "default is %d but only %d displays\n",
mach_info->default_display, mach_info->num_displays);
return -EINVAL;
}
display = mach_info->displays + mach_info->default_display; //獲取內核定義的Framebuffer平台設備的LCD配置信息結構體數據
irq = platform_get_irq(pdev, 0); //獲取LCD中斷號
if (irq < 0) { //<0,沒有中斷,返回相應的錯誤
dev_err(&pdev->dev, "no irq for device\n");
return -ENOENT;
}
fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev); //申請一個struct s3c2410_info結構體空間,存儲驅動相關數據
if (!fbinfo)
return -ENOMEM;
platform_set_drvdata(pdev, fbinfo); //這4行,填充info結構體變量
info = fbinfo->par;
info->dev = &pdev->dev;
info->drv_type = drv_type;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //獲得I/O端口資源,大小1M,
if (res == NULL) {
dev_err(&pdev->dev, "failed to get memory registers\n");
ret = -ENXIO;
goto dealloc_fb;
}
size = (res->end - res->start) + 1;
info->mem = request_mem_region(res->start, size, pdev->name); //申請LCD設備的I/O端口所占用的I/O空間
if (info->mem == NULL) {
dev_err(&pdev->dev, "failed to get memory region\n");
ret = -ENOENT;
goto dealloc_fb;
}
info->io = ioremap(res->start, size); //將LCD的I/O端口占用的這段I/O空間映射到內存的虛擬地址:I/O空間映射后才能使用
if (info->io == NULL) {
dev_err(&pdev->dev, "ioremap() of registers failed\n");
ret = -ENXIO;
goto release_mem;
}
info->irq_base = info->io + ((drv_type == DRV_S3C2412) ? S3C2412_LCDINTBASE : S3C2410_LCDINTBASE);
dprintk("devinit\n");
strcpy(fbinfo->fix.id, driver_name);
/* Stop the video */
lcdcon1 = readl(info->io + S3C2410_LCDCON1);
writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1);
fbinfo->fix.type = FB_TYPE_PACKED_PIXELS;
fbinfo->fix.type_aux = 0;
fbinfo->fix.xpanstep = 0;
fbinfo->fix.ypanstep = 0;
fbinfo->fix.ywrapstep = 0;
fbinfo->fix.accel = FB_ACCEL_NONE;
fbinfo->var.nonstd = 0;
fbinfo->var.activate = FB_ACTIVATE_NOW;
fbinfo->var.accel_flags = 0;
fbinfo->var.vmode = FB_VMODE_NONINTERLACED;
fbinfo->fbops = &s3c2410fb_ops;
fbinfo->flags = FBINFO_FLAG_DEFAULT;
fbinfo->pseudo_palette = &info->pseudo_pal;
for (i = 0; i < 256; i++)
info->palette_buffer[i] = PALETTE_BUFF_CLEAR;
ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info);
if (ret) {
dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret);
ret = -EBUSY;
goto release_regs;
}
info->clk = clk_get(NULL, "lcd"); //獲得LCD時鍾,因為各種那個控制信號的延遲都與LCD的時鍾有關
if (!info->clk || IS_ERR(info->clk)) {
printk(KERN_ERR "failed to get lcd clock source\n");
ret = -ENOENT;
goto release_irq;
}
clk_enable(info->clk); //使能時鍾
dprintk("got and enabled clock\n");
msleep(1);
/* find maximum required memory size for display */
for (i = 0; i < mach_info->num_displays; i++) {
unsigned long smem_len = mach_info->displays[i].xres;
smem_len *= mach_info->displays[i].yres;
smem_len *= mach_info->displays[i].bpp;
smem_len >>= 3;
if (fbinfo->fix.smem_len < smem_len)
fbinfo->fix.smem_len = smem_len;
}
/* Initialize video memory */
ret = s3c2410fb_map_video_memory(fbinfo); //分配DRAM內存給Framebuffer,並且初始化這段內存
if (ret) {
printk(KERN_ERR "Failed to allocate video RAM: %d\n", ret);
ret = -ENOMEM;
goto release_clock;
}
dprintk("got video memory\n");
fbinfo->var.xres = display->xres;
fbinfo->var.yres = display->yres;
fbinfo->var.bits_per_pixel = display->bpp;
s3c2410fb_init_registers(fbinfo); //初始化LCD控制器相關的寄存器
s3c2410fb_check_var(&fbinfo->var, fbinfo); //檢查Framebuffer的相關參數
ret = register_framebuffer(fbinfo); //注冊這個幀緩沖設備fb_info到系統中
if (ret < 0) {
printk(KERN_ERR "Failed to register framebuffer device: %d\n",
ret);
goto free_video_memory;
}
/* create device files */
ret = device_create_file(&pdev->dev, &dev_attr_debug); //對設備文件系統支持
if (ret) {
printk(KERN_ERR "failed to add debug attribute\n");
}
printk(KERN_INFO "fb%d: %s frame buffer device\n",
fbinfo->node, fbinfo->fix.id);
return 0;
//以下是錯誤處理:釋放時鍾、取消內存映射、釋放資源
free_video_memory:
s3c2410fb_unmap_video_memory(fbinfo);
release_clock:
clk_disable(info->clk);
clk_put(info->clk);
release_irq:
free_irq(irq, info);
release_regs:
iounmap(info->io);
release_mem:
release_resource(info->mem);
kfree(info->mem);
dealloc_fb:
platform_set_drvdata(pdev, NULL);
framebuffer_release(fbinfo);
return ret;
}
與s3c2412fb_probe()函數完成相反功能的是s3c2410fb_remove():
static int s3c2410fb_remove(struct platform_device *pdev)
{
struct fb_info *fbinfo = platform_get_drvdata(pdev); //調用platform_get_drvdata()函數從平台設備中獲得fbinfo結構體指針,該結構包含了Framebuffer主要信息
struct s3c2410fb_info *info = fbinfo->par; //從fbinfo->par中獲得info指針
int irq;
unregister_framebuffer(fbinfo); //注銷幀緩存設備
s3c2410fb_lcd_enable(info, 0); //關閉LCD控制器,第二個參數為1,打開;為0,關閉
msleep(1); //等待1ms時間,等待向LCD控制器的寄存器寫入成功。因為寄存器的寫入速度要比程序執行速度慢得多
s3c2410fb_unmap_video_memory(fbinfo); //釋放顯示緩沖區
if (info->clk) { //釋放時鍾源
clk_disable(info->clk);
clk_put(info->clk);
info->clk = NULL;
}
irq = platform_get_irq(pdev, 0);
free_irq(irq, info); //釋放IRQ中斷
iounmap(info->io); //取消寄存器的內存映射
release_resource(info->mem); //釋放內存區域占用的資源
kfree(info->mem); //釋放內存
platform_set_drvdata(pdev, NULL);
framebuffer_release(fbinfo); //向內核注銷Framebuffer設備
return 0;
}