LCD驱动深入分析

framebuff 大小是  申请地址的 乘以长度

遇到的问题,各种报错出现引脚抢占 pwm1 与lcd引脚重突,禁止pwm

屏幕出现了 花屏   驱动不兼容

芯片io地址是为了开发板上找到io,我们换一个屏幕地址是不变的

时钟设备树使用了时钟子系统,只需要在设备树中填入值就可以

framebuffer 大小在驱动中初始化的使用一次性申请

LCD操作原理

 RGB 接口的 TFT-LCD 驱动

芯片集成了显存(ddr、SDRM)和控制器

1、看lcd手册,修改对应的lcd控制器

2、看lcd控制器手册,

一个像素点的颜色使用 24位表示,这样的话硬件对应数据引脚有24个

LCD硬件模型

RGB  传输数据

HSYNC  行同步

VSYNC  帧同步

DE  使能有效

declock  移动一个像素(频率)

HSYNC 信号发送过来电子枪就会换行,VSYNC信号发送过来就会电子枪换下一帧,DE处于高电平,电子枪才能有效

LCD驱动框架

 很多人都会说操纵lcd显示就是操纵framebuffer,表面上来看是这样的。实际上是frambuffer就是linux内核驱动申请的一片内存空间,然后lcd内有一片sram,cpu内部有个lcd控制器,它有个单独的dma用来将frambuffer中的数据拷贝到lcd的sram中去 拷贝到lcd的sram中的数据就会显示在lcd上,LCD驱动和framebuffer驱动没有必然的联系,它只是驱动LCD正常工作的,比如有信号传过来,那么LCD驱动负责把信号转成显示屏上的内容,至于什么内容这就是应用层要处理的

整体驱动框架流程

应用程序通过open 打开主设备为29的设备节点 ,然后通过文件描述符使用 ioctl获取到var fix等硬件参数 然后使用mmap映射显存 最后通过映射出来的地址读写Framebuffer

                                                           |

                                                           |

                                                           |

                                                          V

应用程序的read、write调用到 内核中的fbmem.c 中的file_operations结构体fb_ops 中的fb_read、fb_write 

                                                           |

                                                           |

                                                           |

                                                          V

fb_read、fb_write 中通过 该设备节点对应的次设备号从registered_fb【】这个数组中 得到描述具体设备的fb_info结构体 info ,通过info->fbops 就可以得到具体设备对应的具体操作函数了!

fbmen 从传入的innode节点中获取egistered_fb[],最后获得匹配的info机构体

1、fbmen 驱动框架

  通用框架

 

operation 操作函数*(都是空定义,具体操作在别的文件,通过fb_info结构体获得具体的硬件之类的信息)

 

register 注册函数

 

  ·创建 class

 

注销函数

在fbmen.c中写出字符设备所需的一切,当应用程序调用fbmen。然后通过fb_info跳转到具体硬件操作函数中去(具体的硬件操写在fb_fo中实现,fb_info 实现在具体硬件程序中)

 核心层(抽象层):只有驱动架构没有具体的硬件操作

在这里插入图片描述

fbmen.c中存在

fb_fops  操作定义函数,设备号创建函数、设备注册函数、设备文件创造函数

1、fbmen_init (1848)

fbmem_init(void)
{
	int ret;

	if (!proc_create_seq("fb", 0, NULL, &proc_fb_seq_ops))
		return -ENOMEM;

	ret = register_chrdev(FB_MAJOR, "fb", &fb_fops);
	if (ret) {
		printk("unable to get major %d for fb devs\n", FB_MAJOR);
		goto err_chrdev;
	}

	fb_class = class_create(THIS_MODULE, "graphics");
	if (IS_ERR(fb_class)) {
		ret = PTR_ERR(fb_class);
		pr_warn("Unable to create fb class; errno = %d\n", ret);
		fb_class = NULL;
		goto err_class;
	}

	fb_console_init();

	return 0;

    register_chrdev

将fb注册进内核,

if (register_chrdev(FB_MAJOR,"fb",&fb_fops))//fb设备注册进内核
          ->static inline int register_chrdev(unsigned int major, const char *name,//只有一个定义
				  const struct file_operations *fops)

   创建graphices类

创建一个新的 "graphics" 类别在 sysfs 中用于管理帧缓冲设备,直接对文件读写修改一个配置

2、register_framebuffer(1774)

注册一个帧缓冲设备,函数执行了帧缓冲设备的实际注册过程,包括设备节点的创建、内存分配和初始化等操作,并调用了已注册的fb_notifier链表的相关处理函数

static int do_register_framebuffer(struct fb_info *fb_info)
{
	int i, ret;
	struct fb_event event;
	struct fb_videomode mode;

	// 检查 fb_info 是否是外部设备(foreignness)。如果是外部设备,则返回错误码 -ENOSYS。
	if (fb_check_foreignness(fb_info))
		return -ENOSYS;

	// 检查是否存在冲突的帧缓冲设备,并在有冲突的情况下尝试移除冲突设备。
	ret = do_remove_conflicting_framebuffers(fb_info->apertures,
						 fb_info->fix.id,
						 fb_is_primary_device(fb_info));
	if (ret)
		return ret;

	// 检查已注册的帧缓冲设备数量是否已达到 FB_MAX,如果是,则返回错误码 -ENXIO。
	if (num_registered_fb == FB_MAX)
		return -ENXIO;

	// 增加已注册帧缓冲设备数量并寻找一个未被使用的索引 i。
	num_registered_fb++;
	for (i = 0 ; i < FB_MAX; i++)
		if (!registered_fb[i])
			break;
	fb_info->node = i;

	// 初始化帧缓冲设备的计数器 count 和互斥锁 lock、mm_lock。
	atomic_set(&fb_info->count, 1);
	mutex_init(&fb_info->lock);
	mutex_init(&fb_info->mm_lock);

	// 在 sysfs 中创建设备节点,fb_info->dev 用于表示创建的设备。
	// 如果创建失败,打印警告消息,并将 fb_info->dev 设置为 NULL。
	fb_info->dev = device_create(fb_class, fb_info->device,
				     MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
	if (IS_ERR(fb_info->dev)) {
		printk(KERN_WARNING "无法为帧缓冲设备 %d 创建设备节点; 错误号 = %ld\n", i, PTR_ERR(fb_info->dev));
		fb_info->dev = NULL;
	} else
		fb_init_device(fb_info); // 初始化设备

	// 在 fb_info 中分配并设置帧缓冲设备的 pixmap 参数。
	// pixmap 用于维护图形操作时的缓冲区信息,例如 X server 的像素深度等。
	if (fb_info->pixmap.addr == NULL) {
		fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
		if (fb_info->pixmap.addr) {
			fb_info->pixmap.size = FBPIXMAPSIZE;
			fb_info->pixmap.buf_align = 1;
			fb_info->pixmap.scan_align = 1;
			fb_info->pixmap.access_align = 32;
			fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;
		}
	}	
	fb_info->pixmap.offset = 0;

	// 设置帧缓冲设备的 pixmap.blit_x 和 pixmap.blit_y 的默认值。
	// 这些值用于定义帧缓冲设备支持的 blit 操作的边界。
	if (!fb_info->pixmap.blit_x)
		fb_info->pixmap.blit_x = ~(u32)0;

	if (!fb_info->pixmap.blit_y)
		fb_info->pixmap.blit_y = ~(u32)0;

	// 如果帧缓冲设备的 modelist.prev 或 modelist.next 为空,则将其初始化为双向链表头。
	if (!fb_info->modelist.prev || !fb_info->modelist.next)
		INIT_LIST_HEAD(&fb_info->modelist);

	// 根据设备配置决定是否需要交由 pm_vt_switch_required 来控制终端的切换。
	if (fb_info->skip_vt_switch)
		pm_vt_switch_required(fb_info->dev, false);
	else
		pm_vt_switch_required(fb_info->dev, true);

	// 将 fb_var(可变信息)转换为 fb_videomode,并将其添加到 modelist 链表中。
	fb_var_to_videomode(&mode, &fb_info->var);
	fb_add_videomode(&mode, &fb_info->modelist);

	// 将 fb_info 添加到已注册的帧缓冲设备数组中。
	registered_fb[i] = fb_info;

	// 触发 FB_EVENT_FB_REGISTERED 事件,并传递 fb_info 作为事件信息。
	event.info = fb_info;
	console_lock();
	if (!lock_fb_info(fb_info)) {
		console_unlock();
		return -ENODEV;
	}

	// 调用已注册的 fb_notifier,并传递 FB_EVENT_FB_REGISTERED 事件。
	fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
	unlock_fb_info(fb_info);
	console_unlock();
	return 0;
}

3、unresign_framebuffer

注销一个帧缓冲设备

 2、驱动部分*(硬件操作)

总线平台架构 ,下面的都在probe函数中执行

1. 分配一个fb_info结构体

fb_info = framebuffer_alloc(sizeof(struct fb_info), &pdev->dev);


2. 设置fb_info结构体参数

 1、填充固定参数结构体

fb_fix_screeninfo

//名字
strcpy(s3c_lcd->fix.id, "mylcd");
// MINI2440的LCD位宽是24,但是2440里会分配4字节即32位(浪费1字节) 
s3c_lcd->fix.smem_len = 320*240*32/8; 
//类型,这里选择默认值       
s3c_lcd->fix.type     = FB_TYPE_PACKED_PIXELS;
 //我们是TFT屏,所以选择真彩色
s3c_lcd->fix.visual   = FB_VISUAL_TRUECOLOR;
//一行的长度大小,320位*4字节
s3c_lcd->fix.line_length = 320*4;

 2、填充不固定参数结构体

fb_var_screeninfo

    s3c_lcd->var.xres           = 320;//x方向的分辨率
    s3c_lcd->var.yres           = 240;//y方向的分辨率
    s3c_lcd->var.xres_virtual   = 320;//x方向的虚拟分辨率
    s3c_lcd->var.yres_virtual   = 240;//y方向的虚拟分辨率
    s3c_lcd->var.bits_per_pixel = 32;//每个像素用32位

    /* RGB:888 */
    s3c_lcd->var.red.offset     = 16;//偏移
    s3c_lcd->var.red.length     = 8;//长度

    s3c_lcd->var.green.offset   = 8;
    s3c_lcd->var.green.length   = 8;

    s3c_lcd->var.blue.offset    = 0;
    s3c_lcd->var.blue.length    = 8;
    //立刻生效
    s3c_lcd->var.activate       = FB_ACTIVATE_NOW;

3、设置操作函数

static struct fb_ops mxsfb_ops = {
	.owner = THIS_MODULE,
	.fb_check_var = mxsfb_check_var,
	.fb_set_par = mxsfb_set_par,
	.fb_setcolreg = mxsfb_setcolreg, //设置调色板
	.fb_ioctl = mxsfb_ioctl,
	.fb_blank = mxsfb_blank,
	.fb_pan_display = mxsfb_pan_display,
	.fb_mmap = mxsfb_mmap,
	.fb_fillrect = cfb_fillrect,    //画矩形
	.fb_copyarea = cfb_copyarea,   //拷贝区域
	.fb_imageblit = cfb_imageblit,//画图
};


3. 硬件相关的操作

3.1 引脚设置

根据开发板lcd原理图,和外界屏幕的lcd原理图

 这个是芯片的lcd原理图,我们根据外接屏幕原理图判断出芯片lcd只有上面24+1+4个引脚起作用。

背光引脚复用为·gpio负责输出高低电平,其他配置为lcd功能,


这个不用该

GPIO设置

      LCD引脚

      背光引脚

使用pinctrl配置LCD引脚,然后使用GPIO控制背光

看原理图,要点亮lcd需要配置什么gpio,还有背光引脚


3.2  时钟设置

一般都是51.2kMZ

/* 时钟计算公式如下
	  * Video PLL output frequency(PLL5)= Fref * (DIV_SELECT + NUM/DENOM)
	  * PLL5_MAIN_CLK                    = PLL5 / POST_DIV_SELECT / VIDEO_DIV
	  * LCDIF1_CLK_ROOT                  = PLL5_MAIN_CLK /LCDIF1_PRED / LCDIF1_PODF
      * 
      * 输出LCDIF1_CLK_ROOT七寸屏:51.2Mhz
      * DIV_SELECT             = 32 (寄存器相应位设置为32)
      * NUM                    = 0  (寄存器相应位设置为0)
      * DENOM                  = 0  (寄存器相应位设置为0)
      * POST_DIV_SELECT        = 1  (寄存器相应位设置为2)
      * VIDEO_DIV              = 1  (寄存器相应位设置为0)
      * LCDIF1_PRED			   = 3  (寄存器相应位设置为2)
      * LCDIF1_PODF            = 5  (寄存器相应位设置为4)
      *
	  */ 
	  
	 /*
	  *为方便使用 ,不使用小数分频器
	  *NUM	   = 0
	  *DENOM = 0
	  *公式简化为:
	  * Video PLL output frequency(PLL5)= Fref * DIV_SELECT
	  * PLL5_MAIN_CLK                    = PLL5 / POST_DIV_SELECT / VIDEO_DIV
	  * LCDIF1_CLK_ROOT                  = PLL5_MAIN_CLK /LCDIF1_PRED / LCDIF1_PODF

根据LCD手册设置LCD控制器, 比如VCLK的频率等

      确定LCD 控制器的时钟(驱动代码中修改)

  • pix:Pixel clock,用于LCD接口,设置为LCD手册上的参数

  • axi:AXI clock,用于传输数据、读写寄存器,使能即可

      根据LCD的DCLK计算相关时钟

3.3 配置LCD控制器

0、在设备树中指定参数(lcd手册)

 

framebuffer-mylcd {
			compatible = "100ask,lcd_drv";
	        pinctrl-names = "default";
			pinctrl-0 = <&mylcd_pinctrl>;
			backlight-gpios = <&gpio1 8 GPIO_ACTIVE_HIGH>;

            clocks = <&clks IMX6UL_CLK_LCDIF_PIX>,
                     <&clks IMX6UL_CLK_LCDIF_APB>;
            clock-names = "pix", "axi";
            
            display = <&display0>;

			display0: display {
				bits-per-pixel = <24>;
				bus-width = <24>;

				display-timings {
					native-mode = <&timing0>;
                    
                timing
					 timing0: timing0_1024x768 {
					 clock-frequency = <50000000>;  //频率
					 hactive = <1024>;              //分辨率
					 vactive = <600>;
					 hfront-porch = <160>;        
					 hback-porch = <140>;
					 hsync-len = <20>;
					 vback-porch = <20>;
					 vfront-porch = <12>;
					 vsync-len = <3>;
               极性

					 hsync-active = <0>;  水平方向 低脉冲有效
					 vsync-active = <0>;   垂直方向  低脉冲有效
					 de-active = <1>;       DE高电平有效
					 pixelclk-active = <0>;  像素时钟 下降沿有效
					 };

				};
			};            
	};

1、从设备树中获得参数

时序参数、引脚极性等信息,都被保存在一个display_timing结构体里:

获得节点共有属性

 获得子节点display-timing私有属性

使用read函数,以设备树节点为基点读取属性参数

时序参数、引脚极性等信息,都被保存在一个display_timing结构体里:

 flag  引脚极性


2、使用参数配置lcd控制器(核心)

  • Framebuffer地址设置

  • Framebuffer中数据格式设置

  • LCD时序参数设置

  • LCD引脚极性设置

4. 注册

ret = register_framebuffer(fb_info);//注册设备结构体,info里面包含了opeatioion操作函数

直接调用框架中的函数就可以

5、卸载unregister_framebuffer

调用传入fbmen 框架中的unregister_framebuffer,然后传入fb_info,

驱动代码

1、平台总线框架

2、probe函数

改进双buffer

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值