Linux驱动分析之Framebuffer驱动

前言

    前面我们了解了LCD的基本架构《Linux驱动分析之LCD驱动架构》,接下来我们拿个具体的实例来分析分析。这样可以了解其大概是如何使用和工作的。

 

FrameBuffer驱动分析

内核版本:4.20

芯片平台:s3c2410

依然是使用之前的方式进行分析,大部分内容在注释。

(1)装载和卸载函数

static struct platform_driver s3c2410fb_driver = {
  .probe    = s3c2410fb_probe,
  .remove    = s3c2410fb_remove,
  .suspend  = s3c2410fb_suspend,
  .resume    = s3c2410fb_resume,
  .driver    = {
    .name  = "s3c2410-lcd",
  },
};


int __init s3c2410fb_init(void)
{
  int ret = platform_driver_register(&s3c2410fb_driver);
  return ret;
}


static void __exit s3c2410fb_cleanup(void)
{
  platform_driver_unregister(&s3c2410fb_driver);
}


module_init(s3c2410fb_init);
module_exit(s3c2410fb_cleanup);

上面比较简单,就是注册一个platform_driver, 控制器一般都是使用platform总线。

(2)probe()

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);
    //省略......
   //display包含了屏幕的分辨率信息等
  display = mach_info->displays + mach_info->default_display;
   //获取中断号
  irq = platform_get_irq(pdev, 0);


   //分配一个fb_info
  fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
    //保存fbinfo到driver data
  platform_set_drvdata(pdev, fbinfo);


  info = fbinfo->par;
  info->dev = &pdev->dev;
  info->drv_type = drv_type;
    //获取IO资源并申请
  res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  size = resource_size(res);
  info->mem = request_mem_region(res->start, size, pdev->name);


    //映射为虚拟地址
  info->io = ioremap(res->start, size);
   //获取中断基地址
  info->irq_base = info->io + S3C2410_LCDINTBASE;


  strcpy(fbinfo->fix.id, driver_name);


  //操作寄存器,停止LCDC输出
  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);
    //获取时钟并使能
  info->clk = clk_get(NULL, "lcd");
  clk_prepare_enable(info->clk);


  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;
  }


  /* 初始化显存,这里面设置了DMA */
  ret = s3c2410fb_map_video_memory(fbinfo);
   //根据屏幕信息初始化fbinfo中的可变参数
  fbinfo->var.xres = display->xres;
  fbinfo->var.yres = display->yres;
  fbinfo->var.bits_per_pixel = display->bpp;
  //初始化LCDC相关寄存器
  s3c2410fb_init_registers(fbinfo);


   //注册framebuffer
  ret = register_framebuffer(fbinfo);


  return 0;


}

上面省略了一些错误判断。

上面总结下来就两个部分:

1. 根据屏幕信息填充fb_info, 然后调用register_framebuffer进行注册

2. LCDC相对应的寄存器的配置(硬件平台相关的)

(3)各种操作屏幕的函数

static struct fb_ops s3c2410fb_ops = {
  .owner    = THIS_MODULE,
  .fb_check_var  = s3c2410fb_check_var, //检查可变参数合法性
  .fb_set_par  = s3c2410fb_set_par, //设置可变参数
  .fb_blank  = s3c2410fb_blank, //设置开关屏
  .fb_setcolreg  = s3c2410fb_setcolreg, //设置颜色寄存器
     //下面三个都是使用内核自带的函数
  .fb_fillrect  = cfb_fillrect, //绘制矩形
  .fb_copyarea  = cfb_copyarea, //区域拷贝
  .fb_imageblit  = cfb_imageblit,//绘制位图
};

简单看几个函数,其实就是对寄存器的操作。

  • s3c2410fb_set_par

static int s3c2410fb_set_par(struct fb_info *info)
{
    //设置参数信息
  struct fb_var_screeninfo *var = &info->var;
    
  switch (var->bits_per_pixel) {
  case 32:
  case 16:
  case 12:
    info->fix.visual = FB_VISUAL_TRUECOLOR;
    break;
  case 1:
    info->fix.visual = FB_VISUAL_MONO01;
    break;
  default:
    info->fix.visual = FB_VISUAL_PSEUDOCOLOR;
    break;
  }


  info->fix.line_length = (var->xres_virtual * var->bits_per_pixel) / 8;
    //设置寄存器,该函数中都是寄存器的操作
  s3c2410fb_activate_var(info);
  return 0;
}
  • s3c2410fb_blank

static int s3c2410fb_blank(int blank_mode, struct fb_info *info)
{
  struct s3c2410fb_info *fbi = info->par;
  void __iomem *tpal_reg = fbi->io;


  dprintk("blank(mode=%d, info=%p)\n", blank_mode, info);
  tpal_reg += is_s3c2412(fbi) ? S3C2412_TPAL : S3C2410_TPAL;
    //根据开关设置寄存器
  if (blank_mode == FB_BLANK_POWERDOWN)
    s3c2410fb_lcd_enable(fbi, 0);
  else
    s3c2410fb_lcd_enable(fbi, 1);


  if (blank_mode == FB_BLANK_UNBLANK)
    writel(0x0, tpal_reg);
  else {
    dprintk("setting TPAL to output 0x000000\n");
    writel(S3C2410_TPAL_EN, tpal_reg);
  }


  return 0;
}

这些操作函数和裸机程序基本差不多,就是一些对寄存器的操作。

 

总结

    上面其实没有多少内容,我们并不需要过多关注细节。比如寄存器配置的含义之类的,因为每个平台都不一样,即使你把这款芯片的所有细节理解了,换个平台,依然要重新来。所以我们应该理解的是框架。

    随着技术的发展,特别是GPU的出现,单纯使用Framebuffer来显示越来越少,它已经渐渐成为DRM的一部分了。特别是现在的Android设备, 对显示要求越来越高。后期会带来一些DRM相关的文章!

 

​​​​​​​

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页