msm8909_LT8912B_MIPI转HDMI
项目中需要把开发板的MIPI输出信号转换为HDMI和LVDS输出,使用龙迅的LT8912B进行转换。龙迅的FAE提供的资料相对来说还是比较少的。先简单的看一下吧:
厂商资料
寄存器配置
该文件提供了对LT8912B初始化的寄存器配置。对于我们来说需要做的就是,写一个驱动,在开机的时候调用相关的函数,把对应的值写道LT8912B的寄存器里面即可。
MIPI信号要求
- MIPI信号需要关闭展频。
- MIPI的CLK不能进LP
- MIPI data 的一个采样周期不能有两个 LP
- MIPI 信号需要关闭 EOTP
https://klelee-files.oss-cn-qingdao.aliyuncs.com/LT8912B/LT8912B 输入MIPI 信号的要求.pdf
配置文档
开发记录
驱动和设备树配置
根据厂商提供的资料了解到,在开机过程中只需要把上面提到的寄存器写一遍即可,但是在这之前需要先把LT8912B reset上一下电,这个需要一个单独的GPIO去控制。
我们项目中使用LCD的reset去当MIPI的reset键。
设备树的配置
因为我们需要通过IIC去和LT8912B通讯来初始化LT8912B,所以就把reset键挂载对应的IIC总线下即可。我们项目中LT8912B使用的IIC所在的总线是i2c_2.
根据以上信息就可以进行设备树的配置了。
直接修改设备树文件:
kernel/arch/arm/boot/dts/qcom/sc806-evk/msm8909.dtsi
&i2c_2 {
lt8912 {
compatible = "lontium,lt8912";
reg = <0x48>;
pinctrl-names = "default","sleep";
pinctrl-0 = <<_default>;
pinctrl-1 = <<_sleep>;
reset-gpios = <&msm_gpio 25 0x0>;
};
};
kernel/arch/arm/boot/dts/qcom/sc806-evk/msm8909-pinctrl.dtsi
lt8912_reset_pin {
qcom,pins = <&gp 25>;
qcom,pin-func = <0>;
qcom,num-grp-pins = <1>;
label = "lt8912_reset_pin";
lt_default: lt_default {
drive-strength = <6>;
bias-pull-up;
};
lt_sleep: lt_sleep {
drive-strength = <2>;
bias-pull-down;
};
};
kernel/arch/arm/boot/dts/qcom/sc806-evk/dsi-panel-otm1287a-yushun-720p-video.dtsi
&mdss_mdp {
dsi_otm1287a_yushun_720_vid: qcom,mdss_dsi_otm1287a_yushun_720p_video {
qcom,mdss-dsi-panel-name = "OTM1287A 01 720p video mode dsi panel";
qcom,mdss-dsi-panel-controller = <&mdss_dsi0>;
qcom,mdss-dsi-panel-type = "dsi_video_mode"; // 确认是video_mode
qcom,mdss-dsi-panel-destination = "display_1";
qcom,mdss-dsi-panel-framerate = <60>;
qcom,mdss-dsi-virtual-channel-id = <0>;
qcom,mdss-dsi-stream = <0>;
/*
qcom,mdss-dsi-panel-width = <720>;
qcom,mdss-dsi-panel-height = <1280>;
qcom,mdss-dsi-h-front-porch = <52>;
qcom,mdss-dsi-h-back-porch = <100>;
qcom,mdss-dsi-h-pulse-width = <24>;
qcom,mdss-dsi-h-sync-skew = <0>;
qcom,mdss-dsi-v-back-porch = <40>;
qcom,mdss-dsi-v-front-porch = <8>;
qcom,mdss-dsi-v-pulse-width = <4>;
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-panel-width = <1280>;
qcom,mdss-dsi-panel-height = <720>;
qcom,mdss-dsi-h-front-porch = <110>;
qcom,mdss-dsi-h-back-porch = <220>;
qcom,mdss-dsi-h-pulse-width = <40>;
qcom,mdss-dsi-h-sync-skew = <0>;
qcom,mdss-dsi-v-back-porch = <20>;
qcom,mdss-dsi-v-front-porch = <5>;
qcom,mdss-dsi-v-pulse-width = <5>;
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-underflow-color = <0xff>;
qcom,mdss-dsi-border-color = <0>;
qcom,mdss-dsi-on-command = [
39 01 00 00 00 00 04 B9 FF 83 94
39 01 00 00 00 00 07 BA 63 03 68 6B B2 C0
39 01 00 00 00 00 0B B1 48 12 72 09 32 44 71 31 4F 35
39 01 00 00 00 00 06 B2 00 80 64 05 07
39 01 00 00 00 00 1f B4 26 76 26 76 26 26 05 10 86 35 00 3F 26 76 26 76 26 26 05 10 86 3F 00 FF 81 81 81 81 08 01
39 01 00 00 00 00 22 D3 00 00 0F 0F 01 01 10 10 32 10 00 00 00 32 15 04 05 04 32 15 14 05 14 37 33 04 04 37 00 00 47 05 40
39 01 00 00 00 00 2d D5 18 18 25 24 27 26 11 10 15 14 13 12 17 16 01 00 18 18 18 18 18 18 18 18 18 18 05 04 03 02 07 06 18 18 18 18 21 20 23 22 18 18 18 18
39 01 00 00 00 00 2d D6 18 18 22 23 20 21 12 13 16 17 10 11 14 15 06 07 18 18 18 18 18 18 18 18 18 18 02 03 04 05 00 01 18 18 18 18 26 27 24 25 18 18 18 18
39 01 00 00 00 00 3b E0 00 03 0B 0E 10 13 17 15 2D 3D 51 51 5E 75 7C 84 94 9A 98 A6 B2 57 57 5A 60 64 6A 72 7F 00 03 0B 0E 10 13 17 15 2D 3D 51 51 5E 75 7C 84 94 9A 98 A6 B2 57 57 5A 60 64 6A 72 7F
39 01 00 00 00 00 03 B6 4E 4E
39 01 00 00 00 00 02 CC 0B
39 01 00 00 00 00 03 C0 1F 31
39 01 00 00 00 00 02 D2 88
39 01 00 00 00 00 02 D4 02
39 01 00 00 00 00 02 BD 01
39 01 00 00 00 00 02 B1 60
39 01 00 00 00 00 02 BD 00
39 01 00 00 00 00 08 BF 40 81 50 00 1A FC 01
05 01 00 00 c8 00 02 11 00
05 01 00 00 0a 00 02 29 00
];
qcom,mdss-dsi-off-command = [05 01 00 00 00 00 02 28 00
05 01 00 00 00 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-traffic-mode = "non_burst_sync_event"; // 修改为:non_burst_sync_event
qcom,mdss-dsi-force-clock-lane-hs; // 添加这行
qcom,mdss-dsi-always-on; // 添加这行
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 = [79 1a 12 00 3e 42 16 1e 15 03 04 00];
qcom,mdss-dsi-panel-timings = [7F 1C 12 00 40 44 16 1E 17 03 04 00]; // 修改
qcom,mdss-dsi-t-clk-post = <0x04>;
qcom,mdss-dsi-t-clk-pre = <0x1b>;
qcom,mdss-dsi-bl-min-level = <10>;
qcom,mdss-dsi-bl-max-level = <255>;
qcom,mdss-dsi-dma-trigger = "trigger_sw";
qcom,mdss-dsi-mdp-trigger = "none";
qcom,mdss-dsi-bl-pmic-pwm-frequency = <50>;
qcom,mdss-dsi-bl-pmic-bank-select = <0>;
qcom,mdss-dsi-pwm-gpio = <&pm8909_mpps 2 0>;
qcom,mdss-dsi-bl-pmic-control-type = "bl_ctrl_pwm";
qcom,mdss-dsi-bl-ctrl-pwm-invert;
qcom,mdss-dsi-reset-sequence = <1 20>, <0 1>, <1 20>;
qcom,mdss-pan-physical-width-dimension = <59>;
qcom,mdss-pan-physical-height-dimension = <104>;
qcom,cont-splash-enabled;
};
};
驱动编写
遇到的坑后面说,这里先贴驱动代码。
kernel/drivers/video/msm/mdss/lt8912.c
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/irqreturn.h>
#include <linux/kd.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/errno.h>
#include <linux/input.h>
#include <linux/serio.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <asm/irq.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
#include <linux/jiffies.h>
#include <linux/hrtimer.h>
#include <linux/workqueue.h>
#include <linux/regulator/consumer.h>
#include <linux/debugfs.h>
#include <linux/proc_fs.h>
#include <linux/kthread.h>
#include <linux/regmap.h>
#include <linux/pm_runtime.h>
#include "lt8912.h"
#define MIPI_1080P
#define device_name "lt8912"
/*****************************************************************************************/
/* lt8912 相关参数配置 */
uint8_t I2CADR;
#define _HDMI_Output_
#define _LVDS_Output_
enum
{
H_act = 0,
V_act,
H_tol,
V_tol,
H_bp,
H_sync,
V_sync,
V_bp
};
#ifdef _HDMI_Output_
enum {
_32KHz = 0,
_44d1KHz,
_48KHz,
_88d2KHz,
_96KHz,
_176Khz,
_196KHz
};
uint16_t IIS_N[] =
{
4096, // 32K
6272, // 44.1K
6144, // 48K
12544, // 88.2K
12288, // 96K
25088, // 176K
24576 // 196K
};
uint16_t Sample_Freq[] =
{
0x30, // 32K
0x00, // 44.1K
0x20, // 48K
0x80, // 88.2K
0xa0, // 96K
0xc0, // 176K
0xe0 // 196K
};
#endif // _HDMI_Output_
#ifdef _LVDS_Output_
/* 设置输出模式,ByPass模式,MIPI输出多大,LVDS就输出多大;Scaler模式,可以调整缩放比例 */
#define _Bypass_Mode_
// #define _Scaler_Mode_
/* 设置色深 */
#define _8_Bit_Color_ // 24 bit
// #define _6_Bit_Color_ // 18 bit
/* 定义LVDS输出模式 */
#define _VESA_
// #define _JEIDA_
// #define _De_mode_ // only DE
#define _Sync_Mode_ // Hsync + Vsync + DE
/***Output sync mode sync polarity***/
#define _Hsync_polarity_active_high 0x02 // default, POSITIVE
#define _Hsync_polarity_active_low 0x00 // NEGATIVE
#define _Vsync_polarity_active_high 0x01 // default, POSITIVE
#define _Vsync_polarity_active_low 0x00 // NEGATIVE
#define _Hsync_polarity _Hsync_polarity_active_high
#define _Vsync_polarity _Vsync_polarity_active_high
#define _VesaJeidaMode 0x00
#define _DE_Sync_mode 0x00
#ifdef _VESA_
#define _VesaJeidaMode 0x00
#else
#define _VesaJeidaMode 0x20
#endif // _VESA_
#ifdef _De_mode_
#define _DE_Sync_mode 0x08
#else
#define _DE_Sync_mode 0x00
#endif // _De_mode_
#ifdef _8_Bit_Color_
#define _ColorDeepth 0x10
#else
#define _ColorDeepth 0x14
#endif // _8_Bit_Color_
#endif // _LVDS_Output_
#ifdef _HDMI_Output_
u8 AVI_PB0 = 0x00;
u8 AVI_PB1 = 0x00;
u8 AVI_PB2 = 0x00;
/*
Resolution HDMI_VIC
--------------------------------------
640x480 1
720x480P 60Hz 2
720x480i 60Hz 6
720x576P 50Hz 17
720x576i 50Hz 21
1280x720P 24Hz 60
1280x720P 25Hz 61
1280x720P 30Hz 62
1280x720P 50Hz 19
1280x720P 60Hz 4
1920x1080P 24Hz 32
1920x1080P 25Hz 33
1920x1080P 30Hz 34
1920x1080i 50Hz 20
1920x1080i 60Hz 5
1920x1080P 50Hz 31
1920x1080P 60Hz 16
3840x2160 30Hz 95 // 4K30
Other resolution 0(default)
*/
u8 HDMI_VIC = 0x00;
#endif // _HDMI_Output_
/*****************************************************************************************/
/* MIPI 输入信号配置 */
#define MIPI_Lane 4
// 根据前端MIPI输入信号的Timing修改以下宏定义的值:
#define MIPI_H_Active 1280
#define MIPI_V_Active 720
#define MIPI_H_Total 1650
#define MIPI_V_Total 750
#define MIPI_H_FrontPorch 110
#define MIPI_H_SyncWidth 40
#define MIPI_H_BackPorch 220
#define MIPI_V_FrontPorch 5
#define MIPI_V_SyncWidth 5
#define MIPI_V_BackPorch 20
u8 MIPI_Lane_CH_Swap = 0x00;// 00: 0123 normal ; a8 : 3210 swap
#define _PN_Swap_En 0x20
#define _PN_Swap_Dis 0x00
u8 MIPI_Lane_PN_Swap = _PN_Swap_Dis ;
/* VESA配置 */
#define VESA_720x480_60 0
#define VESA_1280x720_60 (VESA_720x480_60+1)
#define VESA_1280x800_60 (VESA_1280x720_60+1)
#define VESA_1024x600_60 (VESA_1280x800_60+1)
#define VESA_1920x1080_60 (VESA_1024x600_60+1)
struct lt8912_data {
struct i2c_client *lt8912_client;
struct regmap *regmap;
struct input_dev *input_hotplug;
struct delayed_work hotplug_work;
int reset_gpio;
int last_val;
};
struct lt8912_data *lt;
/*****************************************************************************************/
/* LT8912 操作函数开始 */
static int lt8912_i2c_write_byte(uint8_t reg, uint8_t val)
{
int ret = 0;
if (!lt) {
printk("dsi0 xxxx%s: Invalid argument\n", __func__);
return -EINVAL;
}
lt->lt8912_client->addr = I2CADR >> 1;
ret = i2c_smbus_write_byte_data(lt->lt8912_client, reg, val);
if (ret)
pr_err_ratelimited("%s: wr err: addr 0x%x, reg 0x%x, val 0x%x\n", __func__, I2CADR, reg, val);
return ret;
}
static int lt8912_i2c_read_byte(uint8_t reg)
{
int ret = 0;
if (!lt) {
printk("dsi0 xxxx%s: Invalid argument\n", __func__);
return -EINVAL;
}
lt->lt8912_client->addr = I2CADR >> 1;
ret = i2c_smbus_read_byte_data(lt->lt8912_client, reg);
if (ret < 0) {
printk("failed to read byte lt index=%d\n", reg);
return -1;
} else {
printk("Like: Read Reg [%02X][%02X]\n", reg, ret);
}
return 0;
}
void MIPI_Digital(void)
{
I2CADR = 0x92;
lt8912_i2c_write_byte( 0x18, (u8)( MIPI_H_SyncWidth % 256 ) ); // hwidth
lt8912_i2c_write_byte( 0x19, (u8)( MIPI_V_SyncWidth % 256 ) ); // vwidth
lt8912_i2c_write_byte( 0x1c, (u8)( MIPI_H_Active % 256 ) ); // H_active[7:0]
lt8912_i2c_write_byte( 0x1d, (u8)( MIPI_H_Active / 256 ) ); // H_active[15:8]
lt8912_i2c_write_byte( 0x1e, 0x67 );
lt8912_i2c_write_byte( 0x2f, 0x0c );
lt8912_i2c_write_byte( 0x34, (u8)( MIPI_H_Total % 256 ) ); // H_total[7:0]
lt8912_i2c_write_byte( 0x35, (u8)( MIPI_H_Total / 256 ) ); // H_total[15:8]
lt8912_i2c_write_byte( 0x36, (u8)( MIPI_V_Total % 256 ) ); // V_total[7:0]
lt8912_i2c_write_byte( 0x37, (u8)( MIPI_V_Total / 256 ) ); // V_total[15:8]
lt8912_i2c_write_byte( 0x38, (u8)( MIPI_V_BackPorch % 256 ) ); // VBP[7:0]
lt8912_i2c_write_byte( 0x39, (u8)( MIPI_V_BackPorch / 256 ) ); // VBP[15:8]
lt8912_i2c_write_byte( 0x3a, (u8)( MIPI_V_FrontPorch % 256 ) ); // VFP[7:0]
lt8912_i2c_write_byte( 0x3b, (u8)( MIPI_V_FrontPorch / 256 ) ); // VFP[15:8]
lt8912_i2c_write_byte( 0x3c, (u8)( MIPI_H_BackPorch % 256 ) ); // HBP[7:0]
lt8912_i2c_write_byte( 0x3d, (u8)( MIPI_H_BackPorch / 256 ) ); // HBP[15:8]
lt8912_i2c_write_byte( 0x3e, (u8)( MIPI_H_FrontPorch % 256 ) ); // HFP[7:0]
lt8912_i2c_write_byte( 0x3f, (u8)( MIPI_H_FrontPorch / 256 ) ); // HFP[15:8]
}
/***********************************************************
***********************************************************/
void DDS_Config(void)
{
I2CADR = 0x92;
lt8912_i2c_write_byte( 0x4e, 0x52 );
lt8912_i2c_write_byte( 0x4f, 0xde );
lt8912_i2c_write_byte( 0x50, 0xc0 );
lt8912_i2c_write_byte( 0x51, 0x80 );
// lt8912_i2c_write_byte( 0x51, 0x00 );
lt8912_i2c_write_byte( 0x1e, 0x4f );
lt8912_i2c_write_byte( 0x1f, 0x5e );
lt8912_i2c_write_byte( 0x20, 0x01 );
lt8912_i2c_write_byte( 0x21, 0x2c );
lt8912_i2c_write_byte( 0x22, 0x01 );
lt8912_i2c_write_byte( 0x23, 0xfa );
lt8912_i2c_write_byte( 0x24, 0x00 );
lt8912_i2c_write_byte( 0x25, 0xc8 );
lt8912_i2c_write_byte( 0x26, 0x00 );
lt8912_i2c_write_byte( 0x27, 0x5e );
lt8912_i2c_write_byte( 0x28, 0x01 );
lt8912_i2c_write_byte( 0x29, 0x2c );
lt8912_i2c_write_byte( 0x2a, 0x01 );
lt8912_i2c_write_byte( 0x2b, 0xfa );
lt8912_i2c_write_byte( 0x2c, 0x00 );
lt8912_i2c_write_byte( 0x2d, 0xc8 );
lt8912_i2c_write_byte( 0x2e, 0x00 );
lt8912_i2c_write_byte( 0x42, 0x64 );
lt8912_i2c_write_byte( 0x43, 0x00 );
lt8912_i2c_write_byte( 0x44, 0x04 );
lt8912_i2c_write_byte( 0x45, 0x00 );
lt8912_i2c_write_byte( 0x46, 0x59 );
lt8912_i2c_write_byte( 0x47, 0x00 );
lt8912_i2c_write_byte( 0x48, 0xf2 );
lt8912_i2c_write_byte( 0x49, 0x06 );
lt8912_i2c_write_byte( 0x4a, 0x00 );
lt8912_i2c_write_byte( 0x4b, 0x72 );
lt8912_i2c_write_byte( 0x4c, 0x45 );
lt8912_i2c_write_byte( 0x4d, 0x00 );
lt8912_i2c_write_byte( 0x52, 0x08 );
lt8912_i2c_write_byte( 0x53, 0x00 );
lt8912_i2c_write_byte( 0x54, 0xb2 );
lt8912_i2c_write_byte( 0x55, 0x00 );
lt8912_i2c_write_byte( 0x56, 0xe4 );
lt8912_i2c_write_byte( 0x57, 0x0d );
lt8912_i2c_write_byte( 0x58, 0x00 );
lt8912_i2c_write_byte( 0x59, 0xe4 );
lt8912_i2c_write_byte( 0x5a, 0x8a );
lt8912_i2c_write_byte( 0x5b, 0x00 );
lt8912_i2c_write_byte( 0x5c, 0x34 );
lt8912_i2c_write_byte( 0x51, 0x00 );
}
#ifdef _HDMI_Output_
void Audio_Config(void)
{
// Audio config
I2CADR = 0x90;
lt8912_i2c_write_byte( 0xB2, 0x01 ); // 0x01:HDMI; 0x00: DVI
I2CADR = 0x94;
lt8912_i2c_write_byte( 0x06, 0x08 ); // 0x09
lt8912_i2c_write_byte( 0x07, 0xF0 ); // enable Audio: 0xF0; Audio Mute: 0x00
lt8912_i2c_write_byte( 0x09, 0x00 ); // 0x00:Left justified; default
lt8912_i2c_write_byte( 0x0f, 0x0b + Sample_Freq[_48KHz] );
lt8912_i2c_write_byte( 0x37, (u8)( IIS_N[_48KHz] / 0x10000 ) );
lt8912_i2c_write_byte( 0x36, (u8)( ( IIS_N[_48KHz] & 0x00FFFF ) / 0x100 ) );
lt8912_i2c_write_byte( 0x35, (u8)( IIS_N[_48KHz] & 0x0000FF ) );
lt8912_i2c_write_byte( 0x34, 0xD2 ); // 32 bit的数据长度
// lt8912_i2c_write_byte( 0x34, 0xE2 ); // 16 bit的数据长度
lt8912_i2c_write_byte( 0x3c, 0x41 ); // Null packet enable
}
void AVI_Config(void)
{
I2CADR = 0x94;
lt8912_i2c_write_byte( 0x3e, 0x0A );
// 0x43寄存器是checksums,改变了0x45或者0x47 寄存器的值,0x43寄存器的值也要跟着变,
// 0x43,0x44,0x45,0x47四个寄存器值的总和是0x6F
HDMI_VIC = 0x04; // 720P 60; Corresponding to the resolution to be output
// HDMI_VIC = 0x10; // 1080P 60
// HDMI_VIC = 0x1F; // 1080P 50
// HDMI_VIC = 0x00; // If the resolution is non-standard, set to 0x00
AVI_PB1 = 0x10; // PB1,color space: YUV444 0x70;YUV422 0x30; RGB 0x10
AVI_PB2 = 0x2A; // PB2; picture aspect rate: 0x19:4:3 ; 0x2A : 16:9
AVI_PB0 = ( ( AVI_PB1 + AVI_PB2 + HDMI_VIC ) <= 0x6f ) ? ( 0x6f - AVI_PB1 - AVI_PB2 - HDMI_VIC ) : ( 0x16f - AVI_PB1 - AVI_PB2 - HDMI_VIC );
lt8912_i2c_write_byte( 0x43, AVI_PB0 ); //avi packet checksum ,avi_pb0
lt8912_i2c_write_byte( 0x44, AVI_PB1 ); //avi packet output RGB 0x10
lt8912_i2c_write_byte( 0x45, AVI_PB2 ); //0x19:4:3 ; 0x2A : 16:9
lt8912_i2c_write_byte( 0x47, HDMI_VIC ); //VIC(as below);1080P60 : 0x10
}
#endif
#ifdef _Bypass_Mode_
void LVDS_Bypass_Config(void)
{
//lvds power up
I2CADR = 0x90; //0x90;
//lt8912_i2c_write_byte( 0x51, 0x05 );
//core pll bypass
lt8912_i2c_write_byte( 0x50, 0x24 ); //cp=50uA
lt8912_i2c_write_byte( 0x51, 0x2d ); //Pix_clk as reference,second order passive LPF PLL
lt8912_i2c_write_byte( 0x52, 0x04 ); //loopdiv=0;use second-order PLL
//PLL CLK
lt8912_i2c_write_byte( 0x69, 0x0e );
lt8912_i2c_write_byte( 0x69, 0x8e );
lt8912_i2c_write_byte( 0x6a, 0x00 );
lt8912_i2c_write_byte( 0x6c, 0xb8 );
lt8912_i2c_write_byte( 0x6b, 0x51 );
lt8912_i2c_write_byte( 0x04, 0xfb ); //core pll reset
lt8912_i2c_write_byte( 0x04, 0xff );
//scaler bypass
lt8912_i2c_write_byte( 0x7f, 0x00 );
lt8912_i2c_write_byte( 0xa8, _VesaJeidaMode + _DE_Sync_mode + _ColorDeepth + _Hsync_polarity + _Vsync_polarity);
msleep( 100 );
lt8912_i2c_write_byte( 0x02, 0xf7 ); //lvds pll reset
lt8912_i2c_write_byte( 0x02, 0xff );
lt8912_i2c_write_byte( 0x03, 0xcb ); //scaler module reset
lt8912_i2c_write_byte( 0x03, 0xfb ); //lvds tx module reset
lt8912_i2c_write_byte( 0x03, 0xff );
}
#endif
void lt8912_reset(void)
{
gpio_direction_output(lt->reset_gpio, 0);
msleep(100);
gpio_direction_output(lt->reset_gpio, 1);
msleep(100);
}
void lt8912_read_test(void)
{
// video check
lt8912_i2c_read_byte( 0x9c );
lt8912_i2c_read_byte( 0x9d );
lt8912_i2c_read_byte( 0x9e );
lt8912_i2c_read_byte( 0x9f );
// pixel clock
lt8912_i2c_read_byte( 0x0c );
lt8912_i2c_read_byte( 0x0d );
lt8912_i2c_read_byte( 0x0e );
lt8912_i2c_read_byte( 0x0f );
// mipi lane
lt8912_i2c_read_byte( 0x13 );
// lane swap
lt8912_i2c_read_byte( 0x15 );
}
void lt8912_start(void)
{
lt8912_reset();
I2CADR = 0x90; // IIC address
/* LVDS 配置 */
lt8912_i2c_write_byte( 0x08, 0xff ); // Register address : 0x08; Value : 0xff
lt8912_i2c_write_byte( 0x09, 0xff );
lt8912_i2c_write_byte( 0x0a, 0xff );
lt8912_i2c_write_byte( 0x0b, 0x7c ); //
lt8912_i2c_write_byte( 0x0c, 0xff );
lt8912_i2c_write_byte( 0x51, 0x15 );
I2CADR = 0x90;
lt8912_i2c_write_byte( 0x31, 0xa1 );
lt8912_i2c_write_byte( 0x32, 0xbf );
lt8912_i2c_write_byte( 0x33, 0x17 ); // bit0/bit1 =1 Turn On HDMI Tx; bit0/bit1 = 0 Turn Off HDMI Tx
lt8912_i2c_write_byte( 0x37, 0x00 );
lt8912_i2c_write_byte( 0x38, 0x22 );
lt8912_i2c_write_byte( 0x60, 0x82 );
I2CADR = 0x90;
lt8912_i2c_write_byte( 0x39, 0x45 );
lt8912_i2c_write_byte( 0x3a, 0x00 );
lt8912_i2c_write_byte( 0x3b, 0x00 );
I2CADR = 0x90;
lt8912_i2c_write_byte( 0x3e, 0x96 + MIPI_Lane_PN_Swap ); //
lt8912_i2c_write_byte( 0x41, 0x7c ); //HS_eq current
I2CADR = 0x90;
lt8912_i2c_write_byte( 0x44, 0x31 );
lt8912_i2c_write_byte( 0x55, 0x44 );
lt8912_i2c_write_byte( 0x57, 0x01 );
lt8912_i2c_write_byte( 0x5a, 0x02 );
I2CADR = 0x92;
lt8912_i2c_write_byte( 0x10, 0x01 ); // 0x05
lt8912_i2c_write_byte( 0x11, 0x08 ); // 0x12
lt8912_i2c_write_byte( 0x12, 0x04 );
lt8912_i2c_write_byte( 0x13, MIPI_Lane % 0x04 ); // 00 4 lane // 01 lane // 02 2lane //03 3 lane
lt8912_i2c_write_byte( 0x14, 0x00 );
lt8912_i2c_write_byte( 0x15, MIPI_Lane_CH_Swap ); // 00: 0123 normal ; a8 : 3210 swap
lt8912_i2c_write_byte( 0x1a, 0x03 );
lt8912_i2c_write_byte( 0x1b, 0x03 );
// 设置 MIPI Timing
MIPI_Digital();
DDS_Config();
#ifdef _HDMI_Output_
Audio_Config();
AVI_Config();
#endif
I2CADR = 0x90;
lt8912_i2c_write_byte( 0x03, 0x7f ); // mipi rx reset
msleep( 10 );
lt8912_i2c_write_byte( 0x03, 0xff );
lt8912_i2c_write_byte( 0x05, 0xfb ); // DDS reset
msleep( 10 );
lt8912_i2c_write_byte( 0x05, 0xff );
#ifdef _LVDS_Output_
#ifdef _Bypass_Mode_
LVDS_Bypass_Config();
#endif
I2CADR = 0x90;
lt8912_i2c_write_byte( 0x44, 0x30 ); // Turn on LVDS output
#endif
I2CADR = 0x90; // IIC address
lt8912_read_test();
}
EXPORT_SYMBOL_GPL(lt8912_start);
/*****************************************************************************************/
static const struct of_device_id of_lt8912_match[] = {
{ .compatible = "lontium,lt8912",},
{},
};
static const struct i2c_device_id lt8912_id[] = {
{device_name, 0},
{}
};
MODULE_DEVICE_TABLE(i2c, lt8912_id);
static int lt8912_parse_dt(struct device *dev)
{
struct device_node *np = dev->of_node;
lt->reset_gpio = of_get_named_gpio(np, "reset-gpios", 0);
printk("lt8912_reset_gpio number: %d\n", lt->reset_gpio);
if (lt->reset_gpio < 0)
return lt->reset_gpio;
return 0;
}
static int lontium_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret = 0;
printk("Like: %s enter!\n", __func__);
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
dev_err(&client->dev, "lt8912 i2c check failed.\n");
return -ENODEV;
}
lt = devm_kzalloc(&client->dev, sizeof(struct lt8912_data), GFP_KERNEL);
lt->lt8912_client = client;
if (client->dev.of_node) {
ret = lt8912_parse_dt(&client->dev);
if (ret) {
printk("Like: lt8912_parse_dt failed!\n");
goto out;
}
} else {
printk("Like: lt8912 device tree is failed!\n");
ret = -ENODEV;
goto out;
}
dev_set_drvdata(&client->dev, lt);
if (gpio_is_valid(lt->reset_gpio)) {
ret = gpio_request(lt->reset_gpio, "lt8912_reset_gpio");
if (ret) {
printk("Like: reset gpio request failed!\n");
goto out;
}
printk("Like: enter gpio_request!\n");
ret = gpio_direction_output(lt->reset_gpio, 0);
if (ret) {
printk("set_direction for reset gpio failed\n");
goto free_reset_gpio;
}
msleep(20);
gpio_set_value_cansleep(lt->reset_gpio, 1);
printk("Like: enter gpio_set_value_cansleep\n");
}
lt8912_start();
pm_runtime_enable(&client->dev);
pm_runtime_set_active(&client->dev);
free_reset_gpio:
if (gpio_is_valid(lt->reset_gpio))
gpio_free(lt->reset_gpio);
out:
printk("Like: lt8912_probe exit...\n");
return ret;
}
static int lontium_i2c_remove(struct i2c_client *client)
{
lt = dev_get_drvdata(&client->dev);
pm_runtime_disable(&client->dev);
if (gpio_is_valid(lt->reset_gpio))
gpio_free(lt->reset_gpio);
return 0;
}
static int lt8912_suspend(struct device *tdev) {
printk("Like: lt8912 will suspend!\n");
// gpio_set_value(lt->reset_gpio, 0);
return 0;
}
static int lt8912_resume(struct device *tdev) {
printk("Like: Will restart lt8912!\n");
lt8912_start();
return 0;
}
static const struct dev_pm_ops lt8912_pm_ops =
{
.suspend = lt8912_suspend,
.resume = lt8912_resume,
};
static struct i2c_driver lontium_i2c_driver = {
.driver = {
.name = "lt8912",
.owner = THIS_MODULE,
.of_match_table = of_lt8912_match,
.pm = <8912_pm_ops,
},
.probe = lontium_i2c_probe,
.remove = lontium_i2c_remove,
.id_table = lt8912_id,
};
static int __init lontium_i2c_test_init(void)
{
int ret;
printk("Like: lt8912b driver enter!\n");
ret = i2c_add_driver(&lontium_i2c_driver);
return ret;
}
static void __exit lontium_i2c_test_exit(void)
{
printk("Like: lt8912b driver exit!\n");
i2c_del_driver(&lontium_i2c_driver);
}
module_init(lontium_i2c_test_init);
module_exit(lontium_i2c_test_exit);
MODULE_AUTHOR("like <like@aucma.com.cn>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("LT8912 MIPI·DSI to HDMI and LVDS");
LK配置
Kernel阶段对屏的选择来自LK,所以LK的配置也是必要的。
panel配置
分辨率修改
bootable/bootloader/lk/dev/gcdb/display/include/panel_otm1287a_yushun_720p_video.h
static struct panel_resolution otm1287a_yushun_720p_video_panel_res = {
// 720, 1280, 52, 100, 24, 0, 8, 40, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0
1280, 720, 110, 220, 40, 0, 5, 20, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
timings修改
bootloader/lk/dev/gcdb/display/include/panel_otm1287a_yushun_720p_video.h
static const uint32_t otm1287a_yushun_720p_video_timings[] = {
// 0x79 ,0x1a ,0x12 ,0x00 ,0x3e ,0x42 ,0x16 ,0x1e ,0x15 ,0x03 ,0x04 ,0x00
0x7F, 0x1c, 0x12, 0x00, 0x40, 0x44, 0x16, 0x1e, 0x17, 0x03, 0x04, 0x00
};
lane配置
bootloader/lk/dev/gcdb/display/include/panel_otm1287a_yushun_720p_video.h
static struct lane_configuration otm1287a_yushun_720p_video_lane_config = {
4, 0, 1, 1, 1, 1, 1 // 多加一个1
};
强制CLK使用HS模式
bootable/bootloader/lk/dev/gcdb/display/panel_display.c
/* Data lane configuraiton */
pinfo->mipi.num_of_lanes = pstruct->laneconfig->dsi_lanes;
pinfo->mipi.data_lane0 = pstruct->laneconfig->lane0_state;
pinfo->mipi.data_lane1 = pstruct->laneconfig->lane1_state;
pinfo->mipi.data_lane2 = pstruct->laneconfig->lane2_state;
pinfo->mipi.data_lane3 = pstruct->laneconfig->lane3_state;
pinfo->mipi.lane_swap = pstruct->laneconfig->dsi_lanemap;
pinfo->mipi.force_clk_lane_hs = pstruct->laneconfig->force_clk_lane_hs; // 删不删都行
pinfo->mipi.force_clk_lane_hs = 1; // 添加这行
其实要写的代码很少,要做的配置也不多,但是MIPI转HDMI这件事情整整调了一个月。其中不乏硬件挖的坑、厂商驱动给你挖的坑,下面我一一道来,没兴趣的到这里已经下课了。
遇到的坑
一号坑:IIC不通
形似下面这样的log
i2c-msm-v2 78b6000.i2c: NACK: slave not responding, ensure its powered: msgs(n:1 cur:0 tx) bc(rx:0 tx:2) mode:FIFO slv_addr:0x48 MSTR_STS:0x011363c8 OPER:0x00000090
结论:IIC的SDK和SCL接反了。结束
二号坑:MIPI CLK不连续
形似这样子的CLK,就是龙迅说的会进LP。对屏幕造成的最直接的现象就是屏幕会不断的刷新。哗哗的。
一定要首先确保CLK在LK里面是连续的。
然后是kernel里面要连续:
可以先只改这两处,别的地方包括驱动都不要写,先确保CLK是连续的,类似于正弦波。(我们的示波器不太行,这里就不插图片了)
三号坑:HDMI不亮了
在CLK调好之后发现HDMI突然不亮了!!!这个时候真的一点头绪都没有,就去看原理图,意外发现原理图中PN没有反接,但我记得驱动里面似乎swap_en了!!!
所以解决方案是:_PN_Swap_Dis
四号坑:休眠唤醒之后不亮
休眠唤醒之后需要重新初始化LT8912B
另外,dtsi也需要配置:
到这里,我的HDMI就很正常的显示啦,还有什么问题再记录吧。