Patch Framebuffer Driver of PrimeCell Color LCD Controller with Double-Buffering to Support Android’s Page-Flipping
參考網址: http://pdk.android.com/online-pdk/guide/display_drivers.html
Brief introduction
Android uses a screen composition engine called SurfaceFlinger. It
takes the input of Surface objects from each Windows, then writes the
output to the frame buffer. Each Surface is double-buffered. The front
buffer is taken by SurfaceFlinger for composition, while the back
buffer is used to perform drawing. Once the drawing is completed,
Android does a page-flipping by changing the offset value on Y axis of
the frame buffer to switch them. During the initialization of
EGLDisplaySurface, if the virtual resolution on the Y axis of the frame
buffer is found out to be bigger than the real resolution of the Y
axis, then the frame buffer is used directly for double-buffering.
Otherwise, the back buffer is simply copied to the front buffer, which
causes delay in buffer-swapping. Thus double-buffering would better to
be supported by the frame buffer driver for the sake of performance.
Double-buffering is supported in the goldfish frame buffer driver,
which is used by the QEMU-based emulator shipped with Android SDK. I
happen to be working on porting Android to an ARM RealView board. The
board has PL111 PrimeCell Color LCD Controller, which is AMBA compliant
SoC peripheral. By comparing the source code of goldfish frame buffer
driver (from Android cupcake branch source tree) and that of the ARM
LCD controller frame buffer driver (from 2.6.27 vanilla kernel), one
can easily figure out what should be patched in the ARM LCD frame
buffer driver (kernel/drivers/video/amba-clcd.c
) to support
double-buffering. Firstly, in the initialization of the driver, change
to “fb->fb.fix.ypanstep = 1;” and “fb->fb.var.yres_virtual =
fb->panel->mode.yres * 2;” Then in the declaration of
“clcdfb_ops”, add this line of code “.fb_pan_display =
clcdfb_pan_display,”. Also define this new function
“clcdfb_pan_display” by putting an invocation to the function
“clcdfb_set_start”. In the definition of the function “clcdfb_check” in
kernel/include/linux/amba/clcd.h, change from “var->yres_virtual =
var->yres = (var->yres + 1) & ~1;” to “var->yres =
(var->yres + 1) & ~1; var->yres_virtual = var->yres * 2;”
since the checking happens to reset the value of yres_virtual to that
of yres. One more step required is to increase the frame size to
needed. In kernel/arch/arm/mach-realview/core.c, change from “static
unsigned long framesize = SZ_1M;” to “static unsigned long framesize =
0×12C000;”. 0×12C000 is the value of 640 * 480 * 2 * 2 (VGA, 16bpp and
double-buffer), because VGA is the largest display resolution that is
provided by PL111 and can still support double-buffering due to the
limited display memory.
How page-flipping works
- In frameworks/base/libs/ui/EGLDisplaySurface.cpp, function
“EGLDisplaySurface::swapBuffers()” does the real work by calling ioctl
of FBIOPUT_VSCREENINFO
- In “kernel/drivers/video/fbmem.c”, function “fb_ioctl” handles over
the work in “case FBIOPUT_VSCREENINFO:”, then “fb_set_var” is invoked.
In that function, both “fbops->fb_set_par” and “fb_pan_display” are
invoked. Similarly, in “fb_pan_display”, “fbops->fb_pan_display” is
also invoked. “fbops->fb_set_par” and “fbops->fb_pan_display”
mentioned above are provided by the amba clcd frame buffer driver in
kernel/drivers/video/amba-clcd.c.
- According to the conventions, “fb_set_par” implementation in the
frame buffer driver is supposed to react to possible changes of screen
orientation and resolution. And “fb_pan_display” is supposed to re-map
the frame buffer as the offset changes in X or Y axis. In this way,
page-flipping is properly handled.
Problem of “clcdfb_set_par”
In “clcdfb_set_par” of the current amba clcd implementation, it
turns off the power and clock source of the frame buffer, sets clocks
and the buffer base, then it turns on the frame buffer. This is not
applied to Android because the page-flipping gonna invoke
FBIOPUT_VSCREENINFO ioctl so often that “clcdfb_set_par” is going to be
invoked a lot as well. Then the whole system is not operatable at all
as a result. It’s obviously that at least the implementation of
“clcdfb_set_par” is not written in a way that adheres to the
conventions. Or writting frame buffer implementation in this case still
requires keeping the usage of Android in mind.
The solution to that is to cache the frame buffer settings. Each
time “clcd_set_par” is invoked, the new frame buffer settings are
compared with the cached ones. If the screen orientation, resolution
and other changes that will require reset of clock don’t happen, then
simply skip any operations; otherwise, the new frame buffer settings
are cached and the clock is reseted.
The actual patch
The patch to the said files above is illustrated below:
diff -
Naur kernel-
2.
6.
27.
orig/
arch/
arm/
mach-
realview/
core.
c kernel-
2.
6.
27/
arch/
arm/
mach-
realview/
core.
c -
-
-
kernel-
2.
6.
27.
orig/
arch/
arm/
mach-
realview/
core.
c 2009-
04-
29 16:
16:
29.
000000000 +
0200 +
+
+
kernel-
2.
6.
27/
arch/
arm/
mach-
realview/
core.
c 2009-
04-
29 16:
12:
26.
000000000 +
0200
@@ -
358,
7 +
358,
8 @@
writel(
val,
sys_clcd)
;
}
-
static
unsigned
long
framesize =
SZ_1M;
+
/* 640*480*2*2 (VGA, 16bpp and double-buffer) required by Android */
+
static
unsigned
long
framesize =
0x12C000;
static
int
realview_clcd_setup(
struct
clcd_fb *
fb)
{
diff -
Naur kernel-
2.
6.
27.
orig/
drivers/
video/
amba-
clcd.
c kernel-
2.
6.
27/
drivers/
video/
amba-
clcd.
c -
-
-
kernel-
2.
6.
27.
orig/
drivers/
video/
amba-
clcd.
c 2009-
04-
29 16:
16:
58.
000000000 +
0200 +
+
+
kernel-
2.
6.
27/
drivers/
video/
amba-
clcd.
c 2009-
04-
29 16:
13:
16.
000000000 +
0200
@@ -
194,
6 +
194,
37 @@ return
ret;
}
+
struct
fb_var_screeninfo cached_fb_var;
+
int
is_fb_var_cached =
0;
+
+
static
int
clcdfb_is_fb_changed(
struct
clcd_fb *
fb)
+
{
+
if
(
!
is_fb_var_cached |
|
+
fb-
>
fb.
var.
xres !
=
cached_fb_var.
xres |
|
+
fb-
>
fb.
var.
yres !
=
cached_fb_var.
yres |
|
+
fb-
>
fb.
var.
xres_virtual !
=
cached_fb_var.
xres_virtual |
|
+
fb-
>
fb.
var.
yres_virtual !
=
cached_fb_var.
yres_virtual |
|
+
fb-
>
fb.
var.
bits_per_pixel !
=
cached_fb_var.
bits_per_pixel |
|
+
fb-
>
fb.
var.
grayscale !
=
cached_fb_var.
grayscale |
|
+
fb-
>
fb.
var.
green.
length !
=
cached_fb_var.
green.
length |
|
+
fb-
>
fb.
var.
left_margin !
=
cached_fb_var.
left_margin |
|
+
fb-
>
fb.
var.
right_margin !
=
cached_fb_var.
right_margin |
|
+
fb-
>
fb.
var.
upper_margin !
=
cached_fb_var.
upper_margin |
|
+
fb-
>
fb.
var.
lower_margin !
=
cached_fb_var.
lower_margin |
|
+
fb-
>
fb.
var.
hsync_len !
=
cached_fb_var.
hsync_len |
|
+
fb-
>
fb.
var.
vsync_len !
=
cached_fb_var.
vsync_len |
|
+
fb-
>
fb.
var.
sync !
=
cached_fb_var.
sync |
|
+
fb-
>
fb.
var.
rotate
!
=
cached_fb_var.
rotate
)
{
+
+
cached_fb_var =
fb-
>
fb.
var;
+
is_fb_var_cached =
1;
+
+
return
1;
+
}
+
else
+
return
0;
+
}
+
static
int
clcdfb_set_par(
struct
fb_info *
info)
{
struct
clcd_fb *
fb =
to_clcd(
info)
;
@@ -
207,
22 +
238,
25 @@ else
fb-
>
fb.
fix.
visual =
FB_VISUAL_TRUECOLOR;
-
fb-
>
board-
>
decode(
fb,
&
regs)
;
+
if
(
clcdfb_is_fb_changed(
fb)
)
{
+
+
fb-
>
board-
>
decode(
fb,
&
regs)
;
-
clcdfb_disable(
fb)
;
+
clcdfb_disable(
fb)
;
-
writel(
regs.
tim0,
fb-
>
regs +
CLCD_TIM0)
;
-
writel(
regs.
tim1,
fb-
>
regs +
CLCD_TIM1)
;
-
writel(
regs.
tim2,
fb-
>
regs +
CLCD_TIM2)
;
-
writel(
regs.
tim3,
fb-
>
regs +
CLCD_TIM3)
;
+
writel(
regs.
tim0,
fb-
>
regs +
CLCD_TIM0)
;
+
writel(
regs.
tim1,
fb-
>
regs +
CLCD_TIM1)
;
+
writel(
regs.
tim2,
fb-
>
regs +
CLCD_TIM2)
;
+
writel(
regs.
tim3,
fb-
>
regs +
CLCD_TIM3)
;
-
clcdfb_set_start(
fb)
;
+
clcdfb_set_start(
fb)
;
-
clk_set_rate(
fb-
>
clk,
(
1000000000 /
regs.
pixclock)
*
1000)
;
+
clk_set_rate(
fb-
>
clk,
(
1000000000 /
regs.
pixclock)
*
1000)
;
-
fb-
>
clcd_cntl =
regs.
cntl;
+
fb-
>
clcd_cntl =
regs.
cntl;
-
clcdfb_enable(
fb,
regs.
cntl)
;
+
clcdfb_enable(
fb,
regs.
cntl)
;
+
}
#
ifdef
DEBUG
printk(
KERN_INFO "CLCD: Registers set to/n"
@@ -
289,
6 +
323,
17 @@ return
regno >
255;
}
+
static
int
clcdfb_pan_display(
struct
fb_var_screeninfo *
var,
struct
fb_info *
info)
+
{
+
struct
clcd_fb *
fb =
NULL
;
+
+
info-
>
var =
*
var;
+
fb =
to_clcd(
info)
;
+
clcdfb_set_start(
fb)
;
+
+
return
0;
+
}
+
/*
* Blank the screen if blank_mode != 0, else unblank. If blank == NULL
* then the caller blanks by setting the CLUT (Color Look Up Table) to all
@@ -332,6 +377,7 @@
.fb_check_var = clcdfb_check_var,
.fb_set_par = clcdfb_set_par,
.fb_setcolreg = clcdfb_setcolreg,
+ .fb_pan_display = clcdfb_pan_display,
.fb_blank = clcdfb_blank,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
@@ -367,14 +413,14 @@
fb->fb.fix.type = FB_TYPE_PACKED_PIXELS;
fb->fb.fix.type_aux = 0;
fb->fb.fix.xpanstep = 0;
- fb->fb.fix.ypanstep = 0;
+ fb->fb.fix.ypanstep = 1;
fb->fb.fix.ywrapstep = 0;
fb->fb.fix.accel = FB_ACCEL_NONE;
fb->fb.var.xres = fb->panel->mode.xres;
fb->fb.var.yres = fb->panel->mode.yres;
fb->fb.var.xres_virtual = fb->panel->mode.xres;
- fb->fb.var.yres_virtual = fb->panel->mode.yres;
+ fb->fb.var.yres_virtual = fb->panel->mode.yres * 2;
fb->fb.var.bits_per_pixel = fb->panel->bpp;
fb->fb.var.grayscale = fb->panel->grayscale;
fb->fb.var.pixclock = fb->panel->mode.pixclock;
diff -Naur kernel-2.6.27.orig/include/linux/amba/clcd.h kernel-2.6.27/include/linux/amba/clcd.h
--- kernel-2.6.27.orig/include/linux/amba/clcd.h 2009-04-29 16:16:20.000000000 +0200
+++ kernel-2.6.27/include/linux/amba/clcd.h 2009-04-29 16:12:53.000000000 +0200
@@ -232,7 +232,9 @@
static inline int clcdfb_check(struct clcd_fb *fb, struct fb_var_screeninfo *var)
{
var->xres_virtual = var->xres = (var->xres + 15) & ~15;
- var->yres_virtual = var->yres = (var->yres + 1) & ~1;
+ //var->yres_virtual = var->yres = (var->yres + 1) & ~1;
+ var->yres = (var->yres + 1) & ~1;
+ var->yres_virtual = var->yres * 2;
#define CHECK(e,l,h) (var->e < l || var->e > h)
if (CHECK(right_margin, (5+1), 256) || /* back porch */
|
|
|