drm/i915: workaround IGD i2c bus issue in kernel side (v2)

In IGD, DPCUNIT_CLOCK_GATE_DISABLE bit should be set, otherwise i2c
access will be wrong.

v2: Disable CLOCK_GATE_DISABLE bit after bit bashing as suggested by Eric.

Signed-off-by: Shaohua Li <shaohua.li@intel.com>
Signed-off-by: Eric Anholt <eric@anholt.net>
diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h
index 5211947..15da44c 100644
--- a/drivers/gpu/drm/i915/i915_reg.h
+++ b/drivers/gpu/drm/i915/i915_reg.h
@@ -526,6 +526,7 @@
 #define   DPLLA_INPUT_BUFFER_ENABLE	(1 << 0)
 #define D_STATE		0x6104
 #define CG_2D_DIS	0x6200
+#define DPCUNIT_CLOCK_GATE_DISABLE	(1 << 24)
 #define CG_3D_DIS	0x6204
 
 /*
diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h
index 957daef..cf29430 100644
--- a/drivers/gpu/drm/i915/intel_drv.h
+++ b/drivers/gpu/drm/i915/intel_drv.h
@@ -109,7 +109,7 @@
 void intel_i2c_destroy(struct intel_i2c_chan *chan);
 int intel_ddc_get_modes(struct intel_output *intel_output);
 extern bool intel_ddc_probe(struct intel_output *intel_output);
-
+void intel_i2c_quirk_set(struct drm_device *dev, bool enable);
 extern void intel_crt_init(struct drm_device *dev);
 extern void intel_hdmi_init(struct drm_device *dev, int sdvox_reg);
 extern bool intel_sdvo_init(struct drm_device *dev, int output_device);
diff --git a/drivers/gpu/drm/i915/intel_i2c.c b/drivers/gpu/drm/i915/intel_i2c.c
index 5ee9d4c..f7061f6 100644
--- a/drivers/gpu/drm/i915/intel_i2c.c
+++ b/drivers/gpu/drm/i915/intel_i2c.c
@@ -34,6 +34,21 @@
 #include "i915_drm.h"
 #include "i915_drv.h"
 
+void intel_i2c_quirk_set(struct drm_device *dev, bool enable)
+{
+	struct drm_i915_private *dev_priv = dev->dev_private;
+
+	/* When using bit bashing for I2C, this bit needs to be set to 1 */
+	if (!IS_IGD(dev))
+		return;
+	if (enable)
+		I915_WRITE(CG_2D_DIS,
+			I915_READ(CG_2D_DIS) | DPCUNIT_CLOCK_GATE_DISABLE);
+	else
+		I915_WRITE(CG_2D_DIS,
+			I915_READ(CG_2D_DIS) & (~DPCUNIT_CLOCK_GATE_DISABLE));
+}
+
 /*
  * Intel GPIO access functions
  */
@@ -153,8 +168,10 @@
 		goto out_free;
 
 	/* JJJ:  raise SCL and SDA? */
+	intel_i2c_quirk_set(dev, true);
 	set_data(chan, 1);
 	set_clock(chan, 1);
+	intel_i2c_quirk_set(dev, false);
 	udelay(20);
 
 	return chan;
diff --git a/drivers/gpu/drm/i915/intel_modes.c b/drivers/gpu/drm/i915/intel_modes.c
index 07d7ec9..e0910fe 100644
--- a/drivers/gpu/drm/i915/intel_modes.c
+++ b/drivers/gpu/drm/i915/intel_modes.c
@@ -27,6 +27,7 @@
 #include <linux/fb.h>
 #include "drmP.h"
 #include "intel_drv.h"
+#include "i915_drv.h"
 
 /**
  * intel_ddc_probe
@@ -52,7 +53,10 @@
 		}
 	};
 
+	intel_i2c_quirk_set(intel_output->ddc_bus->drm_dev, true);
 	ret = i2c_transfer(&intel_output->ddc_bus->adapter, msgs, 2);
+	intel_i2c_quirk_set(intel_output->ddc_bus->drm_dev, false);
+
 	if (ret == 2)
 		return true;
 
@@ -70,8 +74,10 @@
 	struct edid *edid;
 	int ret = 0;
 
+	intel_i2c_quirk_set(intel_output->ddc_bus->drm_dev, true);
 	edid = drm_get_edid(&intel_output->base,
 			    &intel_output->ddc_bus->adapter);
+	intel_i2c_quirk_set(intel_output->ddc_bus->drm_dev, false);
 	if (edid) {
 		drm_mode_connector_update_edid_property(&intel_output->base,
 							edid);