本文是基于mini2440开发板Linux版本号是linux-2.6.32.2的学习笔记
2.lcd device的名称: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
}
};
s3c24xx_fb_set_platdata(&mini2440_fb_info);
.type = S3C2410_LCDCON1_TFT,
#define LCD_WIDTH 240
#define LCD_HEIGHT 320
lcd 平台信息的设置
调用注册平台设备接口注册s3c2440的外设设备。
platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));
设备的名称是上面的“s3c2410-lcd”
搜索“s3c2410-lcd”,找到文件s3c2410fb.c,发现在这个文件里面注册了匹配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,
},
};
probe函数是s3c2410fb_probe。
调用platform_driver_register注册driver。
int __init s3c2410fb_init(void)
{
int ret = platform_driver_register(&s3c2410fb_driver);
if (ret == 0)
ret = platform_driver_register(&s3c2412fb_driver);
return ret;
}
static struct s3c2410fb_mach_info mini2440_fb_info __initdata = {
.displays = &mini2440_lcd_cfg,
.num_displays = 1,
.default_display = 0,
.gpccon = 0xaa955699,
.gpccon_mask = 0xffc003cc,
.gpcup = 0x0000ffff,
.gpcup_mask = 0xffffffff,
.gpdcon = 0xaa95aaa1,
.gpdcon_mask = 0xffc0fff0,
.gpdup = 0x0000faff,
.gpdup_mask = 0xffffffff,
.lpcsel = 0xf82,
};
static struct s3c2410fb_display mini2440_lcd_cfg __initdata =
{
#if !defined (LCD_CON5)
.lcdcon5 = S3C2410_LCDCON5_FRM565 |
S3C2410_LCDCON5_INVVLINE |
S3C2410_LCDCON5_INVVFRAME |
S3C2410_LCDCON5_PWREN |
S3C2410_LCDCON5_HWSWP,
#else
.lcdcon5 = LCD_CON5,
#endif
.type = S3C2410_LCDCON1_TFT,
.width = 0,
.height = 0,
.pixclock = LCD_PIXCLOCK,
.xres = LCD_WIDTH,
.yres = LCD_HEIGHT,
.bpp = 16,
.left_margin = LCD_LEFT_MARGIN + 1,
.right_margin = LCD_RIGHT_MARGIN + 1,
.hsync_len = LCD_HSYNC_LEN + 1,
.upper_margin = LCD_UPPER_MARGIN + 1,
.lower_margin = LCD_LOWER_MARGIN + 1,
.vsync_len = LCD_VSYNC_LEN + 1,
};
fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
info = fbinfo->par;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
size = (res->end - res->start) + 1;
info->mem = request_mem_region(res->start, size, pdev->name);
info->io = ioremap(res->start, size);
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;
irq = platform_get_irq(pdev, 0);
ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info);
info->clk = clk_get(NULL, "lcd");
clk_enable(info->clk);
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;
mach_info->displays[i].xres = 240;
mach_info->displays[i].yres = 320;
mach_info->displays[i].bpp = 16;
那么smem_len = 240 * 320 * 16/8 = 153600byte
info->screen_base = dma_alloc_writecombine(fbi->dev, map_size,&map_dma, GFP_KERNEL);
显存需要进行初始化为0
memset(info->screen_base, 0x00, map_size);
ret = register_framebuffer(fbinfo);
.gpccon = 0xaa955699,
0xaa955699 = 10101010100101010101011010011001
具体的引脚配置为:
GPC15:VD[7]; GPC14:VD[6]; GPC13:VD[5]; GPC12:VD[4]; GPC11:VD[3]; GPC10:Output; GPC9:Output; GPC8:Output;
GPC7:Output; GPC6:Output; GPC5:Output; GPC4:VM; GPC3:VFRAME; GPC2:Output; GPC1:VCLK; GPC0:Output;
.gpcup = 0x0000ffff,
.gpdcon = 0xaa95aaa1,
0xaa95aaa1 = 10101010100101011010101010100001
具体的引脚配置为:
GPD15:VD[23]; GPD14:VD[22]; GPD13:VD[21]; GPD12:VD[20]; GPD11:VD[19]; GPD10:Output; GPD9:Output; GPD8:Output;
GPD7:VD[15]; GPD6:VD[14]; GPD5:VD[13]; GPD4:VD[12]; GPD3:VD[11]; GPD2:VD[10]; GPD1:Input; GPD0:Output;
.gpdup = 0x0000faff,
0x0000faff = 1111101011111111
除了GPD8和GPD10两个引脚外,其余的引脚全部禁止上拉。
.lpcsel = 0xf82,
即:选择输出分辨率为240 * 320
选择 Sync mode
Sync mode需要用H-SYNC和V-SYNC同步RGB data;DE mode (Data Enable)则只需要DE信号同步RGB data。
tpal = regs + S3C2410_TPAL;
/* ensure temporary palette disabled */
writel(0x00, tpal);
var->xres_virtual = display->xres;
var->yres_virtual = display->yres;
var->height = display->height;
var->width = display->width;
var->xres_virtual = 240;
var->yres_virtual = 320;
var->height = 0;
var->width = 0;
设置时钟参数
var->pixclock = display->pixclock = 170000
var->left_margin = display->left_margin = 0 + 1
var->right_margin = display->right_margin = 100 + 1
var->upper_margin = display->upper_margin = 0 + 1
var->lower_margin = display->lower_margin = 1 + 1
var->vsync_len = display->vsync_len = 9 + 1
var->hsync_len = display->hsync_len = 4 + 1
设置颜色,颜色模式为RGB565
var->red.offset = 11;
var->green.offset = 5;
var->blue.offset = 0;
var->red.length = 5;
var->green.length = 6;
var->blue.length = 5;
没有透明度的设置
var->transp.offset = 0;
var->transp.length = 0;
设置lcd的y分辨率
var->yres << 14;
设置VBPD
var->upper_margin << 24 = 0 << 24
VBPD:帧同步后,帧数据开始前,无效行信号的数量。
设置VFPD
var->lower_margin << 6 = 1 << 6
VFPD:帧数据结束后,帧同步前,无效行信号的数量
设置VSPW
var->vsync_len << 0 = 9 << 0
VSPW:通过计算无效行的数量,决定帧同步信号脉冲高电平的宽度。
设置HBPD
var->upper_margin << 19 = 100 << 19
HBPD:行同步下降沿后,行数据开始前,无效的VCLK的数量
设置HFPD
var->left_margin << 0 = 0 << 0
HFPD:行数据结束后,行同步上升沿前,无效的VCLK的数量
设置字节在显存中的排放
#define S3C2410_LCDCON5_BSWP (1<<1)
#define S3C2410_LCDCON5_HWSWP (1<<0)
regs->lcdcon5 &= ~S3C2410_LCDCON5_BSWP;
regs->lcdcon5 |= S3C2410_LCDCON5_HWSWP;
显存的分配不能调用kmalloc函数,因为分配的物理地址不是连续的,而lcd控制器需要从连续的显存上取数据。
info->screen_base = dma_alloc_writecombine(fbi->dev, map_size, &map_dma, GFP_KERNEL);
dma_alloc_writecombine返回的是申请的显存的虚拟地址,赋值给info->screen_base。
map_dma是申请的显存的物理地址,赋值给smem_start。
info->fix.smem_start = map_dma;
在mach-mini2440.c文件中,lcd时钟的频率是170000,单位是什么暂时不知道。
#define LCD_PIXCLOCK 170000
从后面给的内核注释看,单位是ps, 及该lcd的时钟频率是170ns。
HCLK在开机时有打印,为100M,我们通过设置CLKVAL的值,可以控制lcd的输出时钟VCLK.
现在已经lcd需要的时钟频率为170000ps, 反推CLKVAL的值。
看一下源代码的计算
clkdiv = DIV_ROUND_UP(s3c2410fb_calc_pixclk(fbi, var->pixclock), 2);
clkdiv = s3c2410fb_calc_pixclk(fbi, var->pixclock) / 2
if (type == S3C2410_LCDCON1_TFT)
{
--clkdiv;
if (clkdiv < 0)
clkdiv = 0;
}
clkdiv = clkdiv -1;
看一下s3c2410fb_calc_pixclk函数
static unsigned int s3c2410fb_calc_pixclk(struct s3c2410fb_info *fbi, unsigned long pixclk)
{
unsigned long clk = fbi->clk_rate;
unsigned long long div;
/* pixclk is in picoseconds, our clock is in Hz
*
* Hz -> picoseconds is / 10^-12
*/
div = (unsigned long long)clk * pixclk;
div >>= 12; /* div / 2^12 */
do_div(div, 625 * 625UL * 625); /* div / 5^12 */
dprintk("pixclk %ld, divisor is %ld\n", pixclk, (long)div);
return div;
}
div = clk * pixclk;
div = div / 2^12;
div = div / 5^12;
即div = div / (10^12)
div = clk * pixclk / (10^12)
clkdiv = clk * pixclk / ( (10^12) * 2 ) - 1,跟上面推导的公式是一致的。
分配了显存之后,要把显存的地址告诉lcd控制器。lcd控制器会自动的从显存中取出每一个值通过VD0 ~ VD23发送出像素数据到lcd屏上去。
saddr1 = info->fix.smem_start >> 1;
因为我的lcd设置的是单扫描模式,saddr1保存的是显存地址的A30~A1位,最高位和最低位不要,因此
saddr1 = info->fix.smem_start >> 1;
saddr2 = info->fix.smem_start;
saddr2 += info->fix.line_length * info->var.yres;
saddr2 >>= 1;
info->fix.line_length = 240 * 2;
info->var.yres = 320;
我们的结束地址是:s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len
saddr2保存显存的A21 ~ A1位,需要右移一位赋值给saddr2
虚拟屏幕页宽度
还是info->fix.line_length = 240 * 2,因为虚拟屏幕和物理屏幕一样大,但是这里表示的是半字,所以要除以2
saddr3 = S3C2410_OFFSIZE(0) | S3C2410_PAGEWIDTH((info->fix.line_length / 2) & 0x3ff);
真彩色时不需要设置调色板,假彩色时才需要调色板
真彩色宏:FB_VISUAL_TRUECOLOR
假彩色宏:FB_VISUAL_PSEUDOCOLOR
调色板采用8位索引,用来索引256种颜色。lcd 有256个寄存器来保存这256中颜色值。
颜色模式有2种: 5:6:5 (R:G:B) 和 5:5:5:1(R:G:B:I)
寄存器地址:0X4D000400 ~ 0X4D0007FC
调色板颜色设置:
val = (red >> 0) & 0xf800;
val |= (green >> 5) & 0x07e0;
val |= (blue >> 11) & 0x001f;
writel(val, regs + S3C2410_TFTPAL(regno));
真彩色不需要调色板,因此使用了一个假调色板,颜色保存在假调色板数组里面。
u32 *pal = info->pseudo_palette;
val = chan_to_field(red, &info->var.red);
val |= chan_to_field(green, &info->var.green);
val |= chan_to_field(blue, &info->var.blue);
pal[regno] = val;