最近研究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的基础上,扩展了四种架构。
完全的command模式,mipi规范里DBI属于这种type。
由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上面。
彻底没有了panel-framebuffer,但还保留有control-interface。这样video-interface和control-interface仍然共用一条physical line,master设备仍然不需要借助其他多余的pin就可以控制panel。为了支持panel可以通过control-interface的命令来控制,panel内部所以还需要保留寄存器。
用牺牲更多的引脚数目(control lines)的代价来简化Panel内部的设计,不再需要内置寄存器。
MIPI规范定义的三种display接口协议,DBI,DPI,DSI。其中DBI可以实现上面4中type的controlinterface,DPI可以实现videointerface。而DSI最灵活,单纯的工作在commandmode可以实现type1,command+videomode共用时可以实现type2/3,单纯的工作在videomode时可以实现type4。对于type4,controlline可以是固定的gpio配置等。
物理层上面的DSI有三种工作模式,control-mode,escape-mode和highspeed-mode。
硬件reset后首先进入的lower-power stop state默认就是在control-mode,host当需要以burst的方式在总线上发送数据时(比如像素数据或者DCS),就要让总线进入high-speed mode。
DataLane上随即会出现SoT(startof transmision)和EoT信号,分别是进入和离开highspeed mode的标记,在一对SoT和EoT之间夹着一个或多个sPa(shortpacket)或者lPa(longpacket)。
escape-mode是一种特殊地允许DSI总线能在低速低功耗状态下发送数据的模式,在escape-mode下还可以主动切换到耗电更低的超低功耗模式。
因为DSI的控制命令的传输是双向的,双向传输的机制由host和panel之间通过bus-turnaround协议用作为交换使用总线所有权的“令牌”。
下图中BTA就用作panel向host响应读请求,使用escape-mode下的LPDT命令在低功耗总线下传输packet的情景
MIPIDCS规范了一系列电源状态,简单的说,有
1).Sleep on/off:除了和host的link-interface保持正常工作电源状态以外,其他模块都处于低功耗模式。(因为hostdisplay需要通过linkinterface来唤醒paneldevice,所以这个不能关)
2).Normal/Idlemode:panel使用正常的像素颜色位深或者使用有限位数的颜色位深,因为video模式的Panel的像素都是来自于host,而command-mode的panel像素来自自身的framebuffer,所以idle-mode只对command-mode的Panel有效
3).Paritial mode:只针对command-mode的panel有效,即只显示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环境中执行命令:
# echo 1 > /sys/devices/virtual/graphics/fb0/blank即可看到手机只剩背光了,这时display controller给panel的pixel/clock信号被切断或者被‘blank’:
不光pixel和clock被切断,可能整个信号链条上的block的power和clk也被关闭了以达到更省电的效果
再往fb0这个节点的blank属性写0时才重新打开panel
# 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信号驱动行列驱动器在玻璃上‘显示’图像,如图:
+-------------------------------------------------+ | +------------+ +-----------+ +------------+ | +-----------+ | | | | 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
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
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
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的源代码,但和其他的都差不多
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/下面并不是这么组织的:
+--+--- 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)的设备对象。