Intel IVCH, Bug 49838
diff mbox

Message ID jwv8ucsjo38.fsf-monnier+gmane.comp.freedesktop.xorg.drivers.intel@gnu.org
State New
Headers show

Commit Message

Stefan Monnier May 13, 2015, 7:11 p.m. UTC
> With some debugging, I was able to resolve this problem. The bug here is
> that the bios of the X30 apparently forgets to re-initialize the registers
> of the DVO; especially, the PLL remains uninitialized and the
> panel output timing remains unitialized. No miracle the panel remains blank.

The patch you posted on
https://bugs.freedesktop.org/show_bug.cgi?id=49838 did not apply cleanly
to 4.1.0-rc2 (it seems to touch some dithering code that I guess
hasn't been installed upstream yet), but I was able to apply it by hand
(see attached patch against 4.1.0-rc2) and have been using it
successfully for the last week.

It's really great to be able to use XRandR (and hence an external
monitor again at a resolution different from 1024x768) again with my X30
(as was the case before the KMS revolution).
I hope this patch finds it way soon into the upstream kernel.


        Stefan

Patch
diff mbox

diff --git a/drivers/gpu/drm/i915/dvo_ivch.c b/drivers/gpu/drm/i915/dvo_ivch.c
index 0f2587f..3a1035a83 100644
--- a/drivers/gpu/drm/i915/dvo_ivch.c
+++ b/drivers/gpu/drm/i915/dvo_ivch.c
@@ -83,7 +83,7 @@ 
 /*
  * LCD Vertical Display Size
  */
-#define VR21	0x20
+#define VR21    0x21
 
 /*
  * Panel power down status
@@ -148,16 +148,41 @@ 
 # define VR8F_POWER_MASK		(0x3c)
 # define VR8F_POWER_POS			(2)
 
+/* Some Bios implementations do not restore the DVO state upon
+ * resume from standby. Thus, this driver has to handle it
+ * instead. The following list contains all registers that
+ * require saving.
+ */
+static const uint16_t backup_addresses[] = {
+	0x11, 0x12,
+	0x18, 0x19, 0x1a, 0x1f,
+	0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+	0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+	0x8e, 0x8f,
+	0x10        /* this must come last */
+};
+
 
 struct ivch_priv {
 	bool quiet;
 
 	uint16_t width, height;
+
+	/* Register backup */
+
+	uint16_t reg_backup[ARRAY_SIZE(backup_addresses)];
 };
 
 
 static void ivch_dump_regs(struct intel_dvo_device *dvo);
 
+static inline struct intel_gmbus *
+to_intel_gmbus(struct i2c_adapter *i2c)
+{
+	return container_of(i2c, struct intel_gmbus, adapter);
+}
+
+
 /**
  * Reads a register on the ivch.
  *
@@ -167,6 +192,7 @@  static bool ivch_read(struct intel_dvo_device *dvo, int addr, uint16_t *data)
 {
 	struct ivch_priv *priv = dvo->dev_priv;
 	struct i2c_adapter *adapter = dvo->i2c_bus;
+	struct intel_gmbus *bus = to_intel_gmbus(adapter);
 	u8 out_buf[1];
 	u8 in_buf[2];
 
@@ -191,9 +217,10 @@  static bool ivch_read(struct intel_dvo_device *dvo, int addr, uint16_t *data)
 	};
 
 	out_buf[0] = addr;
-
+	bus->force_bit++; /* the IVCH requires bit-banging */
 	if (i2c_transfer(adapter, msgs, 3) == 3) {
 		*data = (in_buf[1] << 8) | in_buf[0];
+		bus->force_bit--;
 		return true;
 	}
 
@@ -202,6 +229,7 @@  static bool ivch_read(struct intel_dvo_device *dvo, int addr, uint16_t *data)
 				"%s:%02x.\n",
 			  addr, adapter->name, dvo->slave_addr);
 	}
+	bus->force_bit--;
 	return false;
 }
 
@@ -210,6 +238,7 @@  static bool ivch_write(struct intel_dvo_device *dvo, int addr, uint16_t data)
 {
 	struct ivch_priv *priv = dvo->dev_priv;
 	struct i2c_adapter *adapter = dvo->i2c_bus;
+	struct intel_gmbus *bus = to_intel_gmbus(adapter);
 	u8 out_buf[3];
 	struct i2c_msg msg = {
 		.addr = dvo->slave_addr,
@@ -221,15 +250,19 @@  static bool ivch_write(struct intel_dvo_device *dvo, int addr, uint16_t data)
 	out_buf[0] = addr;
 	out_buf[1] = data & 0xff;
 	out_buf[2] = data >> 8;
+	bus->force_bit++; /* bit-banging required for the IVCH */
 
-	if (i2c_transfer(adapter, &msg, 1) == 1)
+	if (i2c_transfer(adapter, &msg, 1) == 1) {
+		bus->force_bit--;
 		return true;
+	}
 
 	if (!priv->quiet) {
 		DRM_DEBUG_KMS("Unable to write register 0x%02x to %s:%d.\n",
 			  addr, adapter->name, dvo->slave_addr);
 	}
 
+	bus->force_bit--;
 	return false;
 }
 
@@ -239,6 +272,7 @@  static bool ivch_init(struct intel_dvo_device *dvo,
 {
 	struct ivch_priv *priv;
 	uint16_t temp;
+	int i;
 
 	priv = kzalloc(sizeof(struct ivch_priv), GFP_KERNEL);
 	if (priv == NULL)
@@ -266,6 +300,14 @@  static bool ivch_init(struct intel_dvo_device *dvo,
 	ivch_read(dvo, VR20, &priv->width);
 	ivch_read(dvo, VR21, &priv->height);
 
+	/* Make a backup of the registers to be able to restore them
+	 * upon suspend.
+	 */
+	for (i = 0; i < ARRAY_SIZE(backup_addresses); i++)
+		ivch_read(dvo, backup_addresses[i], priv->reg_backup + i);
+
+	ivch_dump_regs(dvo);
+
 	return true;
 
 out:
@@ -287,12 +329,31 @@  static enum drm_mode_status ivch_mode_valid(struct intel_dvo_device *dvo,
 	return MODE_OK;
 }
 
+/* Restore the DVO registers after a resume
+ * from RAM. Registers have been saved during
+ * the initialization.
+ */
+static void ivch_reset(struct intel_dvo_device *dvo)
+{
+	struct ivch_priv *priv = dvo->dev_priv;
+	int i;
+
+	DRM_DEBUG_KMS("Resetting the IVCH registers\n");
+
+	ivch_write(dvo, VR10, 0x0000);
+
+	for (i = 0; i < ARRAY_SIZE(backup_addresses); i++)
+		ivch_write(dvo, backup_addresses[i], priv->reg_backup[i]);
+}
+
 /** Sets the power state of the panel connected to the ivch */
 static void ivch_dpms(struct intel_dvo_device *dvo, bool enable)
 {
 	int i;
 	uint16_t vr01, vr30, backlight;
 
+	ivch_reset(dvo);
+
 	/* Set the new power state of the panel. */
 	if (!ivch_read(dvo, VR01, &vr01))
 		return;
@@ -327,6 +388,8 @@  static bool ivch_get_hw_state(struct intel_dvo_device *dvo)
 {
 	uint16_t vr01;
 
+	ivch_reset(dvo);
+
 	/* Set the new power state of the panel. */
 	if (!ivch_read(dvo, VR01, &vr01))
 		return false;
@@ -344,6 +407,8 @@  static void ivch_mode_set(struct intel_dvo_device *dvo,
 	uint16_t vr40 = 0;
 	uint16_t vr01;
 
+	ivch_reset(dvo);
+
 	vr01 = 0;
 	vr40 = (VR40_STALL_ENABLE | VR40_VERTICAL_INTERP_ENABLE |
 		VR40_HORIZONTAL_INTERP_ENABLE);