Linux LCD设备驱动详解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_38696651/article/details/89431439

本文是基于mini2440开发板Linux版本号是linux-2.6.32.2的学习笔记

一. LCD device硬件信息

1.LCD控制器的寄存器地址从 0X4D000000开始
在这里插入图片描述

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
	}
};
  1. lcd的平台信息的设置
s3c24xx_fb_set_platdata(&mini2440_fb_info);
  1. 现在的lcd的型号:TD240320
    lcd的类型
.type		= S3C2410_LCDCON1_TFT,
  1. lcd的长宽
#define LCD_WIDTH 240
#define LCD_HEIGHT 320

lcd 平台信息的设置

二. lcd device的注册

调用注册平台设备接口注册s3c2440的外设设备。

platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));

设备的名称是上面的“s3c2410-lcd”

三. lcd driver的注册

搜索“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;
}
四. lcd driver的probe函数
  • 获取设备的platform_data。这个platform_data就是在mach-mini2440.c文件中通过s3c24xx_fb_set_platdata函数注册,最终指向的是全局变量mini2440_fb_info。
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,
};
  • 获取display硬件信息,实际上指向的是mini2440_lcd_cfg全局变量
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,
};
  • 申请一个framebuffer
fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
  • s3c2410fb_info结构体和fb_info的相互关联
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);
  • 先禁止lcd 视频和信号输出, LCDCON1寄存器的第0位写0就是禁止。
lcdcon1 = readl(info->io + S3C2410_LCDCON1);
writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1);

在这里插入图片描述

  • 先填充fbinfo的fix结构体
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结构体
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);
  • 获取lcd的时钟,并使能时钟
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

  • 申请video显存,大小为smem_len
info->screen_base = dma_alloc_writecombine(fbi->dev, map_size,&map_dma, GFP_KERNEL);

显存需要进行初始化为0

memset(info->screen_base, 0x00, map_size);
  • lcd 寄存器的初始化,调用的函数是s3c2410fb_init_registers
  • 将上面的framebuffer注册进内核。
ret = register_framebuffer(fbinfo);
五. lcd寄存器初始化
  • GPCCON的配置
.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的设置
.gpcup =        0x0000ffff,

gpc IO口禁止上拉
在这里插入图片描述

  • GPDCON的配置
.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的配置
.gpdup =        0x0000faff,

0x0000faff = 1111101011111111
除了GPD8和GPD10两个引脚外,其余的引脚全部禁止上拉。

  • 可以看出lcd的data引脚配置如下:
    在这里插入图片描述

  • TCONSEL寄存器的设置

.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);
六. lcd设置fbinfo的var的参数
  • 设置的长宽
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几个控制寄存器的设置
  • LCDCON1寄存器设置16位的颜色模式
    在这里插入图片描述
    选择16bpp,LCDCON1 |= 12 << 1;

  • 设置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:通过计算无效行的数量,决定帧同步信号脉冲高电平的宽度。

  • VSPW,VBPD,VFPD之间的关系
    在这里插入图片描述

  • 设置lcd的x分辨率
    在这里插入图片描述
    var->xres << 8 = 320 << 8;

  • 设置HBPD
    在这里插入图片描述
    var->upper_margin << 19 = 100 << 19
    HBPD:行同步下降沿后,行数据开始前,无效的VCLK的数量

  • 设置HFPD
    在这里插入图片描述
    var->left_margin << 0 = 0 << 0
    HFPD:行数据结束后,行同步上升沿前,无效的VCLK的数量

  • 设置HSPW
    在这里插入图片描述
    在这里插入图片描述
    var->hsync_len << 0 = 4 << 0
    HSPW:决定行同步信号脉冲高电平的宽度

  • HSPW,HBPD,HFPD之间的关系
    在这里插入图片描述

  • 设置字节在显存中的排放

#define S3C2410_LCDCON5_BSWP	    (1<<1)
#define S3C2410_LCDCON5_HWSWP	    (1<<0)

regs->lcdcon5 &= ~S3C2410_LCDCON5_BSWP;
regs->lcdcon5 |= S3C2410_LCDCON5_HWSWP;

在这里插入图片描述
BSWP = 0, HWSWP = 1
在这里插入图片描述

八. lcd显存的分配

显存的分配不能调用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;
九. 设置lcd时钟

在mach-mini2440.c文件中,lcd时钟的频率是170000,单位是什么暂时不知道。

#define LCD_PIXCLOCK 170000

从后面给的内核注释看,单位是ps, 及该lcd的时钟频率是170ns。

在这里插入图片描述
HCLK在开机时有打印,为100M,我们通过设置CLKVAL的值,可以控制lcd的输出时钟VCLK.
现在已经lcd需要的时钟频率为170000ps, 反推CLKVAL的值。

  • VCLK转为s: VCLK * 10^(-12)
  • VCLK转为HZ: 1 / (VCLK * 10^(-12))
  • 1 / (VCLK * 10^(-12)) = HCLK / ((CLKVAL + 1) * 2)
  • (CLKVAL + 1) * 2 = HCLK * (VCLK * 10^(-12))
  • (CLKVAL + 1) = HCLK * VCLK / ( 2 * (10^12) )
  • CLKVAL = HCLK * VCLK / ( 2 * (10^12) ) - 1

看一下源代码的计算

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控制器。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

  • 存放虚拟屏幕信息
    虚拟屏幕偏移
    由于这里我们的虚拟屏幕和物理屏幕是一样大的,因此偏移是0

虚拟屏幕页宽度
还是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;

猜你喜欢

转载自blog.csdn.net/weixin_38696651/article/details/89431439