STM32MP157 LCD的linux驱动程序–基于之Framebuffer框架
-
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驱动程序。 -
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
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;
ret = register_chrdev(FB_MAJOR, "fb", &fb_fops);
if (ret) {
printk("unable to get major %d for fb devs\n", FB_MAJOR);
goto err_chrdev;
}
fb_class = class_create(THIS_MODULE, "graphics");
if (IS_ERR(fb_class)) {
ret = PTR_ERR(fb_class);
pr_warn("Unable to create fb class; errno = %d\n", ret);
fb_class = NULL;
goto err_class;
}
fb_console_init();
return 0;
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);
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)
{
int fbidx = iminor(inode);
struct fb_info *info;
int res = 0;
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);
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;
int fbcon_rotate_hint;
struct mutex lock;
struct mutex mm_lock;
struct fb_var_screeninfo var;
struct fb_fix_screeninfo fix;
struct fb_monspecs monspecs;
struct work_struct queue;
struct fb_pixmap pixmap;
struct fb_pixmap sprite;
struct fb_cmap cmap;
struct list_head modelist;
struct fb_videomode *mode;
#if IS_ENABLED(CONFIG_FB_BACKLIGHT)
struct backlight_device *bl_dev;
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;
struct device *device;
struct device *dev;
int class_flag;
#ifdef CONFIG_FB_TILEBLITTING
struct fb_tile_ops *tileops;
#endif
union {
char __iomem *screen_base;
char *screen_buffer;
};
unsigned long screen_size;
void *pseudo_palette;
#define FBINFO_STATE_RUNNING 0
#define FBINFO_STATE_SUSPENDED 1
u32 state;
void *fbcon_par;
void *par;
struct apertures_struct {
unsigned int count;
struct aperture {
resource_size_t base;
resource_size_t size;
} ranges[0];
} *apertures;
bool skip_vt_switch;
};
在fb_info结构体中有几个重要的参数:
-
struct fb_var_screeninfo var*; 屏幕可变信息 ,include/uapi/linux/fb.h文件中定义,定义了显示器的分辨率、图像格式、像素时钟等硬件相关参数 struct fb_var_screeninfo {
__u32 xres;
__u32 yres;
__u32 xres_virtual;
__u32 yres_virtual;
__u32 xoffset;
__u32 yoffset;
__u32 bits_per_pixel;
__u32 grayscale;
struct fb_bitfield red;
struct fb_bitfield green;
struct fb_bitfield blue;
struct fb_bitfield transp;
__u32 nonstd;
__u32 activate;
__u32 height;
__u32 width;
__u32 accel_flags;
__u32 pixclock;
__u32 left_margin;
__u32 right_margin;
__u32 upper_margin;
__u32 lower_margin;
__u32 hsync_len;
__u32 vsync_len;
__u32 sync;
__u32 vmode;
__u32 rotate;
__u32 colorspace;
__u32 reserved[4];
};
-
struct fb_fix_screeninfo fix; 屏幕固定信息,include/uapi/linux/fb.h文件中定义,定义了显存起始地址,长度和FB类型等参数 struct fb_fix_screeninfo {
char id[16];
unsigned long smem_start;
__u32 smem_len;
__u32 type;
__u32 type_aux;
__u32 visual;
__u16 xpanstep;
__u16 ypanstep;
__u16 ywrapstep;
__u32 line_length;
unsigned long mmio_start;
__u32 mmio_len;
__u32 accel;
__u16 capabilities;
__u16 reserved[2];
};
-
struct fb_ops fbops;在include/linux/fb.h文件中定义,定义了fb_read、fb_write、fb_open、fb_close等操作函数。 struct fb_ops {
struct module *owner;
int (*fb_open)(struct fb_info *info, int user);
int (*fb_release)(struct fb_info *info, int user);
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);
int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);
int (*fb_set_par)(struct fb_info *info);
int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
unsigned blue, unsigned transp, struct fb_info *info);
int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);
int (*fb_blank)(int blank, struct fb_info *info);
int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);
void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);
int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor);
int (*fb_sync)(struct fb_info *info);
int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
unsigned long arg);
int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd,
unsigned long arg);
int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);
void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps,
struct fb_var_screeninfo *var);
void (*fb_destroy)(struct fb_info *info);
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)
{
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_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;
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 = 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);
lcdcon1 = readl(info->io + S3C2410_LCDCON1);
writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1);
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);
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;
}
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;
}
ret = register_framebuffer(fbinfo);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register framebuffer device: %d\n",
ret);
goto free_cpufreq;
}
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);
struct fb_info *info;
char *p;
if (size)
fb_info_size += PADDING;
p = kzalloc(fb_info_size + size, GFP_KERNEL);
if (!p)
return NULL;
info = (struct fb_info *) p;
if (size)
info->par = p + fb_info_size;
info->device = dev;
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++;
for (i = 0 ; i < FB_MAX; i++)
if (!registered_fb[i])
break;
fb_info->node = i;
atomic_set(&fb_info->count, 1);
mutex_init(&fb_info->lock);
mutex_init(&fb_info->mm_lock);
fb_info->dev = device_create(fb_class, fb_info->device,
MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
if (IS_ERR(fb_info->dev)) {
printk(KERN_WARNING "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);
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函数中的各个步骤。
static int mylcd_probe(struct platform_device *pdev)
{
return 0;
}
static int mylcd_remove(struct platform_device *pdev)
{
}
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;
ret = platform_driver_register(&mylcd_driver);
return 0;
}
static void __exit mylcd_exit(void)
{
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>;
vactive = <600>;
hback-porch = <140>;
hfront-porch = <160>;
hsync-len = <20>;
vback-porch = <20>;
vfront-porch = <12>;
vsync-len = <3>;
hsync-active = <0>;
vsync-active = <0>;
de-active = <0>;
pixelclk-active = <1>;
};
};
};
&pinctrl{
ltdc_pins_mx: ltdc_mx-0 {
pins1 {
pinmux = <STM32_PINMUX('I', 12, AF14)>,
<STM32_PINMUX('I', 13, AF14)>,
<STM32_PINMUX('I', 15, AF14)>,
<STM32_PINMUX('J', 0, AF14)>,
<STM32_PINMUX('J', 1, AF14)>,
<STM32_PINMUX('J', 2, AF14)>,
<STM32_PINMUX('J', 3, AF14)>,
<STM32_PINMUX('J', 4, AF14)>,
<STM32_PINMUX('J', 5, AF14)>,
<STM32_PINMUX('J', 6, AF14)>,
<STM32_PINMUX('J', 7, AF14)>,
<STM32_PINMUX('J', 8, AF14)>,
<STM32_PINMUX('J', 9, AF14)>,
<STM32_PINMUX('J', 10, AF14)>,
<STM32_PINMUX('J', 11, AF14)>,
<STM32_PINMUX('J', 12, AF14)>,
<STM32_PINMUX('J', 13, AF14)>,
<STM32_PINMUX('J', 14, AF14)>,
<STM32_PINMUX('J', 15, AF14)>,
<STM32_PINMUX('K', 0, AF14)>,
<STM32_PINMUX('K', 1, AF14)>,
<STM32_PINMUX('K', 2, AF14)>,
<STM32_PINMUX('K', 3, AF14)>,
<STM32_PINMUX('K', 4, AF14)>,
<STM32_PINMUX('K', 5, AF14)>,
<STM32_PINMUX('K', 6, AF14)>,
<STM32_PINMUX('K', 7, AF14)>;
bias-disable;
drive-push-pull;
slew-rate = <0>;
};
pins2 {
pinmux = <STM32_PINMUX('I', 14, AF14)>;
bias-disable;
drive-push-pull;
slew-rate = <1>;
};
};
ltdc_sleep_pins_mx: ltdc_sleep_mx-0 {
pins {
pinmux = <STM32_PINMUX('I', 12, ANALOG)>,
<STM32_PINMUX('I', 13, ANALOG)>,
<STM32_PINMUX('I', 14, ANALOG)>,
<STM32_PINMUX('I', 15, ANALOG)>,
<STM32_PINMUX('J', 0, ANALOG)>,
<STM32_PINMUX('J', 1, ANALOG)>,
<STM32_PINMUX('J', 2, ANALOG)>,
<STM32_PINMUX('J', 3, ANALOG)>,
<STM32_PINMUX('J', 4, ANALOG)>,
<STM32_PINMUX('J', 5, ANALOG)>,
<STM32_PINMUX('J', 6, ANALOG)>,
<STM32_PINMUX('J', 7, ANALOG)>,
<STM32_PINMUX('J', 8, ANALOG)>,
<STM32_PINMUX('J', 9, ANALOG)>,
<STM32_PINMUX('J', 10, ANALOG)>,
<STM32_PINMUX('J', 11, ANALOG)>,
<STM32_PINMUX('J', 12, ANALOG)>,
<STM32_PINMUX('J', 13, ANALOG)>,
<STM32_PINMUX('J', 14, ANALOG)>,
<STM32_PINMUX('J', 15, ANALOG)>,
<STM32_PINMUX('K', 0, ANALOG)>,
<STM32_PINMUX('K', 1, ANALOG)>,
<STM32_PINMUX('K', 2, ANALOG)>,
<STM32_PINMUX('K', 3, ANALOG)>,
<STM32_PINMUX('K', 4, ANALOG)>,
<STM32_PINMUX('K', 5, ANALOG)>,
<STM32_PINMUX('K', 6, ANALOG)>,
<STM32_PINMUX('K', 7, ANALOG)>;
};
};
};
驱动代码完整版如下: #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;
lcdif->LTDC_GCR |= 1<<0;
}
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;
lcdif->LTDC_SSCR = (dt->vsync_len.typ << 0) | (dt->hsync_len.typ << 16);
lcdif->LTDC_BPCR = 0 ;
lcdif->LTDC_BPCR |= (dt->vsync_len.typ + dt->vback_porch.typ - 1) << 0 ;
lcdif->LTDC_BPCR |= (dt->hsync_len.typ + dt->hback_porch.typ - 1) << 16;
lcdif->LTDC_AWCR = 0 ;
lcdif->LTDC_AWCR |= (dt->vsync_len.typ + dt->vback_porch.typ + dt->vactive.typ - 1) << 0;
lcdif->LTDC_AWCR |= (dt->hsync_len.typ + dt->hback_porch.typ + dt->hactive.typ - 1) << 16;
lcdif->LTDC_TWCR = 0;
lcdif->LTDC_TWCR |= (dt->vsync_len.typ + dt->vback_porch.typ + dt->vactive.typ + dt->vfront_porch.typ - 1) << 0;
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;
lcdif->LTDC_GCR &= ~(0xF << 28);
lcdif->LTDC_GCR |= pol_vclk << 28;
lcdif->LTDC_GCR |= pol_de << 29;
lcdif->LTDC_GCR |= pol_vsync << 30 ;
lcdif->LTDC_GCR |= pol_hsync << 31 ;
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;
lcdif->LTDC_L1CACR = 0xff;
lcdif->LTDC_L1BFCR = (4<<8) | (5<<0);
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;
return 0;
}
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;
switch (info->fix.visual) {
case FB_VISUAL_TRUECOLOR:
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;
}
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)
{
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);
disp_timings = of_get_display_timings(display_nd);
timing = disp_timings->timings[disp_timings->native_mode];
myfb_info = framebuffer_alloc(0,NULL);
myfb_info->var.xres = timing->hactive.typ;
myfb_info->var.yres = timing->vactive.typ;
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;
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);
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);
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;
myfb_info->fbops = &atk7016_ops;
myfb_info->pseudo_palette = pseudo_palette;
register_framebuffer(myfb_info);
resource_lcd = platform_get_resource(pdev, IORESOURCE_MEM, 0);
lcdif = devm_ioremap_resource(&pdev->dev, resource_lcd);
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)
{
unregister_framebuffer(myfb_info);
framebuffer_release(myfb_info);
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)
{
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");
-
编译驱动文件并运行 将驱动文件存储为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上打印内核启动信息。 烧写到开发板后重启开发板:
|