s3c2440 lcd 驱动分析,涉及到的内容有,LCD图像显示原理、s3c2440的LCD控制器的操作、LCD驱动使用平台总线-设备-驱动模型的实例、LCD相关参数的设置、fb字符设备驱动实例、framebuffer的注册和管理、以及一次LCD显示的完整过程分析。
SDRAM:在SDRAM中申请了一块连续的内存作为LCD显示数据的存储,叫做显存(framebuffer)。
LCD控制器:LCD控制器通过硬件电路和LCD屏连接。
LCD屏:作为一个外设通过硬件电路和MCU(引脚配置为LCD引脚)连接。
图像在LCD屏上显示,可以看成是LCD控制器先从显存中取出一帧图像数据,然后输入到LCD屏上。480*272的屏,所显示的一帧有480*272个像素点、272行、480列。对于每一行的像素点,LCD控制器有一个VCLK信号控制,每来一个VCLK,显示的像素点就向右移动一个,当移动到这一行中的最后一个像素点时,LCD控制器有一个HSYNC信号,控制像素点跳到下一行的第一个像素显示。对于一帧图像(也叫一场),即当像素点移动到最后一行的最后一个位置显示完后,LCD控制器有一个VSYNC信号,控制像素点重新移动到第一行的第一个像素显示下一帧图像。
开启LCD显示,需要使能KEYBOARD(一般EN表示高电平有效,EN上面画一横表示低电平有效)开启背光。
背光开关通过主控,GPB0引脚设置
VLINE:HSYNC信号输出引脚(由LCD控制器操作)
VFRAME:VSYNC信号输出引脚(由LCD控制器操作)
VCLK:VCLK信号输出引脚(由LCD控制器操作)
VD3~VD7:RGB(565)中B数据输出引脚
VD10~VD15:RGB(565)中G数据输出引脚
VD19~VD23:RGB(565)中R数据输出引脚
TS*:供ts触摸屏连接
涉及 GPG、GPD、GPC引脚。
VM:LCD控制器使能引脚(由LCD控制器的寄存器配置),开启LCD显示需要配置相关寄存器的相应位使能。
LCD_PWREN:LCD电源使能引脚(由LCD控制器的寄存器配置),开启LCD显示需要配置相关寄存器的相应位使能。
MACHINE_START(S3C2440, "SMDK2440")
/* Maintainer: Ben Dooks */
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.init_irq = s3c24xx_init_irq,
.map_io = smdk2440_map_io,
.init_machine = smdk2440_machine_init,
.timer = &s3c24xx_timer,
MACHINE_END
将上面的宏展开
static const struct machine_desc __mach_desc_SMDK2440
__attribute_used__
__attribute__((__section__(".arch.info.init"))) = {
.nr = MACH_TYPE_SMDK2410, /* architecture number */
.name = "SMDK2440", /* architecture name */
/* Maintainer: Jonas Dietsche */
.phys_io = S3C2410_PA_UART, /* start of physical io */
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100, /* tagged list */
.map_io = smdk2440_map_io, /* IO mapping function */
.init_irq = s3c24xx_init_irq,
.init_machine = smdk2440_machine_init,
.timer = &s3c24xx_timer,
}
MACHINE_START主要是定义了"struct machine_desc"的类型,放在 section(“.arch.info.init”),是初始化数据,Kernel 起来之后将被丢弃。
各个成员函数在不同时期被调用:
- .init_machine 在 arch/arm/kernel/setup.c 中被 customize_machine 调用,放在 arch_initcall() 段里面,会自动按顺序被调用。
- init_irq在start_kernel() -> init_IRQ() -> init_arch_irq() 被调用
- map_io 在 setup_arch() -> paging_init() -> devicemaps_init()被调用
其他主要都在 setup_arch() 中用到。
系统初始化时,会调用smdk2440_machine_init
static void __init smdk2440_machine_init(void)
{
// 这里设置LCD的参数,和驱动分离。这样要修改LCD时,驱动层程序可以不需要改动,只需修改设备层参数就行了,方便移植。
s3c24xx_fb_set_platdata(&smdk2440_lcd_cfg);
// 将smdk2440_devices数组中的设备注册到平台总线
platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
smdk_machine_init();
}
// smdk2440_devices数组
static struct platform_device *smdk2440_devices[] __initdata = {
&s3c_device_usb,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c,
&s3c_device_iis,
&s3c2440_device_sdi,
};
// lcd设备
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
}
};
// 设置lcd设备参数 smdk2440_lcd_cfg。各参数含义后面在probe分析时解析
/* 480x272 */
static struct s3c2410fb_mach_info smdk2440_lcd_cfg __initdata = {
.regs = {
.lcdcon1 = S3C2410_LCDCON1_TFT16BPP | \
S3C2410_LCDCON1_TFT | \
S3C2410_LCDCON1_CLKVAL(0x04),
.lcdcon2 = S3C2410_LCDCON2_VBPD(1) | \
S3C2410_LCDCON2_LINEVAL(271) | \
S3C2410_LCDCON2_VFPD(1) | \
S3C2410_LCDCON2_VSPW(9),
.lcdcon3 = S3C2410_LCDCON3_HBPD(1) | \
S3C2410_LCDCON3_HOZVAL(479) | \
S3C2410_LCDCON3_HFPD(1),
.lcdcon4 = S3C2410_LCDCON4_HSPW(40),
.lcdcon5 = S3C2410_LCDCON5_FRM565 |
S3C2410_LCDCON5_INVVLINE |
S3C2410_LCDCON5_INVVFRAME |
S3C2410_LCDCON5_PWREN |
S3C2410_LCDCON5_HWSWP,
},
.gpccon = 0xaaaaaaaa,
.gpccon_mask = 0xffffffff,
.gpcup = 0xffffffff,
.gpcup_mask = 0xffffffff,
.gpdcon = 0xaaaaaaaa,
.gpdcon_mask = 0xffffffff,
.gpdup = 0xffffffff,
.gpdup_mask = 0xffffffff,
.fixed_syncs = 1,
.type = S3C2410_LCDCON1_TFT,
.width = 480,
.height = 272,
.xres = {
.min = 480,
.max = 480,
.defval = 480,
},
.yres = {
.max = 272,
.min = 272,
.defval = 272,
},
.bpp = {
.min = 16,
.max = 16,
.defval = 16,
},
};
调用platform_device_register注册平台设备
int platform_add_devices(struct platform_device **devs, int num)
{
int i, ret = 0;
for (i = 0; i < num; i++) {
ret = platform_device_register(devs[i]);
if (ret) {
while (--i >= 0)
platform_device_unregister(devs[i]);
break;
}
}
return ret;
}
编译内核设置,make menuconfig
-> Device Drivers
-> Graphics support
<*> Support for frame buffer devices
linux-2.6.22.6/.config
CONFIG_FB_S3C2410=y
linux-2.6.22.6/drivers/video/Makefile
obj-$(CONFIG_FB_S3C2410) += s3c2410fb.o
然后make uImage,将s3c2410fb驱动编译进内核,系统启动便会加载,即调用驱动的s3c2410fb_init函数。
int __devinit s3c2410fb_init(void)
{
// 注册到平台总线驱动
return platform_driver_register(&s3c2410fb_driver);
}
platform_device_register ->
platform_device_add ->
device_add ->
bus_attach_device ->
device_attach ->
bus_for_each_drv -> // 从平台总线的的驱动链表中,取出每一项驱动进行匹配
__device_attach ->
driver_probe_device ->
if (drv->bus->match && !drv->bus->match(dev, drv)) 此总线类型为平台总线,其存在match函数,即调用platform_match进行匹配
// 平台总线
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.suspend = platform_suspend,
.suspend_late = platform_suspend_late,
.resume_early = platform_resume_early,
.resume = platform_resume,
};
static int platform_match(struct device * dev, struct device_driver * drv)
{
struct platform_device *pdev = container_of(dev, struct platform_device, dev);
// 平台总线匹配设备和驱动的名称
return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);
}
// lcd 设备 name = "s3c2410-lcd"
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
}
};
// lcd 驱动 name = "s3c2410-lcd"
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->
driver_register->
bus_add_driver->
driver_attach->
bus_for_each_dev-> // 从平台总线的的设备链表中,取出每一项设备进行匹配
__driver_attach->
driver_probe_device->
if (drv->bus->match && !drv->bus->match(dev, drv)) // 此总线类型为平台总线,其存在match函数,即调用platform_match进行匹配
// 之后的执行和上一小节分析的一样
driver_probe_device-> // 在此函数中匹配成功的话,就会去调用驱动的probe函数
really_probe->
drv->probe(dev)
struct fb_info *fbinfo;
...
fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
if (!fbinfo) {
return -ENOMEM;
}
...
见4.2 LCD参数设置
...
ret = register_framebuffer(fbinfo);
if (ret < 0) {
printk(KERN_ERR "Failed to register framebuffer device: %d\n", ret);
goto free_video_memory;
}
...
获取平台设备数据,即smdk2440_lcd_cfg结构体内数据
// 获取平台数据即 linux-2.6.22.6/arch/arm/mach-s3c2440/mach-smdk2440.c中配置的smdk2440_lcd_cfg
mach_info = pdev->dev.platform_data;
if (mach_info == NULL) {
dev_err(&pdev->dev,"no platform data for lcd, cannot attach\n");
return -EINVAL;
}
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 reserved[3]; /* Reserved for future compatibility */
};
...
id:driver 标识
strcpy(fbinfo->fix.id, driver_name);
smem_start:frame buffer 的起始地址
fbi->map_cpu = dma_alloc_writecombine(fbi->dev, fbi->map_size,
&fbi->map_dma, GFP_KERNEL);
...
fbi->screen_dma = fbi->map_dma;
fbi->fb->fix.smem_start = fbi->screen_dma;
...
smem_len:frame buffer 的长度字节为单位 = 480*272*16/8 屏宽*屏高*一个像素的数据位数/一个字节的位数
fbinfo->fix.smem_len = mach_info->xres.max *
mach_info->yres.max *
mach_info->bpp.max / 8;
type、type_aux:fb数据类型为像素类型,还有平面模式等
fbinfo->fix.type = FB_TYPE_PACKED_PIXELS;
fbinfo->fix.type_aux = 0;
visual:设置为真彩色,还有单色模式,黑/白
fbi->fb->fix.visual = FB_VISUAL_TRUECOLOR;
默认设置为0,简单理解为边框宽度
fbinfo->fix.xpanstep = 0;
fbinfo->fix.ypanstep = 0;
fbinfo->fix.ywrapstep = 0;
line_length:一行fb数据的字节数 480*16/8
fbi->fb->fix.line_length = (var->width*var->bits_per_pixel)/8;
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 Graylevels instead of colors */
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 reserved[5]; /* Reserved for future compatibility */
};
屏幕分辨率和单个像素点的数据位数
fbinfo->var.xres = mach_info->xres.defval; 480
fbinfo->var.xres_virtual = mach_info->xres.defval; 480
fbinfo->var.yres = mach_info->yres.defval; 272
fbinfo->var.yres_virtual = mach_info->yres.defval; 272
fbinfo->var.bits_per_pixel = mach_info->bpp.defval; 16
设置一个像素点的位数分配,rgb+透明度 rrrrrggggggbbbbb 位数从左边为第0位算
fbinfo->var.red.offset = 11;
fbinfo->var.green.offset = 5;
fbinfo->var.blue.offset = 0;
fbinfo->var.transp.offset = 0;
fbinfo->var.red.length = 5;
fbinfo->var.green.length = 6;
fbinfo->var.blue.length = 5;
fbinfo->var.transp.length = 0;
fbinfo->var.nonstd = 0; // 标准像素格式
fbinfo->var.activate = FB_ACTIVATE_NOW;
// 真实分辨率,设置480*272就行了,影响不大
fbinfo->var.height = mach_info->height;
fbinfo->var.width = mach_info->width;
fbinfo->var.accel_flags = 0;
fbinfo->var.vmode = FB_VMODE_NONINTERLACED;
// VBPD:一个VSYNC信号到来之后,多才时间才开始输出数据(显示图像)。会造成上边黑框
// VFPD:一帧(场)数据结束后多长时间,才来一个VSYNC信号。会造成下边黑框
// VSPW: 一个VSYNC信号的时间宽度
fbinfo->var.upper_margin = S3C2410_LCDCON2_GET_VBPD(mregs->lcdcon2) + 1;
fbinfo->var.lower_margin = S3C2410_LCDCON2_GET_VFPD(mregs->lcdcon2) + 1;
fbinfo->var.vsync_len = S3C2410_LCDCON2_GET_VSPW(mregs->lcdcon2) + 1;
// HFPD:一行数据结束多长时间才来一个HSYNC信号。会造成左边黑框
// HBPD:一个HSYNC信号到来之后,多长时间才开始输出数据(显示图像)。会造成右边黑框
// HSPW: 一个HSYNC信号的时间宽度
fbinfo->var.left_margin = S3C2410_LCDCON3_GET_HFPD(mregs->lcdcon3) + 1;
fbinfo->var.right_margin = S3C2410_LCDCON3_GET_HBPD(mregs->lcdcon3) + 1;
fbinfo->var.hsync_len = S3C2410_LCDCON4_GET_HSPW(mregs->lcdcon4) + 1;
fbinfo->fbops = &s3c2410fb_ops;
fbinfo->flags = FBINFO_FLAG_DEFAULT;
// 设置调色板
fbinfo->pseudo_palette = &info->pseudo_pal;
s3c2410fb_ops:
static struct fb_ops s3c2410fb_ops = {
.owner = THIS_MODULE,
.fb_check_var = s3c2410fb_check_var,
.fb_set_par = s3c2410fb_set_par,
.fb_blank = s3c2410fb_blank,
.fb_setcolreg = s3c2410fb_setcolreg,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
/* Stop the video and unset ENVID if set */
// 设置LCD控制器的lcdcon1寄存器,第0位为0,先失能lcd等设置完其他参数后,需要开启LCD,再使能该位
info->regs.lcdcon1 &= ~S3C2410_LCDCON1_ENVID;
lcdcon1 = readl(S3C2410_LCDCON1);
writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, S3C2410_LCDCON1);
// 设置GPB0 低电平,即关闭LCD背光,后面需要开启背光
// add by thisway.diy@163.com, for eBlocks
s3c2410_gpio_setpin(S3C2410_GPB0, 0); // back light control
// 设置调色板数据
for (i = 0; i < 256; i++)
info->palette_buffer[i] = PALETTE_BUFF_CLEAR;
if (!request_mem_region((unsigned long)S3C24XX_VA_LCD, SZ_1M, "s3c2410-lcd")) {
ret = -EBUSY;
goto dealloc_fb;
}
dprintk("got LCD region\n");
// 设置LCD中断
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_mem;
}
// 使能LCD clk
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;
}
clk_enable(info->clk);
dprintk("got and enabled clock\n");
msleep(1);
/* Initialize video memory */
// 申请frame buff内存并初始化
ret = s3c2410fb_map_video_memory(info);
if (ret) {
printk( KERN_ERR "Failed to allocate video RAM: %d\n", ret);
ret = -ENOMEM;
goto release_clock;
}
dprintk("got video memory\n");
// 根据s3c2440芯片手册和使用的屏的规格书设置相关寄存器和引脚
ret = s3c2410fb_init_registers(info);
ret = s3c2410fb_check_var(&fbinfo->var, fbinfo);
static int s3c2410fb_init_registers(struct s3c2410fb_info *fbi)
{
unsigned long flags;
/* Initialise LCD with values from haret */
local_irq_save(flags);
/* modify the gpio(s) with interrupts set (bjd) */
// 根据s3c2440芯片手册设置
// 设置GPC引脚上拉使能 mach_info->gpcup 0xffffffff
modify_gpio(S3C2410_GPCUP, mach_info->gpcup, mach_info->gpcup_mask);
// 设置GPC引脚为LCD功能 mach_info->gpccon 0xaaaaaaaa -> 1010 1010 1010 1010 1010 1010 1010 1010
modify_gpio(S3C2410_GPCCON, mach_info->gpccon, mach_info->gpccon_mask);
// 设置GPD引脚上拉使能 mach_info->gpdup 0xffffffff
modify_gpio(S3C2410_GPDUP, mach_info->gpdup, mach_info->gpdup_mask);
// 设置GPD引脚为LCD功能 mach_info->gpdcon 0xaaaaaaaa
modify_gpio(S3C2410_GPDCON, mach_info->gpdcon, mach_info->gpdcon_mask);
local_irq_restore(flags);
// 根据s3c2440芯片手册设置
// lcdcon1: S3C2410_LCDCON1_TFT16BPP | S3C2410_LCDCON1_TFT | S3C2410_LCDCON1_CLKVAL(0x04),
writel(fbi->regs.lcdcon1, S3C2410_LCDCON1);
// lcdcon2: S3C2410_LCDCON2_VBPD(1) | S3C2410_LCDCON2_LINEVAL(271) | S3C2410_LCDCON2_VFPD(1) | S3C2410_LCDCON2_VSPW(9)
writel(fbi->regs.lcdcon2, S3C2410_LCDCON2);
// lcdcon3: S3C2410_LCDCON3_HBPD(1) | S3C2410_LCDCON3_HOZVAL(479) | S3C2410_LCDCON3_HFPD(1)
writel(fbi->regs.lcdcon3, S3C2410_LCDCON3);
// lcdcon4: S3C2410_LCDCON4_HSPW(40)
writel(fbi->regs.lcdcon4, S3C2410_LCDCON4);
// lcdcon5: S3C2410_LCDCON5_FRM565 | S3C2410_LCDCON5_INVVLINE | S3C2410_LCDCON5_INVVFRAME | S3C2410_LCDCON5_PWREN | S3C2410_LCDCON5_HWSWP,
writel(fbi->regs.lcdcon5, S3C2410_LCDCON5);
// 根据s3c2440芯片手册设置
// 设置地址相关寄存器 LCDSADDR1、LCDSADDR2、LCDSADDR3
s3c2410fb_set_lcdaddr(fbi);
dprintk("LPCSEL = 0x%08lx\n", mach_info->lpcsel);
writel(mach_info->lpcsel, S3C2410_LPCSEL);
dprintk("replacing TPAL %08x\n", readl(S3C2410_TPAL));
// 不使用调色板
/* ensure temporary palette disabled */
writel(0x00, S3C2410_TPAL);
#if 0
/* ghcstop modified */
s3c2410_gpio_cfgpin(S3C2410_GPC5, S3C2410_GPC5_OUTP); // lcd display enable/disable
s3c2410_gpio_cfgpin(S3C2410_GPB1, S3C2410_GPB1_OUTP); // back light control
s3c2410_gpio_cfgpin(S3C2410_GPH6, S3C2410_GPH6_OUTP);
s3c2410_gpio_pullup(S3C2410_GPC5, 0);
s3c2410_gpio_pullup(S3C2410_GPB1, 0);
s3c2410_gpio_pullup(S3C2410_GPH6, 0);
s3c2410_gpio_setpin(S3C2410_GPC5, 1);
s3c2410_gpio_setpin(S3C2410_GPH6, 1);
s3c2410_gpio_setpin(S3C2410_GPB1, 1);
#else
/* thisway.diy@163.com modify again, for eBlocks */
s3c2410_gpio_cfgpin(S3C2410_GPB0, S3C2410_GPB0_OUTP); // back light control
s3c2410_gpio_pullup(S3C2410_GPB0, 0);
s3c2410_gpio_setpin(S3C2410_GPB0, 1); // back light control, enable
#endif
/* probably not required */
msleep(10);
// 使能lcd
/* Enable video by setting the ENVID bit to 1 */
fbi->regs.lcdcon1 |= S3C2410_LCDCON1_ENVID;
writel(fbi->regs.lcdcon1, S3C2410_LCDCON1);
// add by thisway.diy@163.com, for eBlocks
// 开启背光
s3c2410_gpio_setpin(S3C2410_GPB0, 1); // back light control
return 0;
}
编译进内核,加载
linux-2.6.22.6/drivers/video/Makefile
fb-y := fbmem.o fbmon.o fbcmap.o fbsysfs.o \
modedb.o fbcvt.o
fb-objs := $(fb-y)
linux-2.6.22.6/drivers/video/fbmem.c
subsys_initcall(fbmem_init);
static int __init
fbmem_init(void)
{
create_proc_read_entry("fb", 0, NULL, fbmem_read_proc, NULL);
// 注册字符设备
if (register_chrdev(FB_MAJOR,"fb",&fb_fops))
printk("unable to get major %d for fb devs\n", FB_MAJOR);
// 创建设备类 /sys/class/graphics
fb_class = class_create(THIS_MODULE, "graphics");
if (IS_ERR(fb_class)) {
printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));
fb_class = NULL;
}
return 0;
}
int
register_framebuffer(struct fb_info *fb_info)
{
int i;
struct fb_event event;
struct fb_videomode mode;
// 最多支持32个fb设备
if (num_registered_fb == FB_MAX)
return -ENXIO;
num_registered_fb++;
// 从registered_fb中找到一项空的位置,存放本次fb。下标作为次设备号,创建设备节点,以供应用程序使用
for (i = 0 ; i < FB_MAX; i++)
if (!registered_fb[i])
break;
fb_info->node = i;
// 创建设备 节点。/dev/fb%d
fb_info->dev = device_create(fb_class, fb_info->device,
MKDEV(FB_MAJOR, i), "fb%d", i);
if (IS_ERR(fb_info->dev)) {
/* Not fatal */
printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev));
fb_info->dev = NULL;
} else
fb_init_device(fb_info);
if (fb_info->pixmap.addr == NULL) {
fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
if (fb_info->pixmap.addr) {
fb_info->pixmap.size = FBPIXMAPSIZE;
fb_info->pixmap.buf_align = 1;
fb_info->pixmap.scan_align = 1;
fb_info->pixmap.access_align = 32;
fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;
}
}
fb_info->pixmap.offset = 0;
if (!fb_info->pixmap.blit_x)
fb_info->pixmap.blit_x = ~(u32)0;
if (!fb_info->pixmap.blit_y)
fb_info->pixmap.blit_y = ~(u32)0;
if (!fb_info->modelist.prev || !fb_info->modelist.next)
INIT_LIST_HEAD(&fb_info->modelist);
fb_var_to_videomode(&mode, &fb_info->var);
fb_add_videomode(&mode, &fb_info->modelist);
registered_fb[i] = fb_info;
event.info = fb_info;
fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
return 0;
}
应用程序app: file = open("/dev/fb0",0)
将要显示的图像数据,使用write接口写入,
write->
fb_write->
// 如果lcd驱动的fbops中存在write函数,则使用lcd驱动中的数据
if (info->fbops->fb_write)
return info->fbops->fb_write(info, buf, count, ppos);
否则使用fb_write写到显存中
产生一个LCD中断,进入中断处理函数
static irqreturn_t s3c2410fb_irq(int irq, void *dev_id)
{
struct s3c2410fb_info *fbi = dev_id;
unsigned long lcdirq = readl(S3C2410_LCDINTPND);
if (lcdirq & S3C2410_LCDINT_FRSYNC) {
if (fbi->palette_ready)
s3c2410fb_write_palette(fbi);
// 控制LCD控制器开始从显存中取数据传输到LCD屏,显示图像
writel(S3C2410_LCDINT_FRSYNC, S3C2410_LCDINTPND);
writel(S3C2410_LCDINT_FRSYNC, S3C2410_LCDSRCPND);
}
return IRQ_HANDLED;
}
static struct fb_ops s3c2410fb_ops = {
.owner = THIS_MODULE,
.fb_check_var = s3c2410fb_check_var,
.fb_set_par = s3c2410fb_set_par,
.fb_blank = s3c2410fb_blank,
.fb_setcolreg = s3c2410fb_setcolreg,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
待定