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);