Merge branch 'master' of git://git.denx.de/u-boot-video
diff --git a/arch/arm/cpu/armv7/mx6/soc.c b/arch/arm/cpu/armv7/mx6/soc.c
index a34675c..91a3deb 100644
--- a/arch/arm/cpu/armv7/mx6/soc.c
+++ b/arch/arm/cpu/armv7/mx6/soc.c
@@ -548,7 +548,8 @@
 {
 	struct mxc_ccm_reg *mxc_ccm = (struct mxc_ccm_reg *)CCM_BASE_ADDR;
 	struct hdmi_regs *hdmi  = (struct hdmi_regs *)HDMI_ARB_BASE_ADDR;
-	int reg;
+	int reg, count;
+	u8 val;
 
 	/* Turn on HDMI PHY clock */
 	reg = readl(&mxc_ccm->CCGR2);
@@ -565,6 +566,16 @@
 		 |(CHSCCDR_IPU_PRE_CLK_540M_PFD
 		 << MXC_CCM_CHSCCDR_IPU1_DI0_PRE_CLK_SEL_OFFSET);
 	writel(reg, &mxc_ccm->chsccdr);
+
+	/* Clear the overflow condition */
+	if (readb(&hdmi->ih_fc_stat2) & HDMI_IH_FC_STAT2_OVERFLOW_MASK) {
+		/* TMDS software reset */
+		writeb((u8)~HDMI_MC_SWRSTZ_TMDSSWRST_REQ, &hdmi->mc_swrstz);
+		val = readb(&hdmi->fc_invidconf);
+		/* Need minimum 3 times to write to clear the register */
+		for (count = 0 ; count < 5 ; count++)
+			writeb(val, &hdmi->fc_invidconf);
+	}
 }
 #endif
 
diff --git a/arch/arm/dts/rk3288.dtsi b/arch/arm/dts/rk3288.dtsi
index e51c75c..3dab0fc 100644
--- a/arch/arm/dts/rk3288.dtsi
+++ b/arch/arm/dts/rk3288.dtsi
@@ -9,6 +9,7 @@
 #include <dt-bindings/clock/rk3288-cru.h>
 #include <dt-bindings/power-domain/rk3288.h>
 #include <dt-bindings/thermal/thermal.h>
+#include <dt-bindings/video/rk3288.h>
 #include "skeleton.dtsi"
 
 / {
@@ -683,6 +684,10 @@
 				reg = <1>;
 				remote-endpoint = <&hdmi_in_vopb>;
 			};
+			vopb_out_lvds: endpoint@2 {
+				reg = <2>;
+				remote-endpoint = <&lvds_in_vopb>;
+			};
 		};
 	};
 
@@ -719,7 +724,10 @@
 				reg = <1>;
 				remote-endpoint = <&hdmi_in_vopl>;
 			};
-
+			vopl_out_lvds: endpoint@2 {
+				reg = <2>;
+				remote-endpoint = <&lvds_in_vopl>;
+			};
 		};
 	};
 
@@ -786,6 +794,34 @@
 		};
 	};
 
+	lvds: lvds@ff96c000 {
+		compatible = "rockchip,rk3288-lvds";
+		reg = <0xff96c000 0x4000>;
+		clocks = <&cru PCLK_LVDS_PHY>;
+		clock-names = "pclk_lvds";
+		pinctrl-names = "default";
+		pinctrl-0 = <&lcdc0_ctl>;
+		rockchip,grf = <&grf>;
+		status = "disabled";
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			lvds_in: port@0 {
+				reg = <0>;
+				#address-cells = <1>;
+				#size-cells = <0>;
+				lvds_in_vopb: endpoint@0 {
+					reg = <0>;
+					remote-endpoint = <&vopb_out_lvds>;
+				};
+				lvds_in_vopl: endpoint@1 {
+					reg = <1>;
+					remote-endpoint = <&vopl_out_lvds>;
+				};
+			};
+		};
+	};
+
 	hdmi_audio: hdmi_audio {
 		compatible = "rockchip,rk3288-hdmi-audio";
 		i2s-controller = <&i2s>;
@@ -1109,6 +1145,15 @@
 			};
 		};
 
+		lcdc0 {
+			lcdc0_ctl: lcdc0-ctl {
+				rockchip,pins = <1 24 RK_FUNC_1 &pcfg_pull_none>,
+						<1 25 RK_FUNC_1 &pcfg_pull_none>,
+						<1 26 RK_FUNC_1 &pcfg_pull_none>,
+						<1 27 RK_FUNC_1 &pcfg_pull_none>;
+			};
+		};
+
 		sdmmc {
 			sdmmc_clk: sdmmc-clk {
 				rockchip,pins = <6 20 RK_FUNC_1 &pcfg_pull_none>;
diff --git a/arch/arm/include/asm/arch-rockchip/lvds_rk3288.h b/arch/arm/include/asm/arch-rockchip/lvds_rk3288.h
new file mode 100644
index 0000000..121a898
--- /dev/null
+++ b/arch/arm/include/asm/arch-rockchip/lvds_rk3288.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2016 Rockchip Inc.
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#ifndef _ASM_ARCH_LVDS_RK3288_H
+#define _ASM_ARCH_LVDS_RK3288_H
+
+#define RK3288_LVDS_CH0_REG0			0x00
+#define RK3288_LVDS_CH0_REG0_LVDS_EN		BIT(7)
+#define RK3288_LVDS_CH0_REG0_TTL_EN		BIT(6)
+#define RK3288_LVDS_CH0_REG0_LANECK_EN		BIT(5)
+#define RK3288_LVDS_CH0_REG0_LANE4_EN		BIT(4)
+#define RK3288_LVDS_CH0_REG0_LANE3_EN		BIT(3)
+#define RK3288_LVDS_CH0_REG0_LANE2_EN		BIT(2)
+#define RK3288_LVDS_CH0_REG0_LANE1_EN		BIT(1)
+#define RK3288_LVDS_CH0_REG0_LANE0_EN		BIT(0)
+
+#define RK3288_LVDS_CH0_REG1			0x04
+#define RK3288_LVDS_CH0_REG1_LANECK_BIAS	BIT(5)
+#define RK3288_LVDS_CH0_REG1_LANE4_BIAS		BIT(4)
+#define RK3288_LVDS_CH0_REG1_LANE3_BIAS		BIT(3)
+#define RK3288_LVDS_CH0_REG1_LANE2_BIAS		BIT(2)
+#define RK3288_LVDS_CH0_REG1_LANE1_BIAS		BIT(1)
+#define RK3288_LVDS_CH0_REG1_LANE0_BIAS		BIT(0)
+
+#define RK3288_LVDS_CH0_REG2			0x08
+#define RK3288_LVDS_CH0_REG2_RESERVE_ON		BIT(7)
+#define RK3288_LVDS_CH0_REG2_LANECK_LVDS_MODE	BIT(6)
+#define RK3288_LVDS_CH0_REG2_LANE4_LVDS_MODE	BIT(5)
+#define RK3288_LVDS_CH0_REG2_LANE3_LVDS_MODE	BIT(4)
+#define RK3288_LVDS_CH0_REG2_LANE2_LVDS_MODE	BIT(3)
+#define RK3288_LVDS_CH0_REG2_LANE1_LVDS_MODE	BIT(2)
+#define RK3288_LVDS_CH0_REG2_LANE0_LVDS_MODE	BIT(1)
+#define RK3288_LVDS_CH0_REG2_PLL_FBDIV8		BIT(0)
+
+#define RK3288_LVDS_CH0_REG3			0x0c
+#define RK3288_LVDS_CH0_REG3_PLL_FBDIV_MASK	0xff
+
+#define RK3288_LVDS_CH0_REG4			0x10
+#define RK3288_LVDS_CH0_REG4_LANECK_TTL_MODE	BIT(5)
+#define RK3288_LVDS_CH0_REG4_LANE4_TTL_MODE	BIT(4)
+#define RK3288_LVDS_CH0_REG4_LANE3_TTL_MODE	BIT(3)
+#define RK3288_LVDS_CH0_REG4_LANE2_TTL_MODE	BIT(2)
+#define RK3288_LVDS_CH0_REG4_LANE1_TTL_MODE	BIT(1)
+#define RK3288_LVDS_CH0_REG4_LANE0_TTL_MODE	BIT(0)
+
+#define RK3288_LVDS_CH0_REG5			0x14
+#define RK3288_LVDS_CH0_REG5_LANECK_TTL_DATA	BIT(5)
+#define RK3288_LVDS_CH0_REG5_LANE4_TTL_DATA	BIT(4)
+#define RK3288_LVDS_CH0_REG5_LANE3_TTL_DATA	BIT(3)
+#define RK3288_LVDS_CH0_REG5_LANE2_TTL_DATA	BIT(2)
+#define RK3288_LVDS_CH0_REG5_LANE1_TTL_DATA	BIT(1)
+#define RK3288_LVDS_CH0_REG5_LANE0_TTL_DATA	BIT(0)
+
+#define RK3288_LVDS_CFG_REGC			0x30
+#define RK3288_LVDS_CFG_REGC_PLL_ENABLE		0x00
+#define RK3288_LVDS_CFG_REGC_PLL_DISABLE	0xff
+
+#define RK3288_LVDS_CH0_REGD			0x34
+#define RK3288_LVDS_CH0_REGD_PLL_PREDIV_MASK	0x1f
+
+#define RK3288_LVDS_CH0_REG20			0x80
+#define RK3288_LVDS_CH0_REG20_MSB		0x45
+#define RK3288_LVDS_CH0_REG20_LSB		0x44
+
+#define RK3288_LVDS_CFG_REG21			0x84
+#define RK3288_LVDS_CFG_REG21_TX_ENABLE		0x92
+#define RK3288_LVDS_CFG_REG21_TX_DISABLE	0x00
+
+/* fbdiv value is split over 2 registers, with bit8 in reg2 */
+#define RK3288_LVDS_PLL_FBDIV_REG2(_fbd) \
+		(_fbd & BIT(8) ? RK3288_LVDS_CH0_REG2_PLL_FBDIV8 : 0)
+#define RK3288_LVDS_PLL_FBDIV_REG3(_fbd) \
+		(_fbd & RK3288_LVDS_CH0_REG3_PLL_FBDIV_MASK)
+#define RK3288_LVDS_PLL_PREDIV_REGD(_pd) \
+		(_pd & RK3288_LVDS_CH0_REGD_PLL_PREDIV_MASK)
+
+#define RK3288_LVDS_SOC_CON6_SEL_VOP_LIT	BIT(3)
+
+#define LVDS_FMT_MASK			(7 << 16)
+#define LVDS_MSB			(1 << 3)
+#define LVDS_DUAL			(1 << 4)
+#define LVDS_FMT_1			(1 << 5)
+#define LVDS_TTL_EN			(1 << 6)
+#define LVDS_START_PHASE_RST_1		(1 << 7)
+#define LVDS_DCLK_INV			(1 << 8)
+#define LVDS_CH0_EN			(1 << 11)
+#define LVDS_CH1_EN			(1 << 12)
+#define LVDS_PWRDN			(1 << 15)
+
+#define LVDS_24BIT		(0 << 1)
+#define LVDS_18BIT		(1 << 1)
+
+
+#endif
diff --git a/arch/arm/include/asm/arch-rockchip/vop_rk3288.h b/arch/arm/include/asm/arch-rockchip/vop_rk3288.h
index 0104ba3..0ce3d67 100644
--- a/arch/arm/include/asm/arch-rockchip/vop_rk3288.h
+++ b/arch/arm/include/asm/arch-rockchip/vop_rk3288.h
@@ -89,6 +89,7 @@
 enum vop_modes {
 	VOP_MODE_EDP = 0,
 	VOP_MODE_HDMI,
+	VOP_MODE_LVDS,
 	VOP_MODE_NONE,
 	VOP_MODE_AUTO_DETECT,
 	VOP_MODE_UNKNOWN,
diff --git a/doc/device-tree-bindings/video/rockchip-lvds.txt b/doc/device-tree-bindings/video/rockchip-lvds.txt
new file mode 100644
index 0000000..07e3d3f
--- /dev/null
+++ b/doc/device-tree-bindings/video/rockchip-lvds.txt
@@ -0,0 +1,77 @@
+Rockchip LVDS interface
+------------------
+
+Required properties:
+- compatible: "rockchip,rk3288-lvds";
+
+- reg: physical base address of the controller and length
+	of memory mapped region.
+- clocks: must include clock specifiers corresponding to entries in the
+	clock-names property.
+- clock-names: must contain "pclk_lvds"
+
+- rockchip,grf: phandle to the general register files syscon
+
+- rockchip,data-mapping: should be <LVDS_FORMAT_VESA> or  <LVDS_FORMAT_JEIDA>,
+	This describes how the color bits are laid out in the
+	serialized LVDS signal.
+- rockchip,data-width : should be <18> or <24>;
+- rockchip,output: should be <LVDS_OUTPUT_RGB>, <LVDS_OUTPUT_SINGLE> or
+	<LVDS_OUTPUT_DUAL>, This describes the output face.
+
+- display-timings : described by
+	doc/devicetree/device-tree-bindings/video/display-timing.txt.
+
+Example:
+	lvds: lvds@ff96c000 {
+		compatible = "rockchip,rk3288-lvds";
+		reg = <0xff96c000 0x4000>;
+		clocks = <&cru PCLK_LVDS_PHY>;
+		clock-names = "pclk_lvds";
+		pinctrl-names = "default";
+		pinctrl-0 = <&lcdc0_ctl>;
+		rockchip,grf = <&grf>;
+		status = "disabled";
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			lvds_in: port@0 {
+				reg = <0>;
+
+				#address-cells = <1>;
+				#size-cells = <0>;
+
+				lvds_in_vopb: endpoint@0 {
+					reg = <0>;
+					remote-endpoint = <&vopb_out_lvds>;
+				};
+				lvds_in_vopl: endpoint@1 {
+					reg = <1>;
+					remote-endpoint = <&vopl_out_lvds>;
+				};
+			};
+		};
+	};
+
+	&lvds {
+		rockchip,data-mapping = <LVDS_FORMAT_VESA>;
+		rockchip,data-width = <24>;
+		rockchip,output = <LVDS_OUTPUT_DUAL>;
+		rockchip,panel = <&panel>;
+		status = "okay";
+
+		display-timings {
+			timing@0 {
+				clock-frequency = <40000000>;
+				hactive = <1920>;
+				vactive = <1080>;
+				hsync-len = <44>;
+				hfront-porch = <88>;
+				hback-porch = <148>;
+				vfront-porch = <4>;
+				vback-porch = <36>;
+				vsync-len = <5>;
+			};
+		};
+	};
diff --git a/drivers/video/display-uclass.c b/drivers/video/display-uclass.c
index 31522ea..e4763de 100644
--- a/drivers/video/display-uclass.c
+++ b/drivers/video/display-uclass.c
@@ -36,6 +36,9 @@
 	u8 buf[EDID_EXT_SIZE];
 	int ret;
 
+	if (ops && ops->read_timing)
+		return ops->read_timing(dev, timing);
+
 	if (!ops || !ops->read_edid)
 		return -ENOSYS;
 	ret = ops->read_edid(dev, buf, sizeof(buf));
diff --git a/drivers/video/ipu_common.c b/drivers/video/ipu_common.c
index 9f85102..36d4b23 100644
--- a/drivers/video/ipu_common.c
+++ b/drivers/video/ipu_common.c
@@ -19,6 +19,7 @@
 #include <asm/errno.h>
 #include <asm/arch/imx-regs.h>
 #include <asm/arch/crm_regs.h>
+#include <div64.h>
 #include "ipu.h"
 #include "ipu_regs.h"
 
@@ -275,50 +276,84 @@
 
 static void ipu_pixel_clk_recalc(struct clk *clk)
 {
-	u32 div = __raw_readl(DI_BS_CLKGEN0(clk->id));
-	if (div == 0)
-		clk->rate = 0;
-	else
-		clk->rate = (clk->parent->rate * 16) / div;
+	u32 div;
+	u64 final_rate = (unsigned long long)clk->parent->rate * 16;
+
+	div = __raw_readl(DI_BS_CLKGEN0(clk->id));
+	debug("read BS_CLKGEN0 div:%d, final_rate:%lld, prate:%ld\n",
+	      div, final_rate, clk->parent->rate);
+
+	clk->rate = 0;
+	if (div != 0) {
+		do_div(final_rate, div);
+		clk->rate = final_rate;
+	}
 }
 
 static unsigned long ipu_pixel_clk_round_rate(struct clk *clk,
 	unsigned long rate)
 {
-	u32 div, div1;
-	u32 tmp;
+	u64 div, final_rate;
+	u32 remainder;
+	u64 parent_rate = (unsigned long long)clk->parent->rate * 16;
+
 	/*
 	 * Calculate divider
 	 * Fractional part is 4 bits,
 	 * so simply multiply by 2^4 to get fractional part.
 	 */
-	tmp = (clk->parent->rate * 16);
-	div = tmp / rate;
-
+	div = parent_rate;
+	remainder = do_div(div, rate);
+	/* Round the divider value */
+	if (remainder > (rate / 2))
+		div++;
 	if (div < 0x10)            /* Min DI disp clock divider is 1 */
 		div = 0x10;
 	if (div & ~0xFEF)
 		div &= 0xFF8;
 	else {
-		div1 = div & 0xFE0;
-		if ((tmp/div1 - tmp/div) < rate / 4)
-			div = div1;
-		else
-			div &= 0xFF8;
+		/* Round up divider if it gets us closer to desired pix clk */
+		if ((div & 0xC) == 0xC) {
+			div += 0x10;
+			div &= ~0xF;
+		}
 	}
-	return (clk->parent->rate * 16) / div;
+	final_rate = parent_rate;
+	do_div(final_rate, div);
+
+	return final_rate;
 }
 
 static int ipu_pixel_clk_set_rate(struct clk *clk, unsigned long rate)
 {
-	u32 div = (clk->parent->rate * 16) / rate;
+	u64 div, parent_rate;
+	u32 remainder;
+
+	parent_rate = (unsigned long long)clk->parent->rate * 16;
+	div = parent_rate;
+	remainder = do_div(div, rate);
+	/* Round the divider value */
+	if (remainder > (rate / 2))
+		div++;
+
+	/* Round up divider if it gets us closer to desired pix clk */
+	if ((div & 0xC) == 0xC) {
+		div += 0x10;
+		div &= ~0xF;
+	}
+	if (div > 0x1000)
+		debug("Overflow, DI_BS_CLKGEN0 div:0x%x\n", (u32)div);
 
 	__raw_writel(div, DI_BS_CLKGEN0(clk->id));
 
-	/* Setup pixel clock timing */
+	/*
+	 * Setup pixel clock timing
+	 * Down time is half of period
+	 */
 	__raw_writel((div / 16) << 16, DI_BS_CLKGEN1(clk->id));
 
-	clk->rate = (clk->parent->rate * 16) / div;
+	clk->rate = (u64)(clk->parent->rate * 16) / div;
+
 	return 0;
 }
 
diff --git a/drivers/video/rockchip/Makefile b/drivers/video/rockchip/Makefile
index 0e9a8ac..7962f86 100644
--- a/drivers/video/rockchip/Makefile
+++ b/drivers/video/rockchip/Makefile
@@ -5,4 +5,4 @@
 # SPDX-License-Identifier:	GPL-2.0+
 #
 
-obj-y += rk_edp.o rk_hdmi.o rk_vop.o
+obj-y += rk_edp.o rk_hdmi.o rk_vop.o rk_lvds.o
diff --git a/drivers/video/rockchip/rk_lvds.c b/drivers/video/rockchip/rk_lvds.c
new file mode 100644
index 0000000..dc10b86
--- /dev/null
+++ b/drivers/video/rockchip/rk_lvds.c
@@ -0,0 +1,254 @@
+/*
+ * Copyright 2016 Rockchip Inc.
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include <clk.h>
+#include <display.h>
+#include <dm.h>
+#include <edid.h>
+#include <panel.h>
+#include <regmap.h>
+#include <syscon.h>
+#include <asm/gpio.h>
+#include <asm/io.h>
+#include <asm/arch/clock.h>
+#include <asm/arch/lvds_rk3288.h>
+#include <asm/arch/grf_rk3288.h>
+#include <dt-bindings/clock/rk3288-cru.h>
+#include <dt-bindings/video/rk3288.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+/**
+ * struct rk_lvds_priv - private rockchip lvds display driver info
+ *
+ * @reg: LVDS register address
+ * @grf: GRF register
+ * @panel: Panel device that is used in driver
+ *
+ * @output: Output mode, decided single or double channel,
+ *		LVDS or LVTLL
+ * @format: Data format that RGB data will packing as
+ */
+struct rk_lvds_priv {
+	void __iomem *regs;
+	struct rk3288_grf *grf;
+	struct udevice *panel;
+
+	int output;
+	int format;
+};
+
+static inline void lvds_writel(struct rk_lvds_priv *lvds, u32 offset, u32 val)
+{
+	writel(val, lvds->regs + offset);
+
+	writel(val, lvds->regs + offset + 0x100);
+}
+
+int rk_lvds_enable(struct udevice *dev, int panel_bpp,
+		   const struct display_timing *edid)
+{
+	struct rk_lvds_priv *priv = dev_get_priv(dev);
+	struct display_plat *uc_plat = dev_get_uclass_platdata(dev);
+	int ret = 0;
+	unsigned int val = 0;
+
+	ret = panel_enable_backlight(priv->panel);
+	if (ret) {
+		debug("%s: backlight error: %d\n", __func__, ret);
+		return ret;
+	}
+
+	/* Select the video source */
+	if (uc_plat->source_id)
+		val = RK3288_LVDS_SOC_CON6_SEL_VOP_LIT |
+		    (RK3288_LVDS_SOC_CON6_SEL_VOP_LIT << 16);
+	else
+		val = RK3288_LVDS_SOC_CON6_SEL_VOP_LIT << 16;
+	rk_setreg(&priv->grf->soc_con6, val);
+
+	/* Select data transfer format */
+	val = priv->format;
+	if (priv->output == LVDS_OUTPUT_DUAL)
+		val |= LVDS_DUAL | LVDS_CH0_EN | LVDS_CH1_EN;
+	else if (priv->output == LVDS_OUTPUT_SINGLE)
+		val |= LVDS_CH0_EN;
+	else if (priv->output == LVDS_OUTPUT_RGB)
+		val |= LVDS_TTL_EN | LVDS_CH0_EN | LVDS_CH1_EN;
+	val |= (0xffff << 16);
+	rk_setreg(&priv->grf->soc_con7, val);
+
+	/* Enable LVDS PHY */
+	if (priv->output == LVDS_OUTPUT_RGB) {
+		lvds_writel(priv, RK3288_LVDS_CH0_REG0,
+			    RK3288_LVDS_CH0_REG0_TTL_EN |
+			    RK3288_LVDS_CH0_REG0_LANECK_EN |
+			    RK3288_LVDS_CH0_REG0_LANE4_EN |
+			    RK3288_LVDS_CH0_REG0_LANE3_EN |
+			    RK3288_LVDS_CH0_REG0_LANE2_EN |
+			    RK3288_LVDS_CH0_REG0_LANE1_EN |
+			    RK3288_LVDS_CH0_REG0_LANE0_EN);
+		lvds_writel(priv, RK3288_LVDS_CH0_REG2,
+			    RK3288_LVDS_PLL_FBDIV_REG2(0x46));
+
+		lvds_writel(priv, RK3288_LVDS_CH0_REG3,
+			    RK3288_LVDS_PLL_FBDIV_REG3(0x46));
+		lvds_writel(priv, RK3288_LVDS_CH0_REG4,
+			    RK3288_LVDS_CH0_REG4_LANECK_TTL_MODE |
+			    RK3288_LVDS_CH0_REG4_LANE4_TTL_MODE |
+			    RK3288_LVDS_CH0_REG4_LANE3_TTL_MODE |
+			    RK3288_LVDS_CH0_REG4_LANE2_TTL_MODE |
+			    RK3288_LVDS_CH0_REG4_LANE1_TTL_MODE |
+			    RK3288_LVDS_CH0_REG4_LANE0_TTL_MODE);
+		lvds_writel(priv, RK3288_LVDS_CH0_REG5,
+			    RK3288_LVDS_CH0_REG5_LANECK_TTL_DATA |
+			    RK3288_LVDS_CH0_REG5_LANE4_TTL_DATA |
+			    RK3288_LVDS_CH0_REG5_LANE3_TTL_DATA |
+			    RK3288_LVDS_CH0_REG5_LANE2_TTL_DATA |
+			    RK3288_LVDS_CH0_REG5_LANE1_TTL_DATA |
+			    RK3288_LVDS_CH0_REG5_LANE0_TTL_DATA);
+		lvds_writel(priv, RK3288_LVDS_CH0_REGD,
+			    RK3288_LVDS_PLL_PREDIV_REGD(0x0a));
+		lvds_writel(priv, RK3288_LVDS_CH0_REG20,
+			    RK3288_LVDS_CH0_REG20_LSB);
+	} else {
+		lvds_writel(priv, RK3288_LVDS_CH0_REG0,
+			    RK3288_LVDS_CH0_REG0_LVDS_EN |
+			    RK3288_LVDS_CH0_REG0_LANECK_EN |
+			    RK3288_LVDS_CH0_REG0_LANE4_EN |
+			    RK3288_LVDS_CH0_REG0_LANE3_EN |
+			    RK3288_LVDS_CH0_REG0_LANE2_EN |
+			    RK3288_LVDS_CH0_REG0_LANE1_EN |
+			    RK3288_LVDS_CH0_REG0_LANE0_EN);
+		lvds_writel(priv, RK3288_LVDS_CH0_REG1,
+			    RK3288_LVDS_CH0_REG1_LANECK_BIAS |
+			    RK3288_LVDS_CH0_REG1_LANE4_BIAS |
+			    RK3288_LVDS_CH0_REG1_LANE3_BIAS |
+			    RK3288_LVDS_CH0_REG1_LANE2_BIAS |
+			    RK3288_LVDS_CH0_REG1_LANE1_BIAS |
+			    RK3288_LVDS_CH0_REG1_LANE0_BIAS);
+		lvds_writel(priv, RK3288_LVDS_CH0_REG2,
+			    RK3288_LVDS_CH0_REG2_RESERVE_ON |
+			    RK3288_LVDS_CH0_REG2_LANECK_LVDS_MODE |
+			    RK3288_LVDS_CH0_REG2_LANE4_LVDS_MODE |
+			    RK3288_LVDS_CH0_REG2_LANE3_LVDS_MODE |
+			    RK3288_LVDS_CH0_REG2_LANE2_LVDS_MODE |
+			    RK3288_LVDS_CH0_REG2_LANE1_LVDS_MODE |
+			    RK3288_LVDS_CH0_REG2_LANE0_LVDS_MODE |
+			    RK3288_LVDS_PLL_FBDIV_REG2(0x46));
+		lvds_writel(priv, RK3288_LVDS_CH0_REG3,
+			    RK3288_LVDS_PLL_FBDIV_REG3(0x46));
+		lvds_writel(priv, RK3288_LVDS_CH0_REG4, 0x00);
+		lvds_writel(priv, RK3288_LVDS_CH0_REG5, 0x00);
+		lvds_writel(priv, RK3288_LVDS_CH0_REGD,
+			    RK3288_LVDS_PLL_PREDIV_REGD(0x0a));
+		lvds_writel(priv, RK3288_LVDS_CH0_REG20,
+			    RK3288_LVDS_CH0_REG20_LSB);
+	}
+
+	/* Power on */
+	writel(RK3288_LVDS_CFG_REGC_PLL_ENABLE,
+	       priv->regs + RK3288_LVDS_CFG_REGC);
+
+	writel(RK3288_LVDS_CFG_REG21_TX_ENABLE,
+	       priv->regs + RK3288_LVDS_CFG_REG21);
+
+	return 0;
+}
+
+int rk_lvds_read_timing(struct udevice *dev, struct display_timing *timing)
+{
+	if (fdtdec_decode_display_timing
+	    (gd->fdt_blob, dev->of_offset, 0, timing)) {
+		debug("%s: Failed to decode display timing\n", __func__);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int rk_lvds_ofdata_to_platdata(struct udevice *dev)
+{
+	struct rk_lvds_priv *priv = dev_get_priv(dev);
+	const void *blob = gd->fdt_blob;
+	int node = dev->of_offset;
+	int ret;
+	priv->regs = (void *)dev_get_addr(dev);
+	priv->grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF);
+
+	ret = fdtdec_get_int(blob, node, "rockchip,output", -1);
+	if (ret != -1) {
+		priv->output = ret;
+		debug("LVDS output : %d\n", ret);
+	} else {
+		/* default set it as output rgb */
+		priv->output = LVDS_OUTPUT_RGB;
+	}
+
+	ret = fdtdec_get_int(blob, node, "rockchip,data-mapping", -1);
+	if (ret != -1) {
+		priv->format = ret;
+		debug("LVDS data-mapping : %d\n", ret);
+	} else {
+		/* default set it as format jeida */
+		priv->format = LVDS_FORMAT_JEIDA;
+	}
+
+	ret = fdtdec_get_int(blob, node, "rockchip,data-width", -1);
+	if (ret != -1) {
+		debug("LVDS data-width : %d\n", ret);
+		if (ret == 24) {
+			priv->format |= LVDS_24BIT;
+		} else if (ret == 18) {
+			priv->format |= LVDS_18BIT;
+		} else {
+			debug("rockchip-lvds unsupport data-width[%d]\n", ret);
+			ret = -EINVAL;
+			return ret;
+		}
+	} else {
+		priv->format |= LVDS_24BIT;
+	}
+
+	return 0;
+}
+
+int rk_lvds_probe(struct udevice *dev)
+{
+	struct rk_lvds_priv *priv = dev_get_priv(dev);
+	int ret;
+
+	ret = uclass_get_device_by_phandle(UCLASS_PANEL, dev, "rockchip,panel",
+					   &priv->panel);
+	if (ret) {
+		debug("%s: Cannot find panel for '%s' (ret=%d)\n", __func__,
+		      dev->name, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct dm_display_ops lvds_rockchip_ops = {
+	.read_timing = rk_lvds_read_timing,
+	.enable = rk_lvds_enable,
+};
+
+static const struct udevice_id rockchip_lvds_ids[] = {
+	{.compatible = "rockchip,rk3288-lvds"},
+	{}
+};
+
+U_BOOT_DRIVER(lvds_rockchip) = {
+	.name	= "lvds_rockchip",
+	.id	= UCLASS_DISPLAY,
+	.of_match = rockchip_lvds_ids,
+	.ops	= &lvds_rockchip_ops,
+	.ofdata_to_platdata	= rk_lvds_ofdata_to_platdata,
+	.probe	= rk_lvds_probe,
+	.priv_auto_alloc_size	= sizeof(struct rk_lvds_priv),
+};
diff --git a/drivers/video/rockchip/rk_vop.c b/drivers/video/rockchip/rk_vop.c
index adbc68f..a54af17 100644
--- a/drivers/video/rockchip/rk_vop.c
+++ b/drivers/video/rockchip/rk_vop.c
@@ -102,6 +102,7 @@
 	u32 hfront_porch = edid->hfront_porch.typ;
 	u32 vfront_porch = edid->vfront_porch.typ;
 	uint flags;
+	int mode_flags;
 
 	switch (mode) {
 	case VOP_MODE_HDMI:
@@ -113,9 +114,20 @@
 		clrsetbits_le32(&regs->sys_ctrl, M_ALL_OUT_EN,
 				V_EDP_OUT_EN(1));
 		break;
+	case VOP_MODE_LVDS:
+		clrsetbits_le32(&regs->sys_ctrl, M_ALL_OUT_EN,
+				V_RGB_OUT_EN(1));
+		break;
 	}
 
-	flags = V_DSP_OUT_MODE(15) |
+	if (mode == VOP_MODE_HDMI || mode == VOP_MODE_EDP)
+		/* RGBaaa */
+		mode_flags = 15;
+	else
+		/* RGB888 */
+		mode_flags = 0;
+
+	flags = V_DSP_OUT_MODE(mode_flags) |
 		V_DSP_HSYNC_POL(!!(edid->flags & DISPLAY_FLAGS_HSYNC_HIGH)) |
 		V_DSP_VSYNC_POL(!!(edid->flags & DISPLAY_FLAGS_VSYNC_HIGH));
 
@@ -227,7 +239,7 @@
 
 	ret = rkclk_get_clk(CLK_NEW, &clk);
 	if (!ret) {
-		ret = clk_set_periph_rate(clk, DCLK_VOP0 + vop_id,
+		ret = clk_set_periph_rate(clk, DCLK_VOP0 + remote_vop_id,
 					  timing.pixelclock.typ);
 	}
 	if (ret) {
diff --git a/include/configs/mx6sabre_common.h b/include/configs/mx6sabre_common.h
index 29d1f91..a6d821b 100644
--- a/include/configs/mx6sabre_common.h
+++ b/include/configs/mx6sabre_common.h
@@ -225,7 +225,11 @@
 #define CONFIG_BMP_16BPP
 #define CONFIG_VIDEO_LOGO
 #define CONFIG_VIDEO_BMP_LOGO
-#define CONFIG_IPUV3_CLK 260000000
+#ifdef CONFIG_MX6DL
+#define CONFIG_IPUV3_CLK 198000000
+#else
+#define CONFIG_IPUV3_CLK 264000000
+#endif
 #define CONFIG_IMX_HDMI
 #define CONFIG_IMX_VIDEO_SKIP
 
diff --git a/include/display.h b/include/display.h
index c180e76..b1c4766 100644
--- a/include/display.h
+++ b/include/display.h
@@ -23,7 +23,7 @@
 };
 
 /**
- * display_read_timing() - Read timing information from EDID
+ * display_read_timing() - Read timing information
  *
  * @dev:	Device to read from
  * @return 0 if OK, -ve on error
@@ -43,6 +43,15 @@
 
 struct dm_display_ops {
 	/**
+	 * read_timing() - Read information directly
+	 *
+	 * @dev:	Device to read from
+	 * @timing:	Display timings
+	 * @return 0 if OK, -ve on error
+	 */
+	int (*read_timing)(struct udevice *dev, struct display_timing *timing);
+
+	/**
 	 * read_edid() - Read information from EDID
 	 *
 	 * @dev:	Device to read from
diff --git a/include/dt-bindings/video/rk3288.h b/include/dt-bindings/video/rk3288.h
new file mode 100644
index 0000000..7bfd247
--- /dev/null
+++ b/include/dt-bindings/video/rk3288.h
@@ -0,0 +1,11 @@
+#ifndef __DT_BINDINGS_VIDEO_RK3288_H__
+#define __DT_BINDINGS_VIDEO_RK3288_H__
+
+#define LVDS_OUTPUT_RGB     0
+#define LVDS_OUTPUT_SINGLE  1
+#define LVDS_OUTPUT_DUAL    2
+
+#define LVDS_FORMAT_VESA    0
+#define LVDS_FORMAT_JEIDA   1
+
+#endif