diff --git a/drivers/gpu/ipu-v3/ipu-di.c b/drivers/gpu/ipu-v3/ipu-di.c
index b306f07..8da9c9f 100644
--- a/drivers/gpu/ipu-v3/ipu-di.c
+++ b/drivers/gpu/ipu-v3/ipu-di.c
@@ -398,12 +398,105 @@ static void ipu_di_sync_config_noninterlaced(struct ipu_di *di,
 		ipu_di_sync_config(di, cfg_vga, 0, ARRAY_SIZE(cfg_vga));
 }
 
-static void ipu_di_config_clock(struct ipu_di *di,
-	const struct ipu_di_signal_cfg *sig)
+
+/*
+ * We need to use the DI divider. We should really have a flag here
+ * indicating whether the bridge can cope with a fractional divider
+ * or not.
+ *
+ * For now, assume the chip bug exists in the fractional divider
+ * which causes bad DI pixel clock waveforms for all values of the
+ * fractional part other than 0 (no fraction) or 0x8 (0.5). So this
+ * function returns a divider with only 0x0 or 0x8 in the fractional
+ * part.
+ *
+ * This function reprograms the clock rate of the parent-parent of
+ * the DI clock in order to get as close as possible to the requested
+ * pixel clock. It calculates two values for the parent-parent rate: a
+ * rate that is a whole integer multiple of the pixel clock, and a rate
+ * that is a half-integer multiple. It then programs whichever rate
+ * comes closest to generating the desired rate, and returns the
+ * corresponding integer or half-integer divider (times 16).
+ */
+static int set_di_pre_clk_rate(struct ipu_di *di,
+			       const struct ipu_di_signal_cfg *sig)
+{
+	struct clk *di_pre_clk;
+	unsigned long pre_clk_N_round, pre_clk_N_0_5_round;
+	unsigned long pre_clk_N, pre_clk_N_0_5;
+	unsigned long pixclk_N, pixclk_N_0_5;
+	unsigned div_N, div_N_0_5, error_N, error_N_0_5;
+	int ret;
+
+	di_pre_clk = clk_get_parent(clk_get_parent(di->clk_di));
+	if (IS_ERR(di_pre_clk)) {
+		dev_err(di->ipu->dev, "failed to get di_pre clock\n");
+		return PTR_ERR(di_pre_clk);
+	}
+
+	/*
+	 * calc pre_clk_N and pre_clk_N_0_5, which are an integer multiple
+	 * and half-integer multiple of sig->pixelclock, respectively.
+	 */
+	pre_clk_N = sig->pixelclock;
+	pre_clk_N_round = clk_round_rate(di_pre_clk, pre_clk_N);
+	pre_clk_N *= (pre_clk_N_round / pre_clk_N);
+	pre_clk_N_0_5 = pre_clk_N + sig->pixelclock / 2;
+
+	if (pre_clk_N < pre_clk_N_round)
+		pre_clk_N += sig->pixelclock;
+	if (pre_clk_N_0_5 < pre_clk_N_round)
+		pre_clk_N_0_5 += sig->pixelclock;
+
+	/*
+	 * now get the rounded pre_clk_N and pre_clk_N_0_5, i.e. what the
+	 * pre-clk can actually generate.
+	 */
+	pre_clk_N_round = clk_round_rate(di_pre_clk, pre_clk_N);
+	pre_clk_N_0_5_round = clk_round_rate(di_pre_clk, pre_clk_N_0_5);
+
+	/*
+	 * finally we can determine whether the integer multiple or
+	 * half-integer multiple pre-clk comes closest to generating
+	 * the desired pixel clock. Set pre-clk rate to whichever comes
+	 * closest.
+	 */
+	div_N = (pre_clk_N_round << 4) / sig->pixelclock;
+	div_N = (div_N + 0x8) & ~0xf; /* round to nearest int */
+	pixclk_N = (pre_clk_N_round << 4) / div_N;
+
+	div_N_0_5 = (pre_clk_N_0_5_round << 4) / sig->pixelclock;
+	div_N_0_5 = (div_N_0_5 + 0x4) & ~0x7; /* round to nearest half-int */
+	pixclk_N_0_5 = (pre_clk_N_0_5_round << 4) / div_N_0_5;
+
+	error_N = pixclk_N / (sig->pixelclock / 1000);
+	error_N_0_5 = pixclk_N_0_5 / (sig->pixelclock / 1000);
+
+	if (abs(error_N - 1000) < abs(error_N_0_5 - 1000)) {
+		ret = div_N;
+		clk_set_rate(di_pre_clk, pre_clk_N_round);
+	} else {
+		ret = div_N_0_5;
+		clk_set_rate(di_pre_clk, pre_clk_N_0_5_round);
+	}
+
+	dev_dbg(di->ipu->dev,
+		"di%d: pixclk want %lu, can do %lu / %lu, div 0x%02x / 0x%02x, error %d.%u%% / %d.%u%%\n",
+		di->id, sig->pixelclock, pixclk_N, pixclk_N_0_5,
+		div_N, div_N_0_5,
+		(signed)(error_N - 1000) / 10, error_N % 10,
+		(signed)(error_N_0_5 - 1000) / 10, error_N_0_5 % 10);
+
+	return ret;
+}
+
+static int ipu_di_config_clock(struct ipu_di *di,
+			       const struct ipu_di_signal_cfg *sig)
 {
 	struct clk *clk;
 	unsigned clkgen0;
 	uint32_t val;
+	int ret;
 
 	if (sig->clkflags & IPU_DI_CLKMODE_EXT) {
 		/*
@@ -423,24 +516,10 @@ static void ipu_di_config_clock(struct ipu_di *di,
 			 */
 			clkgen0 = 1 << 4;
 		} else {
-			/*
-			 * We can use the divider.  We should really have
-			 * a flag here indicating whether the bridge can
-			 * cope with a fractional divider or not.  For the
-			 * time being, let's go for simplicitly and
-			 * reliability.
-			 */
-			unsigned long in_rate;
-			unsigned div;
-
-			clk_set_rate(clk, sig->pixelclock);
-
-			in_rate = clk_get_rate(clk);
-			div = (in_rate + sig->pixelclock / 2) / sig->pixelclock;
-			if (div == 0)
-				div = 1;
-
-			clkgen0 = div << 4;
+			ret = set_di_pre_clk_rate(di, sig);
+			if (ret < 0)
+				return ret;
+			clkgen0 = ret;
 		}
 	} else {
 		/*
@@ -470,19 +549,12 @@ static void ipu_di_config_clock(struct ipu_di *di,
 
 			clkgen0 = div << 4;
 		} else {
-			unsigned long in_rate;
-			unsigned div;
-
 			clk = di->clk_di;
 
-			clk_set_rate(clk, sig->pixelclock);
-
-			in_rate = clk_get_rate(clk);
-			div = (in_rate + sig->pixelclock / 2) / sig->pixelclock;
-			if (div == 0)
-				div = 1;
-
-			clkgen0 = div << 4;
+			ret = set_di_pre_clk_rate(di, sig);
+			if (ret < 0)
+				return ret;
+			clkgen0 = ret;
 		}
 	}
 
@@ -513,6 +585,8 @@ static void ipu_di_config_clock(struct ipu_di *di,
 		clk_get_rate(di->clk_di),
 		clk == di->clk_di ? "DI" : "IPU",
 		clk_get_rate(di->clk_di_pixel) / (clkgen0 >> 4));
+
+	return 0;
 }
 
 /*
@@ -546,6 +620,7 @@ int ipu_di_init_sync_panel(struct ipu_di *di, struct ipu_di_signal_cfg *sig)
 	u32 reg;
 	u32 di_gen, vsync_cnt;
 	u32 div;
+	int ret = 0;
 
 	dev_dbg(di->ipu->dev, "di%d: panel size = %d x %d\n",
 		di->id, sig->width, sig->height);
@@ -564,7 +639,9 @@ int ipu_di_init_sync_panel(struct ipu_di *di, struct ipu_di_signal_cfg *sig)
 
 	mutex_lock(&di_mutex);
 
-	ipu_di_config_clock(di, sig);
+	ret = ipu_di_config_clock(di, sig);
+	if (ret)
+		goto unlock;
 
 	div = ipu_di_read(di, DI_BS_CLKGEN0) & 0xfff;
 	div = div / 16;		/* Now divider is integer portion */
@@ -641,9 +718,9 @@ int ipu_di_init_sync_panel(struct ipu_di *di, struct ipu_di_signal_cfg *sig)
 
 	ipu_di_write(di, reg, DI_POL);
 
+unlock:
 	mutex_unlock(&di_mutex);
-
-	return 0;
+	return ret;
 }
 EXPORT_SYMBOL_GPL(ipu_di_init_sync_panel);
 
