IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> STM32MP157 LCD的 linux驱动程序--基于之Framebuffer框架 -> 正文阅读

[嵌入式]STM32MP157 LCD的 linux驱动程序--基于之Framebuffer框架

STM32MP157 LCD的linux驱动程序–基于之Framebuffer框架

  1. LCD硬件分析

    1. LCD直观的描述

      LCD就是多个像素点而组成的,如下图所示:

      上图中的yres值就是y轴方向有多少个像素点,xres的值就是X轴的像素点的个数,平时所说的分辨率如1920X1080,就代表X轴有1920个像素点,Y轴有1080个像素点,那这个显示器就共有(1920*1080)个像素点。我们通过控制每个像素点的显示的颜色进而控制显示的画面。

    2. 像素点的颜色表示方法

      每个像素点都使用红绿蓝三原色来表示,有24位数据格式和16位数据格式表示,其中有个单位bpp.

      • bpp:bits per pixel:每个像素使用多少位

      • 24bpp格式:其实实际中还是使用了32位,其中bit[7:0]表示Blue,bit[15:8]表示Green,bit[23:16]表示Red,bit[31:24]对于ARGB888格式则表示透明度,对于RGB888则没有被使用

      • 16bpp格式:有RGB565格式和RGB555格式

        • RGB565格式:bit[4:0]表示Blue;bit[10:5]表示Green;bit[5:11]表示Red;
        • RGB555格式:bit[4:0]表示Blue;bit[9:5]表示Green;bit[14:10]表示Red;
    3. 如何将每个像素点的颜色数据发送到lcd?

      如果采用24bpp格式,那一个像素点共占用32位数据,若lcd的分辨率为1024x600,那总共就有(1024*600)个像素点,占用的内存大小就是(1024x600)x32/8个字节,在内存中分配出一块用来存放像素点数据的内存,这块内存就被叫做Framebuffer,Framebuffer翻译为中文即帧缓冲区,帧这个单位是影像动画中的最小单位,一帧就是一副静止的画面,连续的帧就形成了动画,那么帧缓冲区就是用来缓存一帧画面的数据。在linux内核当中它就是一种程序接口,这个接口将显示器设备抽象为帧缓冲区,这个帧缓冲区一般是根据显示设备的分辨率和图像格式(ARGB888、RGB888、RGB565等)来确定缓冲区大小,从内存中申请一块区域,当我们要显示一副图片时,我们将图片的数据写入帧缓冲区即可。

      Framebuffer内存示意图如下:

    4. MCU或MPU与Framebuffer及lcd之间的联系

      Framebuffer和lcd之间是通过lcd控制器来实现的,具体连接方式有两种类型:

      • 第一种:MCU内部不带lcd控制器,则就需要LCD模组,如下图所示,一般采用8080接口通信,MCU完全可以把LCD模组当做一块RAM进行操作。一般通过FSMC方式操作。

      • 第二种:一般的MPU都自带LCD控制器,例如STM32MP157外设自带ltdc,也就是LCD控制器,所以直接外接ldc,如下图:

        对于这种模式下。接口为TFT RGB接口,接口的信号线如下:

        信号线描述
        R[7:0]8根红色数据线
        G[7:0]8根绿色数据线
        B[7:0]8根蓝色数据线
        DE数据使能线
        VSYNC垂直同步信号线
        HSYNC水平同步信号线
        PCLK像素时钟信号线

        在这种模式下我们要控制lcd,首先我们要从内存中划分出一块区域给Framebuffer,其次我们要把首地址告诉ltdc控制器,还要根据lcd的参数如分辨率,像素时钟,通信模式(DE模式还是HV模式)等要对ltdc控制器进行配置。下面我们了解以下linux内核中的FrameBuffer驱动程序。

  2. FrameBuffer驱动程序框架

    FrameFBuffer驱动就是一个字符驱动,所以它首先是建立在字符设备基础上的,那字符驱动的一般框架时如何的呢?

    1. 字符设备操作流程

      用户操作一个硬件的过程就上图所示,对于linux而言,一切皆为文件,对于硬件上的每一个设备都抽象为文件,在根目录下的/dev目录,存放的就是我们的设备文件

      如图所示,里面有字符驱动gpio,i2c,还有块驱动mmcblk1,mmcblk1boot0等,其中的fb0就是我的开发板上的lcd framebuffer驱动。通过ls -la /dev/fb0可以查看这个驱动信息:这个c就代表它是一个字符设备,且主设备号为29,次设备号为0

    2. LCD FrameBuffer驱动程序框架分析

      对于一个普通的字符设备,通常有以下五个步骤:

      • 第一步:获取主设备号major,可以自动分配,也可以自己选没有被使用的主设备号
      • 第二步:创建file_operation结构体,初始化成员变量.open、.release、.read、.write等成员变量
      • 第三步:注册字符设备驱动
        • register_chrdev(major,name,file_operation);根据主设备号和file_operation注册设备驱动
        • class_creat();动态创建设备的逻辑类,并完成部分字段的初始化,然后将其添加到内核中。创建的逻辑类位于/sys/class中
        • device_create();在/dev目录下创建相应的设备节点
      • 第四步:入口函数,使用module_init()宏进行修饰,驱动程序加载时将执行这个函数,入口函数中调用第三部的函数
      • 第五步:出口函数,使用module_exit()宏进行修饰,当驱动程序卸载时将执行这个函数,出口函数中进行:
        • unregister_chrdev();注销字符设备驱动
        • device_destroy();删除设备
        • class_destroy();删除class

      然而字符操作的这一系列操作内核已经帮我们实现了,这一下列操作在drivers/video/fbdev/core/fbmem.c文件中实现。实现过程如下:

      • 第一步:主设备号FB_MAJOR在include/uapi/linux/major.h有定义,为29

      • 第二步:file_operation创建和初始化
      static const struct file_operations fb_fops = {
      	.owner =	THIS_MODULE,
      	.read =		fb_read,
      	.write =	fb_write,
      	.unlocked_ioctl = fb_ioctl,
      #ifdef CONFIG_COMPAT
      	.compat_ioctl = fb_compat_ioctl,
      #endif
      	.mmap =		fb_mmap,
      	.open =		fb_open,
      	.release =	fb_release,
      #if defined(HAVE_ARCH_FB_UNMAPPED_AREA) || \
      	(defined(CONFIG_FB_PROVIDE_GET_FB_UNMAPPED_AREA) && \
      	 !defined(CONFIG_MMU))
      	.get_unmapped_area = get_fb_unmapped_area,
      #endif
      #ifdef CONFIG_FB_DEFERRED_IO
      	.fsync =	fb_deferred_io_fsync,
      #endif
      	.llseek =	default_llseek,
      };
      
      • 第三步:入口函数,执行注册字符驱动操作等
      static int __init  fbmem_init(void)
      {
      	int ret;
      
      	if (!proc_create_seq("fb", 0, NULL, &proc_fb_seq_ops))
      		return -ENOMEM;
      
          /*注册字符设备,FB_MAJOR 为主设备号,file_operation结构体为fb_fops*/
      	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;
      	}
          /* 动态创建设备的逻辑类 ,位于/sys/class/目录下,名称为graphics*/
      	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;
      
      err_class:
      	unregister_chrdev(FB_MAJOR, "fb");
      err_chrdev:
      	remove_proc_entry("fb", NULL);
      	return ret;
      }
      module_init(fbmem_init);/* 入口函数 */
      
      • 第四步:出口函数,注销字符驱动操作等
      static void __exit
      fbmem_exit(void)
      {
      	fb_console_exit();
      
      	remove_proc_entry("fb", NULL);
      	class_destroy(fb_class);/* 删除fb_class类 */
      	unregister_chrdev(FB_MAJOR, "fb");/* 注销字符驱动 */
      }
      module_exit(fbmem_exit);
      

      然后我们简单分析一下fb_fops结构体中的.open函数fb_open()

      static int fb_open(struct inode *inode, struct file *file)
      __acquires(&info->lock)
      __releases(&info->lock)
      {
          /* 获得次设备号 inode结构体中存放了设备的设备号,可通过设备号来获取次设备号 */
      	int fbidx = iminor(inode);
      	struct fb_info *info;
      	int res = 0;
      
          /* 获取该设备对应的fb_info结构体 设备驱动注册时会根据次设备号注册fb_info结构体 */
      	info = get_fb_info(fbidx);
      	if (!info) {
      		request_module("fb%d", fbidx);
      		info = get_fb_info(fbidx);
      		if (!info)
      			return -ENODEV;
      	}
      	if (IS_ERR(info))
      		return PTR_ERR(info);
      
      	lock_fb_info(info);
      	if (!try_module_get(info->fbops->owner)) {
      		res = -ENODEV;
      		goto out;
      	}
      	file->private_data = info;
      	if (info->fbops->fb_open) {
      		res = info->fbops->fb_open(info,1);/* 调用fb_info结构体中fbops下的fb_open函数 */
      		if (res)
      			module_put(info->fbops->owner);
      	}
      #ifdef CONFIG_FB_DEFERRED_IO
      	if (info->fbdefio)
      		fb_deferred_io_open(info, inode, file);
      #endif
      out:
      	unlock_fb_info(info);
      	if (res)
      		put_fb_info(info);
      	return res;
      }
      

      在fb_open()函数中iminor(inode)根据设备文件的inode节点中的设备号来获取次设备号,get_fb_info()函数再根据次设备号获取一个fb_info结构体info,最终调用info->fbops->fb_open()函数。

      同理分析fb_release()函数,最终调用info->fbops->fb_release()函数,所以内核为我们提供了一个framebuffer框架,我们只需要实现一个fb_info结构体,这个结构体可通过此设备号查找得到,并且对该结构体中的fbops初始化。经过简单分析我们基本上可以得出lcd FrameBuffer框架流程如下:

      通过以上简单分析可知,内核中FrameBuffer框架驱动最终是调用fb_info结构体来实现对lcd的控制,所以最终我们在写lcd的framebuffer驱动时主要时实现fb_info结构体,让FrameBuffer驱动可以通过次设备号找到该fb_info结构体。

    3. fb_info结构体

      fb_info结构体定义在include/linux/fb.h中,内容如下

      struct fb_info {
      	atomic_t count;
      	int node;
      	int flags;
      	/*
      	 * -1 by default, set to a FB_ROTATE_* value by the driver, if it knows
      	 * a lcd is not mounted upright and fbcon should rotate to compensate.
      	 */
      	int fbcon_rotate_hint; /* 屏幕旋转参数 */
      	struct mutex lock;		/* Lock for open/release/ioctl funcs */
      	struct mutex mm_lock;		/* Lock for fb_mmap and smem_* fields */
      	struct fb_var_screeninfo var;	/* Current var 屏幕可变信息*/
      	struct fb_fix_screeninfo fix;	/* Current fix屏幕 固定信息*/
      	struct fb_monspecs monspecs;	/* Current Monitor specs */
      	struct work_struct queue;	/* Framebuffer event queue */
      	struct fb_pixmap pixmap;	/* Image hardware mapper */
      	struct fb_pixmap sprite;	/* Cursor hardware mapper */
      	struct fb_cmap cmap;		/* Current cmap */
      	struct list_head modelist;      /* mode list */
      	struct fb_videomode *mode;	/* current mode */
      
      #if IS_ENABLED(CONFIG_FB_BACKLIGHT)
      	/* assigned backlight device */
      	/* set before framebuffer registration, 
      	   remove after unregister */
      	struct backlight_device *bl_dev;
      
      	/* Backlight level curve */
      	struct mutex bl_curve_mutex;	
      	u8 bl_curve[FB_BACKLIGHT_LEVELS];
      #endif
      #ifdef CONFIG_FB_DEFERRED_IO
      	struct delayed_work deferred_work;
      	struct fb_deferred_io *fbdefio;
      #endif
      
      	struct fb_ops *fbops;   /* 操作函数(read、write、open、close) */
      	struct device *device;		/* This is the parent */
      	struct device *dev;		/* This is this fb device */
      	int class_flag;                    /* private sysfs flags */
      #ifdef CONFIG_FB_TILEBLITTING
      	struct fb_tile_ops *tileops;    /* Tile Blitting */
      #endif
      	union {
      		char __iomem *screen_base;	/* Virtual address */
      		char *screen_buffer;
      	};
      	unsigned long screen_size;	/* Amount of ioremapped VRAM or 0 */ 
      	void *pseudo_palette;		/* Fake palette of 16 colors */ 
      #define FBINFO_STATE_RUNNING	0
      #define FBINFO_STATE_SUSPENDED	1
      	u32 state;			/* Hardware state i.e suspend */
      	void *fbcon_par;                /* fbcon use-only private area */
      	/* From here on everything is device dependent */
      	void *par;/* 设备私有数据,可通过framebuffer_alloc()函数的第一个参数来分配指定大小的内存 */
      	/* we need the PCI or similar aperture base/size not
      	   smem_start/size as smem_start may just be an object
      	   allocated inside the aperture so may not actually overlap */
      	struct apertures_struct {
      		unsigned int count;
      		struct aperture {
      			resource_size_t base;
      			resource_size_t size;
      		} ranges[0];
      	} *apertures;
      
      	bool skip_vt_switch; /* no VT switch on suspend/resume required */
      };
      

      在fb_info结构体中有几个重要的参数:

      • struct fb_var_screeninfo var*; 屏幕可变信息 ,include/uapi/linux/fb.h文件中定义,定义了显示器的分辨率、图像格式、像素时钟等硬件相关参数

        struct fb_var_screeninfo {
        	__u32 xres;			/* visible resolution 可视分辨率 X轴		*/
        	__u32 yres;			/* visible resolution 可视分辨率Y轴		*/
        	__u32 xres_virtual;		/* virtual resolution 虚拟分辨率X轴		*/
        	__u32 yres_virtual;		/* virtual resolution 虚拟分辨率Y轴		*/
        	__u32 xoffset;			/* offset from virtual to visible 从虚拟到可视分辨率的偏移 */
        	__u32 yoffset;			/* resolution			*/
        
        	__u32 bits_per_pixel;		/* 每个像素点需要的数据位数 */
        	__u32 grayscale;		/* 0 = color, 1 = grayscale,	*/
        					/* >1 = FOURCC			*/
        	struct fb_bitfield red;		/* RGB中的r在一个像素点数据中的位置,在第几位,长度多少*/
        	struct fb_bitfield green;	/* RGB中的g在一个像素点数据中的位置,在第几位,长度多少*/
        	struct fb_bitfield blue;/* RGB中的b在一个像素点数据中的位置,在第几位,长度多少*/
        	struct fb_bitfield transp;	/* transparency			*/	
        
        	__u32 nonstd;			/* != 0 Non standard pixel format */
        
        	__u32 activate;			/* see FB_ACTIVATE_*		*/
        
        	__u32 height;			/* height of picture in mm    */
        	__u32 width;			/* width of picture in mm     */
        
        	__u32 accel_flags;		/* (OBSOLETE) see fb_info.flags */
        
        	/* Timing: All values in pixclocks, except pixclock (of course) */
        	__u32 pixclock;			/* pixel clock in ps (pico seconds)  像素时钟*/
        	__u32 left_margin;		/* time from sync to picture	*/
        	__u32 right_margin;		/* time from picture to sync	*/
        	__u32 upper_margin;		/* time from sync to picture	*/
        	__u32 lower_margin;
        	__u32 hsync_len;		/* length of horizontal sync	*/
        	__u32 vsync_len;		/* length of vertical sync	*/
        	__u32 sync;			/* see FB_SYNC_*		*/
        	__u32 vmode;			/* see FB_VMODE_*		*/
        	__u32 rotate;			/* angle we rotate counter clockwise */
        	__u32 colorspace;		/* colorspace for FOURCC-based modes */
        	__u32 reserved[4];		/* Reserved for future compatibility */
        };
        
      • struct fb_fix_screeninfo fix; 屏幕固定信息,include/uapi/linux/fb.h文件中定义,定义了显存起始地址,长度和FB类型等参数

        struct fb_fix_screeninfo {
        	char id[16];			/* identification string eg "TT Builtin" */
        	unsigned long smem_start;	/* Start of frame buffer mem 显存的buf起始地址 */
        					/* (physical address) */
        	__u32 smem_len;			/* Length of frame buffer mem显存的buf长度  */
        	__u32 type;			/* see FB_TYPE_*		*/
        	__u32 type_aux;			/* Interleave for interleaved Planes */
        	__u32 visual;			/* see FB_VISUAL_*		*/ 
        	__u16 xpanstep;			/* zero if no hardware panning  */
        	__u16 ypanstep;			/* zero if no hardware panning  */
        	__u16 ywrapstep;		/* zero if no hardware ywrap    */
        	__u32 line_length;		/* length of a line in bytes    */
        	unsigned long mmio_start;	/* Start of Memory Mapped I/O   */
        					/* (physical address) */
        	__u32 mmio_len;			/* Length of Memory Mapped I/O  */
        	__u32 accel;			/* Indicate to driver which	*/
        					/*  specific chip/card we have	*/
        	__u16 capabilities;		/* see FB_CAP_*			*/
        	__u16 reserved[2];		/* Reserved for future compatibility */
        };
        
      • struct fb_ops fbops;在include/linux/fb.h文件中定义,定义了fb_read、fb_write、fb_open、fb_close等操作函数。

        struct fb_ops {
        	/* open/release and usage marking */
        	struct module *owner;
        	int (*fb_open)(struct fb_info *info, int user);
        	int (*fb_release)(struct fb_info *info, int user);
        
        	/* For framebuffers with strange non linear layouts or that do not
        	 * work with normal memory mapped access
        	 */
        	ssize_t (*fb_read)(struct fb_info *info, char __user *buf,
        			   size_t count, loff_t *ppos);
        	ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,
        			    size_t count, loff_t *ppos);
        
        	/* checks var and eventually tweaks it to something supported,
        	 * DO NOT MODIFY PAR */
        	int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);
        
        	/* set the video mode according to info->var */
        	int (*fb_set_par)(struct fb_info *info);
        
        	/* set color register */
        	int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
        			    unsigned blue, unsigned transp, struct fb_info *info);
        
        	/* set color registers in batch */
        	int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);
        
        	/* blank display */
        	int (*fb_blank)(int blank, struct fb_info *info);
        
        	/* pan display */
        	int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);
        
        	/* Draws a rectangle */
        	void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
        	/* Copy data from area to another */
        	void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
        	/* Draws a image to the display */
        	void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);
        
        	/* Draws cursor */
        	int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor);
        
        	/* wait for blit idle, optional */
        	int (*fb_sync)(struct fb_info *info);
        
        	/* perform fb specific ioctl (optional) */
        	int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
        			unsigned long arg);
        
        	/* Handle 32bit compat ioctl (optional) */
        	int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd,
        			unsigned long arg);
        
        	/* perform fb specific mmap */
        	int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);
        
        	/* get capability given var */
        	void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps,
        			    struct fb_var_screeninfo *var);
        
        	/* teardown any resources to do with this framebuffer */
        	void (*fb_destroy)(struct fb_info *info);
        
        	/* called at KDB enter and leave time to prepare the console */
        	int (*fb_debug_enter)(struct fb_info *info);
        	int (*fb_debug_leave)(struct fb_info *info);
        };
        
    4. 分析s3c2410fb.c驱动文件

      路径为drivers/video/fbdev/s3c2410fb.c,分析驱动文件首先分析它的入口函数:

      int __init s3c2410fb_init(void)
      {
          /* 注册platform驱动程序 */
      	int ret = platform_driver_register(&s3c2410fb_driver);
      
      	if (ret == 0)
      		ret = platform_driver_register(&s3c2412fb_driver);
      
      	return ret;
      }
      
      static void __exit s3c2410fb_cleanup(void)
      {
          /* 注销platform驱动程序 */
      	platform_driver_unregister(&s3c2410fb_driver);
      	platform_driver_unregister(&s3c2412fb_driver);
      }
      

      通过入口函数可以判断除s3c2410fb驱动为一个platform驱动,这里有两个型号s3c2410和s3c2412的驱动程序。我们分析其中一个s3c2410fb_driver

      static struct platform_driver s3c2410fb_driver = {
      	.probe		= s3c2410fb_probe,
      	.remove		= s3c2410fb_remove,
      	.suspend	= s3c2410fb_suspend,
      	.resume		= s3c2410fb_resume,
      	.driver		= {
      		.name	= "s3c2410-lcd",
      	},
      };
      
      static int s3c2410fb_probe(struct platform_device *pdev)
      {
      	return s3c24xxfb_probe(pdev, DRV_S3C2410);
      }
      
      static int s3c24xxfb_probe(struct platform_device *pdev,
      			   enum s3c_drv_type drv_type)
      {
      	struct s3c2410fb_info *info;
      	struct s3c2410fb_display *display;
      	struct fb_info *fbinfo; /* 声明一个fhinfo结构体指针 */
      	struct s3c2410fb_mach_info *mach_info;
      	struct resource *res;
      	int ret;
      	int irq;
      	int i;
      	int size;
      	u32 lcdcon1;
      
      	mach_info = dev_get_platdata(&pdev->dev);
      	if (mach_info == NULL) {
      		dev_err(&pdev->dev,
      			"no platform data for lcd, cannot attach\n");
      		return -EINVAL;
      	}
      
      	if (mach_info->default_display >= mach_info->num_displays) {
      		dev_err(&pdev->dev, "default is %d but only %d displays\n",
      			mach_info->default_display, mach_info->num_displays);
      		return -EINVAL;
      	}
      
      	display = mach_info->displays + mach_info->default_display;
      
      	irq = platform_get_irq(pdev, 0);
      	if (irq < 0) {
      		dev_err(&pdev->dev, "no irq for device\n");
      		return -ENOENT;
      	}
      
          /* 分配fbinfo结构体
            * 第一个参数sizeof(struct s3c2410fb_info),将会多分配大小为sizeof(struct s3c2410fb_info),并把分		 配的内存基地址放到fb_info结构体中的par中
            */
      	fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
      	if (!fbinfo)
      		return -ENOMEM;
      
      	platform_set_drvdata(pdev, fbinfo);
      
      	info = fbinfo->par;
      	info->dev = &pdev->dev;
      	info->drv_type = drv_type;
      
      	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
      	if (res == NULL) {
      		dev_err(&pdev->dev, "failed to get memory registers\n");
      		ret = -ENXIO;
      		goto dealloc_fb;
      	}
      
      	size = resource_size(res);
      	info->mem = request_mem_region(res->start, size, pdev->name);
      	if (info->mem == NULL) {
      		dev_err(&pdev->dev, "failed to get memory region\n");
      		ret = -ENOENT;
      		goto dealloc_fb;
      	}
      
      	info->io = ioremap(res->start, size);
      	if (info->io == NULL) {
      		dev_err(&pdev->dev, "ioremap() of registers failed\n");
      		ret = -ENXIO;
      		goto release_mem;
      	}
      
      	if (drv_type == DRV_S3C2412)
      		info->irq_base = info->io + S3C2412_LCDINTBASE;
      	else
      		info->irq_base = info->io + S3C2410_LCDINTBASE;
      
      	dprintk("devinit\n");
      
      	strcpy(fbinfo->fix.id, driver_name);
      
      	/* Stop the video */
      	lcdcon1 = readl(info->io + S3C2410_LCDCON1);
      	writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1);
         /* fbinfo结构体初始化 */
      	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;
      
      	fbinfo->fbops		    = &s3c2410fb_ops;
      	fbinfo->flags		    = FBINFO_FLAG_DEFAULT;
      	fbinfo->pseudo_palette      = &info->pseudo_pal;
      
      	for (i = 0; i < 256; i++)
      		info->palette_buffer[i] = PALETTE_BUFF_CLEAR;
      
      	ret = request_irq(irq, s3c2410fb_irq, 0, pdev->name, info);
      	if (ret) {
      		dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret);
      		ret = -EBUSY;
      		goto release_regs;
      	}
      
      	info->clk = clk_get(NULL, "lcd");
      	if (IS_ERR(info->clk)) {
      		dev_err(&pdev->dev, "failed to get lcd clock source\n");
      		ret = PTR_ERR(info->clk);
      		goto release_irq;
      	}
      
      	clk_prepare_enable(info->clk);
      	dprintk("got and enabled clock\n");
      
      	usleep_range(1000, 1100);
      
      	info->clk_rate = clk_get_rate(info->clk);
      
      	/* find maximum required memory size for display */
      	for (i = 0; i < mach_info->num_displays; i++) {
      		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;
      		if (fbinfo->fix.smem_len < smem_len)
      			fbinfo->fix.smem_len = smem_len;
      	}
      
      	/* Initialize video memory */
      	ret = s3c2410fb_map_video_memory(fbinfo);
      	if (ret) {
      		dev_err(&pdev->dev, "Failed to allocate video RAM: %d\n", ret);
      		ret = -ENOMEM;
      		goto release_clock;
      	}
      
      	dprintk("got video memory\n");
      
      	fbinfo->var.xres = display->xres;
      	fbinfo->var.yres = display->yres;
      	fbinfo->var.bits_per_pixel = display->bpp;
      
      	s3c2410fb_init_registers(fbinfo);
      
      	s3c2410fb_check_var(&fbinfo->var, fbinfo);
      
      	ret = s3c2410fb_cpufreq_register(info);
      	if (ret < 0) {
      		dev_err(&pdev->dev, "Failed to register cpufreq\n");
      		goto free_video_memory;
      	}
          
          /* 注册fb_info结构体 */
      	ret = register_framebuffer(fbinfo);
      	if (ret < 0) {
      		dev_err(&pdev->dev, "Failed to register framebuffer device: %d\n",
      			ret);
      		goto free_cpufreq;
      	}
      
      	/* create device files */
      	ret = device_create_file(&pdev->dev, &dev_attr_debug);
      	if (ret)
      		dev_err(&pdev->dev, "failed to add debug attribute\n");
      
      	dev_info(&pdev->dev, "fb%d: %s frame buffer device\n",
      		fbinfo->node, fbinfo->fix.id);
      
      	return 0;
      
       free_cpufreq:
      	s3c2410fb_cpufreq_deregister(info);
      free_video_memory:
      	s3c2410fb_unmap_video_memory(fbinfo);
      release_clock:
      	clk_disable_unprepare(info->clk);
      	clk_put(info->clk);
      release_irq:
      	free_irq(irq, info);
      release_regs:
      	iounmap(info->io);
      release_mem:
      	release_mem_region(res->start, size);
      dealloc_fb:
      	framebuffer_release(fbinfo);
      	return ret;
      }
      
      

      当设备名称为s3c2410-lcd,就会匹配到该驱动,就会执行probe函数,在probe函数中我们可以分析出这么几个步骤:

      • 创建fb_info结构体:通过framebuffer_alloc()实现
      • fb_info结构体成员初始化
      • 注册fb_info结构体:通过register_framebuffer()实现
    5. framebuffer_alloc函数分析

      所以我们写一个lcd驱动程序时主要是要实现以上三个步骤,首先我们简单分析一下framebuffer_alloc()

      struct fb_info *framebuffer_alloc(size_t size, struct device *dev)
      {
      #define BYTES_PER_LONG (BITS_PER_LONG/8)
      #define PADDING (BYTES_PER_LONG - (sizeof(struct fb_info) % BYTES_PER_LONG))
      	int fb_info_size = sizeof(struct fb_info);/* 计算出fb_info结构体的大小 */
      	struct fb_info *info;
      	char *p;
      
      	if (size)
      		fb_info_size += PADDING;/* 如果处理器为64位,则进行8字节对其 如果为32位,则4字节对其 */
      
      	p = kzalloc(fb_info_size + size, GFP_KERNEL);/* 分配fb_info结构体内存和传入参数size大小的内存*/
      
      	if (!p)
      		return NULL;
      
      	info = (struct fb_info *) p;
      
      	if (size)
      		info->par = p + fb_info_size;/* fb_info结构体中的par指向fb_info结构体末尾 */
      
      	info->device = dev; /* fb_info结构体中的devive赋值为传入的参数devices */
          /* fbcon_rotate_hint:屏幕旋转方向
           * 这个值可取为:
          #define FB_ROTATE_UR       0 :向上旋转
          #define FB_ROTATE_CW      1 : 顺时针旋转
          #define FB_ROTATE_UD       2 :向下旋转
          #define FB_ROTATE_CCW    3 :逆时针旋转
          在/usr/include/linux/fb.h文件中定义 
           */
      	info->fbcon_rotate_hint = -1;/* fb_info中的 fbcon_rotate_hint默认为-1*/
      
      #if IS_ENABLED(CONFIG_FB_BACKLIGHT)
      	mutex_init(&info->bl_curve_mutex);
      #endif
      
      	return info;
      #undef PADDING
      #undef BYTES_PER_LONG
      }
      

      通过分析**framebuffer_alloc(size_t size, struct device *dev)**函数,我们可得出它最终将会分配一块 (sizeof(struct fb_info)(4字节对其或8字节对其))+(size)大小的一块内存,并将给size内存放到末尾,首地址存放到fb_info结构体中的元素par,fb_info中的device来存放第二个参数dev

      所以framebuffer_alloc(size_t size, struct device *dev)函数主要为fb_info结构体分配内存,另外还可以多分配出指定大小为size的内存,并将该内存基地址放入fb_info->par中

    6. register_framebuffer()函数分析

      接下来再分析以下register_framebuffer(struct fb_info *fb_info);在drivers/video/fbdev/core/fbmem.c文件中有定义。

      struct fb_info *registered_fb[FB_MAX] __read_mostly;
      
      int register_framebuffer(struct fb_info *fb_info)
      {
      	int ret;
      	mutex_lock(&registration_lock);
      	ret = do_register_framebuffer(fb_info);
      	mutex_unlock(&registration_lock);
      	return ret;
      }
      
      static int do_register_framebuffer(struct fb_info *fb_info)
      {
      	int i, ret;
      	struct fb_videomode mode;
      
      	if (fb_check_foreignness(fb_info))
      		return -ENOSYS;
      
      	do_remove_conflicting_framebuffers(fb_info->apertures,
      					   fb_info->fix.id,
      					   fb_is_primary_device(fb_info));
      
      	if (num_registered_fb == FB_MAX)
      		return -ENXIO;
      
      	num_registered_fb++;
          /* registered_fb[]数组从0到FB_MAX开始查找,如果有为空的则退出*/
      	for (i = 0 ; i < FB_MAX; i++)
      		if (!registered_fb[i])
      			break;
      	fb_info->node = i;/* fb_info结构体中的node项来存放该fb在registered_fb数组中的索引 */
      	atomic_set(&fb_info->count, 1);
      	mutex_init(&fb_info->lock);
      	mutex_init(&fb_info->mm_lock);
      	/* 
      	  * 创建fb(n)设备,设备的主设备号为FB_MAJOR  次设备号为i,所以次设备号就是registered_fb
      	  * 数组中相对于的fb的索引 
      	   */
      	fb_info->dev = device_create(fb_class, fb_info->device,
      				     MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
      	if (IS_ERR(fb_info->dev)) {
      		/* Not fatal */
      		printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev));
      		fb_info->dev = NULL;
      	} else
      		fb_init_device(fb_info);
      
      	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;
      
      	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;
      
      	if (!fb_info->modelist.prev || !fb_info->modelist.next)
      		INIT_LIST_HEAD(&fb_info->modelist);
      
      	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_to_videomode(&mode, &fb_info->var);
      	fb_add_videomode(&mode, &fb_info->modelist);
      	registered_fb[i] = fb_info;
      
      #ifdef CONFIG_GUMSTIX_AM200EPD
      	{
      		struct fb_event event;
      		event.info = fb_info;
      		fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
      	}
      #endif
      
      	if (!lockless_register_fb)
      		console_lock();
      	else
      		atomic_inc(&ignore_console_lock_warning);
      	lock_fb_info(fb_info);
          /* 帧缓冲区硬件设备fb的注册事件,在这个操作之后lcd才能显示logo和终端ttyn进行打印 */
      	ret = fbcon_fb_registered(fb_info);
      	unlock_fb_info(fb_info);
      
      	if (!lockless_register_fb)
      		console_unlock();
      	else
      		atomic_dec(&ignore_console_lock_warning);
      	return ret;
      }
      
      

      从上述代码可以看出,在中定义了registered_fb[FB_MAX] ,是一个fb_info类型的结构体指针数组,也就是该数组中存放的时fb_info结构体的地址,我们在注册fb_info结构体时就会把我们所要注册的结构体指针放入到该数组中。而且我们可以看到fb_info->node存放了注册的fb_info结构体在registered_fb[]数组中的索引,除此之外还使用device_create()创建了一个设备,该设备的主设备号为MKDEV(FB_MAJOR, i),主设备号为FB_MAJOR,前面讲过,固定为29,此设备号为i,i就是注册的fb_info结构体在registered_fb[]数组中的索引,而且创建的设备文件名为 “fb%d”, i;所以会创建一个fbn的一个设备,在注册了fb_info结构体之后,我们就可以在/dev/目录下查看到fb0、fb1等设备文件,而fb后面跟的数据就是该设备文件所对应的fb_info结构体在registered_fb[]数组中的索引,同时也是次设备号,也是fb_info->node,前面所提到的FrameBuffer框架驱动中static int fb_open(struct inode *inode, struct file *file)等函数会先根据struct inode *inode找到设备的次设备号,struct inode *inode这个结构体保存了设备文件的元数据,里面有设备的设备号,根据设备号和主设备号就可以找到次设备号,然后就可以在registered_fb[]数组中找到对应的fb_info结构体。

      最后fbcon_fb_registered(fb_info);实现了如何在lcd上打印logo的过程,也是第一个开机画面显示的过程,具体分析可参考这篇文章:Android系统的开机画面显示过程分析

      以上就是在lcd驱动程序中如何将一个fb_info结构体注册到内核,提供给FrameBuffer驱动程序调用。初次之外我们还要对lcd的硬件进行初始化。

      所以最后得出一个ltdc最简单的一个驱动框架:

      • 创建fb_info结构体:通过framebuffer_alloc()
      • 实现fb_info结构体成员初始化
      • 注册fb_info结构体:通过register_framebuffer()实现
      • 对MPU内部的ltdc(ldc 控制器)初始化,也就是硬件初始化
  3. STM32MP157的ltdc驱动程序编写

    我使用的是正点原子的STM32MP157开发板,使用的ldc是ATK7016,分辨率为1024*600。

    1. lcd硬件参数分析

      LCD屏的相关参数说明可参考这篇文章:imx6 LCD 参数配置(lvds为例)

      我使用的lcd参数如下:

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZoX7tbXP-1627382018795)(https://gitee.com/jiasa/cloud-images/raw/master/img/ATK_7016_lcd%E6%97%B6%E5%BA%8F%E5%9B%BE.png)]

      时序如下:

      再看lcd原理图

      MODE被上拉到3.3V,被拉高时采用的模式为DE模式,拉低时为HSD/VSD模式,image-20210727151558153

      所以该lcd的模式为DE模式。

    2. 驱动程序编写

      我们参考s3c2410fb.c驱动文件来编写,采用platform驱动框架,这里我们写一个简单的lcd驱动,也就是实现lcd基本的显示功能,基本的platform驱动框架如下,接下来就是实现probe函数中的各个步骤。

      /* Probe函数函数 */
      static int mylcd_probe(struct platform_device *pdev)
      {
      
          /* 1、分配fb_info */
          
          /* 2、fb_info成员初始化 */
      
          /* 3、注册fb_info */
          
          /* 4、硬件操作(lcd控制器相关寄存器初始化) */
          return 0;
      }
      
      /* 出口函数 */
      static int mylcd_remove(struct platform_device *pdev)
      {
          /* 1、注销fb_info */
      
          /* 2、释放fb_info */
       
          /* 3、释放硬件 */
      }
      
      static const struct of_device_id mylcd_of_match[] = {
      	{ .compatible = "cowain,lcd-7016", },
      	{ },
      };
      MODULE_DEVICE_TABLE(of, mylcd_of_match);
      
      static struct platform_driver mylcd_driver = {
      	.driver = {
      		.name = "lcd-7016-framebuffer",
      		.of_match_table = mylcd_of_match,
      	},
      	.probe = mylcd_probe,
      	.remove = mylcd_remove,
      };
      /* 入口函数 */
      static int __init mylcd_init(void)
      {
      	int ret;
          /* 注册mylcd_driver驱动 */
      	ret = platform_driver_register(&mylcd_driver);
      	return 0;
      }
      /* 出口函数 */
      static void __exit mylcd_exit(void)
      {
          /* 1、注销mylcd_driver */
          platform_driver_unregister(&mylcd_driver);
      }
      module_init(mylcd_init);
      module_exit(mylcd_exit);
      
      MODULE_AUTHOR("xxx");
      MODULE_LICENSE("GPL");
      MODULE_ALIAS("platform:ATK-7016-lcd");
      

      设备树部分代码如下

      /{
      framebuffer-atk7016-lcd{
      		compatible = "atk,lcd-7016";
      		pinctrl-names = "default", "sleep";
      		pinctrl-0 = <&ltdc_pins_mx>;
      		pinctrl-1 = <&ltdc_sleep_pins_mx>;
      		clocks = <&rcc LTDC_PX>;
      		clock-names = "lcd";/* 时钟名称 */
      		backlight-gpio = <&gpiod 13 GPIO_ACTIVE_HIGH>;
      		status = "okay";
      		reg = <0x5a001000 0x400>;
      
      		display = <&display0>;
      		display0:display0{
      			bits-per-pixel = <32>;
      			bus-width = <24>;
      			display-timings {
      						native-mode = <&timing0>;
      						timing0: timing0 {
      							clock-frequency = <51200000>;/* 像素时钟 */
      							hactive = <1024>;	 /* 水平像素点数xres */
      							vactive = <600>; 	 /* 垂直像素点数yres */
      							hback-porch = <140>; /* 水平方向后沿 */
      							hfront-porch = <160>;/* 水平方向前沿 */
      							hsync-len = <20>;    /* 水平方向同步脉宽 */
      
      							vback-porch = <20>;  /* 垂直方向后沿 */
      							vfront-porch = <12>; /* 垂直方向前沿 */
      							vsync-len = <3>;     /* 垂直方向同步脉宽 */
      
      							/* polarity */
      							/* DE模式下hsync-active和vsync-active将不起作用,在HV模式下才起作用 */
      							hsync-active = <0>;
      							vsync-active = <0>;
      							/* STMP157中de-active指的是非数据时的有效电平,由于ATK-7016屏设定
      							 * 设定的模式为DE模式,该模式设置直接硬件上设置死了,它设置为DE模式,在
      							 * DE模式下DEN的有效电平为高电平,非数据时为高电平
      							 * 在HV模式下de-active没有用
      							 */
      							de-active = <0>;
      							pixelclk-active = <1>;
      						};
      					};
      	};
          
          &pinctrl{
              ltdc_pins_mx: ltdc_mx-0 {
      		pins1 {
      			pinmux = <STM32_PINMUX('I', 12, AF14)>, /* LTDC_HSYNC */
      					 <STM32_PINMUX('I', 13, AF14)>, /* LTDC_VSYNC */
      					 <STM32_PINMUX('I', 15, AF14)>, /* LTDC_R0 */
      					 <STM32_PINMUX('J', 0, AF14)>, /* LTDC_R1 */
      					 <STM32_PINMUX('J', 1, AF14)>, /* LTDC_R2 */
      					 <STM32_PINMUX('J', 2, AF14)>, /* LTDC_R3 */
      					 <STM32_PINMUX('J', 3, AF14)>, /* LTDC_R4 */
      					 <STM32_PINMUX('J', 4, AF14)>, /* LTDC_R5 */
      					 <STM32_PINMUX('J', 5, AF14)>, /* LTDC_R6 */
      					 <STM32_PINMUX('J', 6, AF14)>, /* LTDC_R7 */
      					 <STM32_PINMUX('J', 7, AF14)>, /* LTDC_G0 */
      					 <STM32_PINMUX('J', 8, AF14)>, /* LTDC_G1 */
      					 <STM32_PINMUX('J', 9, AF14)>, /* LTDC_G2 */
      					 <STM32_PINMUX('J', 10, AF14)>, /* LTDC_G3 */
      					 <STM32_PINMUX('J', 11, AF14)>, /* LTDC_G4 */
      					 <STM32_PINMUX('J', 12, AF14)>, /* LTDC_B0 */
      					 <STM32_PINMUX('J', 13, AF14)>, /* LTDC_B1 */
      					 <STM32_PINMUX('J', 14, AF14)>, /* LTDC_B2 */
      					 <STM32_PINMUX('J', 15, AF14)>, /* LTDC_B3 */
      					 <STM32_PINMUX('K', 0, AF14)>, /* LTDC_G5 */
      					 <STM32_PINMUX('K', 1, AF14)>, /* LTDC_G6 */
      					 <STM32_PINMUX('K', 2, AF14)>, /* LTDC_G7 */
      					 <STM32_PINMUX('K', 3, AF14)>, /* LTDC_B4 */
      					 <STM32_PINMUX('K', 4, AF14)>, /* LTDC_B5 */
      					 <STM32_PINMUX('K', 5, AF14)>, /* LTDC_B6 */
      					 <STM32_PINMUX('K', 6, AF14)>, /* LTDC_B7 */
      					 <STM32_PINMUX('K', 7, AF14)>; /* LTDC_DE */
      			bias-disable;
      			drive-push-pull;
      			slew-rate = <0>;
      		};
      		pins2 {
      			pinmux = <STM32_PINMUX('I', 14, AF14)>; /* LTDC_CLK */
      			bias-disable;
      			drive-push-pull;
      			slew-rate = <1>;
      		};
      	};
      
      	ltdc_sleep_pins_mx: ltdc_sleep_mx-0 {
      		pins {
      			pinmux = <STM32_PINMUX('I', 12, ANALOG)>, /* LTDC_HSYNC */
      					 <STM32_PINMUX('I', 13, ANALOG)>, /* LTDC_VSYNC */
      					 <STM32_PINMUX('I', 14, ANALOG)>, /* LTDC_CLK */
      					 <STM32_PINMUX('I', 15, ANALOG)>, /* LTDC_R0 */
      					 <STM32_PINMUX('J', 0, ANALOG)>, /* LTDC_R1 */
      					 <STM32_PINMUX('J', 1, ANALOG)>, /* LTDC_R2 */
      					 <STM32_PINMUX('J', 2, ANALOG)>, /* LTDC_R3 */
      					 <STM32_PINMUX('J', 3, ANALOG)>, /* LTDC_R4 */
      					 <STM32_PINMUX('J', 4, ANALOG)>, /* LTDC_R5 */
      					 <STM32_PINMUX('J', 5, ANALOG)>, /* LTDC_R6 */
      					 <STM32_PINMUX('J', 6, ANALOG)>, /* LTDC_R7 */
      					 <STM32_PINMUX('J', 7, ANALOG)>, /* LTDC_G0 */
      					 <STM32_PINMUX('J', 8, ANALOG)>, /* LTDC_G1 */
      					 <STM32_PINMUX('J', 9, ANALOG)>, /* LTDC_G2 */
      					 <STM32_PINMUX('J', 10, ANALOG)>, /* LTDC_G3 */
      					 <STM32_PINMUX('J', 11, ANALOG)>, /* LTDC_G4 */
      					 <STM32_PINMUX('J', 12, ANALOG)>, /* LTDC_B0 */
      					 <STM32_PINMUX('J', 13, ANALOG)>, /* LTDC_B1 */
      					 <STM32_PINMUX('J', 14, ANALOG)>, /* LTDC_B2 */
      					 <STM32_PINMUX('J', 15, ANALOG)>, /* LTDC_B3 */
      					 <STM32_PINMUX('K', 0, ANALOG)>, /* LTDC_G5 */
      					 <STM32_PINMUX('K', 1, ANALOG)>, /* LTDC_G6 */
      					 <STM32_PINMUX('K', 2, ANALOG)>, /* LTDC_G7 */
      					 <STM32_PINMUX('K', 3, ANALOG)>, /* LTDC_B4 */
      					 <STM32_PINMUX('K', 4, ANALOG)>, /* LTDC_B5 */
      					 <STM32_PINMUX('K', 5, ANALOG)>, /* LTDC_B6 */
      					 <STM32_PINMUX('K', 6, ANALOG)>, /* LTDC_B7 */
      					 <STM32_PINMUX('K', 7, ANALOG)>; /* LTDC_DE */
      		};
      	};
       };
      

      驱动代码完整版如下:

      #include <linux/module.h>
      #include <linux/kernel.h>
      #include <linux/err.h>
      #include <linux/errno.h>
      #include <linux/string.h>
      #include <linux/mm.h>
      #include <linux/slab.h>
      #include <linux/delay.h>
      #include <linux/fb.h>
      #include <linux/init.h>
      #include <linux/dma-mapping.h>
      #include <linux/interrupt.h>
      #include <linux/platform_device.h>
      #include <linux/clk.h>
      #include <linux/cpufreq.h>
      #include <linux/io.h>
      #include <video/display_timing.h>
      #include <video/of_display_timing.h>
      #include <asm/div64.h>
      #include <linux/of_gpio.h>
      #include <asm/mach/map.h>
      
      
      
      
      static struct fb_info *myfb_info;
      struct stm32mp157_lcdif {
      	volatile unsigned int LTDC_IDR; 						 
      	volatile unsigned int LTDC_LCR; 					   
      	volatile unsigned int LTDC_SSCR;						 
      	volatile unsigned int LTDC_BPCR;	   
      	volatile unsigned int LTDC_AWCR;							 
      	volatile unsigned int LTDC_TWCR;						 
      	volatile unsigned int LTDC_GCR; 					  
      	volatile unsigned int LTDC_GC1R;   
      	volatile unsigned int LTDC_GC2R;							
      	volatile unsigned int LTDC_SRCR;	
      		unsigned char RESERVED_0[4];
      	volatile unsigned int LTDC_BCCR;	 
      		unsigned char RESERVED_1[4];
      	volatile unsigned int LTDC_IER; 					   
      	volatile unsigned int LTDC_ISR;   
      	volatile unsigned int LTDC_ICR; 						 
      	volatile unsigned int LTDC_LIPCR;						 
      	volatile unsigned int LTDC_CPSR;						  
      	volatile unsigned int LTDC_CDSR; 
      		unsigned char RESERVED_2[56];	
      	volatile unsigned int LTDC_L1CR;					  
      	volatile unsigned int LTDC_L1WHPCR; 					
      	volatile unsigned int LTDC_L1WVPCR; 				   
      	volatile unsigned int LTDC_L1CKCR;							
      	volatile unsigned int LTDC_L1PFCR;							
      	volatile unsigned int LTDC_L1CACR;							
      	volatile unsigned int LTDC_L1DCCR;							 
      	volatile unsigned int LTDC_L1BFCR;
      		unsigned char RESERVED_3[8]; 
      	volatile unsigned int LTDC_L1CFBAR;   
      	volatile unsigned int LTDC_L1CFBLR; 					   
      	volatile unsigned int LTDC_L1CFBLNR;
      		unsigned char RESERVED_4[12];  
      	volatile unsigned int LTDC_L1CLUTWR; 
      		unsigned char RESERVED_5[60];	
      	volatile unsigned int LTDC_L2CR;  
      	volatile unsigned int LTDC_L2WHPCR; 					   
      	volatile unsigned int LTDC_L2WVPCR; 					   
      	volatile unsigned int LTDC_L2CKCR;						  
      	volatile unsigned int LTDC_L2PFCR;	 
      	volatile unsigned int LTDC_L2CACR;	
      	volatile unsigned int LTDC_L2DCCR;	
      	volatile unsigned int LTDC_L2BFCR;	
      		unsigned char RESERVED_6[8];   
      	volatile unsigned int LTDC_L2CFBAR; 					   
      	volatile unsigned int LTDC_L2CFBLR; 					   
      	volatile  unsigned int LTDC_L2CFBLNR;
      		unsigned char RESERVED_7[12]; 
      	volatile unsigned int LTDC_L2CLUTWR;							   
      };
      static unsigned int  pseudo_palette[16];/* 假调色板 */
      struct stm32mp157_lcdif *lcdif;
      static void Stm32mp157_lcd_controller_enable(struct stm32mp157_lcdif *lcdif)
      {	
      	lcdif->LTDC_SRCR |= 1;         /*加载LAYER的参数*/
      	lcdif->LTDC_GCR  |= 1<<0;      /* 使能STM32MP157的LCD控制器 */
      }
      static int lcd_controller_init(struct stm32mp157_lcdif *lcdif, struct display_timing *dt, int lcd_bpp, int fb_bpp, unsigned int fb_phy)
      {
      	int bpp_mode;
      	int pol_vclk = 0;
      	int pol_vsync = 0;
      	int pol_hsync = 0;
      	int pol_de = 0;
      	
      	lcd_bpp = lcd_bpp;
      	
      	/*[11:0]垂直同步信号宽度tvp,[27:16]水平同步信号宽度thp*/
      	lcdif->LTDC_SSCR = (dt->vsync_len.typ << 0) | (dt->hsync_len.typ << 16);
      	
      	/*清空LTDC_BPCR寄存器*/
      	lcdif->LTDC_BPCR = 0 ;
      	/*[11:0] VSYNC宽度tvp + 上黑框tvb - 1*/
      	lcdif->LTDC_BPCR |= (dt->vsync_len.typ +  dt->vback_porch.typ - 1) << 0 ;
      	/*[27:16]HSYNC宽度thp + 左黑框thb - 1*/
      	lcdif->LTDC_BPCR |=	(dt->hsync_len.typ + dt->hback_porch.typ - 1) << 16;
      	
      	/*清空LTDC_AWCR寄存器*/
      	lcdif->LTDC_AWCR = 0 ;
      	/*[11:0]  VSYNC宽度tvp + 上黑框tvb + 垂直有效高度yres - 1*/
      	lcdif->LTDC_AWCR |= (dt->vsync_len.typ + dt->vback_porch.typ + dt->vactive.typ - 1) << 0;
      	/*[27:16] HSYNC宽度thp + 左黑框thb +  水平有效高度xres - 1*/ 
      	lcdif->LTDC_AWCR |= (dt->hsync_len.typ + dt->hback_porch.typ + dt->hactive.typ - 1) << 16;
      	
      	/*清空LTDC_TWCR寄存器*/
      	lcdif->LTDC_TWCR = 0;
      	/*[11:0]  VSYNC宽度tvp + 上黑框tvb + 垂直有效高度yres + 下黑框tvf - 1 , 即垂直方向上的总周期*/
      	lcdif->LTDC_TWCR |= (dt->vsync_len.typ + dt->vback_porch.typ + dt->vactive.typ + dt->vfront_porch.typ - 1) << 0;
      	/*[27:16] HSYNC宽度thp + 左黑框thb + 垂直有效高度xres + 右黑框thf - 1 , 即水平方向上的总周期*/
      	lcdif->LTDC_TWCR |= (dt->hsync_len.typ + dt->hback_porch.typ + dt->hactive.typ + dt->hfront_porch.typ - 1) << 16;
      
      	if (dt->flags & DISPLAY_FLAGS_PIXDATA_POSEDGE)
      		pol_vclk = 1;
      	if (dt->flags & DISPLAY_FLAGS_DE_HIGH)
      		pol_de = 1;
      	if (dt->flags & DISPLAY_FLAGS_VSYNC_HIGH)
      		pol_vsync = 1;
      	if (dt->flags & DISPLAY_FLAGS_HSYNC_HIGH)
      		pol_hsync = 1;
      		
      	/*清空LTDC_GCR寄存器*/
      	lcdif->LTDC_GCR &= ~(0xF << 28);
      	/*  1 : DOTCLK下降沿有效 ,根据屏幕配置文件将其设置为1    */
      	lcdif->LTDC_GCR |= pol_vclk  << 28;
      	/*  1 : ENABLE信号高电平有效,根据屏幕配置文件将其设置为1 */
      	lcdif->LTDC_GCR |= pol_de    << 29;
      	/*  0 : VSYNC低电平有效  ,根据屏幕配置文件将其设置为0     */
      	lcdif->LTDC_GCR |= pol_vsync << 30 ;
      	/*  0 : HSYNC低电平有效 , 根据屏幕配置文件将其设置为0     */
      	lcdif->LTDC_GCR |= pol_hsync << 31 ;
      
      	/*layer 1的相关设置如下*/
      	lcdif->LTDC_L1WHPCR |= (dt->hsync_len.typ + dt->hback_porch.typ + dt->hactive.typ - 1) << 16 | (dt->hsync_len.typ + dt->hback_porch.typ ) ;
      
      	lcdif->LTDC_L1WVPCR |= (dt->vsync_len.typ + dt->vback_porch.typ + dt->vactive.typ - 1) << 16 | (dt->vsync_len.typ + dt->vback_porch.typ ) ;
      
      	lcdif->LTDC_L1CFBLR = (dt->hactive.typ * (fb_bpp>>3) + 7) | (dt->hactive.typ * (fb_bpp>>3))<< 16;
      	 
      	lcdif->LTDC_L1CFBLNR = dt->vactive.typ;/*显存总共的行数*/
      	
      	/*透明度填充值,当选的bpp格式是ARGB8888,ARGB1555等会使用到,如选的是RGB565,RBG888等者不设置也可以*/
      	lcdif->LTDC_L1CACR = 0xff;
      
      	/*
      	 *BC = BF1 x C + BF2 x Cs 
      	 *BF1为LTDC_L1BFCR设置的[10:8]值,设置为100:constant alpha即LTDC_L1CACR设置的值0xff,表示完全不透明
      	 *BF2为LTDC_L1BFCR设置的[2:0]值,设置为101:constant alpha即LTDC_L1CACR设置的值0xff,表示完全不透明
      	 *C为当前层的颜色,
      	 *Cs为背景色,不设置,默认值为0,即黑色
      	 *LTDC_L1BFCR寄存器也是针对有透明度的像素格式而设置,如用RGB565等也可不设置
      	 */
      	lcdif->LTDC_L1BFCR = (4<<8) | (5<<0);
      	
      	 /*当bpp为16时,数据格式为RGB565 , 当bpp为32时,数据格式为ARGB8888*/
           switch(fb_bpp)
      	{
      		case 16:{
      			bpp_mode = 0x2;break;}
      		case 32:{
      			bpp_mode = 0x0;break;}
      		default:{
      			bpp_mode = 0x0;break;}
      	}
      	 
      	 lcdif->LTDC_L1PFCR  =       0 ;
      	 lcdif->LTDC_L1PFCR |= bpp_mode; /*设置像素格式*/
      	  
      	 lcdif->LTDC_L1CFBAR = fb_phy; /*设置显存地址*/
      	 
      	 lcdif->LTDC_L1CR |= 0x1;/*1 layer 使能*/
      
      	 return 0;
      }
      
      
      /* from pxafb.c */
      static inline unsigned int chan_to_field(unsigned int chan,
      					 struct fb_bitfield *bf)
      {
      	chan &= 0xffff;
      	chan >>= 16 - bf->length;
      	return chan << bf->offset;
      }
      static int myfb_setcolreg(unsigned regno,
      			       unsigned red, unsigned green, unsigned blue,
      			       unsigned transp, struct fb_info *info)
      {
      	unsigned int val;
      
      	/* dprintk("setcol: regno=%d, rgb=%d,%d,%d\n",
      		   regno, red, green, blue); */
      
      	switch (info->fix.visual) {
      	case FB_VISUAL_TRUECOLOR:
      		/* true-colour, use pseudo-palette */
      
      		if (regno < 16) {
      			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;
      		}
      		break;
      
      	default:
      		return 1;	/* unknown type */
      	}
      
      	return 0;
      }
      
      static struct fb_ops atk7016_ops = {
      	.owner		= THIS_MODULE,
          .fb_setcolreg	= myfb_setcolreg,
      	.fb_fillrect	= cfb_fillrect,
      	.fb_copyarea	= cfb_copyarea,
      	.fb_imageblit	= cfb_imageblit,
      };
      
      /* 入口函数 */
      static int mylcd_probe(struct platform_device *pdev)
      {
          /* 设备树display节点 */
          struct device_node *display_nd;
          /* 时序相关信息 */
          struct display_timings *disp_timings;
          struct resource *resource_lcd;
          struct display_timing *timing;
          struct clk *pixel_clk;
          int bus_width;
          int bits_per_pixel;
          dma_addr_t phy_addr;
          int backlight_gpio;
          int ret;
      
      
      
          display_nd = of_parse_phandle(pdev->dev.of_node,"display",0);
          ret = of_property_read_u32(display_nd,"bus-width",&bus_width);
          ret = of_property_read_u32(display_nd,"bits-per-pixel",&bits_per_pixel);
          /*
           * 从设备树中获取display_timing结构体 
           * of_get_display_timings函数将display节点下的所有子节点timing
           * 都放到了disp_timings->timings数组中,每个timing节点都转化为了
           * 一个display_timing结构体,内核分配内存,并将它们的各个结构体首地址
           * 存放到disp_timings->timings中,可通过disp_timings->timings[i]来
           * 访问每个节点存放的数据,其中有一个native-mode属性指向了本地使用的timing节点,
           * 这个节点所转化的display_timing结构体的首地址的索引存放到了disp_timings->native_mode
           * 所以要使用节点所转化的display_timing结构体可使用disp_timings->timings[disp_timings->native_mode]
           * 方式来访问
           * */
          disp_timings = of_get_display_timings(display_nd);
          timing = disp_timings->timings[disp_timings->native_mode];
          /* 1、分配fb_info */
          /* 第一个参数是size,私有数据内存大小,这里不需要,所以为0 */
          myfb_info = framebuffer_alloc(0,NULL);
      
          /* 2、设置fb_info (可变的屏幕参数)*/
          /* a.var (可变的屏幕参数)*/
          myfb_info->var.xres = timing->hactive.typ; /* X分辨率 */
          myfb_info->var.yres = timing->vactive.typ;  /* Y分辨率 */
      
          myfb_info->var.xres_virtual = myfb_info->var.xres;
          myfb_info->var.yres_virtual = myfb_info->var.yres;
         
          myfb_info->var.bits_per_pixel = bits_per_pixel; /* 每个像素使用的数据位数 rgb565 */
          if(bits_per_pixel == 16)
          {
              myfb_info->var.red.offset = 11;
              myfb_info->var.red.length = 5;
      
              myfb_info->var.green.offset = 5;
              myfb_info->var.green.length = 6;
      
              myfb_info->var.blue.offset = 0;
              myfb_info->var.blue.length = 5;
          }
          else if(bits_per_pixel == 32 || bits_per_pixel == 24)
          {
              myfb_info->var.red.offset = 16;
              myfb_info->var.red.length = 8;
      
              myfb_info->var.green.offset = 8;
              myfb_info->var.green.length = 8;
      
              myfb_info->var.blue.offset = 0;
              myfb_info->var.blue.length = 8;       
          }
          else
          {
              return -EINVAL;
          }
          /* 时钟设置 */
          pixel_clk = devm_clk_get(&pdev->dev, "lcd");/* 从设备树中获取时钟 */
          clk_set_rate(pixel_clk, timing->pixelclock.typ); /* 设置时钟 */
          clk_prepare_enable(pixel_clk); /* 使能时钟 */
      
          /* 背光GPIO获取 */
          backlight_gpio = of_get_named_gpio(pdev->dev.of_node,"backlight-gpio",0);
          gpio_request(backlight_gpio,"backlight-gpio");
          gpio_direction_output(backlight_gpio,1);
          gpio_set_value(backlight_gpio,1);/* 开启背光 */
      
      
          /* b.fix (固定的屏幕参数)*/
          myfb_info->fix.smem_len = myfb_info->var.xres * myfb_info->var.yres * myfb_info->var.bits_per_pixel /8;  
          if(myfb_info->var.bits_per_pixel == 24)
          {
              myfb_info->fix.smem_len = myfb_info->var.xres * myfb_info->var.yres * 4;
          }
      
          myfb_info->fix.line_length = myfb_info->fix.smem_len / myfb_info->var.yres;
          myfb_info->fix.type = FB_TYPE_PACKED_PIXELS;
          myfb_info->fix.visual = FB_VISUAL_TRUECOLOR;
         
          myfb_info->screen_base = dma_alloc_wc(&pdev->dev, myfb_info->fix.smem_len, &phy_addr,
      					 GFP_KERNEL);/* 显存的虚拟地址 */
          myfb_info->fix.smem_start = phy_addr;/* 显存的物理地址 */
      
          /* c.fbops (fb操作函数)*/
          myfb_info->fbops = &atk7016_ops; /* fb_info操作结构体 */
          myfb_info->pseudo_palette = pseudo_palette; /* 假的调色板 */
      
          /* 3、注册fb_info */
          register_framebuffer(myfb_info);
      
          /* 4、硬件操作(硬件相关IO初始化) */
          resource_lcd = platform_get_resource(pdev, IORESOURCE_MEM, 0);
          lcdif = devm_ioremap_resource(&pdev->dev, resource_lcd);//ioremap(resource_lcd->start, PAGE_SIZE);
      
          lcd_controller_init(lcdif,timing,bus_width,bits_per_pixel,phy_addr);
      
          Stm32mp157_lcd_controller_enable(lcdif);
      
          gpio_set_value(backlight_gpio,1);/* 开启背光 */
      
      
          return 0;
      }
      
      /* 出口函数 */
      static int mylcd_remove(struct platform_device *pdev)
      {
          /* 1、注销fb_info */
          unregister_framebuffer(myfb_info);
      
          /* 2、释放fb_info */
          framebuffer_release(myfb_info);
      
          /* 3、释放硬件 */
          iounmap(lcdif);
      
          return 0;
      }
      
      static const struct of_device_id mylcd_of_match[] = {
      	{ .compatible = "atk,lcd-7016", },
      	{ },
      };
      MODULE_DEVICE_TABLE(of, mylcd_of_match);
      
      static struct platform_driver mylcd_driver = {
      	.driver = {
      		.name = "lcd-7016-framebuffer",
      		.of_match_table = mylcd_of_match,
      	},
      	.probe = mylcd_probe,
      	.remove = mylcd_remove,
      };
      
      static int __init mylcd_init(void)
      {
      	int ret;
      
      	ret = platform_driver_register(&mylcd_driver);
      	if (ret)
      		return ret;
      	return 0;
      }
      
      /* 出口函数 */
      static void __exit mylcd_exit(void)
      {
          /* 1、注销mylcd_driver */
          platform_driver_unregister(&mylcd_driver);
      }
      module_init(mylcd_init);
      module_exit(mylcd_exit);
      
      MODULE_AUTHOR("xxx");
      MODULE_LICENSE("GPL");
      MODULE_ALIAS("platform:ATK-7016-lcd");
      
    3. 编译驱动文件并运行

      将驱动文件存储为atk_7016fb.c,拷贝到内核drivers/video/fbdev/目录下,在drivers/video/fbdev/Makefile中添加下列编译选项,也就是将atk_7016fb.c编译进内核

      obj-y          += atk_7016fb.o
      

      同时通过make menuconfig在配置菜单中屏蔽内核中的stm系列的drm驱动,开启开机显示logo,linux并且将设备树中的ltdc设备节点的status改为disable,最后重新编译内核并烧写内核,同时我们可以将u-boot中的bootargs环境变量中添加console = tty1,这样就可以在lcd上打印内核启动信息。

      烧写到开发板后重启开发板:

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-07-28 07:59:49  更:2021-07-28 08:00:36 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/27 9:54:11-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计