高通SDM450 副屏mipi转EDP的调试基础是在之前主屏mipi转EDP的基础上面实现的,调试难度比主屏mipi转EDP简单一些,本来是也是在BootLoader的阶段对lt8911进行初始化,但是SDM450 BootLoader阶段的I2C2初始化一直有问题,可能是域错了,厂家也没给具体的实现,后来就放弃了在BootLoader阶段初始化lt8911,毕竟副屏是要进入Android才有图像输出,因而选择在kernel阶段初始化lt8911.本次调试副屏mipi转EDP,lt8911exb 的I2C接到SDM450的I2C2接口上,其他硬件接口跟主屏mipi转EDP差不多,只是一些控制上电和复位的管脚不一样,具体的硬件电路不再贴出来,可以参考之前的主屏mipi转EDP的电路
主屏mipi转EDP 调试过程 原文链接:https://blog.csdn.net/u010852497/article/details/115266158
调试过程简要:
1、LT8911硬件部分各个电源电压是否正常,晶振是否起振,I2C电平状态是否正常
2、添加LT8911设备,注意包括电源、复位的控制IO口,挂载在LT8911上屏幕是多少lane的
3、添加LT8911驱动,确保通信正常,可以出彩条
4、添加SDM450副屏部分参数,注意主控跟LT8911连接是几lane的
一、LT8911硬件部分各个电源电压是否正常,晶振是否起振,I2C电平状态是否正常
这部分的调试使用示波器来测量,一定要确保LT8911能够正常工作,由于调试过程没保存好图片,这里不贴出来具体测量过程
二、添加LT8911设备
kernel4.9来说,设备的描述采用dts设备树描述,我们副屏的LT8911是接到SDM450的I2C2,所有可以要将LT8911挂到I2C2上面
kernel/msm-4.9/arch/arm64/boot/dts/qcom/msm8953-mtp.dtsi
&i2c_2 {
lt8911exb@29 {
compatible = "lontium,lt8911exb";
reg = <0x29>;
power-gpio = <&tlmm 25 0x0>;//LCD0_EN
reset-gpio = <&tlmm 87 0x0>;//LT8912_RSTN
//bl-gpio = <&tlmm 44 0x0>;//LCD0_BL_EN
lontium,pclk = <73248000>;//<148000000>;
lontium,hfp = <94>;
lontium,hs = <16>;
lontium,hbp = <50>;
lontium,hact = <1366>;
lontium,vfp = <13>;
lontium,vs = <6>;
lontium,vbp = <13>;
lontium,vact = <768>;
lontium,mipi_lane = <4>;
lontium,lane_cnt = <1>;
lontium,color = <1>; //1//Color Depth 0:6bit 1:8bit
lontium,test = <0>;
};
注意事项:
1、本次调试的地址是0x29,具体的要根据LT8911的硬件决定
2、pclk = (hfp+hs+hbo+hact)*(vfp+vs+vbp+vact)*60 这个具体也是要看驱动的解析
3、mipi_lane 主控和lt8911连接的lane数
4、lane_cnt lt8911和屏幕连接的lane数
5、test 是否开启彩条测试模式
三、添加LT8911的驱动
kernel/msm-4.9/drivers/video/lt8911exb/lt8911exb.c
#include <linux/irq.h>
#include <linux/platform_device.h>
#include <linux/pinctrl/consumer.h>
#include <linux/input/mt.h>
#include <linux/slab.h>
#include "lt8911exb.h"
/*******************************************************
Function:
Write data to the i2c slave device.
Input:
client: i2c device.
buf[0]: write start address.
buf[1~len-1]: data buffer
len: LT8911EXB_ADDR_LENGTH + write bytes count
Output:
numbers of i2c_msgs to transfer:
0: succeed, otherwise: failed
*********************************************************/
int lt8911exb_i2c_write(struct i2c_client *client, u8 *buf, int len)
{
unsigned int pos = 0, transfer_length = 0;
u8 address = buf[0];
unsigned char put_buf[64];
int retry, ret = 0;
struct i2c_msg msg = {
.addr = client->addr,
.flags = !I2C_M_RD,
};
if(likely(len < sizeof(put_buf))) {
/* code optimize,use stack memory*/
msg.buf = &put_buf[0];
} else {
msg.buf = kmalloc(len > I2C_MAX_TRANSFER_SIZE
? I2C_MAX_TRANSFER_SIZE : len, GFP_KERNEL);
if(!msg.buf)
return -ENOMEM;
}
len -= LT8911EXB_ADDR_LENGTH;
while(pos != len) {
if(unlikely(len - pos > I2C_MAX_TRANSFER_SIZE - LT8911EXB_ADDR_LENGTH))
transfer_length = I2C_MAX_TRANSFER_SIZE - LT8911EXB_ADDR_LENGTH;
else
transfer_length = len - pos;
msg.buf[0] = address;
msg.len = transfer_length + LT8911EXB_ADDR_LENGTH;
memcpy(&msg.buf[LT8911EXB_ADDR_LENGTH], &buf[LT8911EXB_ADDR_LENGTH + pos], transfer_length);
for(retry = 0; retry < RETRY_MAX_TIMES; retry++) {
if(likely(i2c_transfer(client->adapter, &msg, 1) == 1)) {
pos += transfer_length;
address += transfer_length;
break;
}
dev_info(&client->dev, "I2C write retry[%d]\n", retry + 1);
udelay(2000);
}
if(unlikely(retry == RETRY_MAX_TIMES)) {
dev_err(&client->dev,
"I2c write failed,dev:%02x,reg:%02x,size:%u\n",
client->addr, address, len);
ret = -EAGAIN;
goto write_exit;
}
}
write_exit:
if(len + LT8911EXB_ADDR_LENGTH >= sizeof(put_buf))
kfree(msg.buf);
return ret;
}
/*******************************************************
Function:
Read data from the i2c slave device.
Input:
client: i2c device.
buf[0]: read start address.
buf[1~len-1]: data buffer
len: LT8911EXB_ADDR_LENGTH + read bytes count
Output:
numbers of i2c_msgs to transfer:
0: succeed, otherwise: failed
*********************************************************/
int lt8911exb_i2c_read(struct i2c_client *client, u8 *buf, int len)
{
unsigned int transfer_length = 0;
unsigned int pos = 0;
u8 address = buf[0];
unsigned char get_buf[64], addr_buf[2];
int retry, ret = 0;
struct i2c_msg msgs[] = {
{
.addr = client->addr,
.flags = !I2C_M_RD,
.buf = &addr_buf[0],
.len = LT8911EXB_ADDR_LENGTH,
}, {
.addr = client->addr,
.flags = I2C_M_RD,
}
};
len -= LT8911EXB_ADDR_LENGTH;
if(likely(len < sizeof(get_buf))) {
/* code optimize, use stack memory */
msgs[1].buf = &get_buf[0];
} else {
msgs[1].buf = kzalloc(len > I2C_MAX_TRANSFER_SIZE
? I2C_MAX_TRANSFER_SIZE : len, GFP_KERNEL);
if(!msgs[1].buf)
return -ENOMEM;
}
while(pos != len) {
if(unlikely(len - pos > I2C_MAX_TRANSFER_SIZE))
transfer_length = I2C_MAX_TRANSFER_SIZE;
else
transfer_length = len - pos;
msgs[0].buf[0] = address;
msgs[1].len = transfer_length;
for(retry = 0; retry < RETRY_MAX_TIMES; retry++) {
if(likely(i2c_transfer(client->adapter, msgs, 2) == 2)) {
memcpy(&buf[LT8911EXB_ADDR_LENGTH + pos], msgs[1].buf, transfer_length);
pos += transfer_length;
address += transfer_length;
break;
}
dev_info(&client->dev, "I2c read retry[%d]:0x%x\n",
retry + 1, address);
udelay(2000);
}
if(unlikely(retry == RETRY_MAX_TIMES)) {
dev_err(&client->dev,
"I2c read failed,dev:%02x,reg:%02x,size:%u\n",
client->addr, address, len);
ret = -EAGAIN;
goto read_exit;
}
}
read_exit:
if(len >= sizeof(get_buf))
kfree(msgs[1].buf);
return ret;
}
int lt8911exb_write(struct i2c_client *client, u8 addr, u8 data)
{
u8 buf[2] = {addr, data};
int ret = -1;
ret = lt8911exb_i2c_write(client, buf, 2);
return ret;
}
u8 lt8911exb_read(struct i2c_client *client, u8 addr)
{
u8 buf[2] = {addr};
int ret = -1;
ret = lt8911exb_i2c_read(client, buf, 2);
if(ret == 0) {
return buf[1];
} else {
return 0;
}
}
void lt8911exb_reset_guitar(struct i2c_client *client)
{
struct lt8911exb_data *lt8911exb = i2c_get_clientdata(client);
dev_info(&client->dev, "Guitar reset");
if(!gpio_is_valid(lt8911exb->rst_gpio)) {
dev_warn(&client->dev, "reset failed no valid reset gpio");
return;
}
gpio_direction_output(lt8911exb->rst_gpio, 1);
mdelay(100); //150ms
gpio_direction_output(lt8911exb->rst_gpio, 0);
mdelay(100);
gpio_direction_output(lt8911exb->rst_gpio, 1);
mdelay(10);
//gpio_direction_output(lt8911exb->bl_gpio, 1);
}
static int lt8911exb_parse_dt(struct device *dev,
struct lt8911exb_data *pdata)
{
int ret = 0;
struct device_node *np = dev->of_node;
pdata->pwr_gpio = of_get_named_gpio(np, "power-gpio", 0);
if(!gpio_is_valid(pdata->pwr_gpio)) {
dev_err(dev, "No valid pwr gpio");
return -1;
}
pdata->rst_gpio = of_get_named_gpio(np, "reset-gpio", 0);
if(!gpio_is_valid(pdata->rst_gpio)) {
dev_err(dev, "No valid rst gpio");
return -1;
}
pdata->bl_gpio = of_get_named_gpio(np, "bl-gpio", 0);
if(!gpio_is_valid(pdata->rst_gpio)) {
dev_err(dev, "No valid bl gpio");
return -1;
}
ret = of_property_read_u32(np, "lontium,test", &pdata->test);
if (ret) {
dev_err(dev, "Parse test failed");
return -1;
}
ret = of_property_read_u32(np, "lontium,mipi_lane", &pdata->mipi_lane);
if (ret) {
dev_err(dev, "Parse mipi_lane failed");
return -1;
}
ret = of_property_read_u32(np, "lontium,lane_cnt", &pdata->lane_cnt);
if (ret) {
dev_err(dev, "Parse lane_cnt failed");
return -1;
}
ret = of_property_read_u32(np, "lontium,color", &pdata->color);
if (ret) {
dev_err(dev, "Parse color failed");
return -1;
}
ret = of_property_read_u32(np, "lontium,lane_cnt", &pdata->lane_cnt);
if (ret) {
dev_err(dev, "Parse lane_cnt failed");
return -1;
}
ret = of_property_read_u32(np, "lontium,hact", &pdata->hact);
if (ret) {
dev_err(dev, "Parse hact failed");
return -1;
}
ret = of_property_read_u32(np, "lontium,hact", &pdata->hact);
if (ret) {
dev_err(dev, "Parse hact failed");
return -1;
}
ret = of_property_read_u32(np, "lontium,vact", &pdata->vact);
if (ret) {
dev_err(dev, "Parse vact failed");
return -1;
}
ret = of_property_read_u32(np, "lontium,hbp", &pdata->hbp);
if (ret) {
dev_err(dev, "Parse hbp failed");
return -1;
}
ret = of_property_read_u32(np, "lontium,hfp", &pdata->hfp);
if (ret) {
dev_err(dev, "Parse hfp failed");
return -1;
}
ret = of_property_read_u32(np, "lontium,hs", &pdata->hs);
if (ret) {
dev_err(dev, "Parse hs failed");
return -1;
}
ret = of_property_read_u32(np, "lontium,vbp", &pdata->vbp);
if (ret) {
dev_err(dev, "Parse vbp failed");
return -1;
}
ret = of_property_read_u32(np, "lontium,vfp", &pdata->vfp);
if (ret) {
dev_err(dev, "Parse vfp failed");
return -1;
}
ret = of_property_read_u32(np, "lontium,vs", &pdata->vs);
if (ret) {
dev_err(dev, "Parse vs failed");
return -1;
}
ret = of_property_read_u32(np, "lontium,pclk", &pdata->pclk);
if (ret) {
dev_err(dev, "Parse pclk failed");
return -1;
} else {
pdata->pclk = pdata->pclk/10000; // 10khz
}
pdata->htotal = pdata->hact + pdata->hbp + pdata->hfp + pdata->hs;
pdata->vtotal = pdata->vact + pdata->vbp + pdata->vfp + pdata->vs;
return 0;
}
static int lt8911exb_request_io_port(struct lt8911exb_data *lt8911exb)
{
int ret = 0;
if(gpio_is_valid(lt8911exb->pwr_gpio)) {
ret = gpio_request(lt8911exb->pwr_gpio, "lt8911exb_pwr");
if(ret < 0) {
dev_err(<8911exb->client->dev,
"Failed to request GPIO:%d, ERRNO:%d\n",
(s32)lt8911exb->pwr_gpio, ret);
return -ENODEV;
}
gpio_direction_output(lt8911exb->pwr_gpio , 1);
dev_info(<8911exb->client->dev, "Success request pwr-gpio\n");
}
if(gpio_is_valid(lt8911exb->rst_gpio)) {
ret = gpio_request(lt8911exb->rst_gpio, "lt8911exb_rst");
if(ret < 0) {
dev_err(<8911exb->client->dev,
"Failed to request GPIO:%d, ERRNO:%d\n",
(s32)lt8911exb->rst_gpio, ret);
if(gpio_is_valid(lt8911exb->pwr_gpio))
gpio_free(lt8911exb->pwr_gpio);
return -ENODEV;
}
//gpio_direction_input(lt8911exb->rst_gpio);
dev_info(<8911exb->client->dev, "Success request rst-gpio\n");
}
if(gpio_is_valid(lt8911exb->bl_gpio)) {
ret = gpio_request(lt8911exb->bl_gpio, "lt8911exb_bl");
if(ret < 0) {
dev_err(<8911exb->client->dev,
"Failed to request GPIO:%d, ERRNO:%d\n",
(s32)lt8911exb->bl_gpio, ret);
if(gpio_is_valid(lt8911exb->pwr_gpio))
gpio_free(lt8911exb->pwr_gpio);
if(gpio_is_valid(lt8911exb->rst_gpio))
gpio_free(lt8911exb->rst_gpio);
return -ENODEV;
}
dev_info(<8911exb->client->dev, "Success request bl-gpio\n");
}
return 0;
}
static int lt8911exb_i2c_test(struct i2c_client *client)
{
u8 retry = 0;
u8 chip_id_h = 0, chip_id_m = 0, chip_id_l = 0;
int ret = -EAGAIN;
while(retry++ < 3) {
ret = lt8911exb_write(client, 0xff, 0x81);
if(ret < 0) {
dev_err(&client->dev, "LT8911EXB i2c test write addr:0xff failed\n");
continue;
}
ret = lt8911exb_write(client, 0x08, 0x7f);
if(ret < 0) {
dev_err(&client->dev, "LT8911EXB i2c test write addr:0x08 failed\n");
continue;
}
chip_id_l = lt8911exb_read(client, 0x00);
chip_id_m = lt8911exb_read(client, 0x01);
chip_id_h = lt8911exb_read(client, 0x02);
// LT8911EXB i2c test success chipid: 0xe0517
dev_info(&client->dev, "LT8911EXB i2c test success chipid: 0x%x%x%x\n", chip_id_h, chip_id_m, chip_id_l);
// if (chip_id_h == 0 && chip_id_l == 0) {
// dev_err(&client->dev, "LT8911EXB i2c test failed time %d\n", retry);
// continue;
// }
ret = 0;
break;
}
return ret;
}
void lt8911exb_dpcd_write(struct i2c_client *client, u32 address, u8 Data)
{
u8 address_h = 0x0f & (address >> 16);
u8 address_m = 0xff & (address >> 8);
u8 address_l = 0xff & address;
u8 ret = 0x00;
lt8911exb_write(client, 0xff, 0xa6);
lt8911exb_write(client, 0x2b, (0x80|address_h));
lt8911exb_write(client, 0x2b,address_m); //addr[15:8]
lt8911exb_write(client, 0x2b,address_l); //addr[7:0]
lt8911exb_write(client, 0x2b,0x00); //data lenth
lt8911exb_write(client, 0x2b,Data); //data
lt8911exb_write(client, 0x2c,0x00); //start Aux read edid
mdelay(20);
ret = lt8911exb_read(client, 0x25);
if((ret&0x0f)== 0x0c) {
return;
}
}
u8 lt8911exb_dpcd_read(struct i2c_client *client, u32 address)
{
u8 read_cnt = 0x03;
u8 dpcd_value = 0x00;
u8 address_h = 0x0f & (address >> 16);
u8 address_m = 0xff & (address >> 8);
u8 address_l = 0xff & address;
lt8911exb_write(client, 0xff, 0x80);
lt8911exb_write(client, 0x62, 0xbd);
lt8911exb_write(client, 0x62, 0xbf); //ECO(AUX reset)
lt8911exb_write(client, 0x36, 0x00);
lt8911exb_write(client, 0x30, 0x8f); //0x91
lt8911exb_write(client, 0x33, address_l);
lt8911exb_write(client, 0x34, address_m);
lt8911exb_write(client, 0x35, address_h);
lt8911exb_write(client, 0x36, 0x20);
mdelay(2); //The necessary
if(lt8911exb_read(client, 0x39) == 0x01) {
dpcd_value = lt8911exb_read(client, 0x38);
} else {
while((lt8911exb_read(client, 0x39) != 0x01) && (read_cnt > 0)) {
lt8911exb_write(client, 0x36, 0x00);
lt8911exb_write(client, 0x36, 0x20);
read_cnt--;
mdelay(2);
}
if(lt8911exb_read(client, 0x39) == 0x01) {
dpcd_value = lt8911exb_read(client, 0x38);
}
}
return dpcd_value;
}
void lt8911exb_mipi_video_timing(struct i2c_client *client)
{
struct lt8911exb_data *lt8911exb = i2c_get_clientdata(client);
lt8911exb_write(client, 0xff, 0xd0);
lt8911exb_write(client, 0x0d, (u8)(lt8911exb->vtotal / 256));
lt8911exb_write(client, 0x0e, (u8)(lt8911exb->vtotal % 256)); //vtotal
lt8911exb_write(client, 0x0f, (u8)(lt8911exb->vact / 256));
lt8911exb_write(client, 0x10, (u8)(lt8911exb->vact % 256)); //vactive
lt8911exb_write(client, 0x11, (u8)(lt8911exb->htotal / 256));
lt8911exb_write(client, 0x12, (u8)(lt8911exb->htotal % 256)); //htotal
lt8911exb_write(client, 0x13, (u8)(lt8911exb->hact / 256));
lt8911exb_write(client, 0x14, (u8)(lt8911exb->hact % 256)); //hactive
lt8911exb_write(client, 0x15, (u8)(lt8911exb->vs % 256)); //vsa
lt8911exb_write(client, 0x16, (u8)(lt8911exb->hs % 256)); //hsa
lt8911exb_write(client, 0x17, (u8)(lt8911exb->vfp / 256));
lt8911exb_write(client, 0x18, (u8)(lt8911exb->vfp % 256)); //vfp
lt8911exb_write(client, 0x19, (u8)(lt8911exb->hfp / 256));
lt8911exb_write(client, 0x1a, (u8)(lt8911exb->hfp % 256)); //hfp
}
void lt8911exb_edp_video_cfg(struct i2c_client *client)
{
struct lt8911exb_data *lt8911exb = i2c_get_clientdata(client);
lt8911exb_write(client, 0xff, 0xa8);
lt8911exb_write(client, 0x2d, 0x88); // MSA from register
lt8911exb_write(client, 0x05, (u8)(lt8911exb->htotal / 256));
lt8911exb_write(client, 0x06, (u8)(lt8911exb->htotal % 256));
lt8911exb_write(client, 0x07, (u8)((lt8911exb->hs + lt8911exb->hbp) / 256));
lt8911exb_write(client, 0x08, (u8)((lt8911exb->hs + lt8911exb->hbp) % 256));
lt8911exb_write(client, 0x09, (u8)(lt8911exb->hs / 256));
lt8911exb_write(client, 0x0a, (u8)(lt8911exb->hs % 256));
lt8911exb_write(client, 0x0b, (u8)(lt8911exb->hact / 256));
lt8911exb_write(client, 0x0c, (u8)(lt8911exb->hact % 256));
lt8911exb_write(client, 0x0d, (u8)(lt8911exb->vtotal / 256));
lt8911exb_write(client, 0x0e, (u8)(lt8911exb->vtotal % 256));
lt8911exb_write(client, 0x11, (u8)((lt8911exb->vs + lt8911exb->vbp) / 256));
lt8911exb_write(client, 0x12, (u8)((lt8911exb->vs + lt8911exb->vbp) % 256));
lt8911exb_write(client, 0x14, (u8)(lt8911exb->vs % 256));
lt8911exb_write(client, 0x15, (u8)(lt8911exb->vact / 256));
lt8911exb_write(client, 0x16, (u8)(lt8911exb->vact % 256));
}
void lt8911exb_setup(struct i2c_client *client)
{
u8 i;
u8 pcr_pll_postdiv;
u8 pcr_m;
u16 temp16;
struct lt8911exb_data *lt8911exb = i2c_get_clientdata(client);
/* init */
lt8911exb_write(client, 0xff, 0x81);
lt8911exb_write(client, 0x49, 0xff); //enable 0x87xx
lt8911exb_write(client, 0xff, 0x82); //GPIO test output
lt8911exb_write(client, 0x5a, 0x0e);
/* mipi Rx analog */
lt8911exb_write(client, 0xff, 0x82);
lt8911exb_write(client, 0x32, 0x51);
lt8911exb_write(client, 0x35, 0x42); //EQ current
lt8911exb_write(client, 0x4c, 0x0c);
lt8911exb_write(client, 0x4d, 0x00);
lt8911exb_write(client, 0x3a, 0x44); // 11 // 22 / 33 / 55 / 66 / 77
lt8911exb_write(client, 0x3b, 0x44); // 11 // 22 / 33 / 55 / 66 / 77
/* dessc_pcr pll analog */
lt8911exb_write(client, 0xff, 0x82);
lt8911exb_write(client, 0x6a, 0x40);
lt8911exb_write(client, 0x6b, 0x40);
temp16 = lt8911exb->pclk;
if(lt8911exb->pclk < 8800) {
lt8911exb_write(client, 0x6e, 0x82); //0x44:pre-div = 2 ,pixel_clk=44~ 88MHz
pcr_pll_postdiv = 0x08;
} else {
lt8911exb_write(client, 0x6e, 0x81); //0x40:pre-div = 1, pixel_clk =88~176MHz
pcr_pll_postdiv = 0x04;
}
pcr_m = (u8)(temp16 * pcr_pll_postdiv / 25 / 100);
/* dessc pll digital */
lt8911exb_write(client, 0xff, 0x85);
lt8911exb_write(client, 0xa9, 0x31);
lt8911exb_write(client, 0xaa, 0x17);
lt8911exb_write(client, 0xab, 0xba);
lt8911exb_write(client, 0xac, 0xe1);
lt8911exb_write(client, 0xad, 0x47);
lt8911exb_write(client, 0xae, 0x01);
lt8911exb_write(client, 0xae, 0x11);
/* Digital Top */
lt8911exb_write(client, 0xff, 0x85);
lt8911exb_write(client, 0xc0, 0x01); //select mipi Rx
#ifdef _6bit_
lt8911exb_write(client, 0xb0, 0xd0); //enable dither
#else
lt8911exb_write(client, 0xb0, 0x00); // disable dither
#endif
/* mipi Rx Digital */
lt8911exb_write(client, 0xff, 0xd0);
lt8911exb_write(client, 0x00, lt8911exb->mipi_lane % 4); // 0: 4 Lane / 1: 1 Lane / 2 : 2 Lane / 3: 3 Lane
lt8911exb_write(client, 0x02, 0x08); //settle
lt8911exb_write(client, 0x08, 0x00);
lt8911exb_write(client, 0x0a, 0x12); //pcr mode
lt8911exb_write(client, 0x0c, 0x40);
lt8911exb_write(client, 0x1c, 0x3a);
//lt8911exb_write(client,0x2d,0x19); //M up limit
lt8911exb_write(client, 0x31, 0x0a);
// lt8911exb_write(client, 0x21, 0x4f );
// lt8911exb_write(client, 0x22, 0xff );
// lt8911exb_write(client, 0x2a, 0x08 );
lt8911exb_write(client, 0x3f, 0x10);
lt8911exb_write(client, 0x40, 0x20);
lt8911exb_write(client, 0x41, 0x30);
if (lt8911exb->test) {
dev_info(&client->dev, "LT8911EXB i2c test \n");
lt8911exb_write(client, 0x26, (pcr_m | 0x80));
} else {
lt8911exb_write(client, 0x26, pcr_m);
}
lt8911exb_write(client, 0xff, 0x81); //PCR reset
lt8911exb_write(client, 0x03, 0x7b);
lt8911exb_write(client, 0x03, 0xff);
/* Txpll 2.7G*/
lt8911exb_write(client, 0xff, 0x87);
lt8911exb_write(client, 0x19, 0x31);
lt8911exb_write(client, 0xff, 0x82);
lt8911exb_write(client, 0x02, 0x42);
lt8911exb_write(client, 0x03, 0x00);
lt8911exb_write(client, 0x03, 0x01);
lt8911exb_write(client, 0xff, 0x81);
lt8911exb_write(client, 0x09, 0xfc);
lt8911exb_write(client, 0x09, 0xfd);
lt8911exb_write(client, 0xff, 0x87);
lt8911exb_write(client, 0x0c, 0x11);
for(i = 0; i < 5; i++) { //Check Tx PLL
mdelay(5);
if(lt8911exb_read(client, 0x37) & 0x02) {
dev_info(&client->dev, "LT8911 tx pll locked");
break;
} else {
dev_info(&client->dev, "LT8911 tx pll unlocked");
lt8911exb_write(client, 0xff, 0x81);
lt8911exb_write(client, 0x09, 0xfc);
lt8911exb_write(client, 0x09, 0xfd);
lt8911exb_write(client, 0xff, 0x87);
lt8911exb_write(client, 0x0c, 0x10);
lt8911exb_write(client, 0x0c, 0x11);
}
}
/* tx phy */
lt8911exb_write(client, 0xff, 0x82);
lt8911exb_write(client, 0x11, 0x00);
lt8911exb_write(client, 0x13, 0x10);
lt8911exb_write(client, 0x14, 0x0c);
lt8911exb_write(client, 0x14, 0x08);
lt8911exb_write(client, 0x13, 0x20);
lt8911exb_write(client, 0xff, 0x82);
lt8911exb_write(client, 0x0e, 0x25);
lt8911exb_write(client, 0x12, 0xff);
// lt8911exb_write(client, 0xff, 0x80 );
// lt8911exb_write(client, 0x40, 0x22 );
/*eDP Tx Digital */
lt8911exb_write(client, 0xff, 0xa8);
if (lt8911exb->test) {
lt8911exb_write(client, 0x24, 0x50); // bit2 ~ bit 0 : test panttern image mode
lt8911exb_write(client, 0x25, 0x70); // bit6 ~ bit 4 : test Pattern color
lt8911exb_write(client, 0x27, 0x50); //0x50:Pattern; 0x10:mipi video
} else {
lt8911exb_write(client, 0x27, 0x10); //0x50:Pattern; 0x10:mipi video
}
if(lt8911exb->color) {
//8bit
lt8911exb_write(client, 0x17, 0x10);
lt8911exb_write(client, 0x18, 0x20);
} else {
//6bit
lt8911exb_write(client, 0x17, 0x00);
lt8911exb_write(client, 0x18, 0x00);
}
lt8911exb_write(client, 0xff, 0xa0);
lt8911exb_write(client, 0x00, 0x08);
lt8911exb_write(client, 0x01, 0x00);
}
/* mipi should be ready before configuring below video check setting*/
void lt8911exb_video_check(struct i2c_client *client)
{
u32 ret = 0x00;
/* mipi byte clk check*/
lt8911exb_write(client, 0xff, 0x85);
lt8911exb_write(client, 0x1d, 0x00); //FM select byte clk
lt8911exb_write(client, 0x40, 0xf7);
lt8911exb_write(client, 0x41, 0x30);
lt8911exb_write(client, 0xa1, 0x82); //eDP scramble mode; //video chech from mipi
// lt8911exb_write(client, 0x17, 0xf0 ); //0xf0:Close scramble; 0xD0 : Open scramble
lt8911exb_write(client, 0xff, 0x81); //video check rst
lt8911exb_write(client, 0x09, 0x7d);
lt8911exb_write(client, 0x09, 0xfd);
lt8911exb_write(client, 0xff, 0x85);
mdelay(30);
if(lt8911exb_read(client, 0x50) == 0x03) {
ret = lt8911exb_read(client, 0x4d);
ret = ret * 256 + lt8911exb_read(client, 0x4e);
ret = ret * 256 + lt8911exb_read(client, 0x4f);
dev_info(&client->dev, "video check: mipi clk = %d", ret); //mipi clk = ret * 1000
} else {
dev_info(&client->dev, "video check: mipi clk unstable");
}
/* mipi vtotal check*/
ret = lt8911exb_read(client, 0x76);
ret = ret * 256 + lt8911exb_read(client, 0x77);
dev_info(&client->dev, "video check: Vtotal = %d", ret);
/* mipi word count check*/
lt8911exb_write(client, 0xff, 0xd0);
ret = lt8911exb_read(client, 0x82);
ret = ret * 256 + lt8911exb_read(client, 0x83);
ret = ret / 3;
dev_info(&client->dev, "video check: Hact(word counter) = %d", ret);
/* mipi Vact check*/
ret = lt8911exb_read(client, 0x85);
ret = ret * 256 + lt8911exb_read(client, 0x86);
dev_info(&client->dev, "video check: Vact = %d", ret);
}
void lt8911exb_link_train(struct i2c_client *client)
{
struct lt8911exb_data *lt8911exb = i2c_get_clientdata(client);
lt8911exb_write(client, 0xff, 0x85);
lt8911exb_write(client, 0xa1, 0x82); // eDP scramble mode;
/* Aux operater init */
lt8911exb_write(client,0xff,0xac);
lt8911exb_write(client,0x00,0x20); //Soft Link train
lt8911exb_write(client,0xff,0xa6);
lt8911exb_write(client,0x2a,0x01);
lt8911exb_dpcd_write(client, 0x010a, 0x01);
mdelay(10);
lt8911exb_dpcd_write(client, 0x0102, 0x00);
mdelay(10);
lt8911exb_dpcd_write(client, 0x010a, 0x01);
mdelay(200);
/* Aux setup */
lt8911exb_write(client, 0xff, 0xac);
lt8911exb_write(client, 0x00, 0x60); //Soft Link train
lt8911exb_write(client, 0xff, 0xa6);
lt8911exb_write(client, 0x2a, 0x00);
lt8911exb_write(client, 0xff, 0x81);
lt8911exb_write(client, 0x07, 0xfe);
lt8911exb_write(client, 0x07, 0xff);
lt8911exb_write(client, 0x0a, 0xfc);
lt8911exb_write(client, 0x0a, 0xfe);
/* link train */
lt8911exb_write(client, 0xff, 0x85);
lt8911exb_write(client, 0x1a, lt8911exb->lane_cnt);
// lt8911exb_write(client,0x13,0xd1);
lt8911exb_write(client, 0xff, 0xac);
lt8911exb_write(client, 0x00, 0x64);
lt8911exb_write(client, 0x01, 0x0a);
lt8911exb_write(client, 0x0c, 0x85);
lt8911exb_write(client, 0x0c, 0xc5);
}
void lt8911exb_config(struct i2c_client *client)
{
lt8911exb_mipi_video_timing(client); //defualt setting is 1080P
lt8911exb_edp_video_cfg(client);
lt8911exb_setup(client);
lt8911exb_video_check(client); //just for Check MIPI Input
lt8911exb_link_train(client);
}
static int lt8911exb_probe(struct i2c_client * client, const struct i2c_device_id * id)
{
int ret = -1;
struct lt8911exb_data *lt8911exb;
/* do NOT remove these logs */
dev_info(&client->dev, "LT8911EXB Driver Version: %s\n", LT8911EXB_DRIVER_VERSION);
dev_info(&client->dev, "LT8911EXB I2C Address: 0x%02x\n", client->addr);
if(!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
dev_err(&client->dev, "Failed check I2C functionality");
return -ENODEV;
}
lt8911exb = devm_kzalloc(&client->dev, sizeof(*lt8911exb), GFP_KERNEL);
if(lt8911exb == NULL) {
dev_err(&client->dev, "Failed alloc lt8911exb memory");
return -ENOMEM;
}
if(client->dev.of_node) {
ret = lt8911exb_parse_dt(&client->dev, lt8911exb);
if(ret < 0) {
dev_err(&client->dev, "Failed parse dlt8911exb\n");
goto exit_free_client_data;
}
}
lt8911exb->client = client;
i2c_set_clientdata(client, lt8911exb);
ret = lt8911exb_request_io_port(lt8911exb);
if(ret < 0) {
dev_err(&client->dev, "Failed request IO port\n");
goto exit_free_client_data;
}
lt8911exb_reset_guitar(client);
ret = lt8911exb_i2c_test(client);
if(ret < 0) {
dev_err(&client->dev, "Failed communicate with IC use I2C\n");
goto exit_free_io_port;
}
lt8911exb_config(client);
mdelay(200);
dev_info(&client->dev, "LT8911EXB setup finish.\n");
return 0;
exit_free_io_port:
if(gpio_is_valid(lt8911exb->rst_gpio))
gpio_free(lt8911exb->rst_gpio);
if(gpio_is_valid(lt8911exb->pwr_gpio))
gpio_free(lt8911exb->pwr_gpio);
if(gpio_is_valid(lt8911exb->bl_gpio))
gpio_free(lt8911exb->bl_gpio);
exit_free_client_data:
devm_kfree(&client->dev, lt8911exb);
i2c_set_clientdata(client, NULL);
return ret;
}
static int lt8911exb_remove(struct i2c_client * client)
{
struct lt8911exb_data *lt8911exb = i2c_get_clientdata(client);
if(gpio_is_valid(lt8911exb->rst_gpio))
gpio_free(lt8911exb->rst_gpio);
if(gpio_is_valid(lt8911exb->pwr_gpio))
gpio_free(lt8911exb->pwr_gpio);
if(gpio_is_valid(lt8911exb->bl_gpio))
gpio_free(lt8911exb->bl_gpio);
dev_info(&client->dev, "goodix lt8911exb driver removed");
i2c_set_clientdata(client, NULL);
devm_kfree(&client->dev, lt8911exb);
return 0;
}
static const struct of_device_id lt8911exb_match_table[] = {
{.compatible = "lontium,lt8911exb",},
{ },
};
static const struct i2c_device_id lt8911exb_device_id[] = {
{ LT8911EXB_I2C_NAME, 0 },
{ }
};
static struct i2c_driver lt8911exb_driver = {
.probe = lt8911exb_probe,
.remove = lt8911exb_remove,
.id_table = lt8911exb_device_id,
.driver = {
.name = LT8911EXB_I2C_NAME,
.owner = THIS_MODULE,
.of_match_table = lt8911exb_match_table,
},
};
static int __init lt8911exb_init(void)
{
s32 ret;
pr_info("Lontium LT8911EXB driver installing....\n");
ret = i2c_add_driver(<8911exb_driver);
return ret;
}
static void __exit lt8911exb_exit(void)
{
pr_info("Lontium LT8911EXB driver exited\n");
i2c_del_driver(<8911exb_driver);
}
fs_initcall(lt8911exb_init);
module_exit(lt8911exb_exit);
MODULE_DESCRIPTION("Lontium LT8911EXB Driver");
MODULE_LICENSE("GPL V2");
kernel/msm-4.9/drivers/video/lt8911exb/lt8911exb.h
#include <linux/hrtimer.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/proc_fs.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/vmalloc.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/regulator/consumer.h>
#include <linux/major.h>
#include <linux/kdev_t.h>
#include <linux/of_gpio.h>
#ifdef CONFIG_HAS_EARLYSUSPEND
#include <linux/earlysuspend.h>
#endif
#include <linux/usb.h>
#include <linux/power_supply.h>
#define LT8911EXB_I2C_NAME "lt8911exb"
#define LT8911EXB_DRIVER_VERSION "1.0.0"
#define LT8911EXB_ADDR_LENGTH 1
#define I2C_MAX_TRANSFER_SIZE 255
#define RETRY_MAX_TIMES 3
struct lt8911exb_data {
struct i2c_client *client;
int pwr_gpio;
int rst_gpio;
int bl_gpio;
int hact;
int vact;
int hbp;
int hfp;
int hs;
int vbp;
int vfp;
int vs;
int pclk;
int htotal;
int vtotal;
int lane_cnt;
int mipi_lane;
int color; //Color Depth 0:6bit 1:8bit
int test;
};
#endif /*_LONTIUM_LT8911EXB_H_*/
四、添加SDM450副屏部分参数,注意主控跟LT8911连接是几lane
1、屏幕头文件
kernel/msm-4.9/arch/arm64/boot/dts/qcom/dsi-panel-test-600-video.dtsi
&mdss_mdp {
dsi_ydt101ml659hs24a_600_video: qcom,mdss_dsi_ydt101ml659hs24a_600_video {
qcom,mdss-dsi-panel-name = "YDT101ML659HS24A 600 video mode dsi panel";
// qcom,mdss-dsi-panel-controller = <&mdss_dsi1>;
qcom,mdss-dsi-panel-type = "dsi_video_mode";
// qcom,mdss-dsi-panel-destination = "display_2";
qcom,mdss-dsi-panel-framerate = <60>;
qcom,mdss-dsi-virtual-channel-id = <0>;
qcom,mdss-dsi-stream = <0>;
qcom,mdss-dsi-panel-width = <1366>;
qcom,mdss-dsi-panel-height = <768>;
qcom,mdss-dsi-h-front-porch = <94>;
qcom,mdss-dsi-h-back-porch = <50>;
qcom,mdss-dsi-h-pulse-width = <16>;
qcom,mdss-dsi-h-sync-skew = <0>;
qcom,mdss-dsi-v-back-porch = <13>;
qcom,mdss-dsi-v-front-porch = <13>;
qcom,mdss-dsi-v-pulse-width = <6>;
qcom,mdss-dsi-h-left-border = <0>;
qcom,mdss-dsi-h-right-border = <0>;
qcom,mdss-dsi-v-top-border = <0>;
qcom,mdss-dsi-v-bottom-border = <0>;
qcom,mdss-dsi-bpp = <24>;
qcom,mdss-dsi-color-order = "rgb_swap_rgb";
qcom,mdss-dsi-underflow-color = <0xff>;
qcom,mdss-dsi-border-color = <0>;
qcom,mdss-dsi-on-command = [
05 01 00 00 78 00 02 11 00
05 01 00 00 32 00 02 29 00];
qcom,mdss-dsi-off-command = [
05 01 00 00 32 00 02 28 00
05 01 00 00 78 00 02 10 00];
qcom,mdss-dsi-on-command-state = "dsi_lp_mode";
qcom,mdss-dsi-off-command-state = "dsi_hs_mode";
qcom,mdss-dsi-h-sync-pulse = <1>;
qcom,mdss-dsi-traffic-mode = "burst_mode";
qcom,mdss-dsi-lane-map = "lane_map_0123";
qcom,mdss-dsi-bllp-eof-power-mode;
qcom,mdss-dsi-bllp-power-mode;
qcom,mdss-dsi-lane-0-state;
qcom,mdss-dsi-lane-1-state;
qcom,mdss-dsi-lane-2-state;
qcom,mdss-dsi-lane-3-state;
qcom,mdss-dsi-panel-timings = [82 1c 12 00 40 42 16 1e 14 03 04 00];
qcom,mdss-dsi-t-clk-post = <0x04>;
qcom,mdss-dsi-t-clk-pre = <0x27>;
qcom,mdss-dsi-bl-min-level = <1>;
qcom,mdss-dsi-bl-max-level = <4095>;
qcom,mdss-dsi-dma-trigger = "trigger_sw";
qcom,mdss-dsi-mdp-trigger = "none";
qcom,mdss-dsi-bl-pmic-control-type = "bl_ctrl_pwm";
qcom,mdss-dsi-reset-sequence = <1 20>, <0 30>, <1 30>;
qcom,mdss-dsi-panel-timings-phy-v2 = [
1e 1b 04 06 02 03 04 a0 /*Data 0*/
1e 1b 04 06 02 03 04 a0 /*Data 1*/
1e 1b 04 06 02 03 04 a0 /*Data 2*/
1e 1b 04 06 02 03 04 a0 /*Data 3*/
1e 0e 04 05 02 03 04 a0]; /*CLK lane*/
};
};
2、配置双屏显示模式,并使用mdss_dsi1为副屏显示
kernel/msm-4.9/arch/arm64/boot/dts/qcom/msm8953-nopmi-panel-camera.dtsi
&mdss_dsi {
hw-config = "dual_dsi"; //"dual_dsi"; //"single_dsi";
};
&mdss_dsi1 {
status = "ok";
qcom,dsi-pref-prim-pan = <&dsi_ydt101ml659hs24a_600_video>;
pinctrl-names = "mdss_default", "mdss_sleep";
pinctrl-0 = <&mdss_dsi1_active &mdss_te1_active>;
pinctrl-1 = <&mdss_dsi1_suspend &mdss_te1_suspend>;
qcom,bridge-index = <0>;
qcom,pluggable;
};
&dsi_ydt101ml659hs24a_600_video {
qcom,panel-supply-entries = <&dsi_panel_pwr_supply>;
qcom,dba-panel;
qcom,bridge-name = "dsi1-bridge";
};
3、配置DEFAULT_VFRMT_TIMING
DEFAULT_VFRMT_TIMING 是高通SDM450副屏必须配置的一个参数
#define DEFAULT_VFRMT_TIMING \
{DEFAULT_VFRMT, 1366/*active_h*/, 94/*front_porch_h*/, 16/*pulse_width_h*/, 50/*back_porch_h*/, false, \
768/*active_v*/, 13/*front_porch_v*/, 6/*pulse_width_v*/, 13/*back_porch_v*/, false, \
73248/*pixel_freq/1000*/, 60000/*refresh_rate/1000*/, false, true, HDMI_RES_AR_16_9, 0}
73248 = (1366+94+16+50)*(768+13+6)*60/1000
如果这个参数不对系统会一直打印,然后不断变得很卡,最后系统会崩溃重启
[ 61.361415] __mdss_fb_sync_buf_done_callback: mdp-fence: frame timeout
[ 61.470309] mdss_mdp_video_pollwait: vsync poll timed out! rc=-110 status=0x0 mask=0x20000000
[ 61.470360] __mdss_fb_sync_buf_done_callback: mdp-fence: frame timeout
五、总结
对于副屏mipi转EDP相对于主屏来说比较简单,只需要注意各个接口和硬件的匹配