[Intel-gfx] [PATCH 2/2] drm/i915: use GMBUS for EDID fetching

Jesse Barnes jbarnes at virtuousgeek.org
Wed Jul 21 00:44:45 CEST 2010


Use the GMBUS interface rather than direct bit banging to grab the EDID
over DDC.  The hope is that this method will be more reliable than bit
banging for fetching EDIDs from buggy monitors or through switches.

Signed-off-by: Jesse Barnes <jbarnes at virtuousgeek.org>
---
 drivers/gpu/drm/i915/i915_reg.h    |   52 ++++++++++--
 drivers/gpu/drm/i915/intel_crt.c   |    7 ++
 drivers/gpu/drm/i915/intel_modes.c |  164 +++++++++++++++++++++++++++++++++++-
 3 files changed, 216 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h
index 42c6024..b89599c 100644
--- a/drivers/gpu/drm/i915/i915_reg.h
+++ b/drivers/gpu/drm/i915/i915_reg.h
@@ -507,12 +507,52 @@
 # define GPIO_DATA_VAL_IN		(1 << 12)
 # define GPIO_DATA_PULLUP_DISABLE	(1 << 13)
 
-#define GMBUS0			0x5100
-#define GMBUS1			0x5104
-#define GMBUS2			0x5108
-#define GMBUS3			0x510c
-#define GMBUS4			0x5110
-#define GMBUS5			0x5120
+#define GMBUS0			0x5100 /* clock/port select */
+#define   GMBUS_RATE_100KHZ	(0<<8)
+#define   GMBUS_RATE_50KHZ	(1<<8)
+#define   GMBUS_RATE_400KHZ	(2<<8) /* reserved on Pineview */
+#define   GMBUS_RATE_1MHZ	(3<<8) /* reserved on Pineview */
+#define   GMBUS_HOLD_EXT	(1<<7) /* 300ns hold time, rsvd on Pineview */
+#define   GMBUS_PORT_DISABLED	0
+#define   GMBUS_PORT_SSC	1
+#define   GMBUS_PORT_VGADDC	2
+#define   GMBUS_PORT_PANEL	3
+#define   GMBUS_PORT_DPC	4 /* HDMIC */
+#define   GMBUS_PORT_DPB	5 /* SDVO, HDMIB */
+#define   GMBUS_PORT_DPD	6 /* HDMID */
+				  /* 7 reserved */
+#define GMBUS1			0x5104 /* command/status */
+#define   GMBUS_SW_CLR_INT	(1<<31)
+#define   GMBUS_SW_RDY		(1<<30)
+#define   GMBUS_ENT		(1<<29) /* enable timeout */
+#define   GMBUS_CYCLE_NONE	(0<<25)
+#define   GMBUS_CYCLE_NI_NS_WAIT (1<<25)
+#define   GMBUS_CYCLE_I_NS_WAIT	(3<<25)
+#define   GMBUS_CYCLE_STOP	(4<<25)
+#define   GMBUS_CYCLE_NI_STOP	(5<<25)
+#define   GMBUS_CYCLE_I_STOP	(7<<25)
+#define   GMBUS_BYTE_COUNT_SHIFT 16
+#define   GMBUS_SLAVE_INDEX_SHIFT 8
+#define   GMBUS_SLAVE_ADDR_SHIFT 1
+#define   GMBUS_SLAVE_READ	(1<<0)
+#define   GMBUS_SLAVE_WRITE	(0<<0)
+#define GMBUS2			0x5108 /* status */
+#define   GMBUS_INUSE		(1<<15)
+#define   GMBUS_HW_WAIT_PHASE	(1<<14)
+#define   GMBUS_STALL_TIMEOUT	(1<<13)
+#define   GMBUS_INT		(1<<12)
+#define   GMBUS_HW_RDY		(1<<11)
+#define   GMBUS_SATOER		(1<<10)
+#define   GMBUS_ACTIVE		(1<<9)
+#define GMBUS3			0x510c /* data buffer bytes 3-0 */
+#define GMBUS4			0x5110 /* interrupt mask (Pineview+) */
+#define   GMBUS_SLAVE_TIMEOUT_EN (1<<4)
+#define   GMBUS_NAK_EN		(1<<3)
+#define   GMBUS_IDLE_EN		(1<<2)
+#define   GMBUS_HW_WAIT_EN	(1<<1)
+#define   GMBUS_HW_RDY_EN	(1<<0)
+#define GMBUS5			0x5120 /* byte index */
+#define   GMBUS_2BYTE_INDEX_EN	(1<<31)
 
 /*
  * Clock control & power management
diff --git a/drivers/gpu/drm/i915/intel_crt.c b/drivers/gpu/drm/i915/intel_crt.c
index ee0732b..60dc11b 100644
--- a/drivers/gpu/drm/i915/intel_crt.c
+++ b/drivers/gpu/drm/i915/intel_crt.c
@@ -453,6 +453,12 @@ static int intel_crt_get_modes(struct drm_connector *connector)
 	struct i2c_adapter *ddc_bus;
 	struct drm_device *dev = connector->dev;
 
+	/* Try GMBUS first */
+	ret = intel_gmbus_get_modes(connector, 0);
+	if (ret) {
+		DRM_DEBUG_DRIVER("got EDID from GMBUS\n");
+		goto end;
+	}
 
 	ret = intel_ddc_get_modes(connector, intel_encoder->ddc_bus);
 	if (ret || !IS_G4X(dev))
@@ -466,6 +472,7 @@ static int intel_crt_get_modes(struct drm_connector *connector)
 			   "DDC bus registration failed for CRTDDC_D.\n");
 		goto end;
 	}
+
 	/* Try to get modes by GPIOD port */
 	ret = intel_ddc_get_modes(connector, ddc_bus);
 	intel_i2c_destroy(ddc_bus);
diff --git a/drivers/gpu/drm/i915/intel_modes.c b/drivers/gpu/drm/i915/intel_modes.c
index 35d15f8..c9b946d 100644
--- a/drivers/gpu/drm/i915/intel_modes.c
+++ b/drivers/gpu/drm/i915/intel_modes.c
@@ -1,6 +1,6 @@
 /*
  * Copyright (c) 2007 Dave Airlie <airlied at linux.ie>
- * Copyright (c) 2007 Intel Corporation
+ * Copyright (c) 2007, 2010 Intel Corporation
  *   Jesse Barnes <jesse.barnes at intel.com>
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
@@ -89,3 +89,165 @@ int intel_ddc_get_modes(struct drm_connector *connector,
 
 	return ret;
 }
+
+static void intel_dump_gmbus(drm_i915_private_t *dev_priv)
+{
+	DRM_DEBUG_DRIVER("GMBUS0: 0x%08x\n", I915_READ(GMBUS0));
+	DRM_DEBUG_DRIVER("GMBUS1: 0x%08x\n", I915_READ(GMBUS1));
+	DRM_DEBUG_DRIVER("GMBUS2: 0x%08x\n", I915_READ(GMBUS2));
+	DRM_DEBUG_DRIVER("GMBUS3: 0x%08x\n", I915_READ(GMBUS3));
+	DRM_DEBUG_DRIVER("GMBUS4: 0x%08x\n", I915_READ(GMBUS4));
+	DRM_DEBUG_DRIVER("GMBUS5: 0x%08x\n", I915_READ(GMBUS5));
+}
+
+static bool intel_gmbus_wait_hw(drm_i915_private_t *dev_priv)
+{
+	unsigned long timeout = jiffies + msecs_to_jiffies(500);
+	u32 gmbus2;
+	bool ret = false;
+
+	do {
+		gmbus2 = I915_READ(GMBUS2);
+
+		if (time_after(jiffies, timeout) ||
+		    (gmbus2 & GMBUS_SATOER)) {
+			DRM_DEBUG_DRIVER("timeout waiting for GMBUS hw\n");
+			intel_dump_gmbus(dev_priv);
+			ret = true;
+			break;
+		}
+	} while(!(gmbus2 & GMBUS_HW_RDY));
+
+	return ret;
+}
+
+static bool intel_gmbus_wait_idle(drm_i915_private_t *dev_priv)
+{
+	unsigned long timeout = jiffies + msecs_to_jiffies(500);
+	bool ret = false;
+
+	while (I915_READ(GMBUS2) & GMBUS_ACTIVE) {
+		if (time_after(jiffies, timeout)) {
+			DRM_DEBUG_DRIVER("timeout waiting for GMBUS idle\n");
+			intel_dump_gmbus(dev_priv);
+			ret = true;
+			break;
+		}
+	}
+
+	return ret;
+}
+
+static void intel_gmbus_reset(drm_i915_private_t *dev_priv)
+{
+	I915_WRITE(GMBUS1, GMBUS_SW_CLR_INT);
+	POSTING_READ(GMBUS1);
+	I915_WRITE(GMBUS1, 0);
+
+	if (intel_gmbus_wait_idle(dev_priv))
+		DRM_DEBUG_DRIVER("warning: gmbus reset timed out\n");
+}
+
+struct intel_gmbus_edid_data {
+	struct drm_device *dev;
+	int pin;
+};
+
+static int intel_gmbus_edid_read(void *data, unsigned char *buf, int block,
+				 int len)
+{
+	struct intel_gmbus_edid_data *edid_read_data = data;
+	drm_i915_private_t *dev_priv = edid_read_data->dev->dev_private;
+	u32 gmbus_port;
+	int pin = edid_read_data->pin, i;
+	unsigned char start = block * EDID_LENGTH;
+
+	if (pin != 0) {
+		DRM_DEBUG_DRIVER("pin not supported\n");
+		return -EINVAL;
+	}
+
+	gmbus_port = GMBUS_PORT_VGADDC;
+
+	intel_gmbus_reset(dev_priv);
+
+	I915_WRITE(GMBUS0, GMBUS_RATE_100KHZ | gmbus_port);
+
+	/* Write start address to DDC port */
+	I915_WRITE(GMBUS3, start);
+	I915_WRITE(GMBUS1, GMBUS_CYCLE_NI_STOP | (1 << GMBUS_BYTE_COUNT_SHIFT) |
+		   (DDC_ADDR << GMBUS_SLAVE_ADDR_SHIFT) |
+		   GMBUS_SLAVE_WRITE);
+	I915_WRITE(GMBUS1, I915_READ(GMBUS1) | GMBUS_SW_RDY);
+	if (intel_gmbus_wait_hw(dev_priv)) {
+		DRM_DEBUG_DRIVER("GMBUS timeout waiting for hw ready\n");
+		return -ETIMEDOUT;
+	}
+
+	/* Start data transfer into buf */
+	I915_WRITE(GMBUS1, GMBUS_CYCLE_NI_STOP |
+		   (len << GMBUS_BYTE_COUNT_SHIFT) |
+		   (DDC_ADDR << GMBUS_SLAVE_ADDR_SHIFT) |
+		   GMBUS_SLAVE_READ);
+	I915_WRITE(GMBUS1, I915_READ(GMBUS1) | GMBUS_SW_RDY);
+	for (i = 0; i < len; i += 4) {
+		u32 data;
+		if (intel_gmbus_wait_hw(dev_priv))
+			break;
+		data = I915_READ(GMBUS3);
+		buf[i] = data & 0xff;
+		if (i + 1 < len)
+			buf[i+1] = (data >> 8) & 0xff;
+		if (i + 2 < len)
+			buf[i+2] = (data >> 16) & 0xff;
+		if (i + 3 < len)
+			buf[i+3] = (data >> 24) & 0xff;
+	}
+
+	/* Send STOP (ignore timeouts) and clear GMBUS0 */
+	intel_gmbus_wait_hw(dev_priv);
+	I915_WRITE(GMBUS1, GMBUS_CYCLE_STOP |
+		   (DDC_ADDR << GMBUS_SLAVE_ADDR_SHIFT));
+	I915_WRITE(GMBUS1, I915_READ(GMBUS1) | GMBUS_SW_RDY);
+	intel_gmbus_wait_idle(dev_priv);
+	I915_WRITE(GMBUS0, 0);
+
+	return 0;
+}
+
+/**
+ * intel_gmbus_get_modes - get modes using DDC over GMBUS
+ * @connector: connector in question
+ * @pin: pin to use
+ *
+ * There are 8 pins available:
+ *   0 - VGA DAC DDC
+ *   1 - i2c data (SSC clock control)
+ *   2 - LVDS DDC
+ *   3 - DVOA DDC (reserved in 9xx+)
+ *   4 - i2c data (add2 control)
+ *   5 - DVOA data (reserved in 9xx+)
+ *   6 - thermal sensor (reserved in 9xx+)
+ *   7 - trusted output
+ * The above are the recommended pin connections, they may vary from
+ * platform to platform; the VBT should contain the actual mappings.
+ */
+int intel_gmbus_get_modes(struct drm_connector *connector, int pin)
+{
+	struct edid *edid;
+	struct intel_gmbus_edid_data data = {
+		.dev = connector->dev,
+		.pin = pin,
+	};
+	int ret = 0;
+
+	edid = drm_get_edid(connector, intel_gmbus_edid_read, &data);
+	if (edid) {
+		drm_mode_connector_update_edid_property(connector, edid);
+		ret = drm_add_edid_modes(connector, edid);
+		connector->display_info.raw_edid = NULL;
+		kfree(edid);
+	}
+
+	return ret;
+}
-- 
1.7.0.4




More information about the Intel-gfx mailing list