平台:RK3399
KERNEL版本:kernel4.4
Android版本:android7.1
转换IC:LT9211(龙迅) mipi to lvds
接口: iic
总的思路:rk3399平台不支持lvds接口的输出,如果想要接lvds的屏,必须通过mipi转lvds芯片来转接,实现LCD显示,这里使用的IC是龙迅的LT9211。
获取转换IC(mipi to lvds)的datasheet我们主要关注的是该IC的I2c从设备地址,我这里的芯片是龙迅的LT9211,i2c地址是0x2d。主要还是寄存器datasheet还有确认它的ID值(即i2c读取到的芯片id值,一般是复位之后会通过I2C去读取IC的id值,证明i2c总线识别检测到了该芯片)。
获取LCDdatasheet我们主要关注的是屏的分辨率和屏的接口,双路LVDS还是单路LVDS,还有时序参数TIMING这些数据。
LCD的时序参数(timing),一般在屏的datasheet都可以找到:
+ clock-frequency = <148500000>;
+ hactive = <1920>;
+ vactive = <1080>;
+ hback-porch = <65>;
+ hfront-porch = <65>;
+ vback-porch = <20>;
+ vfront-porch = <20>;
+ hsync-len = <10>;
+ vsync-len = <5>;
屏的参数做一下说明:
Horizontal 代表水平方向,HBP行后沿参数 、HFP 行前沿参数,单位是 clocks
Vertical 代表垂直方向,VBP 帧同步信号后肩、VFP 帧同步信号前肩,单位是 lines。
pclk (pixel clock frequence),像素时钟频率,也就是我们在 dts 中填充的 clock-frequence 这个参数。
另外根据以上的信息,我们还能计算出 Mipi Dsi Clock 。
DCLK = 100 + H_total×V_total × fps × 3 × 8 / lanes_nums
total 这里指的是 sync + front + back + active (分辨率值)
比如 H_total = Hsync + HFP(hfront-proch) + HBP(hback-porch) + Hactive
fps 指的是帧率,一般我们按照 60 帧来计算
3 × 8 代表一个 RGB 为 3 个字节,每个字节 8 bit
lanes 代表 mipi data 通道数。
backlight: backlight {
compatible = "pwm-backlight";
pwms = <&pwm1 0 25000 0>; //配置背光信息,这里用pwm1控制背光
brightness-levels = <
0 1 2 3 4 5 6 7
8 9 10 11 12 13 14 15
16 17 18 19 20 21 22 23
24 25 26 27 28 29 30 31
32 33 34 35 36 37 38 39
40 41 42 43 44 45 46 47
48 49 50 51 52 53 54 55
56 57 58 59 60 61 62 63
64 65 66 67 68 69 70 71
72 73 74 75 76 77 78 79
80 81 82 83 84 85 86 87
88 89 90 91 92 93 94 95
96 97 98 99 100 101 102 103
104 105 106 107 108 109 110 111
112 113 114 115 116 117 118 119
120 121 122 123 124 125 126 127
128 129 130 131 132 133 134 135
136 137 138 139 140 141 142 143
144 145 146 147 148 149 150 151
152 153 154 155 156 157 158 159
160 161 162 163 164 165 166 167
168 169 170 171 172 173 174 175
176 177 178 179 180 181 182 183
184 185 186 187 188 189 190 191
192 193 194 195 196 197 198 199
200 201 202 203 204 205 206 207
208 209 210 211 212 213 214 215
216 217 218 219 220 221 222 223
224 225 226 227 228 229 230 231
232 233 234 235 236 237 238 239
240 241 242 243 244 245 246 247
248 249 250 251 252 253 254 255>;
default-brightness-level = <140>;
enable-gpios = <&gpio1 0 GPIO_ACTIVE_HIGH>; //GPIO1_A0,背光IC使能控制的GPIO,这里查原理图知道是GPIO1_A0
pinctrl-names = "default";
pinctrl-0 = <&bl_en>;
};
&dsi { //添加dsi mipi节点,这里&dsi是mipi0的节点,还有mipi1的节点是&dsi1,这里只配置单mipi输出,rk3399有两路mipi输出
status = "okay";
panel@0 {
compatible = "simple-panel-dsi";
reg = <0>;
backlight = <&backlight>;
enable-gpios = <&gpio1 1 GPIO_ACTIVE_HIGH>; //GPIO1_A1,LCD屏使能控制的GPIO,这里查原理图知道是GPIO1_A1
pinctrl-names = "default";
pinctrl-0 =<&lcd_pwr_en>;
dsi,flags = <(MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST)>;
dsi,format = <MIPI_DSI_FMT_RGB888>;
dsi,lanes = <4>;
panel-init-sequence = [ //初始化参数,根据不同的屏规格来填写,一般由屏厂fae提供
05 64 01 11
05 14 01 29
];
panel-exit-sequence = [
05 64 01 28
05 96 01 10
];
display_timings: display-timings {
native-mode = <&timing0>;
timing0: timing0 {
clock-frequency = <148500000>;
hactive = <1920>;
vactive = <1080>;
hback-porch = <65>;
hfront-porch = <65>;
vback-porch = <20>;
vfront-porch = <20>;
hsync-len = <10>;
vsync-len = <5>;
hsync-active = <0>;
vsync-active = <0>;
de-active = <0>;
pixelclk-active = <0>;
};
};
};
};
&pwm1 {
status = "okay";
};
&backlight {
status = "okay";
};
&route_dsi { //打开mipi 显示的UBOOT logo
status = "okay";
connect = <&vopl_out_dsi>;
};
&dsi_in_vopb {
status = "disabled";
};
&dsi_in_vopl { //vop控制器的设置,这里设置在vopl,最大支持2k显示,而vopb最大支持4k显示
status = "okay";
};
&pinctrl { //pinctrl声明一下,不然有时候控制不到GPIO状态
panel {
lcd_pwr_en: lcd-pwr-en {
rockchip,pins = <1 1 RK_FUNC_GPIO &pcfg_pull_none>;
};
};
backlight {
bl_en: bl-en {
rockchip,pins = <1 0 RK_FUNC_GPIO &pcfg_pull_none>;
};
};
}
通过上面的配置,RK3399 的LCD mipi控制器应该就可以输出mipi信号了。
可以拿示波器来量一下mipi信号的波形,可以量到mipi信号脚的脉冲信号了,波形如下图所示。
这里还遇到个问题,就是开机过程中,mipi是有信号的,但是开机之后,mipi信号消失了,查看vop的状态发现已经disabled了。后面查看build.prop才知道,上层做了主副屏的设置,导致系统起来之后,LCD mipi信号被强行关掉了。去掉主副屏设置之后,终于能在开机之后正常输出mipi信号。
可以在产品配置目录下的system.prop里(如 device/rockchip/rk3288/rk3288_mid/system.prop)添加如下两个属性,如果要调试MIPI则需要把mipi接口的也加上,如果不加也行,把这两个属性注释掉,系统会自动匹配:
sys.hwc.device.primary = HDMI-A //设置显示接口作为主显,这里设置HDMI接口为主显
sys.hwc.device.extend = LVDS //设置显示接口作为副显,这里设置LVDS接口为副显
首先硬件上要确认ic的供电正常,I2C上拉,根据原理图确认使用哪组I2C总线。
在IC初始化之前一般都要求先复位,因此需要使用主控IO控制复位脚,以方便控制时序。这里的复位操作就是先拉低,延时100ms,然后拉高IO口,接着就可以读取IC的id了。
分析和解决:后面查看发现是i2c地址不对,这里mcu地址是0x5a,而linux的i2c地址需要右移一位,是0x2d,以前调试的经验是i2c地址值溢出的时候,才会考虑左移或者右移,犯了经验主义的错误,以后要注意,mcu的i2c地址是跟linux的i2c地址不一样的,当然了,如果有资源的话,i2c地址最好参考规格书或者问询ic原厂fae工程师。后面改了i2c地址以后,终于读取到正确的id寄存器值。
分析和解决:system clk int方面寄存器值设置,最好参照DEMO驱动设置,目前lt9211的i2c寄存器地址是类似于0xd0xx一样的,即四个字节的i2c寄存器地址值,分高位和低位。所以写i2c的时候应该这样操作:先往0xff写高位的地址0xd0,然后往低位地址写寄存器值,例如0xd080写入0x01的写法应该是:
lt9211_i2c_write_byte(0xff,0xd0);
lt9211_i2c_write_byte(0x80,0x01); //这样就完成了往寄存器0xd080写入0x01的寄存器值
读i2c寄存器0xd080的做法是:先往0xff写入高位地址0xd0,然后直接去读取低位地址0x80,相当于普通2字节的i2c寄存器读法,例如:
lt9211_i2c_write_byte(0xff,0xd0);
hactv = lt9211_i2c_read_byte(0x82); //这样就可以读取寄存器地址0xd082的值
分析和解决:通过查看代码发现,读取输入的mipi信号的分辨率是通过读取对应寄存器值,然后转换成10进制数值的分辨率。这里只需要正确读取到mipi的分辨率即可对应去设置要输出的lvds信号的时序参数,前后阶参数,pclk相位时钟等参数。这里通过读取0xd082和0xd083计算行分辨率hact,通过读取0xd085和0xd086计算场分辨率vact。
lt9211_i2c_write_byte(0xff,0xd0);
/* 计算从lt9211 mipi 输入端读取到的行分辨率:hact,注意左移和相加,然后转换成10进制数即是行分辨率 */
hact = (lt9211_i2c_read_byte(0x82)<<8) + lt9211_i2c_read_byte(0x83);
hact = hact/3;
/* 计算从lt9211 mipi 输入端读取到的场分辨率:vact */
vact = (lt9211_i2c_read_byte(0x85)<<8) +lt9211_i2c_read_byte(0x86);
分析与解决:通过与龙迅fae沟通得知,时序参数这些要跟rk3399平台mipi设置的时序参数一致,否则无法读取到正确的寄存器状态值值,即无法读取得到rk3399 mipi 信号对应的分辨率。读取到竖屏的分辨率1080x1920,这个跟rk3399 mipi输出的信号有关,填入一组横屏的timing参数即可输出横屏的分辨率值1920x1080。
分析和解决:通过检查代码发现,是自己写代码有问题,读函数那里没有直接返回16进制寄存器值,导致每次都是返回0值了,所以计算得到的分辨率值都为0,后面修改了函数以后,即让读函数接口返回对应寄存器值,就得到了正确分辨率值。修改痕迹如下:
// static int lt9211_i2c_read_byte(u8 regaddr)
static char lt9211_i2c_read_byte(u8 regaddr)
{
unsigned char p_data[1];
if (lt9211_i2c_read(p_data, regaddr, 0x01)>=0) {
pr_info("\n### %s :read reg[0x%x] <==> reg_val[0x%x]. ###\n", __func__, regaddr, p_data[0]);
// return 0; //这里返回0值去掉
return p_data[0];
}else{
pr_info("%s :read reg[0x%x] faild.\n", __func__, regaddr);
return -1;
}
}
分析和解决:后面经过查看和验证,发现是pclk要设置跟rk3399 mipi的参数配置那里一致。
//根据需要调试的屏datesheet设置需要的timing时序参数,前后阶参数(这里的参数要和rk平台mipi的参数配置保持一致)
/* rk3399 dts里面的timing参数配置:
display_timings: display-timings {
native-mode = <&timing0>;
timing0: timing0 {
clock-frequency = <148500000>;
hactive = <1920>;
vactive = <1080>;
hback-porch = <65>;
hfront-porch = <65>;
vback-porch = <20>;
vfront-porch = <20>;
hsync-len = <10>;
vsync-len = <5>;
hsync-active = <0>;
vsync-active = <0>;
de-active = <0>;
pixelclk-active = <0>;
};
*/
//行总像素 Hdisplay + HFP + HSYNC +HBP
//列总像素 Vdisplay + VFP + VSYNC +VBP
//pclk_khz的值跟clock-frequency的值一致,例如这里pclk_khz = 148500000,输出 Frame Rate 60hz左右
//pclk_khz的值跟clock-frequency的值一致,例如这里pclk_khz = 74250000,输出 Frame Rate 30hz左右
//------------------------------------------{ hfp, hs, hbp,hact,htotal,vfp, vs, vbp, vact,vtotal,pclk_khz};
struct video_timing video_640x480_60Hz ={ 8, 96, 40, 640, 800, 33, 2, 10, 480, 525, 25000};
struct video_timing video_720x480_60Hz ={16, 62, 60, 720, 858, 9, 6, 30, 480, 525, 27000};
struct video_timing video_800x480_60Hz ={40, 20, 60, 800, 920, 10, 5, 10, 480, 505, 27876};
struct video_timing video_1280x720_60Hz ={110,40, 220,1280, 1650, 5, 5, 20, 720, 750, 74250};
struct video_timing video_1280x720_30Hz ={110,40, 220,1280, 1650, 5, 5, 20, 720, 750, 74250};
struct video_timing video_1366x768_60Hz ={26, 110,110,1366, 1592, 13, 6, 13, 768, 800, 81000};
struct video_timing video_1920x1080_30Hz ={88, 44, 148,1920, 2200, 4, 5, 36, 1080, 1125, 74250};
struct video_timing video_1920x1080_60Hz ={65, 10, 65,1920, 2060, 20, 5, 20, 1080, 1125, 148500};
struct video_timing video_3840x1080_60Hz ={176,88, 296,3840, 4400, 4, 5, 36, 1080, 1125, 297000};
struct video_timing video_1920x1200_60Hz ={48, 32, 80,1920, 2080, 3, 6, 26, 1200, 1235, 154000};
struct video_timing video_3840x2160_30Hz ={176,88, 296,3840, 4400, 8, 10, 72, 2160, 2250, 297000};
struct video_timing video_3840x2160_60Hz ={176,88, 296,3840, 4400, 8, 10, 72, 2160, 2250, 594000};
分析和解决:通过万用表量i2c的两条总线,发现I2C数据线SCA在拉低了之后就没有再拉高,而CLK一直是高电平状态(1.8v),后面去量取RESET脚的波形发现波形也正常,可以复位。最后再查看发现是接的晶振没起振,而且接的是有缘晶振。(有源晶振,即不需要外接时钟电路通过正常供电就可以有时钟信号输出,示波器可以量取到正弦波形,这里是25Mhz。)。
最好通过示波器探头去点这个晶振发现有时候能起振,有时候不能起振,参考龙迅的参考设计资料,发现少贴了个反馈电阻,贴上之后,显示就正常了。
分析和解决:查看帧率发现设置的lvds帧率太低,只30Hz,需要改为60Hz。前面的屏的datasheet截图有标记。
//------------------------------------------{ hfp, hs, hbp,hact,htotal,vfp, vs, vbp, vact,vtotal,pclk_khz};
struct video_timing video_1920x1080_30Hz ={88, 44, 148,1920, 2200, 4, 5, 36, 1080, 1125, 74250}; //帧率30Hz
struct video_timing video_1920x1080_60Hz ={65, 10, 65,1920, 2060, 20, 5, 20, 1080, 1125, 148500}; //帧率60Hz
所以需要把pclk设置为148500,就可以输出60Hz帧率了。注意,dts那里的clock-frequency也要改一下。
修改方案:每次唤醒都重新复位,重新初始化配置一下ic(lt9211):
+++ b/drivers/gpu/drm/panel/panel-simple.c
@@ -39,6 +39,9 @@
#include
#include
+/* add for lt9211 resume/suspend init */
+extern void lt9211_mipitolvds_init(void);
+
struct cmd_ctrl_hdr {
u8 dtype; /* data type */
u8 wait; /* ms */
@@ -636,6 +639,9 @@ static int panel_simple_enable(struct drm_panel *panel)
if (p->enabled)
return 0;
+ /* add for lt9211 resume/suspend init */
+ lt9211_mipitolvds_init();
+
if (p->desc && p->desc->delay.enable)
panel_simple_sleep(p->desc->delay.enable);
说明:没有打开RK3399端的EOTP模式,MIPI rx setting 的寄存器0xd002寄存器值可以适当增大,目前是0x05。
rk3399_all:/ # io -4 0xff96002c
ff96002c: 00000000 //没有使能EOTP模式
修改方案:
尝试在dts那里直接配置MIPI_DSI_MODE_EOT_PACKET,后面跟代码发现,压根没执行到这个MIPI_DSI_MODE_EOT_PACKET
+ dsi,flags = <(MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
+ MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_EOT_PACKET)>;
只好在drivers/gpu/drm/rockchip/dw-mipi-dsi.c 的dw_mipi_dsi_host_attach接口中强制设置寄存器:
+//add force enable EOTP mode for lt9211
+ printk("# %s, line[%d], enter, enable eotp mode for lt9211\n", __func__, __LINE__);
+ regmap_write(dsi->regmap, DSI_PCKHDL_CFG, 1);
+//add end
后来在测试时候发现,休眠唤醒时候还是会出现黑屏的现象,查看寄存器0xff96002c值发现,在休眠唤醒之后这个寄存器值会改变,变为:0000001c 。查看代码发现,是因为每次休眠唤醒都会去写这个寄存器值0xff96002c,所以又直接在代码里改:直接强制每次休眠唤醒都使能EOTP模式。
--- a/drivers/gpu/drm/rockchip/dw-mipi-dsi.c
+++ b/drivers/gpu/drm/rockchip/dw-mipi-dsi.c
@@ -1122,12 +1122,15 @@ static void dw_mipi_dsi_dpi_config(struct dw_mipi_dsi *dsi,
static void dw_mipi_dsi_packet_handler_config(struct dw_mipi_dsi *dsi)
{
+/*
u32 val = CRC_RX_EN | ECC_RX_EN | BTA_EN | EOTP_TX_EN;
if (dsi->mode_flags & MIPI_DSI_MODE_EOT_PACKET)
val &= ~EOTP_TX_EN;
+*/
- regmap_write(dsi->regmap, DSI_PCKHDL_CFG, val);
+ printk("# %s, line[%d], enable eotp mode for lt9211\n", __func__, __LINE__);
+ regmap_write(dsi->regmap, DSI_PCKHDL_CFG, EOTP_TX_EN); //force enable EOTP mode for lt9211
}
更新之后通过io工具和命令,串口查看寄存器确认:
rk3399_all:/ # io -4 0xff96002c
ff96002c: 00000001
手动执行寄存器写入验证:
rk3399_all:/ # io -w -4 0xff96002c 0x00000001
rk3399_all:/ # io -4 0xff96002c
ff96002c: 00000001
lt9211驱动中寄存器0xd002寄存器值设置成0x0a(如果驱动少了这个寄存器配置,lt9211端mipi信号也会出不来,这点要注意)
/* MIPI settle setting 0x15 */
lt9211_i2c_write_byte(0xff,0xd0);
- lt9211_i2c_write_byte(0x02,0x05); //settle value
+ lt9211_i2c_write_byte(0x02,0x0a); //0x05 //settle value