一,前言
s3c2440 lcd 驱动分析,涉及到的内容有,LCD图像显示原理、s3c2440的LCD控制器的操作、LCD驱动使用平台总线-设备-驱动模型的实例、LCD相关参数的设置、fb字符设备驱动实例、framebuffer的注册和管理、以及一次LCD显示的完整过程分析。
二,LCD原理和硬件分析
2.1 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信号,控制像素点重新移动到第一行的第一个像素显示下一帧图像。
2.2 硬件电路
2.2.1 LCD背光电路
开启LCD显示,需要使能KEYBOARD(一般EN表示高电平有效,EN上面画一横表示低电平有效)开启背光。
2.2.2 LCD屏
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触摸屏连接
2.2.3 S3c2440主控
涉及 GPG、GPD、GPC引脚。
VM:LCD控制器使能引脚(由LCD控制器的寄存器配置),开启LCD显示需要配置相关寄存器的相应位使能。 LCD_PWREN:LCD电源使能引脚(由LCD控制器的寄存器配置),开启LCD显示需要配置相关寄存器的相应位使能。
三,LCD应用平台总线-设备-驱动模型
3.1 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 起来之后将被丢弃。 各个成员函数在不同时期被调用:
1.init_machine 在 arch/arm/kernel/setup.c 中被 customize_machine 调用,放在 arch_initcall() 段里面,会自动按顺序被调用。
2. init_irq在start_kernel() -> init_IRQ() -> init_arch_irq() 被调用 。
3. 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;
}
3.2 lcd 驱动的加载和注册
3.2.1 编译进内核,加载驱动
编译内核设置,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);
}
3.3 lcd 设备和驱动的匹配
platform_driver_register->
driver_register->
bus_add_driver->
driver_attach->
bus_for_each_dev->
__driver_attach
__driver_attach(kernel 2.6.22)
static int __driver_attach(struct device * dev, void * data)
{
struct device_driver * drv = data;
/*
* Lock device and try to bind to it. We drop the error
* here and always return 0, because we need to keep trying
* to bind to devices and some drivers will return an error
* simply if it didn't support the device.
*
* driver_probe_device() will spit a warning if there
* is an error.
*/
if (dev->parent) /* Needed for USB */
down(&dev->parent->sem);
down(&dev->sem);
if (!dev->driver)
driver_probe_device(drv, dev); // 调用驱动层程序的 probe函数
up(&dev->sem);
if (dev->parent)
up(&dev->parent->sem);
return 0;
}
__driver_attach(kernel 3.4)
static int __driver_attach(struct device *dev, void *data)
{
struct device_driver *drv = data;
/*
* Lock device and try to bind to it. We drop the error
* here and always return 0, because we need to keep trying
* to bind to devices and some drivers will return an error
* simply if it didn't support the device.
*
* driver_probe_device() will spit a warning if there
* is an error.
*/
// 匹配驱动和设备
if (!driver_match_device(drv, dev))
return 0;
if (dev->parent) /* Needed for USB */
device_lock(dev->parent);
device_lock(dev);
if (!dev->driver)
driver_probe_device(drv, dev);
device_unlock(dev);
if (dev->parent)
device_unlock(dev->parent);