全部博文(75)
分类: LINUX
2009-08-24 22:12:51
Analysis to frame buffer
driver on development board smdk2410
This article briefly introduces the implementation of frame buffer driver for smdk2410. This driver provides great flexibility to configure more display panels that are supported by LCD controller of development board smdk2410.
First of all, let’s see several key data structures.
struct
s3c2410fb_mach_info {
struct s3c2410fb_display *displays; /* attached diplays info */
unsigned num_displays; /* number of defined
displays */
unsigned default_display;
/* GPIOs */
unsigned long gpcup;
unsigned long gpcup_mask;
unsigned long gpccon;
unsigned long gpccon_mask;
unsigned long gpdup;
unsigned long gpdup_mask;
unsigned long gpdcon;
unsigned long gpdcon_mask;
/* lpc3600 control register */
unsigned long lpcsel;
};
This structure describes the overall
configuration of the smdk2410 board for the LCD controller equipped on it. For
example, struct s3c2410fb_display *displays, the static configurations to all the
supported display panels; unsigned num_displays tells you the number of
defined(supported) displays; unsigned default_display informs you of the
current attached display panel, and some configuration values to GPIOs.
/* LCD description */
struct
s3c2410fb_display {
/* LCD type */
unsigned type;
/* Screen size */
unsigned short width;
unsigned short height;
/* Screen info */
unsigned short xres;
unsigned short yres;
unsigned short bpp;
unsigned pixclock; /* pixclock in picoseconds */
unsigned short left_margin; /* value in pixels (TFT) or HCLKs (STN) */
unsigned short right_margin; /* value
in pixels (TFT) or HCLKs (STN) */
unsigned short hsync_len; /* value in pixels (TFT) or HCLKs (STN) */
unsigned short upper_margin; /* value in lines (TFT) or 0 (STN) */
unsigned short lower_margin; /* value in lines (TFT) or 0 (STN) */
unsigned short vsync_len; /* value in lines (TFT) or 0 (STN) */
/* lcd configuration registers */
unsigned long lcdcon5;
};
struct s3c2410fb_display defines all the necessary properties for one display panel. Eg, the width, length, the x, y resolutions, the pixel clock with unite(picoseconds), and the left, right, upper, lower margins.
struct s3c2410fb_info
{
struct device *dev;
struct clk *clk;
struct resource *mem;
void __iomem *io;
void __iomem *irq_base;
enum s3c_drv_type drv_type;
struct s3c2410fb_hw regs;
unsigned int palette_ready;
/* keep these registers in case we need
to re-write palette */
u32 palette_buffer[256];
u32 pseudo_pal[16];
};
struct s3c2410fb_info defines all the parameters for the frame buffer driver. For example, the mem is used to store the register region, and the “IO” is the virtual addresses mapped for the control registers of LCD controller. The “drv_type” identifies which board the controller is armed on, SMDK2410 or SMDK2412. And the “regs” keeps the current configuration values of LCD controller registers for the current display panel, from register con1 to con5.
Some important data structures for Linux Frame Buffer Driver, struct fb_info, struct fb_var_screeninfo, struct fb_fix_screeninfo fix, truct fb_cmap cmap, etc., are introduced in details in the excellent article << Writing Linux LCD drivers>> wrote by JimSheng, you can get it here,
Registration of display panel
/* LCD driver info */
static struct
s3c2410fb_display qt2410_lcd_cfg[] __initdata = {
{
/* Configuration for 640x480
SHARP LQ080V3DG01 */
.lcdcon5 =
S3C2410_LCDCON5_FRM565 |
S3C2410_LCDCON5_INVVLINE |
S3C2410_LCDCON5_INVVFRAME |
S3C2410_LCDCON5_PWREN |
S3C2410_LCDCON5_HWSWP,
.type = S3C2410_LCDCON1_TFT,
.width = 640,
.height = 480,
.pixclock = 40000, /* HCLK/4 */
.xres = 640,
.yres = 480,
.bpp = 16,
.left_margin = 44,
.right_margin = 116,
.hsync_len = 96,
.upper_margin = 19,
.lower_margin = 11,
.vsync_len = 15,
},
{
/* Configuration for 480x640
toppoly TD028TTEC1 */
.lcdcon5 =
S3C2410_LCDCON5_FRM565 |
S3C2410_LCDCON5_INVVLINE |
S3C2410_LCDCON5_INVVFRAME |
S3C2410_LCDCON5_PWREN |
S3C2410_LCDCON5_HWSWP,
.type = S3C2410_LCDCON1_TFT,
.width = 480,
.height = 640,
.pixclock = 40000, /* HCLK/4 */
.xres = 480,
.yres = 640,
.bpp = 16,
.left_margin = 8,
.right_margin = 24,
.hsync_len = 8,
.upper_margin = 2,
.lower_margin = 4,
.vsync_len = 2,
},
{
/* Config for 240x320 LCD */
.lcdcon5 =
S3C2410_LCDCON5_FRM565 |
S3C2410_LCDCON5_INVVLINE |
S3C2410_LCDCON5_INVVFRAME |
S3C2410_LCDCON5_PWREN |
S3C2410_LCDCON5_HWSWP,
.type = S3C2410_LCDCON1_TFT,
.width = 240,
.height = 320,
.pixclock = 100000, /* HCLK/10 */
.xres = 240,
.yres = 320,
.bpp = 16,
.left_margin = 13,
.right_margin = 8,
.hsync_len = 4,
.upper_margin = 2,
.lower_margin = 7,
.vsync_len = 4,
},
};
static struct
s3c2410fb_mach_info qt2410_fb_info __initdata = {
.displays = qt2410_lcd_cfg,
.num_displays = ARRAY_SIZE(qt2410_lcd_cfg),
.default_display = 0,
.lpcsel =
((0xCE6) & ~7) | 1<<4,
};
There are 3 types display panels supported by the LCD controller, now, for the specified board, on which the first type display panel is armed designated by “default_display”. The above configuration of the board for LCD portion is defined in structure qt2410_fb_info, the variable will be set to platform_data stuff of platform device by s3c24xx_fb_set_platdata(&qt2410_fb_info) function.
/* LCD Controller */
static struct
resource s3c_lcd_resource[] = {
[0] = {
.start = S3C24XX_PA_LCD,
.end = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_LCD,
.end = IRQ_LCD,
.flags = IORESOURCE_IRQ,
}
};
struct
platform_device s3c_device_lcd = {
.name = "s3c2410-lcd",
.id = -1,
.num_resources =
ARRAY_SIZE(s3c_lcd_resource),
.resource = s3c_lcd_resource,
.dev = {
.dma_mask = &s3c_device_lcd_dmamask,
.coherent_dma_mask = 0xffffffffUL
}
};
This is the definition of platform_device s3c_device_lcd.
void __init
s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd)
{
struct s3c2410fb_mach_info *npd;
npd = kmalloc(sizeof(*npd),
GFP_KERNEL);
if (npd) {
memcpy(npd, pd,
sizeof(*npd));
s3c_device_lcd.dev.platform_data = npd;
} else {
printk(KERN_ERR "no
memory for LCD platform data\n");
}
}
The s3c24xx_fb_set_platdata() stores the global board info for the LCD portion into the platform_data of platform_device s3c_device_lcd, which will be registered to the system by general kernel API platform_add_devices(qt2410_devices, ARRAY_SIZE(qt2410_devices)); which is used to register more than one platform devices, in essence, the platform_add_devices() calls platform_device_register() API to add each of devices described in array qt2410_devices.
Registration of frame buffer driver for smdk2410
static struct
platform_driver s3c2410fb_driver = {
.probe =
s3c2410fb_probe,
.remove =
s3c2410fb_remove,
.suspend = s3c2410fb_suspend,
.resume =
s3c2410fb_resume,
.driver =
{
.name = "s3c2410-lcd",
.owner = THIS_MODULE,
},
};
platform_driver_register(&s3c2410fb_driver) will register s3c2410fb_driver, due to the identified name of s3c2410fb_driver and s3c_device_lcd, the probe() callback will be triggered.
static int __init
s3c2410fb_probe(struct platform_device *pdev)
{
return s3c24xxfb_probe(pdev,
DRV_S3C2410);
}
static int __init
s3c24xxfb_probe(struct platform_device *pdev,
enum s3c_drv_type drv_type)
{
struct s3c2410fb_info *info;
struct s3c2410fb_display *display;
struct fb_info *fbinfo;
struct s3c2410fb_mach_info *mach_info;
struct resource *res;
int ret;
int irq;
int i;
int size;
u32 lcdcon1;
mach_info = pdev->dev.platform_data;
----- > because s3c24xx_fb_set_platdata(&qt2410_fb_info)
is called, qt2410_fb_info is set to dev.platform_data of truct platform_device
s3c_device_lcd, and the pdev points to struct platform_device s3c_device_lcd,
mach_info is used to reference qt2410_fb_info.
if (mach_info == NULL) {
dev_err(&pdev->dev,
"no platform
data for lcd, cannot attach\n");
return -EINVAL;
}
---- > if the machine configuration
for the LCD controller is not provide, we cannot do any initialization without adequate
information.
if (mach_info->default_display >=
mach_info->num_displays) {
dev_err(&pdev->dev,
"default is %d but only %d displays\n",
mach_info->default_display,
mach_info->num_displays);
return -EINVAL;
}
display = mach_info->displays + mach_info->default_display;
--- > herein, we get
the display information, which is attached to the LCD controller.
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev,
"no irq for device\n");
return -ENOENT;
}
--- > obtain the interrupt
number of LCD controller.
fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
if (!fbinfo)
return -ENOMEM;
platform_set_drvdata(pdev, fbinfo);
--- > Create a frame buffer device for the current display
panel, and set it to the platform_data of platform_device
pdev->dev->platform_data. framebuffer_alloc - creates a new frame buffer info structure @size: size of driver private data, can be
zero @dev: pointer to the device for this fb, this can be NULL, it Creates a
new frame buffer info structure. Also reserves @size bytes for driver private
data (info->par). info->par (if any) will be aligned to sizeof(long).
info = fbinfo->par;
--- > struct
s3c2410fb_info *info points to the reserved space of type sizeof(struct
s3c2410fb_info through framebuffer_alloc(sizeof(struct s3c2410fb_info),
&pdev->dev), then by use of which, we can fill its content.
info->dev = &pdev->dev;
---- > herein, we can
obtain the global configuration of truct s3c2410fb_mach_info.
info->drv_type = drv_type;
res = platform_get_resource(pdev,
IORESOURCE_MEM, 0);
if (res == NULL) {
dev_err(&pdev->dev,
"failed to get memory registers\n");
ret = -ENXIO;
goto dealloc_fb;
}
size = (res->end - res->start) +
1;
info->mem = request_mem_region(res->start,
size, pdev->name);
if (info->mem == NULL) {
dev_err(&pdev->dev,
"failed to get memory region\n");
ret = -ENOENT;
goto dealloc_fb;
}
---- > herein, reserve
physical space of resource of display panel and save it to info->mem.
info->io = ioremap(res->start,
size);
if (info->io == NULL) {
dev_err(&pdev->dev,
"ioremap() of registers failed\n");
ret = -ENXIO;
goto release_mem;
}
--- > map the physical
space to virtual address and save it to info->io.
info->irq_base = info->io + ((drv_type
== DRV_S3C2412) ? S3C2412_LCDINTBASE : S3C2410_LCDINTBASE);
--- > obtain the interrupt
controller physical base address and save it to info->irq_base.
dprintk("devinit\n");
strcpy(fbinfo->fix.id, driver_name);
/* Stop the video */
lcdcon1 = readl(info->io +
S3C2410_LCDCON1);
writel(lcdcon1 &
~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1);
fbinfo->fix.type
= FB_TYPE_PACKED_PIXELS;
fbinfo->fix.type_aux
= 0;
fbinfo->fix.xpanstep
= 0;
fbinfo->fix.ypanstep
= 0;
fbinfo->fix.ywrapstep
= 0;
fbinfo->fix.accel
= FB_ACCEL_NONE;
fbinfo->var.nonstd =
0;
fbinfo->var.activate =
FB_ACTIVATE_NOW;
fbinfo->var.accel_flags = 0;
fbinfo->var.vmode =
FB_VMODE_NONINTERLACED;
fbinfo->fbops = &s3c2410fb_ops;
fbinfo->flags =
FBINFO_FLAG_DEFAULT;
fbinfo->pseudo_palette = &info->pseudo_pal;
---- > fill the
corresponding stuffs of struct fbinfo.
for (i = 0; i < 256; i++)
info->palette_buffer[i] =
PALETTE_BUFF_CLEAR;
ret = request_irq(irq, s3c2410fb_irq,
IRQF_DISABLED, pdev->name, info);
if (ret) {
dev_err(&pdev->dev,
"cannot get irq %d - err %d\n", irq, ret);
ret = -EBUSY;
goto release_regs;
}
--- > register
interrupt service routine.
info->clk = clk_get(NULL,
"lcd");
if (!info->clk ||
IS_ERR(info->clk)) {
printk(KERN_ERR "failed
to get lcd clock source\n");
ret = -ENOENT;
goto release_irq;
}
---- > obtain the
clock of LCD controller.
clk_enable(info->clk);
dprintk("got and enabled
clock\n");
---- > enable the
clock of LCD controller.
msleep(1);
/* find maximum required memory size for display */
for (i = 0; i <
mach_info->num_displays; i++) {
unsigned long smem_len =
mach_info->displays[i].xres;
smem_len *=
mach_info->displays[i].yres;
smem_len *= mach_info->displays[i].bpp;
smem_len >>= 3;
if (fbinfo->fix.smem_len
< smem_len)
fbinfo->fix.smem_len
= smem_len;
}
/* Initialize video memory */
ret =
s3c2410fb_map_video_memory(fbinfo);
if (ret) {
printk(KERN_ERR "Failed
to allocate video RAM: %d\n", ret);
ret = -ENOMEM;
goto release_clock;
}
dprintk("got video
memory\n");
fbinfo->var.xres = display->xres;
fbinfo->var.yres = display->yres;
fbinfo->var.bits_per_pixel =
display->bpp;
s3c2410fb_init_registers(fbinfo);
---- > s3c2410fb_init_registers
- Initialise all LCD-related registers
s3c2410fb_check_var(&fbinfo->var,
fbinfo);
ret = register_framebuffer(fbinfo);
--- > register_framebuffer
- registers a frame buffer device
if (ret < 0) {
printk(KERN_ERR "Failed
to register framebuffer device: %d\n",
ret);
goto free_video_memory;
}
/* create device files */
ret =
device_create_file(&pdev->dev, &dev_attr_debug);
if (ret) {
printk(KERN_ERR "failed
to add debug attribute\n");
}
printk(KERN_INFO "fb%d: %s frame
buffer device\n",
fbinfo->node,
fbinfo->fix.id);
return 0;
free_video_memory:
s3c2410fb_unmap_video_memory(fbinfo);
release_clock:
clk_disable(info->clk);
clk_put(info->clk);
release_irq:
free_irq(irq, info);
release_regs:
iounmap(info->io);
release_mem:
release_resource(info->mem);
kfree(info->mem);
dealloc_fb:
platform_set_drvdata(pdev, NULL);
framebuffer_release(fbinfo);
return ret;
}
The patch file for s3c2410fb of linux-2.6.29:
|