由於我使用的Mini2440開發板采用的LCD為TFT屏,型號為LCD-T35(TD035STEB4)。這一節,我們將參考s3c2410fb.c編寫LCD驅動程序。
一、LCD驅動編寫基礎函數
1.1 dma_alloc_wc
該函數定義在include/linux/dma-mapping.h:
static inline void *dma_alloc_wc(struct device *dev, size_t size, dma_addr_t *dma_addr, gfp_t gfp)
該函數用於申請一段DMA緩沖區,分配出來的內存會禁止cache緩存(因為DMA傳輸不需要CPU)。
返回值為:申請到的DMA緩沖區的虛擬地址,若為NULL,表示分配失敗,則需要使用dma_free_wc釋放內存,避免內存泄漏。
參數如下:
- dev:設備指針;
- size:分配的地址大小(字節單位);
- dma_addr:申請到的物理起始地址;
- gfp:分配出來的內存參數,標志定義在<linux/gfp.h>,常用標志如下:
- GFP_ATOMIC 用來從中斷處理和進程上下文之外的其他代碼中分配內存. 從不睡眠;
- GFP_KERNEL 內核內存的正常分配. 可能睡眠;
- GFP_USER 用來為用戶空間頁來分配內存; 它可能睡眠.;
1.2 dma_free_wc
static inline void dma_free_wc(struct device *dev, size_t size, void *cpu_addr, dma_addr_t dma_addr)
該函數用於釋放DMA緩沖區,參數和dma_alloc_wc一樣。
1.3 framebuffer_alloc
struct fb_info *framebuffer_alloc(size_t size, struct device *dev)
動態申請一個fb_info結構體,參數如下:
- size:額外的內存大小;
- dev:設備指針,用於初始化fb_info->device成員;
二、LCD驅動編寫步驟
2.1 入口函數
在驅動入口函數中實現:
(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引腳:設置GPIO端口C和GPIO端口D用於LCD;
(3.2) 根據LCD手冊設置LCD控制器時序參數;
(3.3) 分配顯存(framebuffer),把顯存地址告訴LCD控制器和fb_info;
(4) 開啟LCD,並注冊fb_info
(4.1) 開啟LCD
-
- 控制LCDCON5允許PWREN信號,開啟背光;
- 控制LCDCON1使能LCD;
(4.2) 注冊fb_info;
2.2 出口函數
在驅動出口函數中實現:
(1) 卸載內核中的fb_info;
(2) 控制LCDCON1關閉PWREN信號,關背光,iounmap注銷地址;
(3) 釋放DMA緩存地址dma_free_wc;
(4) 釋放注冊的fb_info;
三、LCD驅動編寫
首先在/work./sambashare/drivers創建項目文件夾11.lcd_dev,然后我們在11.lcd_dev下創建lcd_dev.c文件。首先我們引入頭文件,並定義一些常亮和全局變量等:
#include <linux/module.h> #include <linux/fs.h> #include <linux/platform_device.h> #include <linux/fb.h> #include <linux/dma-mapping.h> #include <linux/clk.h> /* LCD T35參數設定 */ #define LCD_WIDTH 240 /* LCD面板的行寬 */ #define LCD_HEIGHT 320 /* LCD面板的列寬 */ #define VSPW 1 /* 通過計算無效行數垂直同步脈沖寬度決定VSYNC脈沖的高電平寬度 */ #define VBPD 1 /* 垂直同步周期后的無效行數 */ #define LINEVAL (LCD_HEIGHT-1) /* LCD的垂直寬度-1 */ #define VFPD 1 /* 垂直同步周期前的的無效行數 */ #define CLKVAL 7 /* VCLK = HCLK / [(CLKVAL + 1) × 2] */ #define HSPW 9 /* 通過計算VCLK的數水平同步脈沖寬度決定HSYNC脈沖的高電平寬度 */ #define HBPD 19 /* 描述水平后沿為HSYNC的下降沿與有效數據的開始之間的VCLK周期數 */ #define HOZVAL (LCD_WIDTH-1) /* LCD的水平寬度-1 */ #define HFPD 9 /* 水平后沿為有效數據的結束與HSYNC的上升沿之間的VCLK周期數 */ /* 定義fb_info */ static struct fb_info *s3c_lcd; /* 調色板數組,被fb_info->pseudo_palette調用 */ static u32 pseudo_palette[16]; /* 時鍾 */ static struct clk *lcd_clk; /* GPIO相關寄存器 */ static volatile unsigned long *gpcup; static volatile unsigned long *gpccon; static volatile unsigned long *gpdup; static volatile unsigned long *gpdcon; static volatile unsigned long *gpgcon; static volatile unsigned long *gpgdat; static volatile unsigned long *gpgup; /* lcd相關寄存器 */ struct lcd_regs{ 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 lpcsel; }; static volatile struct lcd_regs* lcd_regs;
3.1 分配一個fb_info結構
/* 1. 初始化fb_info */ s3c_lcd = framebuffer_alloc(0,0); if (!s3c_lcd) return -ENOMEM;
3.2 設置fb_info(設置固定的參數fb_info->fix)
首先設置fb_info->fix,成員變量fix類型為struct fb_fix_screeninfo,用於保存LCD屏幕的相關參數,定義在include/uapi/linux/fb.h中:
struct fb_fix_screeninfo { char id[16]; /* identification string eg "TT Builtin" */ unsigned long smem_start; /* Start of frame buffer mem */ /* (physical address) */ __u32 smem_len; /* Length of frame buffer mem */ __u32 type; /* see FB_TYPE_* */ __u32 type_aux; /* Interleave for interleaved Planes */ __u32 visual; /* see FB_VISUAL_* */ __u16 xpanstep; /* zero if no hardware panning */ __u16 ypanstep; /* zero if no hardware panning */ __u16 ywrapstep; /* zero if no hardware ywrap */ __u32 line_length; /* length of a line in bytes */ unsigned long mmio_start; /* Start of Memory Mapped I/O */ /* (physical address) */ __u32 mmio_len; /* Length of Memory Mapped I/O */ __u32 accel; /* Indicate to driver which */ /* specific chip/card we have */ __u16 capabilities; /* see FB_CAP_* */ __u16 reserved[2]; /* Reserved for future compatibility */ };
其中參數含義如下:
- id:唯一標識符;
- smem_start:framebuffer緩沖區物理起始位置(一般是顯示控制器DMA起始地址);
- smem_len:framebuffer緩沖區的的長度,單位為字節;
- type:lcd類型,默認FB_TYPE_PACKED_PIXELS即可,可選參數如下;
#define FB_TYPE_PACKED_PIXELS 0 /* Packed Pixels */ #define FB_TYPE_PLANES 1 /* Non interleaved planes */ #define FB_TYPE_INTERLEAVED_PLANES 2 /* Interleaved planes */ #define FB_TYPE_TEXT 3 /* Text/attributes */ #define FB_TYPE_VGA_PLANES 4 /* EGA/VGA planes */ #define FB_TYPE_FOURCC 5 /* Type identified by a V4L2 FOURCC */
- type_aux:附加類型,默認0即可;
- visual:顏色設置,常用參數如下:
#define FB_VISUAL_MONO01 0 /* Monochr. 1=Black 0=White 單側 0白色 1黑色 */ #define FB_VISUAL_MONO10 1 /* Monochr. 1=White 0=Black 單色 0黑色 1白色 */ #define FB_VISUAL_TRUECOLOR 2 /* True color 真彩 */ #define FB_VISUAL_PSEUDOCOLOR 3 /* Pseudo color (like atari) 偽彩 */ #define FB_VISUAL_DIRECTCOLOR 4 /* Direct color 直彩 */ #define FB_VISUAL_STATIC_PSEUDOCOLOR 5 /* Pseudo color readonly 只讀偽彩 */ #define FB_VISUAL_FOURCC 6 /* Visual identified by a V4L2 FOURCC */
- xpanstep:如果沒有硬件panning賦值0;
- ypanstep:如果沒有硬件panning賦值0;
- ywrapstep:若果沒有硬件ywarp賦值0;
- line_length:一行所占的字節數,例:(RGB565)240*320,那么這里就等於240*16/8;
- mmio_start:內存映射IO的起始地址,用於應用層直接訪問寄存器,可以不需要;
- mmio_len:內存映射IO的長度,可以不需要;
- accel:指明使用的芯片,用於硬件加速,默認FB_ACCEL_NONE即可,可選參數如下;:
#define FB_ACCEL_NONE 0 /* no hardware accelerator */ #define FB_ACCEL_ATARIBLITT 1 /* Atari Blitter */ #define FB_ACCEL_AMIGABLITT 2 /* Amiga Blitter */ #define FB_ACCEL_S3_TRIO64 3 /* Cybervision64 (S3 Trio64) */ #define FB_ACCEL_NCR_77C32BLT 4 /* RetinaZ3 (NCR 77C32BLT) */ ......
- capabilities:查看FB_CAP_;
- reserved:為將來的兼容保留位;
設置:
/* 2. 設置fb_info */ /* 2.1設置固定參數 */ strcpy(s3c_lcd->fix.id, "mylcd"); s3c_lcd->fix.smem_len = LCD_WIDTH * LCD_HEIGHT * 2; // framebuffer緩沖區的大小 每個像素兩個字節 s3c_lcd->fix.type = FB_TYPE_PACKED_PIXELS; s3c_lcd->fix.type_aux = 0; s3c_lcd->fix.xpanstep = 0; s3c_lcd->fix.ypanstep = 0; s3c_lcd->fix.ywrapstep = 0; s3c_lcd->fix.accel = FB_ACCEL_NONE; s3c_lcd->fix.visual = FB_VISUAL_TRUECOLOR; //真彩色 s3c_lcd->fix.line_length = LCD_WIDTH * 2; // LCD一行所占字節數
3.3 設置fb_info(設置可變的參數fb_info->var)
然后設置fb_info->var,成員變量var類型為struct fb_var_screeninfo,用於保存LCD屏幕的相關參數,定義在include/uapi/linux/fb.h中:
struct fb_var_screeninfo { __u32 xres; /* visible resolution */ __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 = color, 1 = grayscale, */ /* >1 = FOURCC */ struct fb_bitfield red; /* bitfield in fb mem if true color, */ 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 */ /* 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 colorspace; /* colorspace for FOURCC-based modes */ __u32 reserved[4]; /* Reserved for future compatibility */ };
其中參數含義如下:
- xres:行分辨率
- yres:列分辨率
- xres_virtual:行虛擬分辨率,設置和硬件一樣即可;
- yres_virtual:列虛擬分辨率,設置和硬件一樣即可;
- xoffset:行偏移,設置為0即可;
- yoffset:列偏移,設置為0即可;
- bits_per_pixel:每個像素用多少位,對於s3c2440不支持18位,只支持16位;
- grayscale:灰度值,默認即可;
- red:RGB:565對於R的offset為從最低位(右起)偏移11位,占5bit;
- green:RGB:565對於G的offset為從最低位(右起)偏移5位,占6bit;
- blue:RGB:565對於B的offset為從最低位(右起)偏移0位,占5bit;
- transp:透明度,默認即可;
- nonstd:0標准像素格式,默認即可;
- activate:默認即可;
- height:LCD物理高度,單位mm;
- width:LCD物理寬度,單位mm;
- accel_flags:默認即可,過時參數;
- pixclock:像素時鍾,單位皮秒;
- left_margin:行切換,從同步到繪圖之間的延遲;
- right_margin:行切換,從繪圖到同步之間的延遲;
- upper_margin:幀切換,從同步到繪圖之間的延遲;
- lower_margin:幀切換,從繪圖到同步之間的延遲;
- hsync_len:水平同步的長度;
- vsync_len:垂直同步的長度;
- sync:參考FB_SYNC_*;
- vmode:參考FB_BMODE_*,默認即可;
- rotate::時針旋轉的角度;
- colorspace:色彩空間;
- reserved:保留值;
設置:
/* 2.2 設置可變參數 */ s3c_lcd->var.xres = LCD_WIDTH; // 行分辨率 s3c_lcd->var.yres = LCD_HEIGHT; // 列分辨率 s3c_lcd->var.xres_virtual = LCD_WIDTH; // 行虛擬分辨率 s3c_lcd->var.yres_virtual = LCD_HEIGHT; // 列虛擬分辨率 s3c_lcd->var.xoffset = 0; //虛擬到可見屏幕之間的行偏移 s3c_lcd->var.yoffset = 0; //虛擬到可見屏幕之間的行偏移 s3c_lcd->var.bits_per_pixel = 16; // 每像素使用位數 /* RGB:565 */ s3c_lcd->var.red.offset = 11; s3c_lcd->var.red.length = 5; s3c_lcd->var.green.offset = 5; s3c_lcd->var.green.length = 6; s3c_lcd->var.blue.offset = 0; s3c_lcd->var.blue.length = 5; s3c_lcd->var.nonstd = 0; s3c_lcd->var.activate = FB_ACTIVATE_NOW; // 使設置的值立即生效 s3c_lcd->var.accel_flags = 0; s3c_lcd->var.vmode = FB_VMODE_NONINTERLACED;
3.4 設置fb_info(設置操作函數fb_info->fbops)
/* 調色板數組,被fb_info->pseudo_palette調用 */ static u32 pseudo_palette[16]; /* * 將內核單色使用bf表示 * @param chan:單色 內核中的單色都是16位 * @param bf:顏色位信息 */ static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf) { chan &= 0xffff; chan >>= 16 - bf->length; return chan << bf->offset; } /* * 調色板操作函數 * @param regno: 調色板數組元素索引 */ static int s3c2440fb_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue, unsigned transp, struct fb_info *info) { unsigned int val; //調色板數組不能大於16 if (regno > 16) return 1; /* 小於16位,進行轉換 */ u32 *pal = info->pseudo_palette; /* 用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); /* 放到調色板數組中 */ pseudo_palette[regno] = val; return 0; } /* * fb_info操作函數fbops */ static struct fb_ops s3c2440fb_ops = { .owner = THIS_MODULE, .fb_setcolreg = s3c2440fb_setcolreg, // 調色板操作函數 設置調色板fb_info-> pseudo_palette .fb_fillrect = cfb_fillrect, // 填充矩形 函數在drivers/video/fbdev/core/cfbfillrect.c中定義 .fb_copyarea = cfb_copyarea, // 復制數據 函數在drivers/video/fbdev/core/cfbcopyarea.c中定義 .fb_imageblit = cfb_imageblit, // 繪制圖形 函數在drivers/video/fbdev/core/cfbimgblt.c中定義 };
3.5 設置fb_info其它的成員
/* 2.3 設置操作函數 */ s3c_lcd->fbops = &s3c2440fb_ops; /* 2.4 其他設置 */ s3c_lcd->flags = FBINFO_FLAG_DEFAULT; s3c_lcd->pseudo_palette = pseudo_palette; // 保存調色板數組 s3c_lcd->screen_size = LCD_WIDTH * LCD_HEIGHT * 2; // framebuffer緩沖區的大小 // 時鍾相關,獲取lcd時鍾 lcd_clk = clk_get(NULL, "lcd"); if (IS_ERR(lcd_clk)) { printk("failed to get lcd clock source\n"); return PTR_ERR(lcd_clk); } clk_prepare_enable(lcd_clk); // 時鍾使能 printk("got and enabled clock\n");
3.6 設置硬件相關的操作(配置GPIO用於LCD)
參考之前介紹的Mini2440裸機開發之LCD編程(GB2312、ASCII字庫制作)初始化GPIO,主要是GPCCON、GPDCON寄存器:
/* 3.硬件相關操作 */ /* 3.1 配置GPIO口用於LCD */ gpcup = ioremap(0x56000028,4); gpccon = ioremap(0x56000020,4); gpdup = ioremap(0x56000038,4); gpdcon = ioremap(0x56000030,4); gpgcon = ioremap(0x56000060,12); gpgdat = gpgcon + 1; gpgup = gpgdat + 1; // GPIO管腳用於VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND *gpcup = 0xffffffff; *gpccon = 0xaaaaaaaa; // GPIO管腳用於VD[23:8] *gpdup = 0xffffffff; *gpdcon = 0xaaaaaaaa; /* Pull - up disable */ *gpgup |= (1 << 4); // 設置GPG4引腳為LCD_PWREN模式 *gpgcon |= (3 << 8);
3.7 設置硬件相關的操作(根據LCD手冊設置LCD控制器時許參數)
/* 3.2 根據LCD手冊設置LCD控制器, 比如VCLK的頻率等 */ lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs)); printk("lcd_regs=%px map_size %u\n", lcd_regs, sizeof(struct lcd_regs)); /* [17:8] CLKVAL * [6:5] PNRMODE;選擇顯示模式 * 00 = 4 位雙掃描顯示模式 01 = 4 位單掃描顯示模式(STN) * 10 = 8 位單掃描顯示模式 11 = TFT LCD 面板 * [4:1] BPPMODE 選擇BPP(位每像素)模式 1100 = TFT 的16 BPP * [0] ENVID LCD 視頻輸出和邏輯使能/禁止。 * 0 = 禁止視頻輸出和LCD 控制信號 1 = 允許視頻輸出和LCD 控制信號 */ lcd_regs->lcdcon1 = (CLKVAL<<8)| (3<<5) | (0xC<<1); /* 16 bpp for TFT */ /* [31:24] VBPD:幀同步信號的后肩 * [23:14] LINEVAL:LCD面板的垂直尺寸 * [13:6] VFPD:幀同步信號的前肩 * [5:0] VSPW:同步信號的脈寬 */ lcd_regs->lcdcon2 = (VBPD<<24)|(LINEVAL<<14)|(VFPD<<6)|(VSPW); /* [25:19] HBPD: 行同步信號的后肩 * [18:8] HOZVAL: LCD面板的水平尺寸 * [7:0] HFPD: 行同步信號的前肩 */ lcd_regs->lcdcon3 = (HBPD<<19)|(HOZVAL<<8)|(HFPD); lcd_regs->lcdcon4 = (HSPW); /* [11] FRM565: 此位選擇16 BPP 輸出視頻數據的格式 0 = 5:5:5:1 格式 1= 5:6:5 格式 * [10] STN/TFT: 此位控制VCLK 有效沿的極性 * [9] INVVLINE: STN/TFT:此位表明VLINE/HSYNC 脈沖極性 0 = 正常 1 = 反轉 * [8] INVVFRAME: STN/TFT:此位表明VFRAME/VSYNC 脈沖極性 0 = 正常 1 = 反轉 * VLINE/HSYNC 脈沖極性、VFRAME/VSYNC 脈沖極性反轉(LCD型號決定) * [0] HWSWP: STN/TFT:半字節交換控制位 0 = 交換禁止 1 = 交換使能 */ lcd_regs->lcdcon5 = ((1<<11) | (1<<10) | (1 << 9) | (1 << 8) | (1 << 0)); /* 關閉PWREN信號輸出 */ lcd_regs->lcdcon1 &= ~(1<<0); /* 禁止PWREN信號 */ lcd_regs->lcdcon5 &=~(1<<3); /* 第一位設置為1 選擇輸出分片率類型0:320 * 240 1:240*320 */ lcd_regs->lpcsel = ((0xCE6) & ~7) | 1<<1;
3.8 設置硬件相關的操作(分配顯存)
/* 3.3 分配顯存(framebuffer), 並把地址告訴LCD控制器 */ ret = s3c2440fb_map_video_memory(s3c_lcd); if (ret) { printk("failed to allocate video RAM: %d\n", ret); // todo 這里應該進行資源釋放,我這里就不釋放了 return -ENOMEM; } /* [29:21] LCDBANK:存放幀緩沖起始地址的[30:22] * [20:0] LCDBASEU: 存放幀緩沖起始地址的[21:1] */ lcd_regs->lcdsaddr1 = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30); /* 存放幀結束地址[21:1] */ lcd_regs->lcdsaddr2 = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1) & 0x1fffff; /* [21:11] OFFSIZE:表示虛擬屏偏移尺寸 即上一行最后像素點到下一行第一個像素點之間間隔多少個像素點 * [10:0] PAGEWIDTH:表示行的寬度(半字為單位 16bit) */ lcd_regs->lcdsaddr3 = LCD_WIDTH & 0x3ff;
其中s3c2440fb_map_video_memory:
#define samsung_device_dma_mask (*((u64[]) { DMA_BIT_MASK(32) })) /*platform設備 */ static struct platform_device s3c_device_lcd = { .name = "s3c2440-lcd", .id = -1, .num_resources = 0, .dev = { .dma_mask = &samsung_device_dma_mask, .coherent_dma_mask = DMA_BIT_MASK(32), } }; /* * s3c2440fb_map_video_memory(): * Allocates the DRAM memory for the frame buffer. This buffer is * remapped into a non-cached, non-buffered, memory region to * allow palette and pixel writes to occur without flushing the * cache. Once this area is remapped, all virtual memory * access to the video memory should occur at the new region. */ static int s3c2440fb_map_video_memory(struct fb_info *fbinfo) { dma_addr_t map_dma; unsigned long map_size = PAGE_ALIGN(fbinfo->fix.smem_len); printk("map_video_memory(info=%px) map_size %u\n", fbinfo, map_size); // 第一個參數不能為空 否則會拋異常 fbinfo->screen_base = dma_alloc_wc(&s3c_device_lcd.dev, map_size, &map_dma, GFP_KERNEL); if (fbinfo->screen_base) { /* prevent initial garbage on screen */ printk("map_video_memory: clear %px:%08x\n", fbinfo->screen_base, map_size); memset(fbinfo->screen_base, 0x00, map_size); fbinfo->fix.smem_start = map_dma; printk("map_video_memory: dma=%08lx cpu=%p size=%08x\n", fbinfo->fix.smem_start, fbinfo->screen_base, map_size); } else { printk("map_video_memory fail\n"); } return fbinfo->screen_base ? 0 : -ENOMEM; }
3.9 開啟LCD
/* 4.1 開啟LCD */ /* 控制LCDCON5允許PWREN信號 */ lcd_regs->lcdcon5 |= (1 << 3); /* 控制LCDCON1 LCD使能 */ lcd_regs->lcdcon1 |= (1<<0); /* 輸出高電平, 使能背光 */ *gpgdat |= 1<<4; printk("lcdcon[1] = 0x%08lx\n", lcd_regs->lcdcon1); printk("lcdcon[2] = 0x%08lx\n", lcd_regs->lcdcon2); printk("lcdcon[3] = 0x%08lx\n", lcd_regs->lcdcon3); printk("lcdcon[4] = 0x%08lx\n", lcd_regs->lcdcon4); printk("lcdcon[5] = 0x%08lx\n", lcd_regs->lcdcon5); printk("lcdsaddr[1]= 0x%08lx\n", lcd_regs->lcdsaddr1); printk("lcdsaddr[2]= 0x%08lx\n", lcd_regs->lcdsaddr2); printk("lcdsaddr[3]= 0x%08lx\n", lcd_regs->lcdsaddr3);
3.10 注冊
/* 4.2 注冊 */ register_framebuffer(s3c_lcd);
3.11 lcd_dev.c完整代碼

#include <linux/module.h> #include <linux/fs.h> #include <linux/platform_device.h> #include <linux/fb.h> #include <linux/dma-mapping.h> #include <linux/clk.h> /* LCD T35參數設定 */ #define LCD_WIDTH 240 /* LCD面板的行寬 */ #define LCD_HEIGHT 320 /* LCD面板的列寬 */ #define VSPW 1 /* 通過計算無效行數垂直同步脈沖寬度決定VSYNC脈沖的高電平寬度 */ #define VBPD 1 /* 垂直同步周期后的無效行數 */ #define LINEVAL (LCD_HEIGHT-1) /* LCD的垂直寬度-1 */ #define VFPD 1 /* 垂直同步周期前的的無效行數 */ #define CLKVAL 7 /* VCLK = HCLK / [(CLKVAL + 1) × 2] */ #define HSPW 9 /* 通過計算VCLK的數水平同步脈沖寬度決定HSYNC脈沖的高電平寬度 */ #define HBPD 19 /* 描述水平后沿為HSYNC的下降沿與有效數據的開始之間的VCLK周期數 */ #define HOZVAL (LCD_WIDTH-1) /* LCD的水平寬度-1 */ #define HFPD 9 /* 水平后沿為有效數據的結束與HSYNC的上升沿之間的VCLK周期數 */ /* 定義fb_info */ static struct fb_info *s3c_lcd; /* 調色板數組,被fb_info->pseudo_palette調用 */ static u32 pseudo_palette[16]; /* 時鍾 */ static struct clk *lcd_clk; /* GPIO相關寄存器 */ static volatile unsigned long *gpcup; static volatile unsigned long *gpccon; static volatile unsigned long *gpdup; static volatile unsigned long *gpdcon; static volatile unsigned long *gpgcon; static volatile unsigned long *gpgdat; static volatile unsigned long *gpgup; /* lcd相關寄存器 */ struct lcd_regs{ 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 lpcsel; }; static volatile struct lcd_regs* lcd_regs; /* * 將內核單色使用bf表示 * @param chan:單色 內核中的單色都是16位 * @param bf:顏色位信息 */ static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf) { chan &= 0xffff; chan >>= 16 - bf->length; return chan << bf->offset; } /* * 調色板操作函數 * @param regno: 調色板數組元素索引 */ static int s3c2440fb_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue, unsigned transp, struct fb_info *info) { unsigned int val; //調色板數組不能大於15 if (regno >= 16) 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); /* 放到調色板數組中 */ pseudo_palette[regno] = val; return 0; } #define samsung_device_dma_mask (*((u64[]) { DMA_BIT_MASK(32) })) /*platform設備 */ static struct platform_device s3c_device_lcd = { .name = "s3c2440-lcd", .id = -1, .num_resources = 0, .dev = { .dma_mask = &samsung_device_dma_mask, .coherent_dma_mask = DMA_BIT_MASK(32), } }; /* * s3c2440fb_map_video_memory(): * Allocates the DRAM memory for the frame buffer. This buffer is * remapped into a non-cached, non-buffered, memory region to * allow palette and pixel writes to occur without flushing the * cache. Once this area is remapped, all virtual memory * access to the video memory should occur at the new region. */ static int s3c2440fb_map_video_memory(struct fb_info *fbinfo) { dma_addr_t map_dma; unsigned long map_size = PAGE_ALIGN(fbinfo->fix.smem_len); printk("map_video_memory(info=%px) map_size %u\n", fbinfo, map_size); // 第一個參數不能為空 否則會拋異常 fbinfo->screen_base = dma_alloc_wc(&s3c_device_lcd.dev, map_size, &map_dma, GFP_KERNEL); if (fbinfo->screen_base) { /* prevent initial garbage on screen */ printk("map_video_memory: clear %px:%08x\n", fbinfo->screen_base, map_size); memset(fbinfo->screen_base, 0x00, map_size); fbinfo->fix.smem_start = map_dma; printk("map_video_memory: dma=%08lx cpu=%p size=%08x\n", fbinfo->fix.smem_start, fbinfo->screen_base, map_size); } else { printk("map_video_memory fail\n"); } return fbinfo->screen_base ? 0 : -ENOMEM; } /* * fb_info操作函數fbops */ static struct fb_ops s3c2440fb_ops = { .owner = THIS_MODULE, .fb_setcolreg = s3c2440fb_setcolreg, // 調色板操作函數 設置調色板fb_info-> pseudo_palette .fb_fillrect = cfb_fillrect, // 填充矩形 函數在drivers/video/fbdev/core/cfbfillrect.c中定義 .fb_copyarea = cfb_copyarea, // 復制數據 函數在drivers/video/fbdev/core/cfbcopyarea.c中定義 .fb_imageblit = cfb_imageblit, // 繪制圖形 函數在drivers/video/fbdev/core/cfbimgblt.c中定義 }; /* * lcd驅動模塊入口 */ static int lcd_init(void) { int ret; printk("lcd device registered\n"); /* 1. 初始化fb_info */ s3c_lcd = framebuffer_alloc(0,NULL); if (!s3c_lcd) { return -ENOMEM; } printk("s3c_lcd=%px\n", s3c_lcd); /* 2. 設置fb_info */ /* 2.1設置固定參數 */ strcpy(s3c_lcd->fix.id, "mylcd"); s3c_lcd->fix.smem_len = LCD_WIDTH * LCD_HEIGHT * 2; // framebuffer緩沖區的大小 每個像素兩個字節 s3c_lcd->fix.type = FB_TYPE_PACKED_PIXELS; s3c_lcd->fix.type_aux = 0; s3c_lcd->fix.xpanstep = 0; s3c_lcd->fix.ypanstep = 0; s3c_lcd->fix.ywrapstep = 0; s3c_lcd->fix.accel = FB_ACCEL_NONE; s3c_lcd->fix.visual = FB_VISUAL_TRUECOLOR; //真彩色 s3c_lcd->fix.line_length = LCD_WIDTH * 2; // LCD一行所占字節數 /* 2.2 設置可變參數 */ s3c_lcd->var.xres = LCD_WIDTH; // 行分辨率 s3c_lcd->var.yres = LCD_HEIGHT; // 列分辨率 s3c_lcd->var.xres_virtual = LCD_WIDTH; // 行虛擬分辨率 s3c_lcd->var.yres_virtual = LCD_HEIGHT; // 列虛擬分辨率 s3c_lcd->var.xoffset = 0; //虛擬到可見屏幕之間的行偏移 s3c_lcd->var.yoffset = 0; //虛擬到可見屏幕之間的行偏移 s3c_lcd->var.bits_per_pixel = 16; // 每像素使用位數 /* RGB:565 */ s3c_lcd->var.red.offset = 11; s3c_lcd->var.red.length = 5; s3c_lcd->var.green.offset = 5; s3c_lcd->var.green.length = 6; s3c_lcd->var.blue.offset = 0; s3c_lcd->var.blue.length = 5; s3c_lcd->var.nonstd = 0; s3c_lcd->var.activate = FB_ACTIVATE_NOW; // 使設置的值立即生效 s3c_lcd->var.accel_flags = 0; s3c_lcd->var.vmode = FB_VMODE_NONINTERLACED; /* 2.3 設置操作函數 */ s3c_lcd->fbops = &s3c2440fb_ops; /* 2.4 其他設置 */ s3c_lcd->flags = FBINFO_FLAG_DEFAULT; s3c_lcd->pseudo_palette = pseudo_palette; // 保存調色板數組 s3c_lcd->screen_size = LCD_WIDTH * LCD_HEIGHT * 2; // framebuffer緩沖區的大小 // 時鍾相關,獲取lcd時鍾 lcd_clk = clk_get(NULL, "lcd"); if (IS_ERR(lcd_clk)) { printk("failed to get lcd clock source\n"); return PTR_ERR(lcd_clk); } clk_prepare_enable(lcd_clk); // 時鍾使能 printk("got and enabled clock\n"); /* 3.硬件相關操作 */ /* 3.1 配置GPIO口用於LCD */ gpcup = ioremap(0x56000028,4); gpccon = ioremap(0x56000020,4); gpdup = ioremap(0x56000038,4); gpdcon = ioremap(0x56000030,4); gpgcon = ioremap(0x56000060,12); gpgdat = gpgcon + 1; gpgup = gpgdat + 1; // GPIO管腳用於VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND *gpcup = 0xffffffff; *gpccon = 0xaaaaaaaa; // GPIO管腳用於VD[23:8] *gpdup = 0xffffffff; *gpdcon = 0xaaaaaaaa; /* Pull - up disable */ *gpgup |= (1 << 4); // 設置GPG4引腳為LCD_PWREN模式 *gpgcon |= (3 << 8); /* 3.2 根據LCD手冊設置LCD控制器, 比如VCLK的頻率等 */ lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs)); printk("lcd_regs=%px map_size %u\n", lcd_regs, sizeof(struct lcd_regs)); /* [17:8] CLKVAL * [6:5] PNRMODE;選擇顯示模式 * 00 = 4 位雙掃描顯示模式 01 = 4 位單掃描顯示模式(STN) * 10 = 8 位單掃描顯示模式 11 = TFT LCD 面板 * [4:1] BPPMODE 選擇BPP(位每像素)模式 1100 = TFT 的16 BPP * [0] ENVID LCD 視頻輸出和邏輯使能/禁止。 * 0 = 禁止視頻輸出和LCD 控制信號 1 = 允許視頻輸出和LCD 控制信號 */ lcd_regs->lcdcon1 = (CLKVAL<<8)| (3<<5) | (0xC<<1); /* 16 bpp for TFT */ /* [31:24] VBPD:幀同步信號的后肩 * [23:14] LINEVAL:LCD面板的垂直尺寸 * [13:6] VFPD:幀同步信號的前肩 * [5:0] VSPW:同步信號的脈寬 */ lcd_regs->lcdcon2 = (VBPD<<24)|(LINEVAL<<14)|(VFPD<<6)|(VSPW); /* [25:19] HBPD: 行同步信號的后肩 * [18:8] HOZVAL: LCD面板的水平尺寸 * [7:0] HFPD: 行同步信號的前肩 */ lcd_regs->lcdcon3 = (HBPD<<19)|(HOZVAL<<8)|(HFPD); lcd_regs->lcdcon4 = (HSPW); /* [11] FRM565: 此位選擇16 BPP 輸出視頻數據的格式 0 = 5:5:5:1 格式 1= 5:6:5 格式 * [10] STN/TFT: 此位控制VCLK 有效沿的極性 * [9] INVVLINE: STN/TFT:此位表明VLINE/HSYNC 脈沖極性 0 = 正常 1 = 反轉 * [8] INVVFRAME: STN/TFT:此位表明VFRAME/VSYNC 脈沖極性 0 = 正常 1 = 反轉 * VLINE/HSYNC 脈沖極性、VFRAME/VSYNC 脈沖極性反轉(LCD型號決定) * [0] HWSWP: STN/TFT:半字節交換控制位 0 = 交換禁止 1 = 交換使能 */ lcd_regs->lcdcon5 = ((1<<11) | (1<<10) | (1 << 9) | (1 << 8) | (1 << 0)); /* 關閉PWREN信號輸出 */ lcd_regs->lcdcon1 &= ~(1<<0); /* 禁止PWREN信號 */ lcd_regs->lcdcon5 &=~(1<<3); /* 第一位設置為1 選擇輸出分片率類型0:320 * 240 1:240*320 */ lcd_regs->lpcsel = ((0xCE6) & ~7) | 1<<1; /* 3.3 分配顯存(framebuffer), 並把地址告訴LCD控制器 */ ret = s3c2440fb_map_video_memory(s3c_lcd); if (ret) { printk("failed to allocate video RAM: %d\n", ret); // todo 這里應該進行資源釋放,我這里就不釋放了 return -ENOMEM; } /* [29:21] LCDBANK:存放幀緩沖起始地址的[30:22] * [20:0] LCDBASEU: 存放幀緩沖起始地址的[21:1] */ lcd_regs->lcdsaddr1 = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30); /* 存放幀結束地址[21:1] */ lcd_regs->lcdsaddr2 = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1) & 0x1fffff; /* [21:11] OFFSIZE:表示虛擬屏偏移尺寸 即上一行最后像素點到下一行第一個像素點之間間隔多少個像素點 * [10:0] PAGEWIDTH:表示行的寬度(半字為單位 16bit) */ lcd_regs->lcdsaddr3 = LCD_WIDTH & 0x3ff; /* 4.1 開啟LCD */ /* 控制LCDCON5允許PWREN信號 */ lcd_regs->lcdcon5 |= (1 << 3); /* 控制LCDCON1 LCD使能 */ lcd_regs->lcdcon1 |= (1<<0); /* 輸出高電平, 使能背光 */ *gpgdat |= 1<<4; printk("lcdcon[1] = 0x%08lx\n", lcd_regs->lcdcon1); printk("lcdcon[2] = 0x%08lx\n", lcd_regs->lcdcon2); printk("lcdcon[3] = 0x%08lx\n", lcd_regs->lcdcon3); printk("lcdcon[4] = 0x%08lx\n", lcd_regs->lcdcon4); printk("lcdcon[5] = 0x%08lx\n", lcd_regs->lcdcon5); printk("lcdsaddr[1]= 0x%08lx\n", lcd_regs->lcdsaddr1); printk("lcdsaddr[2]= 0x%08lx\n", lcd_regs->lcdsaddr2); printk("lcdsaddr[3]= 0x%08lx\n", lcd_regs->lcdsaddr3); /* 4.2 注冊 */ register_framebuffer(s3c_lcd); return 0; } /* * lcd驅動模塊出口 */ static void __exit lcd_exit(void) { printk("lcd device unregistered\n"); unregister_framebuffer(s3c_lcd); /* 禁止LCD使能 */ lcd_regs->lcdcon1 &= ~(1<<0); /* 關閉背光 */ *gpgdat &= ~(1<<4); dma_free_wc(&s3c_device_lcd.dev, s3c_lcd->fix.smem_len, s3c_lcd->screen_base, s3c_lcd->fix.smem_start); iounmap(lcd_regs); iounmap(gpcup); iounmap(gpccon); iounmap(gpdup); iounmap(gpdcon); iounmap(gpgcon); framebuffer_release(s3c_lcd); } module_init(lcd_init); module_exit(lcd_exit); MODULE_LICENSE("GPL");
四、測試
4.1 配置內核
我們切換到linux內核目錄下,
cd /wqoroot@zhengyang:~# cd /work/sambashare/linux-5.2.8/
在linux內核根目錄下執行,生成默認配置文件.config:
make distclean
make s3c2440_defconfig # 這個是之前我之前配置的
進行內核配置:
root@zhengyang:/work/sambashare/linux-5.2.8# make menuconfig
配置步驟如下:
Device Drivers --->
- Graphics support --->
- Frame buffer Device --->
- <Y>Support for frame buffer devices
- <M> S3C2410 LCD framebuffer support
- <M> Samsung S3C framebuffer support
- Frame buffer Device --->
需要注意的是:
Support for frame buffer devices:這個設置成Y就行,如果將這個編譯進內核,同時會將cfbcopyarea.c、cfbfillrect.c、cfbimgblt.c、font_8x16(lib/fonts路徑下)編譯進內核,這樣就不用單獨安裝這些了;
具體參考drivers/video/fbdev/Kconfig:
config FB_CMDLINE bool config FB_NOTIFY bool menuconfig FB tristate "Support for frame buffer devices" select FB_CMDLINE select FB_NOTIFY ---help--- The frame buffer device provides an abstraction for the graphics hardware. It represents the frame buffer of some video hardware and allows application software to access the graphics hardware through a well-defined interface, so the software doesn't need to know anything about the low-level (hardware register) stuff. Frame buffer devices work identically across the different architectures supported by Linux and make the implementation of application programs easier and more portable; at this point, an X server exists which uses the frame buffer device exclusively. On several non-X86 architectures, the frame buffer device is the only way to use the graphics hardware. The device is accessed through special device nodes, usually located in the /dev directory, i.e. /dev/fb*. ... config FB_CFB_FILLRECT tristate depends on FB ---help--- Include the cfb_fillrect function for generic software rectangle filling. This is used by drivers that don't provide their own (accelerated) version. config FB_CFB_COPYAREA tristate depends on FB ---help--- Include the cfb_copyarea function for generic software area copying. This is used by drivers that don't provide their own (accelerated) version. config FB_CFB_IMAGEBLIT tristate depends on FB ---help--- Include the cfb_imageblit function for generic software image blitting. This is used by drivers that don't provide their own (accelerated) version.
修改完配置后,保存文件,輸入文件名s3c2440_defconfig,在當前路徑下生成s3c2440_defconfig:存檔:
mv s3c2440_defconfig ./arch/arm/configs/
此時重新執行:
make s3c2440_defconfig
查看.config文件可以看到:
4.2 編譯內核和模塊
編譯內核:
make V=1 uImage
將uImage復制到tftp服務器路徑下:
cp /work/sambashare/linux-5.2.8/arch/arm/boot/uImage /work/tftpboot/
4.3 燒錄內核
開發板uboot啟動完成后,內核啟動前,按下任意鍵,進入uboot,可以通過print查看uboot中已經設置的環境變量。
設置開發板ip地址,從而可以使用網絡服務:
SMDK2440 # set ipaddr 192.168.0.105 SMDK2440 # save Saving Environment to NAND... Erasing NAND... Erasing at 0x40000 -- 100% complete. Writing to NAND... OK SMDK2440 # ping 192.168.0.200 dm9000 i/o: 0x20000000, id: 0x90000a46 DM9000: running in 16 bit mode MAC: 08:00:3e:26:0a:5b operating at unknown: 0 mode Using dm9000 device host 192.168.0.200 is alive
設置tftp服務器地址,也就是我們ubuntu服務器地址:
set serverip 192.168.0.200 save
下載內核到內存,並寫NAND FLASH:
tftp 30000000 uImage nand erase.part kernel nand write 30000000 kernel
運行結果如下:
SMDK2440 # tftp 30000000 uImage dm9000 i/o: 0x20000000, id: 0x90000a46 DM9000: running in 16 bit mode MAC: 08:00:3e:26:0a:5b operating at unknown: 0 mode Using dm9000 device TFTP from server 192.168.0.200; our IP address is 192.168.0.188 Filename 'uImage'. Load address: 0x30000000 Loading: *################################################################# ################################################################# ################################################################# ############################################################## 429.7 KiB/s done Bytes transferred = 3766128 (397770 hex) SMDK2440 # nand erase.part kernel NAND erase.part: device 0 offset 0x60000, size 0x400000 Erasing at 0x60000 -- 3% complete. ... OK SMDK2440 # nand write 30000000 kernel NAND write: device 0 offset 0x60000, size 0x400000 4194304 bytes written: OK
4.4 編譯LCD驅動
在11.lcd_dev路徑下編譯:
root@zhengyang:/work/sambashare/drivers/11.lcd_dev# cd /work/sambashare/drivers/11.lcd_dev root@zhengyang:/work/sambashare/drivers/11.lcd_dev# make
拷貝驅動文件到nfs文件系統:
root@zhengyang:/work/sambashare/drivers/11.lcd_dev# cp /work/sambashare/drivers/11.lcd_dev/lcd_dev.ko /work/nfs_root/rootfs/
4.5 安裝驅動
重啟開發板,加載lcd驅動,執行如下命令:
insmod lcd_dev.ko
加載之后我們發現輸出的LCD寄存器信息均是0x00,這里很奇怪,說明我們並沒有成功修改LCD寄存器的值,這個問題排查了很久也沒有找到原因。
掛載LCD驅動后, 如下圖,可以通過如下命令查看已掛載的LCD設備節點::
ls -l /dev/fb*
五、編譯LCD驅動到內核
5.1 LCD驅動編譯進內核
我們直接將lcd_dev.ko源碼拷貝到linux源碼下:cp lcd_dev.c /work/sambashare/linux-5.2.8/drivers/video/fbdev/
5.2 修改Kconfig
cd /work/sambashare/linux-5.2.8/drivers/video/fbdev vim Kconfig
新增如下內容:
config MY_LCD tristate "my_lcd_dev" depends on FB && ARCH_S3C2410 select FB_CFB_FILLRECT select FB_CFB_COPYAREA select FB_CFB_IMAGEBLIT default n help my_lcd driver
在源碼頂層運行make menuconfig,在Device Drivers -> Graphics support -> Frame buffer Devices可以看到:
勾選這,並保存。
5.3 修改Makefile
cd /work/sambashare/linux-5.2.8/drivers/video/fbdev vim Makefile
新增如下內容:
obj-$(CONFIG_MY_LCD) += lcd_dev.o
5.4 編譯內核
在源碼頂層運行:
make uImage
復制到nfs文件系統:
cp /work/sambashare/linux-5.2.8/arch/arm/boot/uImage /work/tftpboot/
5.5 測試運行
重新下載內核到開發板:
tftp 30000000 uImage nand erase.part kernel nand write 30000000 kernel
bootm
啟動輸出寄存器信息:
lcd device registered s3c_lcd=c39a0c00 lcd_regs=c48a4000 map_size 104 map_video_memory(info=c39a0c00) map_size 155648 map_video_memory: clear c48a6000:00026000 map_video_memory: dma=339c0000 cpu=(ptrval) size=00026000 lcdcon[1] = 0x00000779 lcdcon[2] = 0x014fc041 lcdcon[3] = 0x0098ef09 lcdcon[4] = 0x00000009 lcdcon[5] = 0x00004f09 lcdsaddr[1]= 0x19ce0000 lcdsaddr[2]= 0x000f2c00 lcdsaddr[3]= 0x000000f0
可以在LCD上看到企鵝logo(向像上一節一樣配置了Bootup logo)。但是過一會屏幕就什么都沒了,針對該問題,目前問題還未排查到原因。
六、代碼下載
Young / s3c2440_project[drivers]
參考文章