diff mbox

[2/2] drm/i915: use GMBUS for EDID fetching

Message ID 20100720154445.06c62f25@virtuousgeek.org
State Deferred, archived
Headers show

Commit Message

Jesse Barnes July 20, 2010, 10:44 p.m. UTC
None
diff mbox

Patch

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@linux.ie>
- * Copyright (c) 2007 Intel Corporation
+ * Copyright (c) 2007, 2010 Intel Corporation
  *   Jesse Barnes <jesse.barnes@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;
+}