linux lcd移植


LCD驱动分析
LCD屏的驱动总体上分成两块,一块是GUI显示输出驱动;一块是触摸驱动(该部分单独一节另外描述)。
LCD驱动概念
LCD是Liquid Crystal Display的简称,也就是经常所说的液晶显示器。LCD能够支持彩色图像的显示和视频的播放,是一种非常重要的输出设备。
如果我们的系统要用GUI(图形界面接口),比如minigui,MicroWindows。这时LCD设备驱动程序就应该编写成frambuffer接口,而不是编写成仅仅操作底层的LCD控制器接口。
framebuffer是Linux系统为显示设备提供的一个接口,它将显示缓冲区抽象,屏蔽图像硬件的底层差异,允许上层应用程序在图形模式下直接对显示缓冲区进行操作。 framebuffer又叫帧缓冲,是Linux为操作显示设备提供的一个用户接口。用户应用程序可以通过framebuffer透明地访问不同类型的显示设备。 从这个方面来说,framebuffer是硬件设备显示缓冲区的抽象。 Linux抽象出framebuffer这个帧缓冲区可以供用户应用程序直接读写,通过更改framebuffer中的内容,就可以立刻显示在LCD显示屏上。
framebuffer是一个标准的字符设备,主设备号是29,次设备号根据缓冲区的数目而定。framebuffer对应/dev/fb%d设备文件。
根据显卡的多少,设备文件可能是/dev/fb0、/dev/fb1等。缓冲区设备也是一种普通的内存设备,可以直接对其进行读写。 对用户程序而言,它和/dev下面的其他设备没有什么区别,用户可以把frameBuffer看成一块内存,既可以写,又可以读。 显示器将根据内存数据显示对应的图像界面。这一切都由LCD控制器和响应的驱动程序来完成。
LCD驱动框架分析
总体上是一个平台设备驱动与字符驱动的组合;
先从LCD的设备 dev/fb* 是怎么实现的来进行追溯;
1.开发板启动,进行设备注册;
在 arch/arm/mach-mx28/device.c 设备注册文件中,注册LCD的fd平台设备 “mxs-fb”

              // mxs-fb平台设备资源定义
              static struct resource framebuffer_resource[] = {
              {
              .flags = IORESOURCE_MEM,
              .start = LCDIF_PHYS_ADDR,
              .end = LCDIF_PHYS_ADDR + 0x2000 - 1,
              },
              {
              .flags = IORESOURCE_IRQ,
              .start = IRQ_LCDIF,
              .end = IRQ_LCDIF,
              },
              };

              // mxs-fb平台设备私有数据,包含显示屏名称、分辨率、位宽、时钟、面板操作等,在drivers/video/mxs/lcd_43wvf1g.c 中通过subsys_initcall
              接口将设备私有数据添加到链表中

              static struct mxs_platform_fb_data mxs_framebuffer_pdata = {
              .list = LIST_HEAD_INIT(mxs_framebuffer_pdata.list),
              };

              // lcd设备启动初始化,在 m28evk.c 中 mx28_device_init()会调用

              static void __init mx28_init_lcdif(void)
              {
              struct platform_device *pdev;
              pdev = mxs_get_device("mxs-fb", 0);  //获取匹配的设备结构体,定义在 arch/arm/plat-mxs/device.c 中

              if (pdev == NULL || IS_ERR(pdev))
              return;
              pdev->resource = framebuffer_resource;
              pdev->num_resources = ARRAY_SIZE(framebuffer_resource);
              pdev->dev.platform_data = &mxs_framebuffer_pdata;
              mxs_add_device(pdev, 3);  // 添加到设备注册列表,设备注册在 arch/arm/plat-mxs/device.c 中实现 通过
              device_initcall(mxs_device_init);遍历设备列表并进行平台设备注册

              }

              
2.接下来是 platform_driver mxsfb_driver 的注册,匹配之后触发 mxsfb_probe 函数执行以下操作:
进行相关硬件初始化和 framebuffer 设置;
register_framebuffer() 注册 LCD 屏的 fd 设备;
LCD的驱动包含:
drivers/video/mxs/lcd_43wvf1g.c  // LCD设备私有数据,包含名称、分辨率、位宽、时钟、面板操作
drivers/video/mxs/lcdif.c      // lcd的一些接口操作
drivers/video/mxs/mxsfb.c     // 平台设备驱动 platform_driver 注册 drivers/video/mxs/mxsfb.c
会编译成 mxsfb.ko
              static struct platform_driver mxsfb_driver = {
              .probe = mxsfb_probe,
              .remove = mxsfb_remove,
              .suspend = mxsfb_suspend,
              .resume = mxsfb_resume,
              .driver = {
              .name = "mxs-fb",    // 与启动时的平台设备注册的 platform_device 名称相同
              .owner = THIS_MODULE,
              },
              };

              static int __init mxsfb_init(void)
              {
              return platform_driver_register(&mxsfb_driver);  // 显示屏平台设备驱动注册
              }
              
驱动安装时与平台设备匹配之后触发 mxsfb_probe 函数,这个时核心。
              static int __devinit mxsfb_probe(struct platform_device *pdev)
              {
              int ret = 0;
              struct mxs_fb_data *data;
              struct resource *res;
              struct fb_info *info;
              struct mxs_platform_fb_data *pdata = pdev->dev.platform_data;
              struct mxs_platform_fb_entry *pentry = NULL;

              mydbg("\n");
              if (pdata == NULL) {
              ret = -ENODEV;
              goto out;
              }

              if (default_panel_name) {
              mydbg("default_panel_name=%s\n",default_panel_name);
                   // 通过LCD面板名称匹配获取面板参数及设置句柄(平台设备私有数据传递过来)
              pentry = (void *)mxs_lcd_iterate_pdata(pdata,
              get_matching_pentry_by_name,
              default_panel_name);
              if (pentry) {
              mxs_lcd_move_pentry_up(pentry, pdata);
              pdata->cur = pentry;
              }
              }
              if (!default_panel_name || !pentry) {
              mydbg("\n");
              pentry = pdata->cur;
              }
              if (!pentry || !pentry->init_panel || !pentry->run_panel ||
              !pentry->release_panel) {
              mydbg("\n");
              ret = -EINVAL;
              goto out;
              }

              data =
              (struct mxs_fb_data *)framebuffer_alloc(sizeof(struct mxs_fb_data) +
              sizeof(u32) * 256 -
              sizeof(struct fb_info),
              &pdev->dev);
              if (data == NULL) {
              ret = -ENOMEM;
              goto out;
              }

              cdata = data;
              data->dev = &pdev->dev;
              data->pdata = pdata;
              platform_set_drvdata(pdev, data);
              info = &data->info;

              dev_dbg(&pdev->dev, "resolution %dx%d, bpp %d\n", pentry->x_res,
              pentry->y_res, pentry->bpp);

              mxs_lcd_iterate_pdata(pdata, get_max_memsize, data);

              data->map_size = PAGE_ALIGN(data->mem_size) * NUM_SCREENS;
              dev_dbg(&pdev->dev, "memory to allocate: %d\n", data->map_size);

              data->virt_start = dma_alloc_writecombine(&pdev->dev,
              data->map_size,
              &data->phys_start,
              GFP_KERNEL);

              if (data->virt_start == NULL) {
              ret = -ENOMEM;
              goto out_dma;
              }
              dev_dbg(&pdev->dev, "allocated at %p:0x%x\n", data->virt_start,
              data->phys_start);
              mutex_init(&data->blank_mutex);
              INIT_WORK(&data->work, mxsfb_task);
              data->state = F_ENABLE;

              mxsfb_default.bits_per_pixel = pentry->bpp;
              /* NB: rotated */
              mxsfb_default.xres = pentry->y_res;
              mxsfb_default.yres = pentry->x_res;
              mxsfb_default.xres_virtual = pentry->y_res;
              mxsfb_default.yres_virtual = data->map_size /
              (pentry->y_res * pentry->bpp / 8);
              if (mxsfb_default.yres_virtual >= mxsfb_default.yres * 2)
              mxsfb_default.yres_virtual = mxsfb_default.yres * 2;
              else
              mxsfb_default.yres_virtual = mxsfb_default.yres;

              mxsfb_fix.smem_start = data->phys_start;
              mxsfb_fix.smem_len = pentry->y_res * pentry->x_res * pentry->bpp / 8;
              mxsfb_fix.ypanstep = 1;

              switch (pentry->bpp) {
              case 32:
              case 24:
              mxsfb_default.red.offset = 16;
              mxsfb_default.red.length = 8;
              mxsfb_default.green.offset = 8;
              mxsfb_default.green.length = 8;
              mxsfb_default.blue.offset = 0;
              mxsfb_default.blue.length = 8;
              break;

              case 16:
              #if 0
              mxsfb_default.red.offset = 11;
              mxsfb_default.red.length = 5;
              mxsfb_default.green.offset = 5;
              mxsfb_default.green.length = 6;
              mxsfb_default.blue.offset = 0;
              mxsfb_default.blue.length = 5;
              break;
              #else
              mxsfb_default.red.offset = 0 ;
              mxsfb_default.red.length = 5;
              mxsfb_default.green.offset = 5;
              mxsfb_default.green.length = 6;
              mxsfb_default.blue.offset = 11;
              mxsfb_default.blue.length = 5;
              break;
              #endif
              default:
              dev_err(&pdev->dev, "unsupported bitwidth %d\n", pentry->bpp);
              ret = -EINVAL;
              goto out_dma;
              }

              info->screen_base = data->virt_start;
              info->fbops = &mxsfb_ops;
              info->var = mxsfb_default;
              info->fix = mxsfb_fix;
              info->pseudo_palette = &data->par;
              data->par = NULL;
              info->flags = FBINFO_FLAG_DEFAULT;

              init_waitqueue_head(&data->vsync_wait_q);
              data->vsync_count = 0;

              res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
              if (res == NULL) {
              dev_err(&pdev->dev, "cannot get IRQ resource\n");
              ret = -ENODEV;
              goto out_dma;
              }
              data->regbase = (unsigned long)IO_ADDRESS(res->start);

              res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
              if (res == NULL) {
              dev_err(&pdev->dev, "cannot get IRQ resource\n");
              ret = -ENODEV;
              goto out_dma;
              }
              data->irq = res->start;

              mxsfb_check_var(&info->var, info);

              ret = fb_alloc_cmap(&info->cmap, 256, 0);
              if (ret)
              goto out_cmap;

              mxsfb_set_par(info);

              mxs_init_lcdif();
              ret = pentry->init_panel(data->dev, data->phys_start,
              mxsfb_fix.smem_len, pentry);
              if (ret) {
              dev_err(&pdev->dev, "cannot initialize LCD panel\n");
              goto out_panel;
              }
              dev_dbg(&pdev->dev, "LCD panel initialized\n");
              init_timings(data); // not effect dotclk mode

              ret = request_irq(data->irq, lcd_irq_handler, 0, "fb_irq", data);
              if (ret) {
              dev_err(&pdev->dev, "request_irq (%d) failed with error %d\n",
              data->irq, ret);
              goto out_panel;
              }
              ret = register_framebuffer(info); // 注册fb_info
              if (ret)
              goto out_irq;

              pentry->run_panel();
              /* REVISIT: temporary workaround for MX23EVK */
              mxsfb_disable_controller(data);
              mxsfb_enable_controller(data);
              data->cur_phys = data->phys_start;
              dev_dbg(&pdev->dev, "LCD running now\n");

              #ifdef CONFIG_CPU_FREQ
              mxsfb_nb.fb_data = data;
              cpufreq_register_notifier(&mxsfb_nb.nb, CPUFREQ_TRANSITION_NOTIFIER);
              #endif /* CONFIG_CPU_FREQ */

              goto out;

              out_irq:
              free_irq(data->irq, data);
              out_panel:
              fb_dealloc_cmap(&info->cmap);
              out_cmap:
              dma_free_writecombine(&pdev->dev, data->map_size, data->virt_start,
              data->phys_start);
              out_dma:
              kfree(data);
              out:
              return ret;
              }

              
LCD驱动移植总结
1.LCD引脚配置及初始化
上述原理图包含了数据、时钟、背光控制、触摸、复位等引脚的分配,具体有机会在深入理解LCD硬件驱动原理有在进行说明。
在 arch/arm/mach-mx28/mx28evk_pins.c 的 mx28evk_fixed_pins[ ] 引脚列表中添加 LCD 的驱动引脚,相关引脚转义在 mx28_pins.h 结合
arch/arm/mach/pinctrl.h 实现
              #if defined(CONFIG_FB_MXS) || defined(CONFIG_FB_MXS_MODULE)
              {
              .name = "LCD_D00",
              .id = PINID_LCD_D00,
              .fun = PIN_FUN1,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              {
              .name = "LCD_D01",
              .id = PINID_LCD_D01,
              .fun = PIN_FUN1,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              {
              .name = "LCD_D02",
              .id = PINID_LCD_D02,
              .fun = PIN_FUN1,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              {
              .name = "LCD_D03",
              .id = PINID_LCD_D03,
              .fun = PIN_FUN1,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              {
              .name = "LCD_D04",
              .id = PINID_LCD_D04,
              .fun = PIN_FUN1,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              {
              .name = "LCD_D05",
              .id = PINID_LCD_D05,
              .fun = PIN_FUN1,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              {
              .name = "LCD_D06",
              .id = PINID_LCD_D06,
              .fun = PIN_FUN1,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              {
              .name = "LCD_D07",
              .id = PINID_LCD_D07,
              .fun = PIN_FUN1,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              {
              .name = "LCD_D08",
              .id = PINID_LCD_D08,
              .fun = PIN_FUN1,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              {
              .name = "LCD_D09",
              .id = PINID_LCD_D09,
              .fun = PIN_FUN1,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              {
              .name = "LCD_D10",
              .id = PINID_LCD_D10,
              .fun = PIN_FUN1,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              {
              .name = "LCD_D11",
              .id = PINID_LCD_D11,
              .fun = PIN_FUN1,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              {
              .name = "LCD_D12",
              .id = PINID_LCD_D12,
              .fun = PIN_FUN1,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              {
              .name = "LCD_D13",
              .id = PINID_LCD_D13,
              .fun = PIN_FUN1,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              {
              .name = "LCD_D14",
              .id = PINID_LCD_D14,
              .fun = PIN_FUN1,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              {
              .name = "LCD_D15",
              .id = PINID_LCD_D15,
              .fun = PIN_FUN1,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              /*
              {
              .name = "LCD_D16",
              .id = PINID_LCD_D16,
              .fun = PIN_FUN1,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              {
              .name = "LCD_D17",
              .id = PINID_LCD_D17,
              .fun = PIN_FUN1,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              {
              .name = "LCD_D18",
              .id = PINID_LCD_D18,
              .fun = PIN_FUN1,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              {
              .name = "LCD_D19",
              .id = PINID_LCD_D19,
              .fun = PIN_FUN1,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              {
              .name = "LCD_D20",
              .id = PINID_LCD_D20,
              .fun = PIN_FUN1,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              {
              .name = "LCD_D21",
              .id = PINID_LCD_D21,
              .fun = PIN_FUN1,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              {
              .name = "LCD_D22",
              .id = PINID_LCD_D22,
              .fun = PIN_FUN1,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              {
              .name = "LCD_D23",
              .id = PINID_LCD_D23,
              .fun = PIN_FUN1,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              */
              {
              .name = "LCD_RESET",
              .id = PINID_LCD_RESET,
              .fun = PIN_FUN1,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              {
              .name = "LCD_VSYNC",
              .id = PINID_LCD_RD_E,
              .fun = PIN_FUN2,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              {
              .name = "LCD_HSYNC",
              .id = PINID_LCD_WR_RWN,
              .fun = PIN_FUN2,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              {
              .name = "LCD_ENABLE",
              .id = PINID_LCD_CS,
              .fun = PIN_FUN2,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              {
              .name = "LCD_DOTCLK",
              .id = PINID_LCD_RS,
              .fun = PIN_FUN2,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              {
              .name = "LCD_BACKLIGHT",
              .id = PINID_PWM3,
              .fun = PIN_FUN1,
              .strength = PAD_8MA,
              .voltage = PAD_3_3V,
              .drive = 1,
              },
              #endif
              
然后通过类似 LCDIF_PHYS_ADDR + BM_LCDIF_CTRL1_RESET 组合就可以操作相关引脚寄存器了
2. 驱动移植到drivers 目录
1).将LCD驱动放到 drivers/video/ 目录下,本例为 mxs
2).修改 drivers/video/Kconfig,添加如下配置,表示会提取 mxs 驱动的 Kconfig 配置
if ARCH_MXS
source "drivers/video/mxs/Kconfig"
endif
3. 修改板级文件
板级文件有两个
mach-mx28   // mx28系列特有的
plat-mxs    // fsl通用共有的功能
1)在设备注册 arch/arm/mach-mx28/device.c 中添加 mxs LCD 平台设备注册
              #if defined(CONFIG_FB_MXS) || defined(CONFIG_FB_MXS_MODULE)
              // LCD平台设备资源 resource
              static struct resource framebuffer_resource[] = {
              {
              .flags = IORESOURCE_MEM,    //寻址地址空间资源


              .start = LCDIF_PHYS_ADDR,
              .end = LCDIF_PHYS_ADDR + 0x2000 - 1,
              },
              {
              .flags = IORESOURCE_IRQ,   //中断资源
              .start = IRQ_LCDIF,
              .end = IRQ_LCDIF,
              },
              };

              static struct mxs_platform_fb_data mxs_framebuffer_pdata = {
              .list = LIST_HEAD_INIT(mxs_framebuffer_pdata.list),
              };

              static void __init mx28_init_lcdif(void)
              {
              struct platform_device *pdev;
              pdev = mxs_get_device("mxs-fb", 0);  //获取匹配的设备结构体,定义在 arch/arm/plat-mxs/device.c 中
              if (pdev == NULL || IS_ERR(pdev))
              return;
              pdev->resource = framebuffer_resource;
              pdev->num_resources = ARRAY_SIZE(framebuffer_resource);
              pdev->dev.platform_data = &mxs_framebuffer_pdata;
              //设备私有数据链表,包含名称、分辨率、位宽、时钟、面板操作,在drivers/video/mxs/lcd_43wvf1g.c 中通过subsys_initcall 接口将设备私有数据添加到链表中

              mxs_add_device(pdev, 3);  //添加到设备注册列表
              }
              #else
              static void __init mx28_init_lcdif(void)
              {
              ;
              }
              #endif
              
2)在设备列表注册 arch/arm/plat-mxs/device.c 中添加 mxs LCD 平台设备结构及设备列表匹配信息
              #if defined(CONFIG_FB_MXS) || defined(CONFIG_FB_MXS_MODULE)
              // LCD面板平台设备结构体
              static struct platform_device mxs_fb = {
              .name = "mxs-fb",  //平台设备名称,后面的平台设备驱动名称要与这个一致
              .id = 0,
              .dev = {
              .dma_mask = &common_dmamask,
              .coherent_dma_mask = DMA_BIT_MASK(32),
              .release = mxs_nop_release,
              },
              };
              #endif


              static struct mxs_dev_lookup dev_lookup[] = {
              ......

              #if defined(CONFIG_FB_MXS) || defined(CONFIG_FB_MXS_MODULE)
              {
              .name = "mxs-fb",
              .size = 1,
              .pdev = &mxs_fb,
              },
              #endif
              ......
              }
              

相关阅读:
linux基础
linux怎么学
linux和GNU
GNU Free Documentation License
最受欢迎的linux发行版
initroot编辑整理,转载请注明www.initroot.com

100次点赞 100次阅读