这个文章只是大概流程,很难讲的细
分为两部,第一部分是 整个框架怎么跑的
第二部分是 lcd手册的参数 和soc上lcd控制器的参数 和驱动中需要的参数 到底有什么映射关系
fbbuffer的思想是 应用空间有图像需要 拷贝到驱动空间 如果是cory_To_usr 效率就很低
如果驱动空间能直接映射一块内存(肯定是驱动分配的)到用户空间 mmap 映射到用户空间
应用程序直接写这个地址就会快很多了
当然了 上面驱动中分配的内存 并不是一个块普通内存 是一块DMA内存(外设和这个内存传输就不用cpu的参与)
比如 这块内存中数据达到一定的值 自动就刷入到外设中 比如串口 比如lcdbuffer
这样的话就不用 cpu进行 ldr等操作 直接就搬过去了 省出cpu的调度
达成的效果 如果想显示一张图片 只要用户空间 把这个图片丢入内存 就自动发送到外设了
一般把lcd设备叫帧缓冲设备 : 操作一块内存就是等于操作lcd屏 这块内存也就直接叫做显存 所以这一块在soc上的内存叫集显显存
第一部分
在linux驱动中只有两层
1:lcd控制器层 作用: 1初始化lcd控制器 2分配DMA的显存 (知道分配显存,但是不知道映射给谁)
2: 核心层fb 作用:与应用进行交互 2完成显存的映射 (知道映射不知道显存在哪里) ./drivers/video/fbdev/core/fbmem.c
其实我们这边要写的 只有应用程序 和 lcd_fb_win0里面的数据 但是要清楚流程
第一步
如果是老的内核,开机自启的时候会注册各种控制器的平台总线dev
新的内核就是用设备树 往平台总线上面注册pdev
那肯定是初始化lcd_control 也就是把这个lcd_control 抽象为一个pdev
这边用的是老的内核,所以开始开机会 跑arch/arm/mach-s5pv210/mach-smdkv210.c
static void __init smdkv210_machine_init(void) //总的初始化函数platform_add_devices(smdkv210_devices, ARRAY_SIZE(smdkv210_devices));//平台总线增加devicestatic struct platform_device *smdkv210_devices[] __initdata = { //下面这个数组是各种soc上面的控制器&s3c_device_fb,//这里是lcd_contral&s3c_device_cfcon,&s3c_device_i2c2,}
arch/arm/plat-samsung/dev-fb.c 这个应该是soc的lcd_control寄存器资源
struct platform_device s3c_device_fb = { //lcd_contral上面的寄存器信息在这里,因为control资源一样所以都在plat-samsung中.name = "s3c-fb",.id = -1,.num_resources = ARRAY_SIZE(s3c_fb_resource),.resource = s3c_fb_resource,.dev.dma_mask = &s3c_device_fb.dev.coherent_dma_mask,.dev.coherent_dma_mask = 0xffffffffUL,
};
arch/arm/plat-samsung/dev-fb.c
static struct resource s3c_fb_resource[] = {[0] = {.start = S3C_PA_FB,.end = S3C_PA_FB + SZ_16K - 1,.flags = IORESOURCE_MEM,},[1] = {.start = IRQ_LCD_VSYNC,.end = IRQ_LCD_VSYNC,.flags = IORESOURCE_IRQ,},[2] = {.start = IRQ_LCD_FIFO,.end = IRQ_LCD_FIFO,.flags = IORESOURCE_IRQ,},[3] = {.start = IRQ_LCD_SYSTEM,.end = IRQ_LCD_SYSTEM,.flags = IORESOURCE_IRQ,},
};
第二步,把lcd硬件信息也放在pdev里面
上面的pdev里面只有当前的lcd_contral的数据
光有控制器的数据还不行,因为连接的lcd屏幕不一样,需要把屏幕的数据也放在pdev中
也是相同的在初始化函数增加 屏幕的信息
/下面这个是特定板子上面lcd的硬件资源
arch/arm/mach-s5pv210/mach-smdkv210.c
s3c_fb_set_platdata(&smdkv210_lcd0_pdata); //初始化pdev,这里的data用来放屏幕的数据s3c_set_platdata(pd, sizeof(struct s3c_fb_platdata),&s3c_device_fb);//函数内部把s3c_fb_platdata也就是下面的lcd屏幕数据放到了//pdev中,这里lcd的pdev由lcd的屏幕数据和lcd的控制器数据合二为一static struct s3c_fb_platdata smdkv210_lcd0_pdata __initdata = {.win[0] = &smdkv210_fb_win0,.vidcon0 = VIDCON0_VIDOUT_RGB | VIDCON0_PNRMODE_RGB,.vidcon1 = VIDCON1_INV_HSYNC | VIDCON1_INV_VSYNC,.setup_gpio = s5pv210_fb_gpio_setup_24bpp,};static struct s3c_fb_pd_win smdkv210_fb_win0 = {.win_mode = {.left_margin = 13,.right_margin = 8,.upper_margin = 7,.lower_margin = 5,.hsync_len = 3,.vsync_len = 1,.xres = 800,.yres = 480,},.max_bpp = 32,.default_bpp = 24,
};
第三步,进入核心层,注册fbmem
./drivers/video/fbdev/core/fbmem.c
核心层fb 作用:与应用进行交互 2完成显存的映射 (知道映射不知道显存在哪里)
a 申请主设备号 29+fops
b 创建类
c 注册了一个全局数组,register_fb 里面都是fbinfo结构体 下标也是次设备号
这个全局数组是为了存放 lcd控制器构造的显存
下面这个字符设备带的fops中 mmap已经写好了 会做一个物理地址的映射
如果app调用mmap 就会跑到这里
module_init(fbmem_init);
fbmem_init(void) !proc_create_seq("fb", 0, NULL, &proc_fb_seq_ops) //就是在/proc/下面有个fb的文件夹ret = register_chrdev(FB_MAJOR, "fb", &fb_fops); //#define FB_MAJOR 29 主设备号29file_operations fb_fops.mmap = fb_mmap,vm_iomap_memory(vma, start, len);io_remap_pfn_range(vma, vma->vm_start, pfn, vm_len, vma->vm_page_prot); //做一个物理地址的映射fb_class = class_create(THIS_MODULE, "graphics");//创建这个类 /sys/class
第四步,lcd控制器层,平台总线匹配
drivers/video/fbdev/s3c-fb.c
a pdrv->probe执行了probe函数
b 获取lcd控制器的数据
c 获取了平台自定义的数据(也就是这块lcd的数据)
d ioremap
e 获取中断数据等
f 申请中断
g 初始化lcd控制器
上面就是为了 构造fb_info对象
来记录显存的信息 构建创造fb_info对象 通过DMA分配显存也记录到fb_info
最后注册到 core层的regisiter_fb数组中
同时也把var这个可变参数进行记录fb_info 不止记录到lcd_contral中
记录在fb_info中为了让用户拿到lcd的分辨率
pdrv->probes3c_fb_probe(struct platform_device *pdev)pd = dev_get_platdata(&pdev->dev); //就是获取pdev sfb = devm_kzalloc(dev, sizeof(*sfb), GFP_KERNEL);//分配全局对象platform_get_resource(pdev, IORESOURCE_MEM, 0);//获取dev资源sfb->regs = devm_ioremap_resource(dev, res);//ioremap的地址,就是映射lcd寄存器的地址,好操控寄存器,所以寄存器的地址映射在sfb->regspd->setup_gpio();//pd能猜到就是平台pdev,也就是自定义的数据,就是初始化gpio,获取到的pdev有个函数指针是setup_gpio()writel(pd->vidcon1, sfb->regs + VIDCON1);//也和ach-s5pvinit 要进去看看 视频14:05,意思是把pd->vidcon1(在pdev中的数据vidcon1)赋值给基地址偏移VIDCON1的地方,为了修改电频是否反转s3c_fb_probe_win(sfb, win, fbdrv->win[win],&sfb->windows[win]);//内部完成DMA显存分配,fb_info初始化和注册struct fb_info *fbinfo;fbinfo = framebuffer_alloc(sizeof(struct s3c_fb_win) +palette_size * sizeof(u32), sfb->dev);//分配fb_info对象s3c_fb_alloc_memory(sfb, win);//初始化,分配显存struct fb_info *fbi = win->fbinfo;fbi->screen_base = dma_alloc_wc(sfb->dev, size, &map_dma, GFP_KERNEL);//分配了一块dma内存,结果给了fbi->screen_basefbi->fix.smem_start = map_dma;//记录dma的显存起始位置到fb_info的固定参数区s3c_fb_set_par(fbinfo);//把各种值刷入lcd的寄存器中writel(data, regs + sfb->variant.buf_size + (win_no * 4));writel(data, regs + VIDOSD_B(win_no, sfb->variant));register_framebuffer(fbinfo);//注册这个fb对象drivers/video/fbdev/core/fbmem.cfor (i = 0 ; i < FB_MAX; i++) //去数组registered_fb找到一个空的地方去注册registered_fb[i]fb_info->dev = device_create(fb_class, fb_info->device,MKDEV(FB_MAJOR, i), NULL, "fb%d", i); //创建这个设备节点registered_fb[i] = fb_info;fb_videomode_to_var(&fbinfo->var, &initmode);//把fbinfo->var进行填充把平台自定义的数据,lcd的时序值刷入var中var->xres = mode->xres;var->yres = mode->yres;var->xres_virtual = mode->xres;var->yres_virtual = mode->yres;var->xoffset = 0;var->yoffset = 0;var->pixclock = mode->pixclock;var->left_margin = mode->left_margin;var->right_margin = mode->right_margin;var->upper_margin = mode->upper_margin;var->lower_margin = mode->lower_margin;devm_request_irq(dev, sfb->irq_no, s3c_fb_irq,0, "s3c_fb", sfb);//申请中断platform_set_drvdata(pdev, sfb);//也就是在这个dev里面保存一下私有数据dev->driver_data = data;b 构造fb_info对象
用来记录显存的信息 构建创造fb_info对象 通过DMA分配显存也记录到fb_info
最后注册到 core层的regisiter_fb数组中
同时也把var这个可变参数进行记录fb_info 不止记录到lcd_contral中
记录在fb_info中为了让用户拿到lcd的分辨率
第五步应用程序,打开设备节点
1, 打开设备int fd = open("/dev/fb0", O_RDWR);2, 获取到lcd屏的信息xres, yres, bppstruct fb_var_screeninfo var;ioctl(fd, FBIOGET_VSCREENINFO, &var);//到内核空间,在core层|.unlocked_ioctl = fb_ioctl,|struct fb_info *info = file_fb_info(file);//|//从registered_fb数组中得到fb_info,获取到pdev的数据了struct fb_info *info = registered_fb[fbidx];do_fb_ioctl(info, cmd, arg);|void __user *argp = (void __user *)arg;switch (cmd) {case FBIOGET_VSCREENINFO:var = info->var; //把内核空间的var copy_to_user到 argp也就是应用空间的varcopy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0;//回到用户空间,计算出一个frame的大小 size_t length = var.xres * var.yres * var.bits_per_pixel / 8
3,映射显存到用户空间char *addr = mmap(NULL, size_t length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);可以理解为addr就是显存的地址framebuffer_ptr =(char *)mmap( NULL,//如果此值为NULL,则表示用内核来自动给你分配一块虚拟空间screensize, //空间大小PROT_READ|PROT_WRITE,//权限MAP_SHARED, //是否可以共享framebuffer_fd, //文件描述符0); //从哪个地方开始 4, 得到图片数据,将数据写入到addr/*1.打开一副图片*/pic_fd =open(argv[2],O_RDWR);printf("pic_fd=%d\n",pic_fd);/*2.获取图片大小*/len =lseek(pic_fd, 0, SEEK_END);printf("len =%ld\n",len);buffer =(char *)malloc(len);lseek(pic_fd, 0, SEEK_SET);/*3.读取图片数据*/read(pic_fd,buffer,len); // buffer 存了bmp的图片数据, framebuffer_ptr映射之后的显存位置draw_bmp(buffer,(unsigned short *) framebuffer_ptr);|bmp_read_file(bmpfilename, &bmpdata);//跳过bmp文件头bmp2fb16_rgb565(bmpdata, fb);|//bmp2fb16_rgb565(unsigned char *bmpdata,unsigned short *fb16)fb16_buff = fb16; // fb16_buff就是framebuffer_ptr,映射之后的显存位置for (y = ysize; y > 0 ; y--){for (x = 0 ; x < xsize; x++) /*copy one line to frame buffer*/{ /*copy one pixel to frame buffer*/b = *bmpdata;bmpdata++;b >>= 3;g = *bmpdata;bmpdata++;g >>= 2;r = *bmpdata;bmpdata++;r >>= 3;pixel16 = (unsigned short) ((r << 11) | (g << 5) | b); *( fb16_buff + (y* xsize + x) ) = pixel16;//将一个像素填写到显存}}
第二部分
lcd手册的参数 和soc上lcd控制器的参数 和驱动中需要的参数 到底有什么映射关系
因为在pdev中的那个 fb_win的参数是要我们自己填写的 移植新的触摸屏 就需要知道这些参数
如果想象画图就是打点的话 会有时序要求
那么vsync 和hsync 就像时钟 比如拉高了 才能打点这种
Soc中vsync 和hsync就是时序控制的部分
对应soc的数据手册
这里看看我们需要的lcd的时序图 截取于soc
最上面的vsync 上下电频表示一帧 一个完整画面
Hsync的一个上下电频 就表示一行 所以一帧里面有很多行
图中把一行给放大
一行有很多个像素 那么vclk的 上下电频就是一个像素
理解电子枪打一行数据 从图上看 HSYNC为高电频的时候就是无效的周期
但是hsync放下来后还有个虚线 这里在下面的vden是表示数据使能
所以在vden里面的 hozval+1才是真正的稳定时间 这个时间里面要打800个像素
HFPD+1 电子枪跑到了开始的地方 表示打完第一行 打下一行 这个时间用来折返
理解电子枪打一帧数据
所以Vsunc中 开始的高电频也是无效周期 低电频才是稳定的时间
所以在VDEN 中LInval+1 才是真正的打点的时间 打了480行
上面的时序是soc给出来的样式 不是确定的具体时间
那么上面无效时间稳定时间 的决定 应该取决于LCD屏
所以现在打开lcd的屏幕手册
这里VS垂直同步信号 HS水平同步信号
DE像素使能
所以上面有个叫tvpw的时间其实就是 VS垂直同步信号中的无效时间
继续因为用的是 fb_buffer驱动,linux提供的 所以数据也要符合驱动的要求
到现在发现soc的手册 和lcd手册和驱动的叫法不一样但其实都描述一个东西
LINVAL 理解为行号一个frame中有480行
意思就是上面的数据 lcd知道自己的时序 soc把lcd需要的时序构造出来
时序有了 需要把时序给驱动 驱动里面需要的数值到底填多少 要根据驱动需要的数据
构造出来 比如下面就是驱动需要的数据
1,移植的时候主要是设定自定义数据
arch/arm/mach-s5pv210/mach-smdkv210.c
根据参数修改如下 所以下面这个pdev 就被构造出来了
这么想也是 lcd控制器 和屏幕直接就是直连的 所以就依赖着 平台虚拟总线进行连接
static struct s3c_fb_pd_win smdkv210_fb_win0 = {
.win_mode = {
.left_margin = 26,
.right_margin = 210,
.upper_margin = 13,
.lower_margin = 22,
.hsync_len = 20,
.vsync_len = 10,
.xres = 800,
.yres = 480,
},
.max_bpp = 32,
.default_bpp = 16, // 和应用有关
};