Linux LCD设备驱动详解
本文是基于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}
};
- lcd的平台信息的设置
s3c24xx_fb_set_platdata(&mini2440_fb_info);
- 现在的lcd的型号:TD240320
lcd的类型
.type = S3C2410_LCDCON1_TFT,
- 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;
Linux LCD设备驱动详解相关推荐
- Linux字符设备驱动详解七(“插件“设备树实现RGB灯驱动)
文章目录 系列文章目录 前言 正文 Device Tree Overlays:"插件"设备树 传统设备树 "插件"设备树 使用前提 案例说明 设备树:foo.d ...
- Linux字符设备驱动详解四(使用自属的xbus驱动总线)
文章目录 系列文章目录 前言 驱动目录 正文 驱动总线 总线管理 总线注册 设备注册 驱动注册 代码示例 总结 系列文章目录 Linux字符设备驱动详解 Linux字符设备驱动详解二(使用设备驱动模型 ...
- Linux usb设备驱动详解
1.Linux usb设备驱动框架 USB是通用串行总线的总称,Linux内核几乎支持所有的usb设备,包括键盘,鼠标,打印机,modem,扫描仪.Linux的usb驱动分为主机驱动与gadget驱动 ...
- itop4412 LCD设备驱动详解(四)之PROBE再深入
LCD的工作,在kernel中有device和driver两个描述,这也是必然 在第二节中我们详解介绍了 s3cfb_main.c ---probe函数的框架. 回顾一下probe函数的作用: 1. ...
- itop4412 LCD设备驱动详解(三)之PROBE
lcd的工作,在kernel中有device和driver两个描述,这也是必然 在上一节中我们详解介绍了 s3cfb_main.c ---probe函数的框架. 回顾一下probe函数的作用: 1. ...
- 【转】草根老师的 linux字符设备驱动详解
Linux 驱动 之 模块化编程 Linux 驱动之模块参数和符号导出 Linux 设备驱动之字符设备(一) Linux 设备驱动之字符设备(二) Linux 设备驱动之字符设备(三) 转载于:htt ...
- linux usb gadget驱动详解(一)
由于PC的推广,USB(通用串行总线)是我们最熟知的通信总线规范之一,其他的还有诸如以太网.PCIE总线和RS232串口等.这里我们主要讨论USB. USB是一个主从通信架构,但只能一主多从.其中us ...
- linux usb gadget驱动详解(二)
在上篇<linux usb gadget驱动详解(一)>中,我们了解到gadget的测试方法,但在最后,我们留下一个问题,就是怎样使用新的方法进行usb gadget驱动测试. 我们发现l ...
- Linux设备驱动之usb设备驱动详解
原文地址:http://blog.csdn.net/chenjin_zhong/article/details/6329316 1.Linux usb设备驱动框架 USB是通用串行总线的总称,Linu ...
最新文章
- mysql 5.7 编译_Mysql5.7版本编译安装及配置
- 为什么Kubernetes的存储如此艰难?
- 一文读懂全球半导体市场
- javascript判断对象、字符串、数组是否为空(兼容绝大部分浏览器)
- 人生不能过分认真(较真),但必须认真!
- 安卓系列转载,有时间可以参考学习
- sql server数据库课程设计分析
- 也许你需要点实用的-Web前端笔试题
- cuda Device Management
- 安装包制作工具 SetupFactory使用 详解
- 智能网联汽车——传感器与驾驶辅助
- Thinkpad E430c 16GB内存安装成功
- 怎么把外部参照合并到图纸_为什么CAD图纸作为外部参照插入时位置变了?
- 【已解决】找不到某服务器 IP 地址
- 华为交换机RRPP环协议
- 分享10个高质量的插画网站
- Arithmetic Slices 算术序列
- cad捕捉不到标注线上的点_CAD捕捉不到正在绘制的多段线上的点怎么办
- 背景图片和颜色混合叠加多种混合模式
- 计算机组成原理~计算机的基本组成②