在上一節LCD層次分析中,得出寫個LCD驅動入口函數,需要以下4步: 1) 分配一個fb_info結構體: framebuffer_alloc(); 2) 設置fb_info 3) 設置硬體相關的操作 4) 使能LCD,並註冊fb_info: register_framebuffer() 本節需要用 ...
在上一節LCD層次分析中,得出寫個LCD驅動入口函數,需要以下4步:
1) 分配一個fb_info結構體: framebuffer_alloc();
2) 設置fb_info
3) 設置硬體相關的操作
4) 使能LCD,並註冊fb_info: register_framebuffer()
本節需要用到的函數:
void *dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp); //分配DMA緩存區給顯存 //參數如下: //*dev:指針,這裡填0,表示這個申請的緩衝區里沒有內容 //size:分配的地址大小(位元組單位) //*handle:申請到的物理起始地址 //gfp:分配出來的記憶體參數,標誌定義在<linux/gfp.h>,常用標誌如下: //GFP_ATOMIC 用來從中斷處理和進程上下文之外的其他代碼中分配記憶體. 從不睡眠. //GFP_KERNEL 內核記憶體的正常分配. 可能睡眠. //GFP_USER 用來為用戶空間頁來分配記憶體; 它可能睡眠.
分配一段DMA緩存區,分配出來的記憶體不使用cache緩存(因為DMA傳輸不需要CPU)
它和 dma_alloc_coherent ()函數相似,不過 dma_alloc_coherent ()函數是分配出來的記憶體會禁止cache緩存以及禁止寫入緩衝區
dma_free_writecombine(dev,size,cpu_addr,handle); //釋放緩存 //cpu_addr:虛擬地址, //handle:物理地址
釋放DMA緩衝區, dev和size參數和上面的一樣
struct fb_info *framebuffer_alloc(size_t size, struct device *dev); //申請一個fb_info結構體, //size:額外的記憶體, //*dev:指針, 這裡填0,表示這個申請的結構體里沒有內容
int register_framebuffer(struct fb_info *fb_info); //向內核中註冊fb_info結構體,若記憶體不夠,註冊失敗會返回負數 int unregister_framebuffer(struct fb_info *fb_info) ; //註銷內核中fb_info結構體
本節需要用到的結構體:
fb_info結構體如下:
struct fb_info { ... ... struct fb_var_screeninfo var; //可變的參數 struct fb_fix_screeninfo fix; //固定的參數 ... ... struct fb_ops *fbops; //操作函數 ... ... char __iomem *screen_base; //顯存虛擬起始地址 unsigned long screen_size; //顯存虛擬地址長度
void *pseudo_palette; //假的16色調色板,裡面存放了16色的數據,可以通過8bpp數據來找到調色板裡面的16色顏色索引值,模擬出16色顏色來,節省記憶體,不需要的話就指向一個不用的數組即可 ... ... };
其中操作函數fb_info-> fbops 結構體寫法如下:
static struct fb_ops s3c_lcdfb_ops = { .owner = THIS_MODULE,
.fb_setcolreg = my_lcdfb_setcolreg,//設置調色板fb_info-> pseudo_palette,自己構造該函數 .fb_fillrect = cfb_fillrect, //填充矩形,用/drivers/video/ cfbfillrect.c里的函數即可 .fb_copyarea = cfb_copyarea, //複製數據, 用/drivers/video/cfbcopyarea.c里的函數即可 .fb_imageblit = cfb_imageblit, //繪畫圖形, 用/drivers/video/imageblit.c里的函數即可 };
固定的參數fb_info-> fix 結構體如下:
struct fb_fix_screeninfo { char id[16]; //id名字 unsigned long smem_start; //framebuffer物理起始地址 __u32 smem_len; //framebuffer長度,位元組為單位 __u32 type; //lcd類型,預設值0即可 __u32 type_aux; //附加類型,為0 __u32 visual; //畫面設置,常用參數如下 // FB_VISUAL_MONO01 0 單色,0:白色,1:黑色 // FB_VISUAL_MONO10 1 單色,1:白色,0:黑色 // FB_VISUAL_TRUECOLOR 2 真彩(TFT:真彩) // FB_VISUAL_PSEUDOCOLOR 3 偽彩 // FB_VISUAL_DIRECTCOLOR 4 直彩 __u16 xpanstep; /*如果沒有硬體panning就賦值為0 */ __u16 ypanstep; /*如果沒有硬體panning就賦值為0 */ __u16 ywrapstep; /*如果沒有硬體ywrap就賦值為0 */ __u32 line_length; /*一行的位元組數 ,例:(RGB565)240*320,那麼這裡就等於240*16/8 */
/*以下成員都可以不需要*/ unsigned long mmio_start; /*記憶體映射IO的起始地址,用於應用層直接訪問寄存器,可以不需要*/ __u32 mmio_len; /* 記憶體映射IO的長度,可以不需要*/ __u32 accel; __u16 reserved[3]; };
可變的參數fb_info-> var 結構體如下:
structfb_var_screeninfo{ __u32xres; /*可見屏幕一行有多少個像素點*/ __u32 yres; /*可見屏幕一列有多少個像素點*/ __u32 xres_virtual; /*虛擬屏幕一行有多少個像素點 */ __u32 yres_virtual; /*虛擬屏幕一列有多少個像素點*/ __u32 xoffset; /*虛擬到可見屏幕之間的行偏移,若可見和虛擬的解析度一樣,就直接設為0*/ __u32 yoffset; /*虛擬到可見屏幕之間的列偏移*/ __u32 bits_per_pixel; /*每個像素的位數即BPP,比如:RGB565則填入16*/ __u32 grayscale; /*非0時,指的是灰度,真彩直接填0即可*/ struct fb_bitfield red; //fb緩存的R位域, fb_bitfield結構體成員如下: //__u32 offset; 區域偏移值,比如RGB565中的R,就在第11位 //__u32 length; 區域長度,比如RGB565的R,共有5位 //__u32 msb_right; msb_right ==0,表示數據左邊最大, msb_right!=0,表示數據右邊最大
struct fb_bitfield green; /*fb緩存的G位域*/ struct fb_bitfield blue; /*fb緩存的B位域*/
/*以下參數都可以不填,預設為0*/ struct fb_bitfield transp; /*透明度,不需要填0即可*/
__u32nonstd; /* != 0表示非標準像素格式*/ __u32 activate; /*設為0即可*/ __u32height; /*外設高度(單位mm),一般不需要填*/ __u32width; /*外設寬度(單位mm),一般不需要填*/ __u32 accel_flags; /*過時的參數,不需要填*/ /* 除了pixclock本身外,其他的都以像素時鐘為 單位*/ __u32pixclock; /*像素時鐘(皮秒)*/ __u32 left_margin; /*行切換,從同步到繪圖之間的延遲*/ __u32right_margin; /*行切換,從繪圖到同步之間的延遲*/ __u32upper_margin; /*幀切換,從同步到繪圖之間的延遲*/ __u32lower_margin; /*幀切換,從繪圖到同步之間的延遲*/ __u32hsync_len; /*水平同步的長度*/ __u32 vsync_len; /*垂直同步的長度*/ __u32 sync; __u32 vmode; __u32 rotate; __u32reserved[5]; /*保留*/ }
1.寫驅動程式:
(驅動設置:參考自帶的LCD平臺驅動drivers/video/s3c2410fb.c )
(LCD控制寄存器設置:參考之前的LCD裸機驅動:http://www.cnblogs.com/lifexy/p/7144890.html)
1.1 步驟如下:
在驅動init入口函數中:
1)分配一個fb_info結構體
2)設置fb_info
2.1)設置固定的參數fb_info-> fix
2.2) 設置可變的參數fb_info-> var
2.3) 設置操作函數fb_info-> fbops
2.4) 設置fb_info 其它的成員
3)設置硬體相關的操作
3.1)配置LCD引腳
3.2)根據LCD手冊設置LCD控制器
3.3)分配顯存(framebuffer),把地址告訴LCD控制器和fb_info
4)開啟LCD,並註冊fb_info: register_framebuffer()
4.1) 直接在init函數中開啟LCD(後面講到電源管理,再來優化)
控制LCDCON5允許PWREN信號,
然後控制LCDCON1輸出PWREN信號,
輸出GPB0高電平來開背光,
4.2) 註冊fb_info
在驅動exit出口函數中:
1)卸載內核中的fb_info
2) 控制LCDCON1關閉PWREN信號,關背光,iounmap註銷地址
3)釋放DMA緩存地址dma_free_writecombine()
4)釋放註冊的fb_info
1.2 具體代碼如下:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/errno.h> #include <linux/string.h> #include <linux/mm.h> #include <linux/slab.h> #include <linux/delay.h> #include <linux/fb.h> #include <linux/init.h> #include <linux/dma-mapping.h> #include <linux/interrupt.h> #include <linux/workqueue.h> #include <linux/wait.h> #include <linux/platform_device.h> #include <linux/clk.h> #include <asm/io.h> #include <asm/uaccess.h> #include <asm/div64.h> #include <asm/mach/map.h> #include <asm/arch/regs-lcd.h> #include <asm/arch/regs-gpio.h> #include <asm/arch/fb.h> /*LCD : 480*272 */ #define LCD_xres 480 //LCD 行解析度 #define LCD_yres 272 //LCD列解析度 /* GPIO prot */ static unsigned long *GPBcon; static unsigned long *GPCcon; static unsigned long *GPDcon; static unsigned long *GPGcon; //GPG4:控制LCD信號 static unsigned long *GPBdat; //GPB0: 控制背光
/* LCD control */ struct lcd_reg{ unsigned long lcdcon1; unsigned long lcdcon2; unsigned long lcdcon3; unsigned long lcdcon4; unsigned long lcdcon5; unsigned long lcdsaddr1; unsigned long lcdsaddr2; unsigned long lcdsaddr3 ; unsigned long redlut; unsigned long greenlut; unsigned long bluelut; unsigned long reserved[9]; unsigned long dithmode; unsigned long tpal ; unsigned long lcdintpnd; unsigned long lcdsrcpnd; unsigned long lcdintmsk; unsigned long tconsel; }; static struct lcd_reg *lcd_reg; static struct fb_info *my_lcd; //定義一個全局變數 static u32 pseudo_palette[16]; //調色板數組,被fb_info->pseudo_palette調用 static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf) { /*內核中的單色都是16位,預設從左到右排列,比如G顏色[0x1f],那麼chan就等於0XF800*/ chan &= 0xffff; chan >>= 16 - bf->length; //右移,將數據靠到位0上 return chan << bf->offset; //左移一定偏移值,放入16色數據中對應的位置 } static int my_lcdfb_setcolreg(unsigned int regno, unsigned int red,unsigned int green, unsigned int blue,unsigned int transp, struct fb_info *info) //設置調色板函數,供內核調用 { unsigned int val; if (regno >=16) //調色板數組不能大於15 return 1; /* 用red,green,blue三個顏色值構造出16色數據val */ val = chan_to_field(red, &info->var.red); val |= chan_to_field(green, &info->var.green); val |= chan_to_field(blue, &info->var.blue); ((u32 *)(info->pseudo_palette))[regno] = val; //放到調色板數組中 return 0; } static struct fb_ops my_lcdfb_ops = { .owner = THIS_MODULE, .fb_setcolreg = my_lcdfb_setcolreg,//調用my_lcdfb_setcolreg()函數,來設置調色板fb_info-> pseudo_palette .fb_fillrect = cfb_fillrect, //填充矩形 .fb_copyarea = cfb_copyarea, //複製數據 .fb_imageblit = cfb_imageblit, //繪畫圖形, }; static int lcd_init(void) { /*1.申請一個fb_info結構體*/ my_lcd= framebuffer_alloc(0,0); /*2.設置fb_info*/ /* 2.1設置固定的參數fb_info-> fix */ /*my_lcd->fix.smem_start 物理地址後面註冊MDA緩存區設置*/ strcpy(my_lcd->fix.id, "mylcd"); //名字 my_lcd->fix.smem_len =LCD_xres*LCD_yres*2; //地址長 my_lcd->fix.type =FB_TYPE_PACKED_PIXELS; my_lcd->fix.visual =FB_VISUAL_TRUECOLOR; //真彩色 my_lcd->fix.line_length =LCD_xres*2; //LCD 一行的位元組 /* 2.2 設置可變的參數fb_info-> var */ my_lcd->var.xres =LCD_xres; //可見屏X 解析度 my_lcd->var.yres =LCD_yres; //可見屏y 解析度 my_lcd->var.xres_virtual =LCD_xres; //虛擬屏x解析度 my_lcd->var.yres_virtual =LCD_yres; //虛擬屏y解析度 my_lcd->var.xoffset = 0; //虛擬到可見屏幕之間的行偏移 my_lcd->var.yoffset =0; //虛擬到可見屏幕之間的行偏移 my_lcd->var.bits_per_pixel=16; //像素為16BPP my_lcd->var.grayscale = 0; //灰色比例 my_lcd->var.red.offset = 11; my_lcd->var.red.length = 5; my_lcd->var.green.offset = 5; my_lcd->var.green.length = 6; my_lcd->var.blue.offset = 0; my_lcd->var.blue.length = 5; /* 2.3 設置操作函數fb_info-> fbops */ my_lcd->fbops = &my_lcdfb_ops; /* 2.4 設置fb_info 其它的成員 */ /*my_lcd->screen_base 虛擬地址在後面註冊MDA緩存區設置*/ my_lcd->pseudo_palette =pseudo_palette; //保存調色板數組 my_lcd->screen_size =LCD_xres * LCD_yres *2; //虛擬地址長 /*3 設置硬體相關的操作*/ /*3.1 配置LCD引腳*/ GPBcon = ioremap(0x56000010, 8); GPBdat = GPBcon+1; GPCcon = ioremap(0x56000020, 4); GPDcon = ioremap(0x56000030, 4); GPGcon = ioremap(0x56000060, 4); *GPBcon &=~(0x03<<(0*2)); *GPBcon |= (0x01<<(0*2)); //PGB0背光 *GPBdat &=~(0X1<<0); //關背光 *GPCcon =0xaaaaaaaa; *GPDcon =0xaaaaaaaa; *GPGcon |=(0x03<<(4*2)); //GPG4:LCD信號 /*3.2 根據LCD手冊設置LCD控制器,參考之前的裸機驅動*/ lcd_reg=ioremap(0X4D000000, sizeof( lcd_reg) ); /*HCLK:100Mhz */ lcd_reg->lcdcon1 = (4<<8) | (0X3<<5) | (0x0C<<1) ; lcd_reg->lcdcon2 = ((3)<<24) | (271<<14) | ((1)<<6) |((0)<<0); lcd_reg->lcdcon3 = ((16)<<19) | (479<<8) | ((10)); lcd_reg->lcdcon4 = (4); lcd_reg->lcdcon5 = (1<<11) | (1<<9) | (1<<8) |(1<<0); lcd_reg->lcdcon1 &=~(1<<0); // 關閉PWREN信號輸出 lcd_reg->lcdcon5 &=~(1<<3); //禁止PWREN信號 /* 3.3 分配顯存(framebuffer),把地址告訴LCD控制器和fb_info*/ my_lcd->screen_base=dma_alloc_writecombine(0,my_lcd->fix.smem_len, &my_lcd->fix.smem_start, GFP_KERNEL); /*lcd控制器的地址必須是物理地址*/ lcd_reg->lcdsaddr1 =(my_lcd->fix.smem_start>>1)&0X3FFFFFFF; //保存緩衝起始地址A[30:1] lcd_reg->lcdsaddr2 =((my_lcd->fix.smem_start+my_lcd->screen_size)>>1)&0X1FFFFF; //保存存緩衝結束地址A[21:1] lcd_reg->lcdsaddr3 =LCD_xres& 0x3ff; //OFFSIZE[21:11]:保存LCD上一行結尾和下一行開頭的地址之間的差
//PAGEWIDTH [10:0]:保存LCD一行占的寬度(半字數為單位) /*4開啟LCD,並註冊fb_info: register_framebuffer()*/ /*4.1 直接在init函數中開啟LCD(後面講到電源管理,再來優化)*/ lcd_reg->lcdcon1 |=1<<0; //輸出PWREN信號 lcd_reg->lcdcon5 |=1<<3; //允許PWREN信號 *GPBdat |=(0X1<<0); //開背光 /*4.2 註冊fb_info*/ register_framebuffer(my_lcd); return 0; }
static int lcd_exit(void) { /* 1卸載內核中的fb_info*/ unregister_framebuffer(my_lcd);
/*2 控制LCDCON1關閉PWREN信號,關背光,iounmap註銷地址*/ lcd_reg->lcdcon1 &=~(1<<0); // 關閉PWREN信號輸出 lcd_reg->lcdcon5 &=~(1<<3); //禁止PWREN信號 *GPBdat &=~(0X1<<4); //關背光 iounmap(GPBcon); iounmap(GPCcon); iounmap(GPDcon); iounmap(GPGcon); /*3.釋放DMA緩存地址dma_free_writecombine()*/ dma_free_writecombine(0,my_lcd->screen_size,my_lcd->screen_base,my_lcd->fix.smem_start); /*4.釋放註冊的fb_info*/ framebuffer_release(my_lcd); return 0; } module_init(lcd_init); module_exit(lcd_exit); MODULE_LICENSE("GPL");
2.重新編譯內核,去掉預設的LCD
make menuconfig ,進入menu菜單重新設置內核參數:
進入Device Drivers-> Graphics support:
<M> S3C2410 LCD framebuffer support //將自帶的LCD驅動設為模塊, 不編進內核中
然後make uImage 編譯內核
make modules 編譯模塊
為什麼要編譯模塊?
因為LCD驅動相關的文件也沒有編進內核,而fb_ops里的成員fb_fillrect(), fb_copyarea(), fb_imageblit()用的都是drivers/video下麵的3個文件,所以需要這3個的.ko模塊,如下圖所示:
3.掛載驅動
將編譯好的LCD驅動模塊 和drivers/video里的3個.ko模塊 放入nfs文件系統目錄中
然後燒寫內核, 先裝載3個/drivers/video下編譯好的模塊,再來裝載LCD驅動模塊
掛載LCD驅動後, 如下圖,可以通過 ls -l /dev/fb* 命令查看已掛載的LCD設備節點:
4.測試運行
測試有兩種:
(echo和cat命令詳解入口地址: http://www.cnblogs.com/lifexy/p/7601122.html)
echo hello> /dev/tty1 // LCD上便顯示hello欄位
cat Makefile>/dev/tty1 // LCD上便顯示Makeflie文件的內容
4.1使用上節的鍵盤驅動在LCD終端運行linux
vi /etc/inittab //修改inittab, inittab:配置文件,用於啟動init進程時,讀取inittab 添加->tty1::askfirst:-/bin/sh //啟動tty1的-sh進程(開機)之前,在LCD終端上列印提示enter信息
然後重啟,insmod裝載3個/drivers/video下編譯好的模塊,再來insmod裝載LCD驅動模塊,tty1設備便有了,就能看到提示信息:
如下圖,我們insmod上一節的鍵盤驅動後,按下enter鍵,便能在LCD終端上操作linux了
(上一節的鍵盤驅動詳解入口地址: http://www.cnblogs.com/lifexy/p/7553861.html)
從上圖可以看到按下enter鍵,它就啟動了一個進程號772的-sh進程,如下圖發現這個-sh的描述符都指向了tty1: