前言
参考> 高通平台移植mipi LCD屏幕lk过程,还要些其他的网络资料,结合自己的理解输出笔记
一、MIPI DSI
1、mipi介绍
MIPI (Mobile Industry Processor Interface) 是2003年由ARM, Nokia, ST ,TI等公司成立的一个联盟,目的是把手机内部的接口如摄像头、显示屏接口、射频/基带接口等标准化,从而减少手机设计的复杂程度和增加设计灵活性。
MIPI信号是成对传输的,主要是为了减少干扰,MIPI信号成对走线,两根线从波形看是成反相,所以有外部干扰过来,就会被抵消很大部分。
MIPI接口
a. 1对差分时钟(CLKP,CLKN)
b. 4对数据差分线(D0P,D0N;D1P,D1N;D2P,D2N;D3P,D3N),
每一对之间有GND线,4对数据差分线并不一定要全部使用,很多屏只需要2对就可以了
c. RESET(复位脚),STBYB(高电平有效)
d. VGL,VGH(像素点上开关管的开启关闭电压,加在开关管的栅极上,
VGH 高电平打开给像素点电容充电, VGL 负电压 关闭开关管),
VCOM( 液晶像素点的存储电容共用电极),
e. VLED-(背光负极),VLED+(背光正极),电源有1.8V和3.3V。
2、DSI
DSI(Display Serial Interface)协议是一个基于数据包传送的通信协议,主机端和显示模块之间传送的命令和数据基本上都以数据包格式进行。
DSI所定义的数据包有两种:短数据包(short packet) 和 长数据包(long packet)。
短数据包主要用于传输命令、读写寄存器。长数据包主要用于传输大量图象数据或部分控制命令。
长数据包长度为665541字节,包括4byte数据包头、065535byte有效数据、2byte数据包尾。短数据包长度为4byte,只有数据包头。发送数据的时候,主机的协议层将根据协议进行打包,并生成ECC和CRC添加到数据包里面。相反的,从机的协议层负责把接收到的数据包根据ECC和CRC对数据包进行纠错,并且把有用数据提取出来送到应用层。
mipi 接口下的两种模式:vedio mode 和 command mode
- 视频模式(Video Mode)
这种工作模式与传统RGB接口相似,主机需要持续刷新显示器。由于不使用专用的数据信号传输同步信息,控制信号和RGB数据是以报文的形式通过MIPI总线传输的。因为主机需要定期刷新显示器,显示器就不需要帧缓冲器。
- 命令模式(Command mode)
MIPI总线控制器使用显示命令报文来向显示器发送像素数据流。显示器应该有一个全帧长的帧缓冲器来存储所有的像素数据。一旦数据被放在显示器的帧缓冲器中,定时控制器就从帧缓冲器中取出数据,并自动把它们显示在屏幕上。MIPI总线控制器不需要定期刷新显示器。
video mode 主要针对没有驱动芯片内没有帧buffer(ram)的lcd进行操作的,主控要按照lcd的刷新率持续发送pixel数据。command mode主要针对驱动内含有帧buffer(ram)的cpu屏进行操作的,主控只在需要更改显示图像的时候发送pixel数据,其他时候驱动芯片自己从内部buffer里取数据显示,这种屏一般分辨率比较小。command mode 还用来配置驱动芯片的内部寄存器。在调试屏幕的时候具体使用什么模式根据LCD的Spec来配置
3、LCD中常用名字与其意义
panel图:
数据时序图
其中:行同步(HSYNC):行同步,就是告诉控制器知道开始新的一行像素。
场同步(HSYNC):场同步时告诉控制器下面要开始新的画面。
数据使能(DE):在数据使能区是有效的色彩数据,不在使能范围都显示黑色。
前廊(Front porch)/后廊(Back Porch):行同步或者场同步信号发出后,视频数据不能立即使能,要留出电子枪回归的时间(现在没有电子枪了,但仍保留了该模块,可以用于修改mipi时钟),以行为例,从HSYNC结束到DE开始的区间车就是后廊,从DE结束到HSYNC开始称为前廊,同样对于帧扫面也是一样的定义,
HTP = HSYNC + HDP +HFP + HBP
VTR = VSYNC +VDP + VFP + VBP
二、LK软件实现
lk 是高通平台通用的bootloader,类似以前使用的UBOOT。系统在启动前,前调用lk加载和初始化硬件设备,具体流程如下。
aboot_init() //lk
//do while循环遍历所有的LCM初始化,兼容多少LCD就循环多少次
--->target_display_init()
--->gcdb_display_init() //结构体panel赋值
/*根据名字匹配supp_panels,获取对应屏幕的panel id号,
函数在msm8952中*/
--->oem_panel_select()
/*将对应头文件中的值赋值到结构体,同时采用DSI模式
pan_type = PANEL_TYPE_DSI*/
--->init_panel_data(panelstruct, pinfo, phy_db)
/*------DSI信息配置------*/
--->update_dsi_display_config() //初始化 DSI 配置
/*拷贝 display.h// 中的 DSI 配置信息到实际寄存器中*/
--->target_dsi_phy_config(&dsi_video_mode_phy_db);
/*检测DSI 通道是否配置错误, 如果错了, 则将 dsi1 和 dsi0 进行交换*/
--->mdss_dsi_check_swap_status()
--->mdss_dsi_set_pll_src();/*DSI PLL锁相环配置*/
/*屏pinfo节构体初始化:主要初始化屏幕DSI相关的配置及屏的调用函数初始化*/
--->dsi_panel_init(&(panel.panel_info), &panelstruct)
/*----------end-----------*/
/*---------初始化LCM-------*/
--->msm_display_init() /*开始正式init 初始化LCM*/
/*mdss_dsi_panel_power()上电*/
--->pdata->power_func(1, &(panel->panel_info));
/*mdss_dsi_mipi_dfps_config()读取 splash 分区的内容(图片数据
信息), 以及初始化framebuffer的地址*/
--->pdata->dfps_func(&(panel->panel_info));
/*mdss_dsi_panel_clock()使能CLK*/
--->pdata->clk_func(1, &(panel->panel_info));
//为framebuffer分配内存
--->msm_fb_alloc();fbcon_setup()
/*正式显示图片,将图片写入framebuffer显示logo*/
--->display_image_on_screen()
/*mipi 初始化,初始化物理层 相关配置 (包括地址等)2.2.3.9 mipi 初
始化, 初始化物理层 相关配置 (包括地址等)*/
--->msm_display_config()
--->mdss_dsi_config() //配置控制器
//mdss_dsi_panel_pre_init()复位panel
--->panel->pre_init_func()
--->msm_display_on()//初始化panel,开始显示
--->mdp_dma_on() // 使能DSI cmd
//使用初始化命令来初始化panel包括识别屏幕(兼容有关)
//MIPI_CMD_PANEL
--->mdss_dsi_post_on();
--->mdss_dsi_panel_initialize()
/*读取屏幕signature,判断是否匹配,匹配成功执
行完剩下的代码,然后跳出do-while循环,若不成功,
则继续循环*/
--->mdss_dsi_read_panel_signature()
--->pdata->bl_func()
mdss_dsi_bl_enable()//调整背光
--->panel_backlight_ctrl()//控制背光
mdss_dsi_read_panel_signature函数,可用于兼容
在lk/platform/msm_shared/include/mipi_dsi.h定义了read_ddb_start_cmd。
//这里意思是读A1寄存器的包
static char read_id_a1h_cmd[4] = { 0xA1, 0x00, 0x06, 0xA0 };
static struct mipi_dsi_cmd read_ddb_start_cmd =
{sizeof(read_id_a1h_cmd), read_id_a1h_cmd};
/*panel_signature在头文件bootloder/lk/dev/gcdb/display/inclue中定义,
在init_panel_data中赋值*/
uint32_t mdss_dsi_read_panel_signature(uint32_t panel_signature)
{
uint32_t rec_buf[1];
uint32_t *lp = rec_buf, data;
int ret = response_value;
#if (DISPLAY_TYPE_MDSS == 1)
if (ret && ret != panel_signature)
goto exit_read_signature;
//这里把包发出去
ret = mipi_dsi_cmds_tx(&read_ddb_start_cmd, 1);
if (ret)
goto exit_read_signature;
if (!mdss_dsi_cmds_rx(&lp, 1, 1))
goto exit_read_signature;
//收到data
//ntohl()指的是ntohl函数,是将一个无符号长整形数从网络字节顺序转换为主机字节顺序,
//ntohl()返/回一个以主机字节顺序表达的数
data = ntohl(*lp);
data = data >> 8;
response_value = data;
if (response_value != panel_signature)
ret = response_value;
/*可在此通过读取的signature来判断屏幕类型*/
exit_read_signature:
/* Keep the non detectable panel at the end and set panel signature 0xFFFF */
if ((panel_signature == 0) || (panel_signature == 0xFFFF))
ret = 0;
#endif
return ret;
}
三、增加一个panel
- 在oem.panel中添加一个panel枚举
enum {
ILI9881D_720P_VIDEO_PANEL,
HX8394D_480P_VIDEO_PANEL,
HX8394D_720P_VIDEO_PANEL,
SHARP_QHD_VIDEO_PANEL,
TRULY_WVGA_CMD_PANEL,
HX8379A_FWVGA_SKUA_VIDEO_PANEL,
ILI9806E_FWVGA_VIDEO_PANEL,
HX8394D_QHD_VIDEO_PANEL,
HX8379C_FWVGA_VIDEO_PANEL,
FL10802_FWVGA_VIDEO_PANEL,
AUO_QVGA_CMD_PANEL,
AUO_CX_QVGA_CMD_PANEL,
HX8394F_720P_VIDEO_PANEL,
ILI9881C_720P_VIDEO_PANEL,
GC9306_QVGA_SPI_CMD_PANEL,
XXX_VIDEO_PANEL, // 举个例子
UNKNOWN_PANEL
};
- 增加相应的的头文件在
#include "include/xxx_video.h"
找供应商要,或者自己生成,代码路径device/qcom/common/display/tools
在这个路径下存放谷歌默认xml文件以及parser.pl脚本.
新建一个.xml文件,命名格式上模仿默认即可(注意大小写).
xml文件根据拿到的屏幕用户手册文件进行修改,文件中主要都是一些寄存器,
以及一些proc值,对应填好即可.最重要是时序上的问题,这里需要用到高通的开发者文档,需要上高通官网进行下载使用.
.xml准备好以后使用脚本命令perl parser.pl .xml panel生成.h 和. dtsi文件。
用生成的.h文件添加到bootable/bootloader/lk/dev/gcdb/display/include下面
- 在
supp_panels
中添加绑定,在init_panel_data中添加属性,属性为头文件中定义
// 如果要增加一个panel就需要在这里增加一个supp_panels
static struct panel_list supp_panels[] = {
{"truly_1080p_video", TRULY_1080P_VIDEO_PANEL},
{"truly_1080p_cmd", TRULY_1080P_CMD_PANEL},
{"r69006_1080p_video", R69006_1080P_VIDEO_PANEL},
{"r69006_1080p_cmd", R69006_1080P_CMD_PANEL},
{"truly_wuxga_video", TRULY_WUXGA_VIDEO_PANEL},
{"nt35523_720p_video", NT35523_720P_VIDEO_PANEL},
{"xxx_video", XXX_VIDEO_PANEL}, //新增绑定
};
static int init_panel_data(struct panel_struct *panelstruct,
struct msm_panel_info *pinfo,
struct mdss_dsi_phy_ctrl *phy_db)
{
int pan_type = PANEL_TYPE_DSI;
switch (panel_id) {
case GC9306_QVGA_SPI_CMD_PANEL:
panelstruct->paneldata = &gc9306_qvga_cmd_panel_data;
panelstruct->panelres = &gc9306_qvga_cmd_panel_res;
panelstruct->color = &gc9306_qvga_cmd_color;
panelstruct->panelresetseq
= &gc9306_qvga_cmd_reset_seq;
panelstruct->backlightinfo = &gc9306_qvga_cmd_backlight;
pinfo->spi.panel_cmds
= gc9306_qvga_cmd_on_command;
pinfo->spi.num_of_panel_cmds
= GC9306_QVGA_CMD_ON_COMMAND;
pan_type = PANEL_TYPE_SPI;
break;
// 添加 属性
case XXX_VIDEO_PANEL:
// 照猫画虎添加对应的属性。
case UNKNOWN_PANEL:
default:
/*.......*/
break;
}
return pan_type;
}
-
panel_id
在网上一些参考教程中,在oem_panel_select()函数中有两种实现方式,一种是根据panel_name去调用panel_override_id 函数匹配supp_panels, 然后获取到id,一种是通过平台不同,去赋值id,代码如下int oem_panel_select(const char *panel_name, struct panel_struct *panelstruct, struct msm_panel_info *pinfo, struct mdss_dsi_phy_ctrl *phy_db) { if (panel_name) { panel_override_id = panel_name_to_id(supp_panels, ARRAY_SIZE(supp_panels), panel_name); if (panel_override_id < 0) { dprintf(CRITICAL, "oem_panel_select hw_id = %d\n",hw_id); dprintf(CRITICAL, "Not able to search the panel:%s\n", panel_name); } else if (panel_override_id < UNKNOWN_PANEL) { /* panel override using fastboot oem command */ panel_id = panel_override_id; dprintf(INFO, "OEM panel override:%s\n", panel_name); goto panel_init; } } //... switch (hw_id) { case xx: panel_id = XXX_VIDEO_PANEL; break; default: dprintf(CRITICAL, "Display not enabled for %d HW type\n", hw_id); return PANEL_TYPE_UNKNOWN; } //... }
四、驱动框架部分
1、初始化
路径为:kernel/drivers/video/core/fbmem.c
主要任务为:
1、创建graphics类(/sys/class/graphics/fb*)
2、注册FB的字符设备驱动
3、提供 register_framebuffer(内部调用device_create创建某个类的设备)接口给具体framebuffer驱动编写着来注册fb设备的
初始化函数为:
fbmem_init(void)
{
proc_create("fb", 0, NULL, &fb_proc_fops);
if (register_chrdev(FB_MAJOR,"fb",&fb_fops))
printk("unable to get major %d for fb devs\n", FB_MAJOR);
fb_class = class_create(THIS_MODULE, "graphics");
if (IS_ERR(fb_class)) {
printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));
fb_class = NULL;
}
return 0;
}
fbmem_exit(void)
{
remove_proc_entry("fb", NULL);
class_destroy(fb_class);
unregister_chrdev(FB_MAJOR, "fb");
}
2、fb_fops结构体
所有的framebuffer设备共用一个主设备号,用不同的次设备号进行区分,类似于misc类设备。
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
};
五、kernel驱动部分
kernel/drvier/video/fbdev
软件驱动分为三个部分 MDP驱动、DSI控制器驱动、FrameBuffer驱动。
MDP probe → DSI probe → FB probe
1、MDP probe
kernel/drvier/video/fbdev/msm/mdp3.c
主要是对使用的硬件资源进行初始化,同时注册在 fb 设备中使用的mdp 接口,
/*将局部变量赋值给全局变量,便于fb设备注册时初始化mdp*/
rc = mdss_fb_register_mdp_instance(&mdp5);
struct msm_mdp_interface mdp5 = {
.init_fnc = mdss_mdp_overlay_init,
.fb_mem_get_iommu_domain = mdss_fb_mem_get_iommu_domain,
.fb_stride = mdss_mdp_fb_stride,
.check_dsi_status = mdss_check_dsi_ctrl_status,
.get_format_params = mdss_mdp_get_format_params,
};
/*msm_mdp_interface的init_fnc中,为mdp5_interface添加了更多的操作,
包括上下电操作,ioctl控制函数,mdp屏幕信息获取等。*/
mdp5_interface注册的这些函数,会在fb 初始化的时候调用.
2、DSI probe
在lk层结束后,会将panel的型号放在cmdline里,在mdss_dsi_config_panel函数中,解析模组厂提供的panel 的dtsi 文件(在上节增加一个panel中,使用使用脚本生成的一个.h文件和.dtsi文件,配置 xx-mdss-panels.dtsi,)从那个文件中能获取到panel 的mode ,分辨率,刷新率,还有 Driver IC 的初始化 command;
这些command 对于 video mode 和 command mode 是不一样的,在command mode 的时候还需要知道TE 相关的信息。
DSI驱动在mdss_dsi.c中,其中注册了两个驱动,第一个是
static struct platform_driver mdss_dsi_driver = {
.probe = mdss_dsi_probe,
.remove = mdss_dsi_remove,
.shutdown = NULL,
.driver = {
.name = "mdss_dsi",
.of_match_table = mdss_dsi_dt_match,
},
};
static int mdss_dsi_register_driver(void)
{
return platform_driver_register(&mdss_dsi_driver);
}
static int mdss_dsi_probe(struct platform_device *pdev)
{
struct mdss_panel_cfg *pan_cfg = NULL;
struct mdss_util_intf *util;
/* 获得全局变量mdp_instance,在mdp probe时注册 */
util = mdss_get_util_intf();
/* 将dsi资源信息保存到全局变量mdss_dsi_res,初始化dsi时钟 */
rc = mdss_dsi_res_init(pdev);
/* 创建qcom,mdss-dsi-ctrl设备,这里说的第二个驱动就是该设备对应的驱动 */
of_platform_populate(pdev->dev.of_node, mdss_dsi_ctrl_dt_match,
NULL, &pdev->dev);
return rc;
}
在dis_driver中,主要获取了dsi时钟等信息,初始化了mdss_dsi_res.然后创建了dsi_ctrl的平台设备,让其可以和之后注册的dsi_ctrl驱动相匹配
另外一个驱动就是dsi_ctrl驱动了,主要是用于对硬件的控制等。
static struct platform_driver mdss_dsi_ctrl_driver = {
.probe = mdss_dsi_ctrl_probe,
.remove = mdss_dsi_ctrl_remove,
.shutdown = NULL,
.driver = {
.name = "mdss_dsi_ctrl",
.of_match_table = mdss_dsi_ctrl_dt_match,
},
};
static int mdss_dsi_ctrl_register_driver(void)
{
return platform_driver_register(&mdss_dsi_ctrl_driver);
}
@mdss_dsi.c
static int mdss_dsi_ctrl_probe(struct platform_device *pdev)
{
int rc = 0;
struct mdss_dsi_ctrl_pdata *ctrl_pdata = NULL;
const char *ctrl_name;
ctrl_name = of_get_property(pdev->dev.of_node, "label", NULL);
/* panel配置 根据cmdline的参数解析对应的panel设备树*/
dsi_pan_node = mdss_dsi_config_panel(pdev, index);
=================================================================
-->mdss_dsi_get_panel_cfg()//解析cmdline
mdss_dsi_find_panel_of_node()//找到dtsi节点,
/*解析设备树节点,并配置panel操作集*/
mdss_dsi_panel_init(dsi_pan_node, ctrl_pdata, ndx);
int mdss_dsi_panel_init(struct device_node *node,
struct mdss_dsi_ctrl_pdata *ctrl_pdata,
int ndx)
{
int rc = 0;
static const char *panel_name;
struct mdss_panel_info *pinfo;
pinfo = &ctrl_pdata->panel_data.panel_info;
pinfo->panel_name[0] = '\0';
/* 设置panel名字 */
panel_name = of_get_property(node, "qcom,mdss-dsi-panel-name", NULL);
/* 从dsi节点读取panel信息,并保存到结果在ctrl_pdata->panel_data.panel_info中 */
rc = mdss_panel_parse_dt(node, ctrl_pdata);
pinfo->dynamic_switch_pending = false;
pinfo->is_lpm_mode = false;
pinfo->esd_rdy = false;
// 屏上电操作
ctrl_pdata->on = mdss_dsi_panel_on;
ctrl_pdata->post_panel_on = mdss_dsi_post_panel_on;
// 屏下电操作
ctrl_pdata->off = mdss_dsi_panel_off;
ctrl_pdata->low_power_config = mdss_dsi_panel_low_power_config;
// 背光设置
ctrl_pdata->panel_data.set_backlight = mdss_dsi_panel_bl_ctrl;
// 切换模式
ctrl_pdata->switch_mode = mdss_dsi_panel_switch_mode;
ctrl_pdata->panel_data.set_idle = mdss_dsi_panel_set_idle_mode;
ctrl_pdata->panel_data.get_idle = mdss_dsi_panel_get_idle_mode;
return 0;
}
初始化了mdss_dsi_ctrl_pdata结构体中的操作函数指针,这些函数指针将用在fb中的熄灭屏,
设置屏幕亮度等操作
==================================================================
rc = dsi_panel_device_register(pdev, dsi_pan_node, ctrl_pdata);
=====================================================================
@mdss_dsi.c
int dsi_panel_device_register(struct platform_device *ctrl_pdev,
struct device_node *pan_node, struct mdss_dsi_ctrl_pdata *ctrl_pdata)
{
struct mdss_panel_info *pinfo = &(ctrl_pdata->panel_data.panel_info);
mipi = &(pinfo->mipi);
/* mipi模式 */
pinfo->type =
((mipi->mode == DSI_VIDEO_MODE)
? MIPI_VIDEO_PANEL : MIPI_CMD_PANEL);
/* 后面会有event发送给dsi,会在mdss_dsi_event_handler中处理 */
ctrl_pdata->panel_data.event_handler = mdss_dsi_event_handler;
ctrl_pdata->panel_data.get_fb_node = mdss_dsi_get_fb_node_cb;
mdss_dsi_ctrl_init(&ctrl_pdev->dev, ctrl_pdata);
rc = mdss_register_panel(ctrl_pdev, &(ctrl_pdata->panel_data));
}
=====================================================================
return 0;
}
3、FB probe
我使用的是高通平台,FB驱动在/video/fbdev/msm/mdss_fb.c中完成,实现Linux Framebuffer的注册已经相关操作。
static int mdss_fb_probe(struct platform_device *pdev)
{
struct msm_fb_data_type *mfd = NULL;
struct mdss_panel_data *pdata;
struct fb_info *fbi;
int rc;
/* 获取plat_data,含有在mdss_dsi中解析出的屏信息 */
pdata = dev_get_platdata(&pdev->dev);
/* 分配fbi结构体,用来注册fb */
fbi = framebuffer_alloc(sizeof(struct msm_fb_data_type), NULL);
/* fbi->par执行mfd,保存私有数据 mfd保存了屏的所以信息 */
mfd = (struct msm_fb_data_type *)fbi->par;
mfd->key = MFD_KEY;
mfd->fbi = fbi;
mfd->panel_info = &pdata->panel_info;
mfd->panel.type = pdata->panel_info.type;
mfd->fb_imgType = MDP_RGBA_8888;
...
platform_set_drvdata(pdev, mfd);
/*完成fb的注册,从mfd中取出屏信息放到fbi中,进行fb注册,并包含fb给用户空间的的操作函数*/
rc = mdss_fb_register(mfd);
/* 初始化mdp */
if (mfd->mdp.init_fnc) {
rc = mfd->mdp.init_fnc(mfd);
if (rc) {
pr_err("init_fnc failed\n");
return rc;
}
}
/* 注册背光灯设备 */
if (!lcd_backlight_registered) {
backlight_led.brightness = mfd->panel_info->brightness_max;
backlight_led.max_brightness = mfd->panel_info->brightness_max;
if (led_classdev_register(&pdev->dev, &backlight_led))
pr_err("led_classdev_register failed\n");
else
lcd_backlight_registered = 1;
}
mdss_fb_create_sysfs(mfd);
/* 发送注册事件,处理的hanlder在mdss_dsi中定义 */
mdss_fb_send_panel_event(mfd, MDSS_EVENT_FB_REGISTERED, fbi);
}
六、调试
1、休眠唤醒流程
灭屏流程:
fb_ioctl()------->framebuff节点对应的函数操作,位置:fbdev/core/fbmem.c ,创建一个FB节点给上层去操作;
info = file_fb_info(file);---->获取mdss_fb_probe()里面注册的一些函数;
do_fb_ioctl();
fb_blank(struct fb_info *info, int blank);---->参数blank就是下面函数中的blank_mode;这个是亮屏的起始函数;
fb_notifier_call_chain(FB_EARLY_EVENT_BLANK, &event);---->TP通知链,通知TP;
mdss_fb_blank(int blank_mode, struct fb_info *info);------>启动事件子系统;---->fb_blank = mdss_fb_blank();在fb_probe中注册的;
mdss_fb_blank_sub(int blank_mode, struct fb_info *info,int op_enable);----->switch函数中判断的根据blank_mode;m
mdss_fb_blank_blank();
mdss_panel_is_power_off(req_power_state);----->有一个电源状态检测;
mdss_fb_stop_disp_thread(mfd);--->关闭那个dispaly处理线程;
mdss_fb_set_backlight(mfd, 0);---->设置背光亮度为0;关闭背光;
mdss_mdp_overlay_off(mfd);---->mdp5_interface->off_fnc = mdss_mdp_overlay_off;
mdss_mdp_ctl_stop(mdp5_data->ctl, mfd->panel_power_state);--->灭屏
mdss_mdp_cmd_stop(struct mdss_mdp_ctl *ctl, int panel_power_state);-------->ctl->ops.stop_fnc = mdss_mdp_cmd_stop;
mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_BLANK,(void *) (long int) panel_power_state,CTL_INTF_EVENT_FLAG_DEFAULT); -->发送事件
mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_PANEL_OFF,(void *) (long int) panel_power_state,CTL_INTF_EVENT_FLAG_DEFAULT);
/* mdss_mdp_cmd_intfs_stop();
mdss_mdp_cmd_ctx_stop();
mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_REGISTER_RECOVERY_HANDLER, NULL,CTL_INTF_EVENT_FLAG_DEFAULT); // ???
mdss_mdp_ctl_intf_event(ctl, MDSS_EVENT_REGISTER_MDP_CALLBACK, NULL,CTL_INTF_EVENT_FLAG_DEFAULT);*/
mdss_dsi_event_handler
mdss_dsi_blank();----->灭屏事件处理函数
ctrl_pdata->off(pdata);---->灭屏指令的发送;
mdss_dsi_panel_off(struct mdss_panel_data *pdata);
mdss_dsi_panel_cmds_send(ctrl, &ctrl->off_cmds, CMD_REQ_COMMIT);
mdss_dsi_off(pdata, power_state);---->下电操作;
mdss_dsi_panel_power_ctrl(pdata, power_state);----下电;代码片
2、亮度调节
//一些描述leds参数的信息结构体和枚举
enum led_brightness {
LED_OFF = 0,
LED_HALF = 127,
LED_FULL = 255,
};//背光的强度
struct led_classdev {
const char *name;
enum led_brightness brightness;
enum led_brightness max_brightness;
...
}
W:\work\TP_A306_0410\kernel\msm-3.18\drivers\video\msm\mdss\mdss_fb.c
|--static int mdss_fb_probe(struct platform_device *pdev)
|--led_classdev_register(&pdev->dev, &backlight_led);
//创建亮度调节节点,高通平台 sys/class/leds/led-backlight/brighness
|--device_create_with_groups(leds_class, parent, 0,
led_cdev, led_cdev->groups, "%s", name);
/*读取节点调用brightness_store函数中*/
-->led_set_brightness(led_cdev, value);
-->led_cdev->set_brightness_work
--->led_set_brightness_nosleep(led_cdev, brightness);
--->led_set_brightness_nopm()
--->if (!__led_set_brightness(led_cdev, value))
--->led_cdev->brightness_set -------
|
|
|
|--.brightness_set = mdss_fb_set_bl_brightness, |
|--mdss_fb_set_backlight(mfd, bl_lvl); |
/*调用set_backlight,设置背光*/ |
|--pdata->set_backlight; ↓
ctrl_pdata->panel_data.set_backlight = mdss_dsi_panel_bl_ctrl;
//mdss_dsi_panel.c
|--mdss_dsi_panel_bl_ctrl(struct mdss_panel_data *pdata,u32 bl_level)
|--mdss_dsi_panel_bklt_dcs() --> 发送cmd到屏幕51寄存器
//控制PMI的WLED引脚控制背光
|--led_trigger_event(bl_led_trigger, bl_level);
3、ESD(TE信号)
ESD的参数信息在设备树中,当采用读寄存器的方式进行ESD判断时
/*初始化流程*/
int mdss_dsi_panel_init(struct device_node *node,struct mdss_dsi_ctrl_pdata
*ctrl_pdata,int ndx)
|--static int mdss_panel_parse_dt(struct device_node *np,struct
mdss_dsi_ctrl_pdata *ctrl_pdata)
|--rc = mdss_dsi_parse_panel_features(np, ctrl_pdata);
|--mdss_dsi_parse_esd_params(np, ctrl);
|--static void mdss_dsi_parse_esd_params(struct device_node *np,struct
mdss_dsi_ctrl_pdata *ctrl)
|--rc = of_property_read_string(np,"qcom,mdss-dsi-panel-status-check-mode", &string);/*读取dts中的节点名字匹配到"reg_read"时执行读寄存器
ESD检查,当使用TE 时,使用te_signal_check*/
|--ctrl->check_read_status =mdss_dsi_gen_read_status;
|--static int mdss_dsi_gen_read_status(struct mdss_dsi_ctrl_pdata *ctrl_pdata)
|--static bool mdss_dsi_cmp_panel_reg_v2(struct mdss_dsi_ctrl_pdata *ctrl)
/*调用流程*/
static int mdss_mdp_register_driver(void);
|--.probe = mdss_mdp_probe,>>static int mdss_mdp_probe(struct platform_device *pdev)
|--rc = mdss_fb_register_mdp_instance(&mdp5);
|--.check_dsi_status = mdss_check_dsi_ctrl_status,
|--ret = ctrl_pdata->check_status(ctrl_pdata);>>mdss_dsi_reg_status_check;
|--if (ret > 0) {
printk("lcd esd check success %s %d\n", __func__, __LINE__);
schedule_delayed_work(&pstatus_data->check_status,
msecs_to_jiffies(interval));
}
在mdss_check_dsi_ctrl_status()函数中,
mdss_check_dsi_ctrl_status ()
{
...
if (ctrl_pdata->status_mode == ESD_TE) {
uint32_t fps = mdss_panel_get_framerate(&pdata->panel_info,
FPS_RESOLUTION_HZ);
uint32_t timeout = ((1000 / fps) + 1) *
MDSS_STATUS_TE_WAIT_MAX;
if (mdss_check_te_status(ctrl_pdata, pstatus_data, timeout))
goto sim;//正常
else
goto status_dead;//ESD复位出发,
}
...
}
/*
* This function is called when the TE signal from the panel doesn't arrive
* after 'interval' milliseconds. If the TE IRQ is not ready, the workqueue
* gets re-scheduled. Otherwise, report the panel to be dead due to ESD attack.
*/
static bool mdss_check_te_status(struct mdss_dsi_ctrl_pdata *ctrl_pdata,
struct dsi_status_data *pstatus_data, uint32_t interval)
{
bool ret;
atomic_set(&ctrl_pdata->te_irq_ready, 0);
reinit_completion(&ctrl_pdata->te_irq_comp);
enable_irq(gpio_to_irq(ctrl_pdata->disp_te_gpio));
/* Define TE interrupt timeout value as 3x(1/fps) */
ret = wait_for_completion_timeout(&ctrl_pdata->te_irq_comp,
msecs_to_jiffies(interval));
disable_irq(gpio_to_irq(ctrl_pdata->disp_te_gpio));
pr_debug("%s: Panel TE check done with ret = %d\n", __func__, ret);
return ret;
}
4、节点回读DDIC寄存器值
mipi寄存器设置节点回读:在mdss_fb.c文件中设置节点,节点路径为sys/class/graphics/fb0/panel_brightness节点。如果节点不在默认页,需要写入FE对应的页数,写完后再切换回默认FE 00。节点写入也是一样的方法。
static ssize_t mdss_fb_get_panel_brightness(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct fb_info *fbi = dev_get_drvdata(dev);
struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)fbi->par;
struct mdss_panel_data *pdata;
struct mdss_dsi_ctrl_pdata *ctrl_pdata = NULL;
char rx_buf[2]={0,0};
char A_buf[2]={0,0};
char page_off[2] = {0xFE, 0x00};
struct dsi_cmd_desc page_cmd = {{0x29, 1, 0, 0, 0, 2}, page_off};
char page1_off[2] = {0xFE, 0x01};
struct dsi_cmd_desc page1_cmd = {{0x29, 1, 0, 0, 0, 2}, page1_off};
// struct dsi_panel_cmds *cmd0 = ;
// struct dsi_panel_cmds *cmd1 = ;
struct dcs_cmd_req cmdreq;
memset(&cmdreq, 0, sizeof(cmdreq));
cmdreq.cmds = &page1_cmd;
cmdreq.cmds_cnt = 1;
cmdreq.flags = CMD_REQ_COMMIT;
cmdreq.rlen = 0;
cmdreq.rbuf = NULL;
cmdreq.cb = NULL; /* call back */
// int ret;
pdata = dev_get_platdata(&mfd->pdev->dev);
if (!pdata) {
pr_err("no panel connected!\n");
return -EINVAL;
}
ctrl_pdata = container_of(pdata, struct mdss_dsi_ctrl_pdata,
panel_data);
mdss_dsi_cmdlist_put(ctrl_pdata, &cmdreq);
mdss_dsi_panel_cmd_read(ctrl_pdata, 0x36, 0x00,
NULL, rx_buf, 2);
cmdreq.cmds = &page_cmd;
mdss_dsi_cmdlist_put(ctrl_pdata, &cmdreq);
mdss_dsi_panel_cmd_read(ctrl_pdata, 0x0A, 0x00,
NULL, A_buf, 2);
// ret = sprintf(buf, "%d\n", rx_buf[0]);
return sprintf(buf, "36 = %x,0A = %x \n", rx_buf[0],A_buf[0]);
}
5、屏幕干扰天线问题
1、将屏幕拔掉,查看是否存在干扰。
2、将TP的reset引脚拉低,查看干扰是否存在。
3、修改命令,做三个实验,① 下11和29,对比显示白色和黑色,干扰是否存在差异。②、下11,不下29,屏幕应该处于工作状态,但是不显示,③、11和29都不下,屏幕处于SLEEP状态。通过上诉三个实验,如果屏是干扰源,则可以确定干扰的具体位置。
6、在读取节点中打印值
通过如下方式,可以show节点是在终端打印多个数据。
{
int cnt = 0;
#define SPRINT(fmt, ...) \
(cnt += scnprintf(buf + cnt, len - cnt, fmt, ##__VA_ARGS__))
SPRINT("0x%x :%x\n", reg_drr[8], rx_buf[0]);
SPRINT("0x%x :%x\n", reg_drr[5], rx_buf[6]);
#undef SPRINT
return cnt;
}
7、高通TE信号检测时间异常
高通原代码如下,逻辑为5s开启一次te中断去检测te信号下降沿,持续50ms后关闭中断,如果进入了中断,则表示信号正常,如果50ms内没有进入中断,则表示信号异常,然后AP端进入LCM模块上下电复位操作,理想情况是在出现异常后,就能在第一次检测时检测到,从出现异常到恢复的时间小于5s。
实际情况:TE信号异常后,TE在第一次检测会无法检测到异常,而必须等待5s后,在第二次才能检测到,并且在这5s期间,只要信号出现一次下降沿,第二次就依旧无法检测到异常,从出现异常到恢复的时间小于10s,大于5s。
猜想:根据以前做MCU的经验,觉得可能是因为在中断disable_irq后,虽然屏蔽掉了中断,但是没有关闭硬件检测接口,因此依旧能检测到te信号的下降沿,然后使硬件中断标志位置1了。但是因为中断被屏蔽,无法进入中断函数,因此中断标志位无法清零,当中断一旦开启,就会立马进入中断,然后就会判断te信号正常。
验证:这个问题给高通提了case,并给出了我的猜想,但是他们并不是特别愿意解,并认为是我们代码的逻辑问题或者硬件问题。
我自己做了几个实验验证:
前提:将te信号引脚引出,通过按键然后接地,通过按下按键模拟te信号异常,终端打印出te信号检测的流程日志(自己在关键部位添加打印),屏幕常亮,且显示图片不会变化(即显示静态页面)。
①、按下按键,查看ap端检测到异常的时间,结果:第一次检测未能检测到异常,第二次才能检测到,且结果必现。
②、按下按键第一次未能检测到异常,在第一次检测和第二次检测间,松开按键,再按下按键。结果:第一次和第二次均为检测到异常,在第三次检测到异常,结果必现。
③、修改代码,在中断函数中判断每个周期是否为第一次进入中断,如果为第一次进入,则将退出中断,不再执行中断里的代码,(即跳过第一次中断,将中断标志位清零),然后再按下按键,发现在第一个检测te信号的周期就能检测到。
解决办法:
①:就是我目前采用的方法,在中断函数中判断是否为周期内第一次进入中断,如果第一次,则直接return。
②:enable_irq后延时20ms,让te信号触发先进入中断跑两遍,原理和方案1一致,都是通过进入中断后,自动清除第一次的硬件标志位。(这个方案是高通的解决办法,但不是高通告诉我们的,而是在代码中其他的te检测中的代码发现的,高通技术人员当时非常肯定的给说这部分代码没问题的,给其他的厂家提供的也是用的这个方法,唉,国产加油)
③:在enable_irq前将中断标志位清零,但是咨询高通,他们说没有这种功能的函数或者接口。
原因分析:最后还是没能完全确定是由于中断标志位的原因,只能说大概率是因为这个,目前的验证都是符合的,但由于刚入门linux,对很多机制不太了解,以后输了后再重新验证。
高通提供代码:
检测代码:
static bool mdss_check_te_status(struct mdss_dsi_ctrl_pdata *ctrl_pdata,
struct dsi_status_data *pstatus_data, uint32_t interval)
{
bool ret;
atomic_set(&ctrl_pdata->te_irq_ready, 0);
reinit_completion(&ctrl_pdata->te_irq_comp);
enable_irq(gpio_to_irq(ctrl_pdata->disp_te_gpio));
/* Define TE interrupt timeout value as 3x(1/fps) */
ret = wait_for_completion_timeout(&ctrl_pdata->te_irq_comp,
msecs_to_jiffies(interval));
disable_irq(gpio_to_irq(ctrl_pdata->disp_te_gpio));
pr_err("%s: Panel TE check done with ret = %d\n", __func__, ret);
return ret;
}
中断代码:
irqreturn_t hw_vsync_handler(int irq, void *data)
{
struct mdss_dsi_ctrl_pdata *ctrl_pdata =
(struct mdss_dsi_ctrl_pdata *)data;
if (!ctrl_pdata) {
pr_err("%s: DSI ctrl not available\n", __func__);
return IRQ_HANDLED;
}
if (pstatus_data)
mod_delayed_work(system_wq, &pstatus_data->check_status,
msecs_to_jiffies(interval));
else
pr_err("Pstatus data is NULL\n");
if (!atomic_read(&ctrl_pdata->te_irq_ready)) {
complete_all(&ctrl_pdata->te_irq_comp);
atomic_inc(&ctrl_pdata->te_irq_ready);
}
return IRQ_HANDLED;
}
修改后的代码:
检测代码:
static bool mdss_check_te_status(struct mdss_dsi_ctrl_pdata *ctrl_pdata,
struct dsi_status_data *pstatus_data, uint32_t interval)
{
bool ret;
/*filter the first flag of interrupt*/
atomic_set(&ctrl_pdata->te_irq_ready, 1);
reinit_completion(&ctrl_pdata->te_irq_comp);
enable_irq(gpio_to_irq(ctrl_pdata->disp_te_gpio));
/* Define TE interrupt timeout value as 3x(1/fps) */
ret = wait_for_completion_timeout(&ctrl_pdata->te_irq_comp,
msecs_to_jiffies(interval));
disable_irq(gpio_to_irq(ctrl_pdata->disp_te_gpio));
pr_err("%s: Panel TE check done with ret = %d\n", __func__, ret);
return ret;
}
中断代码:
irqreturn_t hw_vsync_handler(int irq, void *data)
{
struct mdss_dsi_ctrl_pdata *ctrl_pdata =
(struct mdss_dsi_ctrl_pdata *)data;
if (!ctrl_pdata) {
pr_err("%s: DSI ctrl not available\n", __func__);
return IRQ_HANDLED;
}
if (pstatus_data)
mod_delayed_work(system_wq, &pstatus_data->check_status,
msecs_to_jiffies(interval));
else
pr_err("Pstatus data is NULL\n");
if (!atomic_read(&ctrl_pdata->te_irq_ready)) {
pr_err("TE IRQ 0\n");
complete_all(&ctrl_pdata->te_irq_comp);
atomic_inc(&ctrl_pdata->te_irq_ready);
}
else{
pr_err("TE IRQ 1\n");
atomic_set(&ctrl_pdata->te_irq_ready, 0);
}
return IRQ_HANDLED;
}
end