[Intel-gfx] Intel IVCH, Bug 49838
Stefan Monnier
monnier at iro.umontreal.ca
Wed May 13 12:11:48 PDT 2015
> 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
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);
More information about the Intel-gfx
mailing list