16.Linux-LCD驅動(詳解)

在上一節LCD層次分析中,得出寫個LCD驅動入口函數,須要如下4步:html

1) 分配一個fb_info結構體: framebuffer_alloc();linux

2) 設置fb_info數組

3) 設置硬件相關的操做緩存

4) 使能LCD,並註冊fb_info: register_framebuffer()app

 

本節須要用到的函數:ide

void *dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);  //分配DMA緩存區給顯存
//返回值爲:申請到的DMA緩衝區的虛擬地址,若爲NULL,表示分配失敗,則須要使用dma_free_writecombine()釋放內存,避免內存泄漏
//參數以下: //*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緩存以及禁止寫入緩衝區post


 

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.測試運行

測試有兩種: 

(echocat命令詳解入口地址: http://www.cnblogs.com/lifexy/p/7601122.html)

echo hello> /dev/tty1     // LCD上便顯示hello字段

cat Makefile>/dev/tty1    // LCD上便顯示Makeflie文件的內容

 

4.1使用上節的鍵盤驅動在LCD終端打印命令行

vi  /etc/inittab         //修改inittab, inittab:配置文件,用於啓動init進程時,讀取inittab

添加->tty1::askfirst:-/bin/sh   //將sh進程(命令行)輸出到tty1裏,也就是使LCD輸出信息

而後重啓,insmod裝載3個/drivers/video下編譯好的模塊,再來insmod裝載LCD驅動模塊,tty1設備便有了,就能看到提示信息:

 

以下圖,咱們insmod上一節的鍵盤驅動後,按下enter鍵,便能在LCD終端上操做linux了

(上一節的鍵盤驅動詳解入口地址: http://www.cnblogs.com/lifexy/p/7553861.html)

 

從上圖能夠看到按下enter鍵,它就啓動了一個進程號772的-sh進程,以下圖發現這個-sh的描述符都指向了tty1:

 

 

下章學習:

18.Llinux-觸摸驅動(詳解)

相關文章
相關標籤/搜索