1044 lines
26 KiB
C
1044 lines
26 KiB
C
#include "panel-lt8911.h"
|
|
#include <linux/version.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/regulator/consumer.h>
|
|
|
|
#define ILI9881_PAGE(_page) DSI_DCS_WRITE(dsi, 0xff, 0x98, 0x81, _page)
|
|
#define IILI9881_COMMAND(_cmd, _data...) DSI_DCS_WRITE(dsi, _cmd, _data)
|
|
#define DCS_CMD_READ_ID1 0xDA
|
|
|
|
#define LT_8911_I2C_ADAPTER 3
|
|
#define LT_8911_I2C_ADDR 0x45
|
|
|
|
static struct i2c_mipi_dsi g_lt8911_mipi_dsi;
|
|
static bool g_is_std_suspend __nosavedata;
|
|
|
|
static const struct drm_display_mode lt8911_default_mode = {
|
|
.clock = 152840,
|
|
.hdisplay = 1920,
|
|
.hsync_start = 1920 + 140,
|
|
.hsync_end = 1920 + 140 + 160,
|
|
.htotal = 1920 + 140 + 160 + 30,
|
|
|
|
.vdisplay = 1080,
|
|
.vsync_start = 1080 + 18,
|
|
.vsync_end = 1080 + 18 + 28,
|
|
.vtotal = 1080 + 18 + 28 + 6,
|
|
|
|
.width_mm = 110,
|
|
.height_mm = 62,
|
|
.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
|
|
};
|
|
|
|
static struct panel_data lt8911_panel_data = {
|
|
.display_mode = (struct drm_display_mode *)<8911_default_mode,
|
|
.mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_VIDEO_BURST,
|
|
.format = MIPI_DSI_FMT_RGB888,
|
|
.lanes = 4,
|
|
};
|
|
|
|
enum {
|
|
hfp = 0,
|
|
hs,
|
|
hbp,
|
|
hact,
|
|
htotal,
|
|
vfp,
|
|
vs,
|
|
vbp,
|
|
vact,
|
|
vtotal,
|
|
pclk_10khz
|
|
};
|
|
|
|
static int mipi_timing[] = {
|
|
140, /* hfp */
|
|
30, /* hs */
|
|
160, /* hbp */
|
|
1920, /* hact */
|
|
2250, /* htotal */
|
|
18, /* vfp */
|
|
6, /* vs */
|
|
28, /* vbp */
|
|
1080, /* vact */
|
|
1132, /* vtotal */
|
|
15284 /* pixel_clk / 10000 */
|
|
};
|
|
|
|
static int lt8911_i2c_write(struct i2c_client *client, uint8_t reg, uint8_t val)
|
|
{
|
|
int ret = -1;
|
|
int retries = 0;
|
|
uint8_t buf[2] = { reg, val };
|
|
struct i2c_msg msg = {
|
|
.flags = !I2C_M_RD,
|
|
.addr = client->addr,
|
|
.len = 2,
|
|
.buf = buf,
|
|
};
|
|
|
|
while (retries < 5) {
|
|
ret = i2c_transfer(client->adapter, &msg, 1);
|
|
if (ret == 1)
|
|
return 0;
|
|
retries++;
|
|
}
|
|
|
|
DBG_FUNC("%s: write addr 0x%02x error! ret = %d\n",
|
|
__func__, reg, ret);
|
|
return ret;
|
|
}
|
|
|
|
static int lt8911_i2c_read(struct i2c_client *client, uint8_t reg)
|
|
{
|
|
int ret = -1;
|
|
int retries = 0;
|
|
uint8_t buf[2] = { reg, 0 };
|
|
struct i2c_msg msgs[2];
|
|
|
|
msgs[0].flags = client->flags;
|
|
msgs[0].addr = client->addr;
|
|
msgs[0].len = 1;
|
|
msgs[0].buf = &buf[0];
|
|
|
|
msgs[1].flags = client->flags | I2C_M_RD;
|
|
msgs[1].addr = client->addr;
|
|
msgs[1].len = 1;
|
|
msgs[1].buf = &buf[1];
|
|
|
|
while (retries < 5) {
|
|
ret = i2c_transfer(client->adapter, msgs, 2);
|
|
if (ret == 2)
|
|
return buf[1];
|
|
retries++;
|
|
}
|
|
|
|
DBG_FUNC("%s: read addr 0x%02x error! ret = %d\n",
|
|
__func__, reg, ret);
|
|
return ret;
|
|
}
|
|
|
|
static void lt8911_reset(struct i2c_mipi_dsi *md)
|
|
{
|
|
if (!gpio_is_valid(md->reset_pin))
|
|
return;
|
|
|
|
gpio_set_value(md->reset_pin, 0);
|
|
msleep(md->rst_delay_ms);
|
|
gpio_set_value(md->reset_pin, 1);
|
|
msleep(md->rst_delay_ms);
|
|
}
|
|
|
|
static void lt8911exb_cfg_set_mipi_timing(struct i2c_mipi_dsi *md)
|
|
{
|
|
struct i2c_client *client = md->client;
|
|
|
|
/* lt8911exb MIPI video timing configuration */
|
|
lt8911_i2c_write(client, 0xff, 0xd0);
|
|
lt8911_i2c_write(client, 0x0d, (u8)(mipi_timing[vtotal] / 256));
|
|
lt8911_i2c_write(client, 0x0e, (u8)(mipi_timing[vtotal] % 256));
|
|
lt8911_i2c_write(client, 0x0f, (u8)(mipi_timing[vact] / 256));
|
|
lt8911_i2c_write(client, 0x10, (u8)(mipi_timing[vact] % 256));
|
|
lt8911_i2c_write(client, 0x11, (u8)(mipi_timing[htotal] / 256));
|
|
lt8911_i2c_write(client, 0x12, (u8)(mipi_timing[htotal] % 256));
|
|
lt8911_i2c_write(client, 0x13, (u8)(mipi_timing[hact] / 256));
|
|
lt8911_i2c_write(client, 0x14, (u8)(mipi_timing[hact] % 256));
|
|
lt8911_i2c_write(client, 0x15, (u8)(mipi_timing[vs] % 256));
|
|
lt8911_i2c_write(client, 0x16, (u8)(mipi_timing[hs] % 256));
|
|
lt8911_i2c_write(client, 0x17, (u8)(mipi_timing[vfp] / 256));
|
|
lt8911_i2c_write(client, 0x18, (u8)(mipi_timing[vfp] % 256));
|
|
lt8911_i2c_write(client, 0x19, (u8)(mipi_timing[hfp] / 256));
|
|
lt8911_i2c_write(client, 0x1a, (u8)(mipi_timing[hfp] % 256));
|
|
}
|
|
|
|
static void lt8911exb_cfg_set_edp_timing(struct i2c_mipi_dsi *md)
|
|
{
|
|
struct i2c_client *client = md->client;
|
|
|
|
/* lt8911exb eDP video timing configuration */
|
|
lt8911_i2c_write(client, 0xff, 0xa8);
|
|
lt8911_i2c_write(client, 0x2d, 0x88);
|
|
lt8911_i2c_write(client, 0x05,
|
|
(u8)(mipi_timing[htotal] / 256));
|
|
lt8911_i2c_write(client, 0x06,
|
|
(u8)(mipi_timing[htotal] % 256));
|
|
lt8911_i2c_write(client, 0x07,
|
|
(u8)((mipi_timing[hs] + mipi_timing[hbp]) / 256));
|
|
lt8911_i2c_write(client, 0x08,
|
|
(u8)((mipi_timing[hs] + mipi_timing[hbp]) % 256));
|
|
lt8911_i2c_write(client, 0x09,
|
|
(u8)(mipi_timing[hs] / 256));
|
|
lt8911_i2c_write(client, 0x0a,
|
|
(u8)(mipi_timing[hs] % 256));
|
|
lt8911_i2c_write(client, 0x0b,
|
|
(u8)(mipi_timing[hact] / 256));
|
|
lt8911_i2c_write(client, 0x0c,
|
|
(u8)(mipi_timing[hact] % 256));
|
|
lt8911_i2c_write(client, 0x0d,
|
|
(u8)(mipi_timing[vtotal] / 256));
|
|
lt8911_i2c_write(client, 0x0e,
|
|
(u8)(mipi_timing[vtotal] % 256));
|
|
lt8911_i2c_write(client, 0x11,
|
|
(u8)((mipi_timing[vs] + mipi_timing[vbp]) / 256));
|
|
lt8911_i2c_write(client, 0x12,
|
|
(u8)((mipi_timing[vs] + mipi_timing[vbp]) % 256));
|
|
lt8911_i2c_write(client, 0x14,
|
|
(u8)(mipi_timing[vs] % 256));
|
|
lt8911_i2c_write(client, 0x15,
|
|
(u8)(mipi_timing[vact] / 256));
|
|
lt8911_i2c_write(client, 0x16,
|
|
(u8)(mipi_timing[vact] % 256));
|
|
}
|
|
|
|
static void lt8911exb_cfg_init_regs(struct i2c_mipi_dsi *md)
|
|
{
|
|
u32 val = 0;
|
|
u8 i, pcr_pll_postdiv, pcr_m;
|
|
struct i2c_client *client = md->client;
|
|
u8 swing_ds1[13][2] = {
|
|
{ 0x83, 0x00 }, /* 27.8 mA */
|
|
{ 0x82, 0xe0 }, /* 26.2 mA */
|
|
{ 0x82, 0xc0 }, /* 24.6 mA */
|
|
{ 0x82, 0xa0 }, /* 23.0 mA */
|
|
{ 0x82, 0x80 }, /* 21.4 mA */
|
|
{ 0x82, 0x40 }, /* 18.2 mA */
|
|
{ 0x82, 0x20 }, /* 16.6 mA */
|
|
{ 0x82, 0x00 }, /* 15.0 mA */
|
|
{ 0x81, 0x00 }, /* 12.8 mA */
|
|
{ 0x80, 0xe0 }, /* 11.2 mA */
|
|
{ 0x80, 0xc0 }, /* 9.6 mA */
|
|
{ 0x80, 0xa0 }, /* 8 mA */
|
|
{ 0x80, 0x80 } /* 6 mA */
|
|
};
|
|
|
|
/* initialization */
|
|
lt8911_i2c_write(client, 0xff, 0x81);
|
|
lt8911_i2c_write(client, 0x49, 0xff);
|
|
lt8911_i2c_write(client, 0xff, 0x82);
|
|
lt8911_i2c_write(client, 0x5a, 0x0e);
|
|
|
|
/* MIPI Rx analog */
|
|
lt8911_i2c_write(client, 0xff, 0x82);
|
|
lt8911_i2c_write(client, 0x32, 0x51);
|
|
lt8911_i2c_write(client, 0x35, 0x22);
|
|
lt8911_i2c_write(client, 0x4c, 0x0c);
|
|
lt8911_i2c_write(client, 0x4d, 0x00);
|
|
|
|
lt8911_i2c_write(client, 0x3a, 0x77);
|
|
lt8911_i2c_write(client, 0x3b, 0x77);
|
|
|
|
/* dessc_pcr pll analog */
|
|
lt8911_i2c_write(client, 0xff, 0x82);
|
|
lt8911_i2c_write(client, 0x6a, 0x40);
|
|
lt8911_i2c_write(client, 0x6b, 0x40);
|
|
|
|
if (mipi_timing[pclk_10khz] < 8800) {
|
|
/* 0x44: pre-div = 2, pixel_clk = 44~88MHz */
|
|
lt8911_i2c_write(client, 0x6e, 0x82);
|
|
pcr_pll_postdiv = 0x08;
|
|
} else {
|
|
/* 0x40: pre-div = 1, pixel_clk = 88~176MHz */
|
|
lt8911_i2c_write(client, 0x6e, 0x81);
|
|
pcr_pll_postdiv = 0x04;
|
|
}
|
|
pcr_m = (u8)(mipi_timing[pclk_10khz] * pcr_pll_postdiv / 25 / 100);
|
|
|
|
/* dessc pll digital */
|
|
lt8911_i2c_write(client, 0xff, 0x85);
|
|
lt8911_i2c_write(client, 0xa9, 0x31);
|
|
lt8911_i2c_write(client, 0xaa, 0x17);
|
|
lt8911_i2c_write(client, 0xab, 0xba);
|
|
lt8911_i2c_write(client, 0xac, 0xe1);
|
|
lt8911_i2c_write(client, 0xad, 0x47);
|
|
lt8911_i2c_write(client, 0xae, 0x01);
|
|
lt8911_i2c_write(client, 0xae, 0x11);
|
|
|
|
/* digital top */
|
|
lt8911_i2c_write(client, 0xff, 0x85);
|
|
lt8911_i2c_write(client, 0xc0, 0x01);/* select mipi rx */
|
|
|
|
if (md->edp_depth == 6)
|
|
val = 0xd0; /* enable dither */
|
|
else if (md->edp_depth == 8)
|
|
val = 0x00; /* disable dither */
|
|
lt8911_i2c_write(client, 0xb0, val);
|
|
|
|
/* MIPI Rx digital */
|
|
lt8911_i2c_write(client, 0xff, 0xd0);
|
|
/* 0: 4 lane; 1: 1 lane; 2: 2 lane; 3: 3 lane */
|
|
lt8911_i2c_write(client, 0x00, md->mipi_lane_cnt % 4);
|
|
lt8911_i2c_write(client, 0x02, 0x08);
|
|
lt8911_i2c_write(client, 0x08, 0x00);
|
|
lt8911_i2c_write(client, 0x0a, 0x12);/* pcr mode */
|
|
lt8911_i2c_write(client, 0x0c, 0x40);
|
|
|
|
lt8911_i2c_write(client, 0x1c, 0x3a);
|
|
lt8911_i2c_write(client, 0x31, 0x0a);
|
|
|
|
lt8911_i2c_write(client, 0x3f, 0x10);
|
|
lt8911_i2c_write(client, 0x40, 0x20);
|
|
lt8911_i2c_write(client, 0x41, 0x30);
|
|
|
|
#ifdef TEST_PATTERN
|
|
lt8911_i2c_write(client, 0x26, pcr_m | 0x80);
|
|
#else
|
|
lt8911_i2c_write(client, 0x26, pcr_m);
|
|
#endif
|
|
|
|
lt8911_i2c_write(client, 0x27, 0x28);
|
|
lt8911_i2c_write(client, 0x28, 0xf8);
|
|
|
|
lt8911_i2c_write(client, 0xff, 0x81);/* pcr reset */
|
|
lt8911_i2c_write(client, 0x03, 0x7b);
|
|
lt8911_i2c_write(client, 0x03, 0xff);
|
|
|
|
/* Tx PLL 2.7GHz */
|
|
lt8911_i2c_write(client, 0xff, 0x87);
|
|
lt8911_i2c_write(client, 0x19, 0x31);
|
|
lt8911_i2c_write(client, 0xff, 0x82);
|
|
lt8911_i2c_write(client, 0x02, 0x42);
|
|
lt8911_i2c_write(client, 0x03, 0x00);
|
|
lt8911_i2c_write(client, 0x03, 0x01);
|
|
lt8911_i2c_write(client, 0xff, 0x81);
|
|
lt8911_i2c_write(client, 0x09, 0xfc);
|
|
lt8911_i2c_write(client, 0x09, 0xfd);
|
|
lt8911_i2c_write(client, 0xff, 0x87);
|
|
lt8911_i2c_write(client, 0x0c, 0x11);
|
|
|
|
for (i = 0; i < 5; i++) {
|
|
msleep(5);
|
|
if (lt8911_i2c_read(client, 0x37) & 0x02) {
|
|
DBG_FUNC("%s: lt8911exb tx pll locked\n",
|
|
__func__);
|
|
break;
|
|
}
|
|
|
|
DBG_FUNC("%s: lt8911exb tx pll unlocked\n", __func__);
|
|
lt8911_i2c_write(client, 0xff, 0x81);
|
|
lt8911_i2c_write(client, 0x09, 0xfc);
|
|
lt8911_i2c_write(client, 0x09, 0xfd);
|
|
lt8911_i2c_write(client, 0xff, 0x87);
|
|
lt8911_i2c_write(client, 0x0c, 0x10);
|
|
lt8911_i2c_write(client, 0x0c, 0x11);
|
|
}
|
|
|
|
/* Tx PHY */
|
|
lt8911_i2c_write(client, 0xff, 0x82);
|
|
lt8911_i2c_write(client, 0x11, 0x00);
|
|
lt8911_i2c_write(client, 0x13, 0x10);
|
|
lt8911_i2c_write(client, 0x14, 0x0c);
|
|
lt8911_i2c_write(client, 0x14, 0x08);
|
|
lt8911_i2c_write(client, 0x13, 0x20);
|
|
lt8911_i2c_write(client, 0xff, 0x82);
|
|
lt8911_i2c_write(client, 0x0e, 0x25);
|
|
lt8911_i2c_write(client, 0x12, 0xff);
|
|
|
|
/* eDP tx digital */
|
|
lt8911_i2c_write(client, 0xff, 0xa8);
|
|
|
|
#ifdef TEST_PATTERN
|
|
/* bit[2:0]: test panttern image mode */
|
|
lt8911_i2c_write(client, 0x24, 0x50);
|
|
/* bit[6:4]: test pattern color */
|
|
lt8911_i2c_write(client, 0x25, 0x70);
|
|
/* 0x50: pattern; 0x10: mipi video */
|
|
lt8911_i2c_write(client, 0x27, 0x50);
|
|
#else
|
|
/* 0x50: pattern; 0x10: mipi video */
|
|
lt8911_i2c_write(client, 0x27, 0x10);
|
|
#endif
|
|
|
|
if (md->edp_depth == 6)
|
|
val = 0x00;
|
|
else if (md->edp_depth == 8)
|
|
val = 0x10;
|
|
lt8911_i2c_write(client, 0x17, val);
|
|
lt8911_i2c_write(client, 0x18, val << 1);
|
|
|
|
lt8911_i2c_write(client, 0xff, 0xa0);
|
|
lt8911_i2c_write(client, 0x00, 0x08);
|
|
lt8911_i2c_write(client, 0x01, 0x00);
|
|
|
|
/* set eDP drive strength */
|
|
lt8911_i2c_write(client, 0xff, 0x82);
|
|
/* lane 0 tap0 */
|
|
lt8911_i2c_write(client, 0x22, swing_ds1[0][0]);
|
|
lt8911_i2c_write(client, 0x23, swing_ds1[0][1]);
|
|
/* lane 0 tap1 */
|
|
lt8911_i2c_write(client, 0x24, 0x80);
|
|
lt8911_i2c_write(client, 0x25, 0x00);
|
|
/* lane 1 tap0 */
|
|
lt8911_i2c_write(client, 0x26, swing_ds1[0][0]);
|
|
lt8911_i2c_write(client, 0x27, swing_ds1[0][1]);
|
|
/* lane 1 tap1 */
|
|
lt8911_i2c_write(client, 0x28, 0x80);
|
|
lt8911_i2c_write(client, 0x29, 0x00);
|
|
}
|
|
|
|
/*
|
|
* MIPI signal from SoC should be ready before
|
|
* configuring below video check setting
|
|
*/
|
|
static void lt8911exb_dbg_check_mipi_timing(struct i2c_mipi_dsi *md)
|
|
{
|
|
u32 val = 0;
|
|
struct i2c_client *client = md->client;
|
|
|
|
/* MIPI byte clk check */
|
|
lt8911_i2c_write(client, 0xff, 0x85);
|
|
/* FM select byte clk */
|
|
lt8911_i2c_write(client, 0x1d, 0x00);
|
|
lt8911_i2c_write(client, 0x40, 0xf7);
|
|
lt8911_i2c_write(client, 0x41, 0x30);
|
|
/* eDP scramble mode; video chech from mipi */
|
|
lt8911_i2c_write(client, 0xa1, 0x02);
|
|
/* 0xf0: close scramble; 0xD0: open scramble */
|
|
//lt8911_i2c_write(client, 0x17, 0xf0);
|
|
|
|
/* video check reset */
|
|
lt8911_i2c_write(client, 0xff, 0x81);
|
|
lt8911_i2c_write(client, 0x09, 0x7d);
|
|
lt8911_i2c_write(client, 0x09, 0xfd);
|
|
|
|
lt8911_i2c_write(client, 0xff, 0x85);
|
|
//msleep(200);
|
|
msleep(10);
|
|
if (lt8911_i2c_read(client, 0x50) == 0x03) {
|
|
val = lt8911_i2c_read(client, 0x4d);
|
|
val = (val << 8) + lt8911_i2c_read(client, 0x4e);
|
|
val = (val << 8) + lt8911_i2c_read(client, 0x4f);
|
|
/* MIPI clk = val * 1000 */
|
|
DBG_FUNC("%s: video check: mipi clk = %d\n",
|
|
__func__, val);
|
|
} else {
|
|
DBG_FUNC("%s: video check: mipi clk unstable",
|
|
__func__);
|
|
}
|
|
|
|
/* MIPI Vtotal check */
|
|
val = lt8911_i2c_read(client, 0x76);
|
|
val = (val << 8) + lt8911_i2c_read(client, 0x77);
|
|
DBG_FUNC("%s: video check: Vtotal = %d\n",
|
|
__func__, val);
|
|
|
|
/* MIPI word count check */
|
|
lt8911_i2c_write(client, 0xff, 0xd0);
|
|
val = lt8911_i2c_read(client, 0x82);
|
|
val = (val << 8) + lt8911_i2c_read(client, 0x83);
|
|
val = val / 3;
|
|
DBG_FUNC("%s: video check: Hact(word counter) = %d\n",
|
|
__func__, val);
|
|
|
|
/* MIPI Vact check */
|
|
val = lt8911_i2c_read(client, 0x85);
|
|
val = (val << 8) + lt8911_i2c_read(client, 0x86);
|
|
DBG_FUNC("%s: video check: Vact = %d\n",
|
|
__func__, val);
|
|
}
|
|
|
|
static void lt8911exb_link_train_start(struct i2c_mipi_dsi *md)
|
|
{
|
|
struct i2c_client *client = md->client;
|
|
|
|
/* lt8911exb link training */
|
|
lt8911_i2c_write(client, 0xff, 0x85);
|
|
/* eDP scramble mode */
|
|
lt8911_i2c_write(client, 0xa1, 0x02);
|
|
|
|
/* AUX setup */
|
|
lt8911_i2c_write(client, 0xff, 0xac);
|
|
/* soft link training */
|
|
lt8911_i2c_write(client, 0x00, 0x60);
|
|
lt8911_i2c_write(client, 0xff, 0xa6);
|
|
lt8911_i2c_write(client, 0x2a, 0x00);
|
|
|
|
lt8911_i2c_write(client, 0xff, 0x81);
|
|
lt8911_i2c_write(client, 0x07, 0xfe);
|
|
lt8911_i2c_write(client, 0x07, 0xff);
|
|
lt8911_i2c_write(client, 0x0a, 0xfc);
|
|
lt8911_i2c_write(client, 0x0a, 0xfe);
|
|
|
|
/* link training */
|
|
lt8911_i2c_write(client, 0xff, 0x85);
|
|
lt8911_i2c_write(client, 0x1a, md->edp_lane_cnt);
|
|
//lt8911_i2c_write(client, 0x13, 0xd1);
|
|
lt8911_i2c_write(client, 0xff, 0xac);
|
|
lt8911_i2c_write(client, 0x00, 0x64);
|
|
lt8911_i2c_write(client, 0x01, 0x0a);
|
|
lt8911_i2c_write(client, 0x0c, 0x85);
|
|
lt8911_i2c_write(client, 0x0c, 0xc5);
|
|
}
|
|
|
|
static void lt8911exb_link_train_get_result(struct i2c_mipi_dsi *md)
|
|
{
|
|
u32 i, val;
|
|
struct i2c_client *client = md->client;
|
|
|
|
lt8911_i2c_write(client, 0xff, 0xac);
|
|
for (i = 0; i < 10; i++) {
|
|
val = lt8911_i2c_read(client, 0x82);
|
|
if (val & 0x20) {
|
|
if ((val & 0x1f) == 0x1e)
|
|
DBG_FUNC("%s: link training succeeded\n",
|
|
__func__);
|
|
else
|
|
DBG_FUNC("%s: link training failed\n",
|
|
__func__);
|
|
|
|
DBG_FUNC("%s: panel link rate: %d\n", __func__,
|
|
lt8911_i2c_read(client, 0x83));
|
|
DBG_FUNC("%s: panel link count: %d\n", __func__,
|
|
lt8911_i2c_read(client, 0x84));
|
|
break;
|
|
}
|
|
DBG_FUNC("%s: link training ongoing...\n", __func__);
|
|
msleep(100);
|
|
}
|
|
}
|
|
|
|
/* panel_funcs */
|
|
static int panel_prepare(struct drm_panel *panel)
|
|
{
|
|
int ret = 0;
|
|
struct i2c_mipi_dsi *md = panel_to_md(panel);
|
|
struct i2c_client *client = md->client;
|
|
|
|
DBG_FUNC("lt8911exb enter\n");
|
|
|
|
if (md->prepared)
|
|
return 0;
|
|
|
|
if (g_is_std_suspend) {
|
|
DBG_FUNC("lt8911exb prepare under std mode, do not prepare\n");
|
|
return 0;
|
|
}
|
|
|
|
if (md->client == NULL) {
|
|
DBG_FUNC("lt8911exb i2c client still not ready\n");
|
|
return 0;
|
|
}
|
|
|
|
if (md->vspn3v3) {
|
|
ret = regulator_enable(md->vspn3v3);
|
|
if (ret)
|
|
goto fail;
|
|
}
|
|
|
|
if (md->hsvcc) {
|
|
ret = regulator_enable(md->hsvcc);
|
|
if (ret)
|
|
goto fail;
|
|
}
|
|
|
|
//msleep(200);
|
|
msleep(10);
|
|
|
|
lt8911_reset(md);
|
|
|
|
lt8911_i2c_write(client, 0xff, 0x81); /* 0x81: register bank */
|
|
lt8911_i2c_write(client, 0x08, 0x7f);
|
|
|
|
DBG_FUNC("%s: lt8911exb chip ID: 0x%02x-0x%02x-0x%02x\n",
|
|
__func__, lt8911_i2c_read(client, 0x00),
|
|
lt8911_i2c_read(client, 0x01),
|
|
lt8911_i2c_read(client, 0x02));
|
|
|
|
md->prepared = true;
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
dev_err(&md->client->dev, "lt8911exb prepare failed: %d\n", ret);
|
|
if (gpio_is_valid(md->reset_pin))
|
|
gpio_set_value(md->reset_pin, 0);
|
|
if (md->hsvcc)
|
|
regulator_disable(md->hsvcc);
|
|
if (md->vspn3v3)
|
|
regulator_disable(md->vspn3v3);
|
|
return ret;
|
|
}
|
|
|
|
static int panel_unprepare(struct drm_panel *panel)
|
|
{
|
|
int ret = 0;
|
|
struct i2c_mipi_dsi *md = panel_to_md(panel);
|
|
|
|
if (!md->prepared)
|
|
return 0;
|
|
|
|
DBG_FUNC("panel_unprepare enter\n");
|
|
if (gpio_is_valid(md->reset_pin))
|
|
gpio_set_value(md->reset_pin, 0);
|
|
|
|
if (md->hsvcc)
|
|
regulator_disable(md->hsvcc);
|
|
if (md->vspn3v3)
|
|
regulator_disable(md->vspn3v3);
|
|
|
|
md->prepared = false;
|
|
return ret;
|
|
}
|
|
|
|
static int panel_enable(struct drm_panel *panel)
|
|
{
|
|
int ret = 0;
|
|
struct i2c_mipi_dsi *md = panel_to_md(panel);
|
|
|
|
DBG_FUNC("panel_enable enter\n");
|
|
|
|
if(g_is_std_suspend){
|
|
DBG_FUNC("lt8911exb enable under std mode, do not enable\n");
|
|
return 0;
|
|
}
|
|
|
|
if (gpio_is_valid(md->backlight_pin))
|
|
gpio_set_value(md->backlight_pin, 1);
|
|
|
|
lt8911exb_cfg_set_mipi_timing(md);
|
|
lt8911exb_cfg_set_edp_timing(md);
|
|
lt8911exb_cfg_init_regs(md);
|
|
lt8911exb_dbg_check_mipi_timing(md);
|
|
lt8911exb_link_train_start(md);
|
|
lt8911exb_link_train_get_result(md);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int panel_disable(struct drm_panel *panel)
|
|
{
|
|
int ret = 0;
|
|
struct i2c_mipi_dsi *md = panel_to_md(panel);
|
|
|
|
DBG_FUNC("panel_disable enter\n");
|
|
|
|
if (gpio_is_valid(md->backlight_pin))
|
|
gpio_set_value(md->backlight_pin, 0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int panel_get_modes(struct drm_panel *panel, struct drm_connector *connector)
|
|
{
|
|
struct i2c_mipi_dsi *md = panel_to_md(panel);
|
|
const struct drm_display_mode *m = md->desc->display_mode;
|
|
struct drm_display_mode *mode;
|
|
|
|
DBG_FUNC("panel_get_modes enter\n");
|
|
|
|
mode = drm_mode_duplicate(connector->dev, m);
|
|
if (!mode) {
|
|
/*
|
|
dev_err(pinfo->base.dev, "failed to add mode %ux%u@%u\n",
|
|
m->hdisplay, m->vdisplay, drm_mode_vrefresh(m));
|
|
*/
|
|
return -ENOMEM;
|
|
}
|
|
|
|
drm_mode_set_name(mode);
|
|
|
|
mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
|
|
drm_mode_probed_add(connector, mode);
|
|
|
|
connector->display_info.width_mm = mode->width_mm;
|
|
connector->display_info.height_mm = mode->height_mm;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static const struct drm_panel_funcs panel_funcs = {
|
|
.prepare = panel_prepare,
|
|
.unprepare = panel_unprepare,
|
|
.enable = panel_enable,
|
|
.disable = panel_disable,
|
|
.get_modes = panel_get_modes,
|
|
};
|
|
|
|
/* backlight */
|
|
static int backlight_update(struct backlight_device *bd)
|
|
{
|
|
struct i2c_mipi_dsi *md = bl_get_data(bd);
|
|
int brightness = bd->props.brightness;
|
|
|
|
if (!md->prepared) {
|
|
dev_dbg(&bd->dev, "lcd not ready yet for setting its backlight!\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
if (bd->props.power != FB_BLANK_UNBLANK ||
|
|
bd->props.fb_blank != FB_BLANK_UNBLANK ||
|
|
(bd->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK))) {
|
|
brightness = 0;
|
|
}
|
|
|
|
md->brightness = brightness;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct backlight_ops backlight_ops = {
|
|
.options = BL_CORE_SUSPENDRESUME,
|
|
.update_status = backlight_update,
|
|
};
|
|
|
|
static int lt8911_pm_notify(struct notifier_block *notify_block,
|
|
unsigned long mode, void *unused)
|
|
{
|
|
DBG_FUNC("pm_notify: mode (%ld)\n", mode);
|
|
|
|
switch (mode) {
|
|
case PM_HIBERNATION_PREPARE:
|
|
DBG_FUNC("pm_notify PM_HIBERNATION_PREPARE\n");
|
|
g_is_std_suspend = true;
|
|
break;
|
|
case PM_POST_HIBERNATION:
|
|
DBG_FUNC("pm_notify PM_HIBERNATION_PREPARE\n");
|
|
g_is_std_suspend = false;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
/**
|
|
static int backlight_init(struct i2c_mipi_dsi *md)
|
|
{
|
|
struct device *dev = &md->client->dev;
|
|
struct backlight_properties props;
|
|
struct backlight_device *bd;
|
|
|
|
printk(KERN_ERR "=====Function %s line %d\n", __FUNCTION__, __LINE__);
|
|
|
|
memset(&props, 0, sizeof(props));
|
|
props.type = BACKLIGHT_RAW;
|
|
props.max_brightness = 255;
|
|
bd = devm_backlight_device_register(dev, dev_name(dev),
|
|
dev, md, &backlight_ops,
|
|
&props);
|
|
if (IS_ERR(bd)) {
|
|
dev_err(dev, "failed to register backlight\n");
|
|
return PTR_ERR(bd);
|
|
}
|
|
|
|
bd->props.brightness = 255;
|
|
backlight_update_status(bd);
|
|
|
|
return 0;
|
|
}
|
|
*/
|
|
|
|
static int i2c_md_probe(struct i2c_client *client)
|
|
{
|
|
struct i2c_mipi_dsi *md = &g_lt8911_mipi_dsi;
|
|
|
|
DBG_FUNC("start");
|
|
|
|
i2c_set_clientdata(client, md);
|
|
mutex_init(&md->mutex);
|
|
md->client = client;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void i2c_md_remove(struct i2c_client *i2c)
|
|
{
|
|
struct i2c_mipi_dsi *md = i2c_get_clientdata(i2c);
|
|
|
|
DBG_FUNC();
|
|
|
|
mipi_dsi_detach(md->dsi);
|
|
drm_panel_remove(&md->panel);
|
|
|
|
return;
|
|
}
|
|
|
|
static void i2c_md_shutdown(struct i2c_client *i2c)
|
|
{
|
|
struct i2c_mipi_dsi *md = i2c_get_clientdata(i2c);
|
|
|
|
DBG_FUNC();
|
|
|
|
mipi_dsi_detach(md->dsi);
|
|
drm_panel_remove(&md->panel);
|
|
}
|
|
|
|
static int lt8911_parse_dt(struct i2c_mipi_dsi *md)
|
|
{
|
|
int ret = -1;
|
|
struct mipi_dsi_device *dsi = md->dsi;
|
|
struct device_node *np = dsi->dev.of_node;
|
|
|
|
md->hsvcc = devm_regulator_get_optional(&dsi->dev, "hsvcc");
|
|
if (IS_ERR(md->hsvcc)) {
|
|
if (PTR_ERR(md->hsvcc) == -ENODEV) {
|
|
DBG_FUNC("%s: hsvcc regulator not defined, continue without it\n", __func__);
|
|
md->hsvcc = NULL;
|
|
} else {
|
|
return dev_err_probe(&dsi->dev, PTR_ERR(md->hsvcc),
|
|
"Failed to request hsvcc regulator\n");
|
|
}
|
|
}
|
|
|
|
md->vspn3v3 = devm_regulator_get_optional(&dsi->dev, "vspn3v3");
|
|
if (IS_ERR(md->vspn3v3)) {
|
|
if (PTR_ERR(md->vspn3v3) == -ENODEV) {
|
|
DBG_FUNC("%s: vspn3v3 regulator not defined, continue without it\n", __func__);
|
|
md->vspn3v3 = NULL;
|
|
} else {
|
|
return dev_err_probe(&dsi->dev, PTR_ERR(md->vspn3v3),
|
|
"Failed to request vspn3v3 regulator\n");
|
|
}
|
|
}
|
|
|
|
md->backlight_pin = of_get_named_gpio(np,
|
|
"lt8911,backlight-gpio",
|
|
0);
|
|
if (!gpio_is_valid(md->backlight_pin)) {
|
|
DBG_FUNC("%s: backlight-gpio is invalid, continue without it\n", __func__);
|
|
md->backlight_pin = -EINVAL;
|
|
} else {
|
|
ret = devm_gpio_request_one(&dsi->dev,
|
|
md->backlight_pin,
|
|
GPIOF_DIR_OUT, NULL);
|
|
if (ret) {
|
|
DBG_FUNC("%s: failed to request backlight gpio\n",
|
|
__func__);
|
|
return ret;
|
|
}
|
|
gpio_set_value(md->backlight_pin, 0);
|
|
DBG_FUNC("%s: succeed to init backlight gpio\n", __func__);
|
|
}
|
|
|
|
md->irq_pin = of_get_named_gpio(np,
|
|
"lt8911,irq-gpio", 0);
|
|
if (!gpio_is_valid(md->irq_pin)) {
|
|
DBG_FUNC("%s: irq-gpio is invalid, continue without it\n", __func__);
|
|
md->irq_pin = -EINVAL;
|
|
} else {
|
|
ret = devm_gpio_request_one(&dsi->dev,
|
|
md->irq_pin,
|
|
GPIOF_DIR_IN, NULL);
|
|
if (ret) {
|
|
DBG_FUNC("%s: failed to request irq gpio\n",
|
|
__func__);
|
|
return ret;
|
|
}
|
|
DBG_FUNC("%s: succeed to init irq gpio\n", __func__);
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "lt8911,rst-delay-ms",
|
|
&md->rst_delay_ms);
|
|
if (ret < 0) {
|
|
DBG_FUNC("%s: no rst-delay-ms property in dts\n",
|
|
__func__);
|
|
md->rst_delay_ms = 100;
|
|
}
|
|
|
|
md->reset_pin = of_get_named_gpio(np,
|
|
"lt8911,reset-gpio", 0);
|
|
if (!gpio_is_valid(md->reset_pin)) {
|
|
DBG_FUNC("%s: reset-gpio is invalid, continue without it\n", __func__);
|
|
md->reset_pin = -EINVAL;
|
|
} else {
|
|
ret = devm_gpio_request_one(&dsi->dev,
|
|
md->reset_pin,
|
|
GPIOF_DIR_OUT, NULL);
|
|
if (ret) {
|
|
DBG_FUNC("%s: failed to request reset gpio\n",
|
|
__func__);
|
|
return ret;
|
|
}
|
|
gpio_set_value(md->reset_pin, 0);
|
|
DBG_FUNC("%s: succeed to init reset gpio\n", __func__);
|
|
}
|
|
|
|
if (of_property_read_u32(np, "lt8911,edp-lane-cnt",
|
|
&md->edp_lane_cnt)) {
|
|
DBG_FUNC("%s: miss edp-lane-cnt property in dts\n",
|
|
__func__);
|
|
md->edp_lane_cnt = 2; /* default value */
|
|
}
|
|
|
|
if (of_property_read_u32(np, "lt8911,mipi-lane-cnt",
|
|
&md->mipi_lane_cnt)) {
|
|
DBG_FUNC("%s: miss mipi-lane-cnt property in dts\n",
|
|
__func__);
|
|
md->mipi_lane_cnt = 4;
|
|
}
|
|
|
|
/*
|
|
* eDP panel color depth:
|
|
* 6 bit: 262K colors
|
|
* 8 bit: 16.7M colors
|
|
*/
|
|
if (of_property_read_u32(np, "lt8911,edp-depth", &md->edp_depth)) {
|
|
DBG_FUNC("%s: miss edp-depth property in dts\n",
|
|
__func__);
|
|
md->edp_depth = 8;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct of_device_id i2c_md_of_ids[] = {
|
|
{ .compatible = "i2c,lt8911", },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, i2c_md_of_ids);
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
|
|
static int edpi2c_suspend(struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int edpi2c_resume(struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops edpi2c_pm_ops = {
|
|
SET_LATE_SYSTEM_SLEEP_PM_OPS(edpi2c_suspend, edpi2c_resume)
|
|
};
|
|
|
|
#define EDPI2C_PM_OPS &edpi2c_pm_ops
|
|
|
|
#else
|
|
|
|
#define EDPI2C_PM_OPS NULL
|
|
|
|
#endif
|
|
|
|
static struct i2c_driver i2c_md_driver = {
|
|
.driver = {
|
|
.name = "i2c_mipi_dsi",
|
|
.pm = EDPI2C_PM_OPS,
|
|
.of_match_table = i2c_md_of_ids,
|
|
},
|
|
.probe = i2c_md_probe,
|
|
.remove = i2c_md_remove,
|
|
.shutdown = i2c_md_shutdown,
|
|
};
|
|
|
|
static int lt8911_dsi_probe(struct mipi_dsi_device *dsi)
|
|
{
|
|
int ret;
|
|
struct i2c_mipi_dsi *ctx;
|
|
|
|
ctx = &g_lt8911_mipi_dsi;
|
|
|
|
if (ctx == NULL)
|
|
return -ENOMEM;
|
|
|
|
if (ctx->client == NULL)
|
|
return -EPROBE_DEFER;
|
|
|
|
g_is_std_suspend = false;
|
|
|
|
ctx->dsi = dsi;
|
|
ctx->desc = <8911_panel_data;
|
|
ret = lt8911_parse_dt(ctx);
|
|
if (ret) {
|
|
dev_err(&dsi->dev, "%s: failed to parse device tree\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
dsi->mode_flags = ctx->desc->mode_flags;
|
|
dsi->format = ctx->desc->format;
|
|
dsi->lanes = ctx->desc->lanes;
|
|
|
|
mipi_dsi_set_drvdata(dsi, ctx);
|
|
|
|
//ctx->panel_data->set_dsi(ctx->dsi);
|
|
drm_panel_init(&ctx->panel, &dsi->dev, &panel_funcs, DRM_MODE_CONNECTOR_DSI);
|
|
|
|
drm_panel_of_backlight(&ctx->panel);
|
|
|
|
drm_panel_add(&ctx->panel);
|
|
|
|
//backlight_init(ctx);
|
|
|
|
if (IS_ENABLED(CONFIG_PM))
|
|
ctx->pm_notify.notifier_call = lt8911_pm_notify;
|
|
|
|
ret = register_pm_notifier(&ctx->pm_notify);
|
|
if (ret)
|
|
dev_err(&dsi->dev, "register_pm_notifier failed: %d\n", ret);
|
|
|
|
ret = mipi_dsi_attach(dsi);
|
|
if (ret < 0) {
|
|
drm_panel_remove(&ctx->panel);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void lt8911_dsi_remove(struct mipi_dsi_device *dsi)
|
|
{
|
|
struct i2c_mipi_dsi *ctx = mipi_dsi_get_drvdata(dsi);
|
|
|
|
unregister_pm_notifier(&ctx->pm_notify);
|
|
|
|
mipi_dsi_detach(dsi);
|
|
drm_panel_remove(&ctx->panel);
|
|
}
|
|
|
|
static void lt8911_dsi_shutdown(struct mipi_dsi_device *dsi)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static const struct of_device_id lt8911_of_match[] = {
|
|
{.compatible = "i2c_dsi,lt8911", },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, lt8911_of_match);
|
|
|
|
static struct mipi_dsi_driver lt8911_dsi_driver = {
|
|
.probe = lt8911_dsi_probe,
|
|
.remove = lt8911_dsi_remove,
|
|
.shutdown = lt8911_dsi_shutdown,
|
|
.driver = {
|
|
.name = "panel-lt8911",
|
|
.of_match_table = lt8911_of_match,
|
|
},
|
|
};
|
|
|
|
static int __init lt8911_drivers_init(void)
|
|
{
|
|
int ret;
|
|
|
|
if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) {
|
|
ret = mipi_dsi_driver_register(<8911_dsi_driver);
|
|
if (ret) {
|
|
pr_err("lt8911: failed to add dsi driver: %d\n", ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
ret = i2c_add_driver(&i2c_md_driver);
|
|
if (ret) {
|
|
pr_err("lt8911: failed to add i2c driver: %d\n", ret);
|
|
if (IS_ENABLED(CONFIG_DRM_MIPI_DSI))
|
|
mipi_dsi_driver_unregister(<8911_dsi_driver);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void __exit lt8911_drivers_exit(void)
|
|
{
|
|
i2c_del_driver(&i2c_md_driver);
|
|
|
|
if (IS_ENABLED(CONFIG_DRM_MIPI_DSI))
|
|
mipi_dsi_driver_unregister(<8911_dsi_driver);
|
|
}
|
|
|
|
module_init(lt8911_drivers_init);
|
|
module_exit(lt8911_drivers_exit);
|
|
|
|
MODULE_DESCRIPTION("LT8911 Controller Driver");
|
|
MODULE_LICENSE("GPL v2");
|