微软交流社区

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 102|回复: 0

初探 Linux 下的 LCD 屏幕驱动

[复制链接]

3

主题

6

帖子

11

积分

新手上路

Rank: 1

积分
11
发表于 2022-11-27 11:47:06 | 显示全部楼层 |阅读模式
LCD是一个常用外设,一般情况下半导体厂商会为自家的芯片编写好相应的LCD接口驱动程序。开发者无需修改LCD驱动部分,只要按照所使用的LCD设备来修改设备树即可。虽然不需要修改驱动,但是我们还是有必要了解一下LCD驱动的流程
1. Framebuffer设备

在Linux中应用程序最终是通过操作RGB LCD的显存来实现在LCD上显示字符、图片等信息。Linux系统中内存的管理很严格,显存是需要申请的,而且因为虚拟内存的存在,驱动程序设置的显存和应用程序访问的显存必须要是同一片物理内存
为了解决该问题,诞生了Framebuffer,即帧缓冲 (简称fb),Framebuffer是保存着一帧图像的一块内存,向这块内存写入数据就相当于向屏幕中写入数据。也就是说Framebuffer把屏幕上的每个点映射成一段线性内存空间,程序可以通过改变这段内存的值来改变屏幕上某一点的颜色
Framebuffer机制为用户空间操作显示设备提供了统一的接口,屏蔽了底层硬件之间的差异,虚拟出一个fb设备,当LCD驱动加载成功后,会生成名为/dev/fbX的设备文件,应用程序通过访问该设备文件就可以访问LCD
/dev/fbX是一个字符设备,因此有其对应的file_operations操作集, 该操作集定义在文件 drivers/video/fbdev/core/fbmem.c中:
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,
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
.get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO
.fsync = fb_deferred_io_fsync,
#endif
.llseek = default_llseek,
};Linux内核将所有的Framebuffer抽象为fb_info结构体,fb_info结构体包含了Framebuffer设备的完整属性和操作集合,因此每一个Framebuffer设备都一定会有一个fb_info,该结构体定义在include/linux/fb.h文件里面,内容如下
struct fb_info {
atomic_t count;
int node;
int flags;
struct mutex lock; /* 互斥锁 */
struct mutex mm_lock; /* 互斥锁,用于fb_mmap和smem_*域*/
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; /* 当前视频模式 */

#ifdef CONFIG_FB_BACKLIGHT /* 如果LCD支持背光的话 */
struct backlight_device *bl_dev; /* 背光设备 */
/* Backlight level curve */
struct mutex bl_curve_mutex;
u8 bl_curve[FB_BACKLIGHT_LEVELS];
#endif ......
struct fb_ops *fbops; /* 帧缓冲操作函数集 */
struct device *device; /* 父设备 */
struct device *dev; /* 当前fb设备 */
int class_flag; /* 私有sysfs标志 */
......
char __iomem *screen_base; /* 虚拟内存基地址(屏幕显存) */
unsigned long screen_size; /* 虚拟内存大小(屏幕显存大小) */
void *pseudo_palette; /* 伪16位调色板 */
......
};fb_info结构体的成员变量很多,我们重点关注var、fix、fbops、screen_base、screen_size和pseudo_palette
2. LCD驱动程序分析

同一个芯片下,不同分辨率LCD屏幕的eLCDIF控制器驱动代码都是一样的,下面以NXP官方编写的IMX6ULL芯片在Linux下的LCD驱动为例,来简单梳理一下LCD驱动的流程
⏩ 打开imx6ull.dtsi,然后找到lcdif节点内容,如下所示:
lcdif: lcdif@021c8000 {
  compatible = "fsl,imx6ul-lcdif", "fsl,imx28-lcdif";
  reg = <0x021c8000 0x4000>;
  interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>;
  clocks = <&clks IMX6UL_CLK_LCDIF_PIX>,
  <&clks IMX6UL_CLK_LCDIF_APB>,
  <&clks IMX6UL_CLK_DUMMY>;
  clock-names = "pix", "axi", "disp_axi";
  status = "disabled";
};⏩ 根据compatible属性值,在Linux源码中搜索 "fsl,imx6ul-lcdif", "fsl,imx28-lcdif" 字符串,即可找到驱动文件drivers/video/fbdev/mxsfb.c
static const struct of_device_id mxsfb_dt_ids[] = {
   { .compatible = "fsl,imx23-lcdif", .data = &mxsfb_devtype[0], },
   { .compatible = "fsl,imx28-lcdif", .data = &mxsfb_devtype[1], },
   { /* sentinel */ }
   };
......
......
static struct platform_driver mxsfb_driver = {
.probe = mxsfb_probe,
.remove = mxsfb_remove,
.shutdown = mxsfb_shutdown,
.id_table = mxsfb_devtype,
.driver = {
     .name = DRIVER_NAME,
     .of_match_table = mxsfb_dt_ids,
     .pm = &mxsfb_pm_ops,
},
};

module_platform_driver(mxsfb_driver);⏩ 可见这是一个标准的platform驱动,当驱动和设备匹配以后mxsfb_probe函数就会执行
static int mxsfb_probe(struct platform_device *pdev) {
const struct of_device_id *of_id = of_match_device(mxsfb_dt_ids, &pdev->dev);
struct resource *res;
struct mxsfb_info *host;  //LCD主控接口
struct fb_info *fb_info;  //fb设备
struct pinctrl *pinctrl;
int irq = platform_get_irq(pdev, 0);
int gpio, ret;
......
//从设备树中获取eLCDIF接口控制器的寄存器首地址
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
  dev_err(&pdev->dev, "Cannot get memory IO resource\n");
  return -ENODEV;
}
  //给host申请内存,
host = devm_kzalloc(&pdev->dev, sizeof(struct mxsfb_info), GFP_KERNEL);
if (!host) {
  dev_err(&pdev->dev, "Failed to allocate IO resource\n");
  return -ENOMEM;
}
  //给fb_info申请内存,也就是申请fb_info
fb_info = framebuffer_alloc(sizeof(struct fb_info), &pdev->dev);
if (!fb_info) {
  dev_err(&pdev->dev, "Failed to allocate fbdev\n");
  devm_kfree(&pdev->dev, host);
  return -ENOMEM;
}
host->fb_info = fb_info;  //设置host的fb_info成员变量为fb_info,
fb_info->par = host;   //设置fb_info的par成员变量为host
  //申请中断,中断服务函数为mxsfb_irq_handler
ret = devm_request_irq(&pdev->dev, irq, mxsfb_irq_handler, 0, dev_name(&pdev->dev), host);
if (ret) {
  dev_err(&pdev->dev, "request_irq (%d) failed with
  error %d\n", irq, ret);
  ret = -ENODEV;
  goto fb_release;
}
  //对从设备树中获取到的寄存器首地址进行内存映射得到虚拟地址,并保存到host的base成员变量
  //因此通过访问host的base成员即可访问IMX6ULL的整个eLCDIF寄存器
host->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(host->base)) {
  dev_err(&pdev->dev, "ioremap failed\n");
  ret = PTR_ERR(host->base);
  goto fb_release;
}
......
  //给fb_info中的pseudo_palette申请内存
fb_info->pseudo_palette = devm_kzalloc(&pdev->dev, sizeof(u32) * 16, GFP_KERNEL);
if (!fb_info->pseudo_palette) {
  ret = -ENOMEM;
  goto fb_release;
}

INIT_LIST_HEAD(&fb_info->modelist);
pm_runtime_enable(&host->pdev->dev);
  //初始化fb_info
ret = mxsfb_init_fbinfo(host);
if (ret != 0)
  goto fb_pm_runtime_disable;

mxsfb_dispdrv_init(pdev, fb_info);

if (!host->dispdrv) {
  pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
  if (IS_ERR(pinctrl)) {
   ret = PTR_ERR(pinctrl);
   goto fb_pm_runtime_disable;
  }
}

if (!host->enabled) {
  writel(0, host->base + LCDC_CTRL);
  mxsfb_set_par(fb_info);  //设置eLCDIF控制器的相应寄存器
  mxsfb_enable_controller(fb_info);  //设置eLCDIF控制器的相应寄存器
  pm_runtime_get_sync(&host->pdev->dev);
}
  //向Linux内核注册fb_info
ret = register_framebuffer(fb_info);
if (ret != 0) {
  dev_err(&pdev->dev, "Failed to register framebuffer\n");
  goto fb_destroy;
}
......
return ret;
}mxsfb_probe函数的主要工作内容为:

  • 申请fb_info
  • 初始化fb_info结构体中的各个成员变量
  • 初始化eLCDIF控制器
  • 使用register_framebuffer函数向内核注册初始化好的fb_info
至此,LCD驱动的基本流程就结束了,后续会继续介绍如何通过调整设备树参数,来点亮LCD屏幕。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|微软交流社区

GMT+8, 2025-1-22 09:49 , Processed in 0.102493 second(s), 18 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表