Qualcomm msm8916 MIPI to RGB 调试


简介

屏的接口种类非常多,常见的包括 RGB、HDMI、VGA、LVDS、EDP、MIPI等接口。
其中,在 Android 移动设备上,大多采用的是MIPI接口。
某些时候,由于某种需求,需要将 Android 设备上的 MIPI 数据显示到其他接口的屏上,此时,则需要利用相关转换芯片将 MIPI 接口的数据转换成其他接口的数据。
在 msm8916 上有过这样的需求:将 MIPI 数据转换成 RGB 进行输出,当时采用的转换芯片是 ICN6211。以下记录之。

RGB 简介

R(红)、G(绿)、B(蓝)是自然界三原色,三原色按照不同比例进行相加,混合成不同的颜色。
RGB其取值范围分别为0~255,其值越大,则越亮,当RGB都取值为255时,则为白色。相反全为0时,则为黑色。
而RGB接口就是分三原色进行输入的图形视频接口,也叫作色差分量接口。

RGB 接口中包括三大类信号:数据信号、时钟信号和控制信号。

  • 数据信号,通过数据线传输,一般分为6 bit 或者 8 bit传输(R0R5/G0G5/B0~B5 或 R0R7/G0G7/B0~B7),也即是数据信号线分为18根或者24根

  • 时钟信号,特指像素时钟信号,传输数据和对数据信号进行读取的基准。

  • 控制信号,包括数据使能信号DE(或有效显示数据选通信号)、行同步信号HS、帧同步信号VS

ICN6211 简介

ICN6211 是一款 MIPI 转 RGB 的芯片,下面是它的简要特性:

  • 其最高支持 MIPI DSI 的4 Lane 输入;当4lane输入时,其支持的最大带宽为1GBps

  • 其能对 MIPI DSI的 RGB565-16bpp、RGB666-18bpp 以及 RGB888-24bpp 的数据包进行解码转换

  • 其能输出 18 bpp 或者 24 bpp 像素的 RGB 图形数据,像素时钟的范围在 5MHz ~ 154MHz

  • 其支持的最大分辨率为 FHD(1920x1080p) 以及 WUXGA(1920x1200p)

  • 支持 I2C 接口

在这里插入图片描述

icn6211 Functional Block Diagram

在这里插入图片描述

System Application Diagram

屏的显示分为两部分,一部分是开机显示厂商 logo,一部分是系统启动完毕后正常的显示。下面就这两部分的开发细节进行介绍。

添加 MIPI 转 RGB 支持

针对 MIPI 接口的屏,屏的配置参数通过 DATA0-、DATA0+ 这对数据线下发到屏端。
如果初始化正确,则切换到 HS 模式下,直接把数据从DATA0~DATA3刷出去即可。
那么针对 ICN6211-MIPI-RGB 的屏,又是怎么操作的呢?其实也非常简单,在给定 ICN6211 正确的电压后,将屏的配置参数通过 ICN6211 的I2C下发到屏端即可。
后续数据的传输则不再需要操心。

ICN6211 参数图

在这里插入图片描述

bootloader

mdss_dsi_panel_initialize() 函数中,会下发屏的配置参数。
它原本是调用 MIPI 接口函数 mipi_dsi_cmds_tx() 去下发的参数,在这里改成或者添加 I2C 的传输接口即可。

int mdss_dsi_panel_initialize(struct mipi_dsi_panel_config *pinfo, uint32_t
		broadcast)
{
	if (pinfo->i2c_cmds_table) {
		mipi_i2c_cmds_tx(pinfo->i2c_cmds_table);
	}
}


static struct qup_i2c_dev  *i2c_dev;
static int qrd_lcd_i2c_write(int addr, int reg, int val)
{
	int ret = 0;
	uint8_t data_buf[] = { reg, val };

	/* Create a i2c_msg buffer, that is used to put the controller into write
	   mode and then to write some data. */
	struct i2c_msg msg_buf[] = { {addr,
		I2C_M_WR, 2, data_buf}
	};

	ret = qup_i2c_xfer(i2c_dev, msg_buf, 1);
	if(ret < 0) {
		dprintf(CRITICAL, "qup_i2c_xfer error %d\n", ret);
		return ret;
	}
	return 0;
}

/*write i2c command*/

int mipi_i2c_cmds_tx(struct i2c_on_command_table *i2c_cmds_table)
{
	int i = 0;
	i2c_dev = qup_blsp_i2c_init(BLSP_ID_1, QUP_ID_1, 100000, 19200000);
	if(!i2c_dev) {
		dprintf(CRITICAL, "qup_blsp_i2c_init failed \n");
		ASSERT(0);
	}

	for(i = 0; i < i2c_cmds_table->cmds_cnt; i++)
	{
		qrd_lcd_i2c_write(i2c_cmds_table->slave_addr, i2c_cmds_table->cmds_list[i].reg, i2c_cmds_table->cmds_list[i].val);
	}
}

当然,pinfo->i2c_cmds_table 这个table,按照 qcom 的屏处理流程,它是在 msm8916/oem_panel.c 里面进行处理的,下面直接贴出处理代码片段:

case ICN6211_QHD_VIDEO_PANEL:
	panelstruct->paneldata    = &icn6211_qhd_video_panel_data;
	panelstruct->panelres     = &icn6211_qhd_video_panel_res;
	panelstruct->color        = &icn6211_qhd_video_color;
	panelstruct->videopanel   = &icn6211_qhd_video_video_panel;
	panelstruct->commandpanel = &icn6211_qhd_video_command_panel;
	panelstruct->state        = &icn6211_qhd_video_state;
	panelstruct->laneconfig   = &icn6211_qhd_video_lane_config;
	panelstruct->paneltiminginfo
			= &icn6211_qhd_video_timing_info;
	panelstruct->panelresetseq
			= &hx8389b_qhd_video_reset_seq;
	pinfo->mipi.i2c_cmds_table = &icn6211_i2c_on_command_table; 
	pinfo->mipi.i2c_cmds_table->cmds_cnt = ICN6211_QHD_VIDEO_ON_COMMAND;
	pinfo->mipi.i2c_cmds_table->slave_addr = ICN6211_I2C_SLAVE_ADDR;
	pinfo->mipi.i2c_cmds_table->blsp_id = I2C_BLSP_ID;
	pinfo->mipi.i2c_cmds_table->qup_id = I2C_QUP_ID;
	panelstruct->backlightinfo = &icn6211_qhd_video_backlight;
	memcpy(phy_db->timing,
			icn6211_qhd_video_timings, TIMING_SIZE);
	break;

kernel

在 msm8916 的 kernel 部分,对一个屏驱动的处理分为三步:

  • 解析该屏的设备树文件(DTSI)

  • 注册屏初始化命令传输接口

  • 下发初始化命令,使能 Panel

下面就一步一步的来看怎么添加的:

  1. 解析 I2C 命令

     static int mdss_panel_parse_dt(struct device_node *np,
     		struct mdss_dsi_ctrl_pdata *ctrl_pdata)
     {
     	//...
     	rc = of_property_read_string(np,
     			"qcom,mdss-command-access", &data);
     		
     		if (!rc && !strcmp(data, "i2c")) {  /*the property is self defined, it may not exist in some dts configure*/                                          
     			ctrl_pdata->cmd_access = CMD_ACCESS_I2C;
     				
     				ctrl_pdata->i2c_handle = mdss_get_icn6211_i2c_client();
     				if (!ctrl_pdata->i2c_handle)
     				{
     					pr_err("ctrl_pdata->i2c_handle is null\n");
     						//goto error;   /*modified by fangchengbing  return error will lead to crash*/
     				}
     			
     				mdss_i2c_parse_dcs_cmds(np, &ctrl_pdata->i2c_on_cmds, "qcom,mdss-i2c-on-command");
     				
     		} else {  /*normal flow*/
     			//....
     		}
     
     }
    
    
     /**
      * @brief parse i2c command in dtsi file, format [%d %d] ==> [reg, val]
      * store them in struct i2c_cmd_list
      *
      * @param np
      * @param pcmds where i2c will store
      * @param cmd_key flag contain i2c command
      *
      * @return 
      */
     static int mdss_i2c_parse_dcs_cmds(struct device_node *np,
     		struct i2c_cmd_list *pcmds, char *cmd_key)
     {
     	const char *data;
     	int blen = 0;
     	char *buf;
     	int i = 0, j = 0;
     
     	data = of_get_property(np, cmd_key, &blen);
     	if (!data) {
     		pr_err("%s: failed, key=%s\n", __func__, cmd_key);
     		return -ENOMEM;
     	}
     
     	if (blen < 0 && blen / 2 == 1)
     		pr_err("%s format err\n", cmd_key);
     
     	pr_debug("mdss_i2c_parse_dcs_cmds blen = %d\n", blen);
     	buf = kzalloc(sizeof(char) * blen, GFP_KERNEL);
     	if (!buf)
     		return -ENOMEM;
     
     	memcpy(buf, data, blen);
     	pcmds->cmds = kzalloc(sizeof(struct i2c_ctrl_hdr) * blen / 2, GFP_KERNEL);
     	pcmds->cmds_cnt = blen / 2;
     	for (i = 0; i < blen; i+=2)
     	{
     		j = i / 2;
     		pcmds->cmds[j].reg = buf[i];
     		pcmds->cmds[j].val = buf[i+1];
     	}
     
     	return 0;
     }
    
  2. 控制器初始化时,注册屏初始化命令表:

     void mdss_dsi_ctrl_init(struct mdss_dsi_ctrl_pdata *ctrl)
     {
     	//...
     	if (ctrl->cmd_access == CMD_ACCESS_DSI)
     		ctrl->cmdlist_commit = mdss_dsi_cmdlist_commit;
     	else if (ctrl->cmd_access == CMD_ACCESS_I2C)
     		ctrl->cmdlist_commit = mdss_i2c_cmdlist_commit;
     	//...
     }
    
     /**
      * @brief  write i2c command to mipi@rgb chip
      *
      * @param ctrl include i2c_client
      * @param from_mdp not important
      *
      * @return 
      */
     int mdss_i2c_cmdlist_commit(struct mdss_dsi_ctrl_pdata *ctrl, int from_mdp)
     {
     	int rc = 0;
     	int i = 0;
     	struct i2c_cmd_list on_cmds;
     	on_cmds = ctrl->i2c_on_cmds;
     	mutex_lock(&ctrl->cmd_mutex);
     	if (ctrl->i2c_handle) {
     		for (i = 0; i < on_cmds.cmds_cnt; i++)
     		{
     			rc = i2c_smbus_write_byte_data(ctrl->i2c_handle, on_cmds.cmds[i].reg, on_cmds.cmds[i].val);
     			if (rc < 0) {
     				mutex_unlock(&ctrl->cmd_mutex);
     				return rc;
     			}
     		}
     	}
     
     	mutex_unlock(&ctrl->cmd_mutex);
     	return rc;
     }
    
  3. 下发屏参数命令

     static int mdss_dsi_panel_on(struct mdss_panel_data *pdata)
     {
     	//...
     	if (ctrl->cmd_access == CMD_ACCESS_DSI) {
     		if (ctrl->on_cmds.cmd_cnt)
     			mdss_dsi_panel_cmds_send(ctrl, &ctrl->on_cmds);
     	} else if (ctrl->cmd_access == CMD_ACCESS_I2C) {
     		if (ctrl->i2c_on_cmds.cmds_cnt)
     			ctrl->cmdlist_commit(ctrl, 0);
     	}
     	//...
     }
    

注意!

最重要的有一点不要忘记,平台需要利用 I2C 总线去下发屏初始化参数,根据 I2C 协议,需要给定 I2C 设备在总线上的地址:

在 bootloader 里直接配置上 0x2C 即可,在 kernel 里面,dtsi 中配置如下:

i2c@78b6000 {
      icn6211_mipi_rgb@2C {
		        compatible = "qcom,icn6211_mipi_rgb";
				      reg = <0x2C>;
					      };

	    };

总结

其实 MIPI 转 RGB 的修改点不多,也不难。就一个地方:初始化命令通过 MIPI 的 DATA0 下发,转移到了 I2C 下发。

2016-08-15

后志

实际上,当年这种做法,并不适合平台间的移植。在今天看来,我们可以把 mipi-2-rgb 分解为两部分:数据流部分和控制流部分。
所谓数据流,其实就是平台数据的输出,对于平台来说,无论外围是 mipi lcd 还是 rgb转换芯片亦或是 hdmi 转换芯片,平台只需要把符合要求的 mipi 数据,通过 mipi lane 丢出去就可以了,至于mipi数据怎么转换成 rgb 数据,那就不是平台的事情了,是外带的转换芯片的事情了。
所谓控制流,其实就是通过 i2c 下发控制命令初始化 rgb 、hdmi 转换芯片的事情。相对于平台来说,转换芯片,无非就是一个i2c或者spi或者其他总线的外设而已,就是一个i2c子设备而已,它无需要多么复杂的逻辑来控制。
那么二者合一,即数据流和控制流加在一起,代码处理,完全没必要按照当年的做法来到处修改,对于mipi来说,按照真实的mipi lcd造一个 fake lcd dtsi 设备树给平台,骗过平台,让平台误认为外面是一个 mipi lcd,可以输出 mipi 数据即可;而针对转换芯片来说,开机上电,按照 i2c 子设备的加载方式,单独一个驱动,处理即可。当然,后面工程中,实际上也是按照这个思路来处理的。

附加一句:在 mipi-2-rgb lcd的基础上,去理解 camera 外设调试,道理亦然。差别在于两点:1.camera的是输入方向;2.camera的上电时序比较严格。仅此而已。
2020-2-14

发布了28 篇原创文章 · 获赞 23 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/q1075355798/article/details/104312421