MIPI DIsplay Panel And Linux Driver Model .

 

原文地址:点击打开链接http://blog.youkuaiyun.com/redredbird/article/details/12648847

 

硬件篇

架构

最近研究MIPI DSI Panel Linux Driver架构,于是一边啃spec性质的官方文档,一边从dri-devel,omap-linux等邮件列表里,搜索所有的跟panel driver相关的讨论,尽力在短时间内把整个subsystem的脉络迅速掌握。

MIPI规范不止于display,也包括camera(MIPI CSI),电源管理,射频的东西。和Display相关的就是MIPI DSI,DPI,DBI等,规范了host display controller到panel之间通信时从物理层,链路层到应用层的协议。目标市场是对功耗、屏幕尺寸有特殊要求的移动设备,不同于VESA针对PC市场。

大而笼统地说,mobile display system一般可分为两种 architecture:


右边这种叫“smart panel”,左边这种叫"dumb panel"(“哑”屏)。区别是smart panel带framebuffer,“刷屏”操作由屏自己来负责,host controller成了甩手掌柜,而哑屏就和任何一种非主动设备一样,可以把它当作一块带有简单时许控制电路的玻璃。

mipis在上面两种基本architecture的基础上,扩展了四种架构。

Type1

完全的command模式,mipi规范里DBI属于这种type

Type2

type1的一条control-interface接口变为了video-interface + control-interface的组合,full-framebuffer也变为了partial-framebuffer,因为没有了full-framebuffer,所以不可能完全依靠panel来刷屏,这就是video-interface存在的必要;但partial-framebuffer也为panel带来了partial-update的灵活性。


control-interface和video-interface可能是在同一个物理总线上的。这里的control-interface类似I2C或者PCI,video-interface传递pixels/clock。control-interface都是传递one-shot的data/command,有请求有应答;video-interface传递continuous的data,不需要slave device的应答。Linux的设备驱动模型树总是围绕control-interface来组织的。master同slave之间的连接组织成树,内核电源管理子系统通过遍历树来得到一个正确的suspend/resume的顺序,否则会产生很多ordering issue;通过control-interface,master可以读写slave的寄存器,读写片上存储内容,向slave发送控制命令等。如下图我的电脑里显示的smbus控制器和挂在其上的i2c从设备的树状图:


树上0000:00:14:00的pci节点就是smbus控制器的设备对象,下面的i2c-0和i2c-1节点分别是挂在这颗i2c总线上的两个i2c从设备(比如某种传感器)。扯远了,继续回到DSI上面。

Type3

彻底没有panel-framebuffer,但还保留有control-interface。这样video-interface和control-interface仍然共用一条physical line,master设备仍然不需要借助其他多余的pin就可以控制panel。为了支持panel可以通过control-interface的命令来控制,panel内部所以还需要保留寄存器。


Type 4

用牺牲更多的引脚数目(control lines)的代价来简化Panel内部的设计,不再需要内置寄存器。


MIPI规范定义的三种display接口协议,DBIDPIDSI。其中DBI可以实现上面4typecontrolinterfaceDPI可以实现videointerface。而DSI最灵活,单纯的工作在commandmode可以实现type1command+videomode共用时可以实现type2/3,单纯的工作在videomode时可以实现type4。对于type4controlline可以是固定的gpio配置等。

DSI物理层

物理层上面的DSI有三种工作模式,control-modeescape-modehighspeed-mode

硬件reset后首先进入的lower-power stop state默认就是在control-modehost当需要以burst的方式在总线上发送数据时(比如像素数据或者DCS),就要让总线进入high-speed mode

DataLane上随即会出现SoT(startof transmision)EoT信号,分别是进入和离开highspeed mode的标记,在一对SoTEoT之间夹着一个或多个sPashortpacket)或者lPalongpacket)。

escape-mode是一种特殊地允许DSI总线能在低速低功耗状态下发送数据的模式,在escape-mode下还可以主动切换到耗电更低的超低功耗模式。

因为DSI的控制命令的传输是双向的,双向传输的机制由hostpanel之间通过bus-turnaround协议用作为交换使用总线所有权的令牌

下图中BTA就用作panelhost响应读请求,使用escape-mode下的LPDT命令在低功耗总线下传输packet的情景


设备电源状态转换

MIPIDCS规范了一系列电源状态,简单的说,有

1).Sleep on/off:除了和hostlink-interface保持正常工作电源状态以外,其他模块都处于低功耗模式。(因为hostdisplay需要通过linkinterface来唤醒paneldevice,所以这个不能关)

2).Normal/Idlemodepanel使用正常的像素颜色位深或者使用有限位数的颜色位深,因为video模式的Panel的像素都是来自于host,而command-modepanel像素来自自身的framebuffer,所以idle-mode只对command-modePanel有效

3).Paritial mode:只针对command-modepanel有效,即只显示framebuffer指定矩形区域的像素。

软件篇

MIPI DSI Panel需要单独的软件支持吗?首先,DSI是一种chip-to-chip的接口,不同于HDMI, DP这种box-to-box的接口,不同的芯片商,可能都有自己的不同于别家的上下电或初始化序列;其次,即使同样可以作为chip-to-chip接口的eDP,也有我称之为自配置的功能,可以通过EDID/AUX等获得panel支持的Mode和timing;而再‘标准’的DSI panel也要硬编码许多panel相关的参数。

panel driver属于Linux/Android display stack的“最后一公里”,PC上的display stack是DRM/KMS子系统,Android的mobile display stack子系统是framebuffer。Mobile display stack提供的用户服务并不复杂,主要就是打开/关闭,不像pc的stack还可以改变mode和timing,因为mobile display的mode和timing一般都是固定的,由LCD模组厂商决定。

相关的一些内核/用户接口通过/sys提供,比如在我的联想a790e手机的adb shell环境中执行命令:

  1. # echo 1 > /sys/devices/virtual/graphics/fb0/blank  
# echo 1 > /sys/devices/virtual/graphics/fb0/blank
即可看到手机只剩背光了,这时display controller给panel的pixel/clock信号被切断或者被‘blank’:


不光pixel和clock被切断,可能整个信号链条上的block的power和clk也被关闭了以达到更省电的效果

再往fb0这个节点的blank属性写0时才重新打开panel

  1. # echo 0 > /sys/devices/virtual/graphics/fb0/blank  
# echo 0 > /sys/devices/virtual/graphics/fb0/blank

其实整个过程同我们按下手机顶部的电源键display stack所触发的动作是一样的,只是后者还把背光驱动给关掉了而已,所以整个屏都是“黑色”。

display soc在硬件上,从framebuffer到panel的video stream流会经过一条pipeline,这条pipeline一般由plane,controller,encoder,panel组成。最前端的plane为多个head的机制提供了单独的framebuffer支持,不同的plane(图层)可以接入不同的display device如hdmi,dsi,vga显示不同的内容;controller负责从framebuffer中的指定区域读取像素,驱动各种需要的PLL,时钟(pixel clock等),如果controller支持多个overlay,controller还要管理overlay的叠加工作;encoder负责将并行的pixel/clock信号转换为终端显示设备需要的串行信号等如DSI,HDMI,最后的panel则是接收输入的pixel/clock信号驱动行列驱动器在玻璃上‘显示’图像,如图:

  1. +-------------------------------------------------+  
  2. | +------------+   +-----------+   +------------+ | +-----------+  
  3. | |            |   | Display   |   | Encoder    | | |           |  
  4. | | Plane      +---+ Controller+---+ (HDMI,DSI) +-+-+  Panel    |  
  5. | |            |   |           |   |            | | |           |  
  6. | +------------+   +-----------+   +------------+ | +-----------+  
  7. +-------------------------------------------------+              
+-------------------------------------------------+
| +------------+   +-----------+   +------------+ | +-----------+
| |            |   | Display   |   | Encoder    | | |           |
| | Plane      +---+ Controller+---+ (HDMI,DSI) +-+-+  Panel    |
| |            |   |           |   |            | | |           |
| +------------+   +-----------+   +------------+ | +-----------+
+-------------------------------------------------+            

这些block除了最后的panel属于external device以外,其他都以ip的形式集成到主控制器的soc里了,同一系列的芯片不同版本的soc可能只是对不同版本的ip block的组合而已,所以,为了代码复用,一般soc vendor的driver在设计时为每个block都设计了自己的驱动对象struct device_driver和由该驱动管理的设备类struct deice的定义,具体的设备实例然后被板级的bringup code动态地‘注册’进系统,driver在probe它们时为其分配必要的资源和注册各自的hook callback函数,比如响应上面的用户对blank属性设置的请求的on/off函数。pipeline也决定了控制函数的调用顺序,比如on/off处理函数,在on系统请求被调用时,就要先调用最上级的plane_on_callback,最后调用最下级的panel_on_callback;如果是off系统请求时,顺序就完全相反。

板级bringup code注册设备的方法有很多,比如在a790e上,在Kernel boot cmdline:


可以看到lcd.name=mipi_video_nt35510_bitland_wvga参数。而在kernel boot时实现了panel driver的module会在module_init()时检查该boot参数,所以只有nt35510_bitland_wvga的panel driver会创建nt35510的panel platform_device。

在基于msm7627a soc的设备a790e的display driver stack中,一条mipi dsi的display pipeline有如下driver对象会被涉及:

msm_fb driver,match的设备对象的名字是“msm_fb”,映射的是plane block

  1. static struct platform_driver msm_fb_driver = {  
  2.      .probe = msm_fb_probe,  
  3.      .remove = msm_fb_remove,  
  4.  #ifndef CONFIG_HAS_EARLYSUSPEND   
  5.      .suspend = msm_fb_suspend,  
  6.      .resume = msm_fb_resume,  
  7.  #endif   
  8.      .shutdown = NULL,  
  9.      .driver = {  
  10.             /* Driver name must match the device name added in platform.c. */  
  11.             .name = "msm_fb",  
  12.             .pm = &msm_fb_dev_pm_ops,  
  13.             },  
  14.  };  
static struct platform_driver msm_fb_driver = {
     .probe = msm_fb_probe,
     .remove = msm_fb_remove,
 #ifndef CONFIG_HAS_EARLYSUSPEND
     .suspend = msm_fb_suspend,
     .resume = msm_fb_resume,
 #endif
     .shutdown = NULL,
     .driver = {
            /* Driver name must match the device name added in platform.c. */
            .name = "msm_fb",
            .pm = &msm_fb_dev_pm_ops,
            },
 };

mdp driver,Match的设备对象名为‘msm_mdp’,对应的是display controller block

  1. static struct platform_driver mdp_driver = {  
  2.      .probe = mdp_probe,  
  3.      .remove = mdp_remove,  
  4.  #ifndef CONFIG_HAS_EARLYSUSPEND   
  5.      .suspend = mdp_suspend,  
  6.      .resume = NULL,  
  7.  #endif   
  8.      .shutdown = NULL,  
  9.      .driver = {  
  10.          /* 
  11.           * Driver name must match the device name added in 
  12.           * platform.c. 
  13.           */  
  14.          .name = "mdp",  
  15.          .pm = &mdp_dev_pm_ops,  
  16.      },  
  17. };  
static struct platform_driver mdp_driver = {
     .probe = mdp_probe,
     .remove = mdp_remove,
 #ifndef CONFIG_HAS_EARLYSUSPEND
     .suspend = mdp_suspend,
     .resume = NULL,
 #endif
     .shutdown = NULL,
     .driver = {
         /*
          * Driver name must match the device name added in
          * platform.c.
          */
         .name = "mdp",
         .pm = &mdp_dev_pm_ops,
     },
};
mipi_dsi driver, match的设备对象名为“mipi_dsi”,对应的是dsi encoder block

  1. static struct platform_driver mipi_dsi_driver = {  
  2.      .probe = mipi_dsi_probe,  
  3.      .remove = mipi_dsi_remove,  
  4.      .shutdown = NULL,  
  5.      .driver = {  
  6.             .name = "mipi_dsi",  
  7.             },  
  8.  };  
static struct platform_driver mipi_dsi_driver = {
     .probe = mipi_dsi_probe,
     .remove = mipi_dsi_remove,
     .shutdown = NULL,
     .driver = {
            .name = "mipi_dsi",
            },
 };
具体的panel driver,这里我没有a790e上NT35510 panel的源代码,但和其他的都差不多

  1. static struct platform_driver this_driver = {  
  2.     .probe  = mipi_novatek_lcd_probe,  
  3.     .driver = {  
  4.         .name   = "mipi_novatek",  
  5.     },  
  6. };  
static struct platform_driver this_driver = {
    .probe  = mipi_novatek_lcd_probe,
    .driver = {
        .name   = "mipi_novatek",
    },
};
这些driver所管理的设备对象就组成一条display pipeline各自所需track的state。

在msm7627a的driver设计中,每个独立的head就有这么一条pipeline,从/sys中我们可以看到:


从device name后面的device id可以看到它们是属于一组的:524801=$(MIPI_VIDEO_PANEL) << 16) | 513);MIPI_VIDEO_PANEL= 8

如果从soc接出两条pipe分别驱动两个不同的panel显示不同的内容,那么还会创建出另一组设备对象来管理它们,可以当作以树形式在组织,虽然/sys/device/下面并不是这么组织的:

  1. +--+--- msm_fb.xxx  
  2. |  |  
  3. |  +--- mdp.xxx  
  4. |  |  
  5. |  +--- mipi_dsi.xxx  
  6. |  |  
  7. |  +--- mipi_first_panel.xxx  
  8. |        
  9. +--+--- msm_fb.yyy  
  10. |  |     
  11. |  +--- mdp.yyy  
  12. |  |     
  13. |  +--- mipi_dsi.yyy  
  14. |  |     
  15. |  +--- mipi_second_panel.yyy  
  16. |        
+--+--- msm_fb.xxx
|  |
|  +--- mdp.xxx
|  |
|  +--- mipi_dsi.xxx
|  |
|  +--- mipi_first_panel.xxx
|      
+--+--- msm_fb.yyy
|  |   
|  +--- mdp.yyy
|  |   
|  +--- mipi_dsi.yyy
|  |   
|  +--- mipi_second_panel.yyy
|      

display driver stack的各个block的device对象必须要有一定的probe顺序,比如在mipi_dsi设备被probe之前,mipi_panel设备就要先被创建和probe,因为在注册on/off调用链时上一级(upstream)的设备probe函数需要知道下一级(downstream)的设备对象。

PS C:\Users\elc> adb shell dmesg | grep -i "display\|drm\|color" [ 0.818869] rockchip-drm display-subsystem: Linked as a consumer to ff8f0000.vop [ 0.818897] rockchip-drm display-subsystem: Linked as a consumer to ff900000.vop [ 0.820222] rockchip-drm display-subsystem: Linked as a consumer to ff940000.hdmi [ 0.820696] rockchip-drm display-subsystem: Linked as a consumer to ff960000.dsi [ 0.821874] rockchip-drm display-subsystem: dmc is disabled [ 0.822222] rockchip-drm display-subsystem: bound ff8f0000.vop (ops vop_component_ops) [ 0.822476] rockchip-drm display-subsystem: bound ff900000.vop (ops vop_component_ops) [ 0.823722] rockchip-drm display-subsystem: bound ff940000.hdmi (ops dw_hdmi_rockchip_ops) [ 0.823766] dw-mipi-dsi ff960000.dsi: [drm:dw_mipi_dsi_bind] *ERROR* Failed to find panel or bridge: -517 [ 2.198622] rockchip-drm display-subsystem: dmc is disabled [ 2.198952] rockchip-drm display-subsystem: bound ff8f0000.vop (ops vop_component_ops) [ 2.199138] rockchip-drm display-subsystem: bound ff900000.vop (ops vop_component_ops) [ 2.203677] rockchip-drm display-subsystem: bound ff940000.hdmi (ops dw_hdmi_rockchip_ops) [ 2.203743] rockchip-drm display-subsystem: bound ff960000.dsi (ops dw_mipi_dsi_ops) [ 2.203755] [drm] Supports vblank timestamp caching Rev 2 (21.10.2013). [ 2.203764] [drm] No driver support for vblank timestamp query. [ 2.217161] rockchip-drm display-subsystem: fb0: frame buffer device [ 2.217765] [drm] Initialized rockchip 2.0.0 20140818 for display-subsystem on minor 0 [ 8.496089] Freeing drm_logo memory: 12148K
最新发布
11-12
### 硬件连接设计 在基于SSD2828和STM32的MIPI显示驱动系统中,要确保正确的硬件连接。STM32作为主控芯片,需要与SSD2828进行通信。MIPI接口通常包含时钟线、数据线等。将STM32的MIPI控制器引脚与SSD2828的对应MIPI接口引脚相连,同时要注意电源引脚、复位引脚等的连接,为系统提供稳定的电源和复位信号。例如,将STM32的MIPI时钟输出引脚连接到SSD2828的MIPI时钟输入引脚,保证时钟同步。 ### 驱动程序开发 #### 初始化SSD2828 在STM32的驱动程序中,首先要对SSD2828进行初始化。这包括发送一系列初始化命令到SSD2828,配置其显示参数,如分辨率、色彩模式等。可以通过STM32的SPI或者I2C接口与SSD2828进行通信来发送初始化命令。以下是一个简单的伪代码示例: ```c // 初始化SSD2828 void SSD2828_Init() { // 复位SSD2828 GPIO_ResetPin(SSD2828_RESET_PIN); delay_ms(10); GPIO_SetPin(SSD2828_RESET_PIN); delay_ms(100); // 发送初始化命令 SendCommand(SSD2828, 0x01); // 软件复位命令 delay_ms(10); SendCommand(SSD2828, 0x11); // 退出睡眠模式 delay_ms(120); // 配置显示参数的命令 SendCommand(SSD2828, 0x36); // 内存数据访问控制 SendData(SSD2828, 0x00); // 数据参数 // 其他配置命令... } ``` #### MIPI数据传输 STM32通过MIPI接口向SSD2828传输显示数据。要配置STM32的MIPI控制器,使其支持相应的MIPI协议版本和数据传输模式。例如,配置MIPI控制器的时钟频率、数据通道数量等。在传输数据时,将需要显示的图像数据按照MIPI协议的格式进行封装,然后通过MIPI接口发送到SSD2828。以下是一个简单的MIPI数据传输函数示例: ```c // MIPI数据传输函数 void MIPI_TransferData(uint8_t *data, uint32_t length) { // 配置MIPI控制器 MIPI_Config(); // 开始数据传输 for (uint32_t i = 0; i < length; i++) { MIPI_SendByte(data[i]); } } ``` ### 显示数据处理 #### 图像数据生成 在STM32中生成要显示的图像数据。可以通过多种方式生成图像数据,如读取存储在外部存储器中的图像文件,或者实时生成图形、文字等。例如,使用位图数据结构来表示图像,将图像数据存储在数组中。 ```c // 示例位图数据 const uint8_t image_data[] = { 0xFF, 0xFF, 0xFF, 0xFF, // 图像数据 // ... }; ``` #### 数据转换 将生成的图像数据转换为SSD2828支持的色彩格式。不同的显示设备可能支持不同的色彩格式,如RGB565、RGB888等。在STM32中进行数据转换,将图像数据从一种色彩格式转换为另一种色彩格式。 ### 系统测试与优化 #### 功能测试 在完成硬件连接和驱动程序开发后,对系统进行功能测试。检查显示是否正常,图像是否清晰、无闪烁等。可以通过显示一些简单的图形、文字来进行初步测试。 #### 性能优化 如果发现显示效果不佳或者数据传输速度慢等问题,需要对系统进行优化。例如,优化MIPI数据传输的带宽,减少数据传输的延迟;优化驱动程序的代码,提高代码的执行效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值