STM32MP157 LCD的linux驱动程序–基于之Framebuffer框架
1. LCD硬件分析
-
LCD直观的描述
LCD就是多个像素点而组成的,如下图所示:
上图中的yres值就是y轴方向有多少个像素点,xres的值就是X轴的像素点的个数,平时所说的分辨率如1920X1080,就代表X轴有1920个像素点,Y轴有1080个像素点,那这个显示器就共有(1920*1080)个像素点。我们通过控制每个像素点的显示的颜色进而控制显示的画面。
-
像素点的颜色表示方法
每个像素点都使用红绿蓝三原色来表示,有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;
-
-
如何将每个像素点的颜色数据发送到lcd?
如果采用24bpp格式,那一个像素点共占用32位数据,若lcd的分辨率为1024x600,那总共就有(1024*600)个像素点,占用的内存大小就是(1024x600)x32/8个字节,在内存中分配出一块用来存放像素点数据的内存,这块内存就被叫做Framebuffer,Framebuffer翻译为中文即帧缓冲区,帧这个单位是影像动画中的最小单位,一帧就是一副静止的画面,连续的帧就形成了动画,那么帧缓冲区就是用来缓存一帧画面的数据。在linux内核当中它就是一种程序接口,这个接口将显示器设备抽象为帧缓冲区,这个帧缓冲区一般是根据显示设备的分辨率和图像格式(ARGB888、RGB888、RGB565等)来确定缓冲区大小,从内存中申请一块区域,当我们要显示一副图片时,我们将图片的数据写入帧缓冲区即可。
Framebuffer内存示意图如下:
-
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驱动就是一个字符驱动,所以它首先是建立在字符设备基础上的,那字符驱动的一般框架时如何的呢?
-
字符设备操作流程
用户操作一个硬件的过程就上图所示,对于linux而言,一切皆为文件,对于硬件上的每一个设备都抽象为文件,在根目录下的/dev目录,存放的就是我们的设备文件
如图所示,里面有字符驱动gpio,i2c,还有块驱动mmcblk1,mmcblk1boot0等,其中的fb0就是我的开发板上的lcd framebuffer驱动。通过ls -la /dev/fb0可以查看这个驱动信息:这个c就代表它是一个字符设备,且主设备号为29,次设备号为0
-
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结构体。
-
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); };
-
-
分析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()实现
-
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中
-
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(®istration_lock); ret = do_register_framebuffer(fb_info); mutex_unlock(®istration_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 控制器)初始化,也就是硬件初始化
-
STM32MP157的ltdc驱动程序编写
我使用的是正点原子的STM32MP157开发板,使用的ldc是ATK7016,分辨率为1024*600。
-
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模式,
所以该lcd的模式为DE模式。
-
驱动程序编写
我们参考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 = <<dc_pins_mx>; pinctrl-1 = <<dc_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编译进内核
```c
obj-y += atk_7016fb.o
```
同时通过make menuconfig在配置菜单中屏蔽内核中的stm系列的drm驱动,开启开机显示logo,linux并且将设备树中的ltdc设备节点的status改为disable,最后重新编译内核并烧写内核,同时我们可以将u-boot中的bootargs环境变量中添加console = tty1,这样就可以在lcd上打印内核启动信息。
烧写到开发板后重启开发板:
![](https://img-blog.csdnimg.cn/img_convert/ccd1174e6eb2f2f4cf278bf6a2df75d7.png)