原文出處:http://bbs.chinaunix.net/thread-3634288-1-1.html
開發環境
- 主 機:VMWare--Fedora 9
- 開發板:Mini2440--64MB Nand, Kernel:2.6.30.4
- 編譯器:arm-linux-gcc-4.3.2
上接:S3C2440上LCD驅動(FrameBuffer)實例開發詳解(一)
四、幀緩衝(FrameBuffer)設備驅動實例代碼:
#include
<linux/kernel.h> #include
<linux/slab.h>
u32 pseudo_pal[16]; |
/*LCD FrameBuffer設備探測的實現,注意這裏使用一個__devinit宏,到lcd_fb_remove接口函數實現的地方講解*/ /*給fb_info分配空間,大小爲my2440fb_var結構的內存,framebuffer_alloc定義在fb.h中在fbsysfs.c中實現*/ /*接着,再初始化fb_info中代表LCD可變參數的結構體fb_var_screeninfo*/ /*指定對底層硬件操作的函數指針, 因內容較多故其定義在第③步中再講*/ fbinfo->flags = FBINFO_FLAG_DEFAULT; fbinfo->pseudo_palette = &fbvar->pseudo_pal;
/*初始化色調色板(顏色表)爲空*/
|
/*Framebuffer底層硬件操作各接口函數*/
static struct fb_ops my2440fb_ops
=
{
.owner = THIS_MODULE,
.fb_check_var = my2440fb_check_var,/*第②步中已實現*/
.fb_set_par = my2440fb_set_par,/*設置fb_info中的參數,主要是LCD的顯示模式*/
.fb_blank = my2440fb_blank,/*顯示空白(即:LCD開關控制)*/
.fb_setcolreg = my2440fb_setcolreg,/*設置顏色表*/
/*以下三個函數是可選的,主要是提供fb_console的支持,在內核中已經實現,這裏直接調用即可*/
.fb_fillrect = cfb_fillrect,/*定義在drivers/video/cfbfillrect.c中*/
.fb_copyarea = cfb_copyarea,/*定義在drivers/video/cfbcopyarea.c中*/
.fb_imageblit = cfb_imageblit,/*定義在drivers/video/cfbimgblt.c中*/
};
/*設置fb_info中的參數,這裏根據用戶設置的可變參數var調整固定參數fix*/
static int my2440fb_set_par(struct fb_info
*fbinfo)
{
/*獲得fb_info中的可變參數*/
struct fb_var_screeninfo
*var = &fbinfo->var;
/*判斷可變參數中的色位模式,根據色位模式來設置色彩模式*/
switch (var->bits_per_pixel)
{
case 32:
case 16:
case 12:/*12BPP時,設置爲真彩色(分成紅、綠、藍三基色)*/
fbinfo->fix.visual
= FB_VISUAL_TRUECOLOR;
break;
case 1:/*1BPP時,設置爲黑白色(分黑、白兩種色,FB_VISUAL_MONO01代表黑,FB_VISUAL_MONO10代表白)*/
fbinfo->fix.visual
= FB_VISUAL_MONO01;
break;
default:/*默認設置爲僞彩色,採用索引顏色顯示*/
fbinfo->fix.visual
= FB_VISUAL_PSEUDOCOLOR;
break;
}
/*設置fb_info中固定參數中一行的字節數,公式:1行字節數=(1行像素個數*每像素位數BPP)/8 */
fbinfo->fix.line_length
= (var->xres_virtual
* var->bits_per_pixel)
/ 8;
/*修改以上參數後,重新激活fb_info中的參數配置(即:使修改後的參數在硬件上生效)*/
my2440fb_activate_var(fbinfo);
return 0;
}
/*重新激活fb_info中的參數配置*/
static void my2440fb_activate_var(struct fb_info
*fbinfo)
{
/*獲得結構體變量*/
struct my2440fb_var *fbvar
= fbinfo->par;
void __iomem *regs
= fbvar->lcd_base;
/*獲得fb_info可變參數*/
struct fb_var_screeninfo
*var = &fbinfo->var;
/*計算LCD控制寄存器1中的CLKVAL值, 根據數據手冊中該寄存器的描述,計算公式如下:
* STN屏:VCLK = HCLK / (CLKVAL * 2), CLKVAL要求>= 2
* TFT屏:VCLK = HCLK / [(CLKVAL + 1) * 2], CLKVAL要求>= 0*/
int clkdiv = my2440fb_calc_pixclk(fbvar, var->pixclock)
/ 2;
/*獲得屏幕的類型*/
int type = fbvar->regs.lcdcon1
& S3C2410_LCDCON1_TFT;
if (type
== S3C2410_LCDCON1_TFT)
{
/*根據數據手冊按照TFT屏的要求配置LCD控制寄存器1-5*/
my2440fb_config_tft_lcd_regs(fbinfo,
&fbvar->regs);
--clkdiv;
if (clkdiv
< 0)
{
clkdiv = 0;
}
}
else
{
/*根據數據手冊按照STN屏的要求配置LCD控制寄存器1-5*/
my2440fb_config_stn_lcd_regs(fbinfo,
&fbvar->regs);
if (clkdiv
< 2)
{
clkdiv = 2;
}
}
/*設置計算的LCD控制寄存器1中的CLKVAL值*/
fbvar->regs.lcdcon1
|= S3C2410_LCDCON1_CLKVAL(clkdiv);
/*將各參數值寫入LCD控制寄存器1-5中*/
writel(fbvar->regs.lcdcon1
& ~S3C2410_LCDCON1_ENVID, regs
+ S3C2410_LCDCON1);
writel(fbvar->regs.lcdcon2, regs
+ S3C2410_LCDCON2);
writel(fbvar->regs.lcdcon3, regs
+ S3C2410_LCDCON3);
writel(fbvar->regs.lcdcon4, regs
+ S3C2410_LCDCON4);
writel(fbvar->regs.lcdcon5, regs
+ S3C2410_LCDCON5);
/*配置幀緩衝起始地址寄存器1-3*/
my2440fb_set_lcdaddr(fbinfo);
fbvar->regs.lcdcon1
|= S3C2410_LCDCON1_ENVID,
writel(fbvar->regs.lcdcon1, regs
+ S3C2410_LCDCON1);
}
/*計算LCD控制寄存器1中的CLKVAL值*/
static unsigned
int my2440fb_calc_pixclk(struct my2440fb_var
*fbvar,
unsigned long pixclk)
{
/*獲得LCD的時鐘*/
unsigned long clk
= clk_get_rate(fbvar->lcd_clock);
/* 像素時鐘單位是皮秒,而時鐘的單位是赫茲,所以計算公式爲:
* Hz -> picoseconds is / 10^-12
*/
unsigned long
long div
= (unsigned
long long)clk
* pixclk;
div >>= 12; /* div / 2^12 */
do_div(div, 625
* 625UL * 625);
/* div / 5^12, do_div宏定義在asm/div64.h中*/
return div;
}
/*根據數據手冊按照TFT屏的要求配置LCD控制寄存器1-5*/
static void my2440fb_config_tft_lcd_regs(const
struct fb_info *fbinfo,
struct s3c2410fb_hw
*regs)
{
const struct my2440fb_var *fbvar
= fbinfo->par;
const struct fb_var_screeninfo
*var =
&fbinfo->var;
/*根據色位模式設置LCD控制寄存器1和5,參考數據手冊*/
switch (var->bits_per_pixel)
{
case 1:/*1BPP*/
regs->lcdcon1
|= S3C2410_LCDCON1_TFT1BPP;
break;
case 2:/*2BPP*/
regs->lcdcon1
|= S3C2410_LCDCON1_TFT2BPP;
break;
case 4:/*4BPP*/
regs->lcdcon1
|= S3C2410_LCDCON1_TFT4BPP;
break;
case 8:/*8BPP*/
regs->lcdcon1
|= S3C2410_LCDCON1_TFT8BPP;
regs->lcdcon5
|= S3C2410_LCDCON5_BSWP
| S3C2410_LCDCON5_FRM565;
regs->lcdcon5
&=
~S3C2410_LCDCON5_HWSWP;
break;
case 16:/*16BPP*/
regs->lcdcon1
|= S3C2410_LCDCON1_TFT16BPP;
regs->lcdcon5
&=
~S3C2410_LCDCON5_BSWP;
regs->lcdcon5
|= S3C2410_LCDCON5_HWSWP;
break;
case 32:/*32BPP*/
regs->lcdcon1
|= S3C2410_LCDCON1_TFT24BPP;
regs->lcdcon5
&=
~(S3C2410_LCDCON5_BSWP
| S3C2410_LCDCON5_HWSWP | S3C2410_LCDCON5_BPP24BL);
break;
default:/*無效的BPP*/
dev_err(fbvar->dev,
"invalid bpp %d\n", var->bits_per_pixel);
}
/*設置LCD配置寄存器2、3、4*/
regs->lcdcon2
= S3C2410_LCDCON2_LINEVAL(var->yres
- 1)
|
S3C2410_LCDCON2_VBPD(var->upper_margin
- 1)
|
S3C2410_LCDCON2_VFPD(var->lower_margin
- 1)
|
S3C2410_LCDCON2_VSPW(var->vsync_len
- 1);
regs->lcdcon3
= S3C2410_LCDCON3_HBPD(var->right_margin
- 1)
|
S3C2410_LCDCON3_HFPD(var->left_margin
- 1)
|
S3C2410_LCDCON3_HOZVAL(var->xres
- 1);
regs->lcdcon4
= S3C2410_LCDCON4_HSPW(var->hsync_len
- 1);
}
/*根據數據手冊按照STN屏的要求配置LCD控制寄存器1-5*/
static void my2440fb_config_stn_lcd_regs(const
struct fb_info *fbinfo,
struct s3c2410fb_hw
*regs)
{
const struct my2440fb_var *fbvar
= fbinfo->par;
const struct fb_var_screeninfo
*var =
&fbinfo->var;
int type = regs->lcdcon1
& ~S3C2410_LCDCON1_TFT;
int hs = var->xres
>> 2;
unsigned wdly
= (var->left_margin
>> 4)
- 1;
unsigned wlh =
(var->hsync_len
>> 4)
- 1;
if (type
!= S3C2410_LCDCON1_STN4)
{
hs >>= 1;
}
/*根據色位模式設置LCD控制寄存器1,參考數據手冊*/
switch (var->bits_per_pixel)
{
case 1:/*1BPP*/
regs->lcdcon1
|= S3C2410_LCDCON1_STN1BPP;
break;
case 2:/*2BPP*/
regs->lcdcon1
|= S3C2410_LCDCON1_STN2GREY;
break;
case 4:/*4BPP*/
regs->lcdcon1
|= S3C2410_LCDCON1_STN4GREY;
break;
case 8:/*8BPP*/
regs->lcdcon1
|= S3C2410_LCDCON1_STN8BPP;
hs *= 3;
break;
case 12:/*12BPP*/
regs->lcdcon1
|= S3C2410_LCDCON1_STN12BPP;
hs *= 3;
break;
default:/*無效的BPP*/
dev_err(fbvar->dev,
"invalid bpp %d\n", var->bits_per_pixel);
}
/*設置LCD配置寄存器2、3、4, 參考數據手冊*/
if (wdly
> 3) wdly
= 3;
if (wlh
> 3) wlh
= 3;
regs->lcdcon2
= S3C2410_LCDCON2_LINEVAL(var->yres
- 1);
regs->lcdcon3
= S3C2410_LCDCON3_WDLY(wdly)
|
S3C2410_LCDCON3_LINEBLANK(var->right_margin
/ 8)
|
S3C2410_LCDCON3_HOZVAL(hs
- 1);
regs->lcdcon4
= S3C2410_LCDCON4_WLH(wlh);
}
/*配置幀緩衝起始地址寄存器1-3,參考數據手冊*/
static void my2440fb_set_lcdaddr(struct fb_info
*fbinfo)
{
unsigned long saddr1, saddr2, saddr3;
struct my2440fb_var *fbvar
= fbinfo->par;
void __iomem *regs
= fbvar->lcd_base;
saddr1 = fbinfo->fix.smem_start
>> 1;
saddr2 = fbinfo->fix.smem_start;
saddr2 += fbinfo->fix.line_length
* fbinfo->var.yres;
saddr2 >>= 1;
saddr3 = S3C2410_OFFSIZE(0)
| S3C2410_PAGEWIDTH((fbinfo->fix.line_length
/ 2)
& 0x3ff);
writel(saddr1, regs
+ S3C2410_LCDSADDR1);
writel(saddr2, regs
+ S3C2410_LCDSADDR2);
writel(saddr3, regs
+ S3C2410_LCDSADDR3);
}
/*顯示空白,blank mode有5種模式,定義在fb.h中,是一個枚舉*/
static int my2440fb_blank(int blank_mode,
struct fb_info *fbinfo)
{
struct my2440fb_var *fbvar
= fbinfo->par;
void __iomem *regs
= fbvar->lcd_base;
/*根據顯示空白的模式來設置LCD是開啓還是停止*/
if (blank_mode
== FB_BLANK_POWERDOWN)
{
my2440fb_lcd_enable(fbvar, 0);/*在第②步中定義*/
}
else
{
my2440fb_lcd_enable(fbvar, 1);/*在第②步中定義*/
}
/*根據顯示空白的模式來控制臨時調色板寄存器*/
if (blank_mode
== FB_BLANK_UNBLANK)
{
/*臨時調色板寄存器無效*/
writel(0x0, regs
+ S3C2410_TPAL);
}
else
{
/*臨時調色板寄存器有效*/
writel(S3C2410_TPAL_EN, regs
+ S3C2410_TPAL);
}
return 0;
}
/*設置顏色表*/
static int my2440fb_setcolreg(unsigned regno,unsigned
red,unsigned green,unsigned blue,unsigned transp,struct
fb_info *fbinfo)
{
unsigned int val;
struct my2440fb_var *fbvar
= fbinfo->par;
void __iomem *regs
= fbvar->lcd_base;
switch (fbinfo->fix.visual)
{
case FB_VISUAL_TRUECOLOR:
/*真彩色*/
if (regno
< 16)
{
u32 *pal
= fbinfo->pseudo_palette;
val = chan_to_field(red,
&fbinfo->var.red);
val |= chan_to_field(green,
&fbinfo->var.green);
val |= chan_to_field(blue,
&fbinfo->var.blue);
pal[regno]
= val;
}
break;
case FB_VISUAL_PSEUDOCOLOR:
/*僞彩色*/
if (regno
< 256)
{
val =
(red >> 0)
& 0xf800;
val |=
(green >> 5)
& 0x07e0;
val |=
(blue >> 11)
& 0x001f;
writel(val, regs
+ S3C2410_TFTPAL(regno));
/*修改調色板*/
schedule_palette_update(fbvar, regno, val);
}
break;
default:
return 1;
}
return 0;
}
static inline
unsigned int chan_to_field(unsigned
int chan,
struct fb_bitfield *bf)
{
chan &= 0xffff;
chan >>= 16
- bf->length;
return chan << bf->offset;
}
/*修改調色板*/
static void schedule_palette_update(struct my2440fb_var *fbvar,
unsigned int regno,
unsigned int val)
{
unsigned long flags;
unsigned long irqen;
/*LCD中斷掛起寄存器基地址*/
void __iomem *lcd_irq_base
= fbvar->lcd_base
+ S3C2410_LCDINTBASE;
/*在修改中斷寄存器值之前先屏蔽中斷,將中斷狀態保存到flags中*/
local_irq_save(flags);
fbvar->palette_buffer[regno]
= val;
/*判斷調色板是否準備就像*/
if (!fbvar->palette_ready)
{
fbvar->palette_ready
= 1;
/*使能中斷屏蔽寄存器*/
irqen = readl(lcd_irq_base
+ S3C24XX_LCDINTMSK);
irqen &=
~S3C2410_LCDINT_FRSYNC;
writel(irqen, lcd_irq_base
+ S3C24XX_LCDINTMSK);
}
/*恢復被屏蔽的中斷*/
local_irq_restore(flags);
}