From fb0f8fbf97e8a25074c81c629500d94cafa9e366 Mon Sep 17 00:00:00 2001
From: Keith Packard <keithp@keithp.com>
Date: Thu, 11 Jun 2009 22:31:31 -0700
Subject: drm/i915: Generate 2MHz clock for display port aux channel I/O. Retry
 I/O.

The display port aux channel clock is taken from the hrawclk value, which is
provided to the chip as the FSB frequency (as far as I can determine). The
strapping values for that are available in the CLKCFG register, now used to
select an appropriate divider to generate a 2MHz clock.

In addition, the DisplayPort spec requires that each aux channel I/O be
retried 'at least 3 times' in case the sink is idle when the first request
comes in.

Signed-off-by: Keith Packard <keithp@keithp.com>
---
 drivers/gpu/drm/i915/intel_dp.c | 102 +++++++++++++++++++++++++++-------------
 1 file changed, 70 insertions(+), 32 deletions(-)

(limited to 'drivers/gpu/drm')

diff --git a/drivers/gpu/drm/i915/intel_dp.c b/drivers/gpu/drm/i915/intel_dp.c
index 818fe34..8f8d37d 100644
--- a/drivers/gpu/drm/i915/intel_dp.c
+++ b/drivers/gpu/drm/i915/intel_dp.c
@@ -154,6 +154,36 @@ unpack_aux(uint32_t src, uint8_t *dst, int dst_bytes)
 		dst[i] = src >> ((3-i) * 8);
 }
 
+/* hrawclock is 1/4 the FSB frequency */
+static int
+intel_hrawclk(struct drm_device *dev)
+{
+	struct drm_i915_private *dev_priv = dev->dev_private;
+	uint32_t clkcfg;
+
+	clkcfg = I915_READ(CLKCFG);
+	switch (clkcfg & CLKCFG_FSB_MASK) {
+	case CLKCFG_FSB_400:
+		return 100;
+	case CLKCFG_FSB_533:
+		return 133;
+	case CLKCFG_FSB_667:
+		return 166;
+	case CLKCFG_FSB_800:
+		return 200;
+	case CLKCFG_FSB_1067:
+		return 266;
+	case CLKCFG_FSB_1333:
+		return 333;
+	/* these two are just a guess; one of them might be right */
+	case CLKCFG_FSB_1600:
+	case CLKCFG_FSB_1600_ALT:
+		return 400;
+	default:
+		return 133;
+	}
+}
+
 static int
 intel_dp_aux_ch(struct intel_output *intel_output,
 		uint8_t *send, int send_bytes,
@@ -169,44 +199,52 @@ intel_dp_aux_ch(struct intel_output *intel_output,
 	int recv_bytes;
 	uint32_t ctl;
 	uint32_t status;
-
-	/* Load the send data into the aux channel data registers */
-	for (i = 0; i < send_bytes; i += 4) {
-		uint32_t    d = pack_aux(send + i, send_bytes - i);;
-
-		I915_WRITE(ch_data + i, d);
-	}
+	uint32_t aux_clock_divider;
+	int try;
 
 	/* The clock divider is based off the hrawclk,
-	 * and would like to run at 2MHz. The 133 below assumes
-	 * a 266MHz hrawclk; need to figure out how we're supposed
-	 * to know what hrawclk is...
+	 * and would like to run at 2MHz. So, take the
+	 * hrawclk value and divide by 2 and use that
 	 */
-	ctl = (DP_AUX_CH_CTL_SEND_BUSY |
-	       DP_AUX_CH_CTL_TIME_OUT_1600us |
-	       (send_bytes << DP_AUX_CH_CTL_MESSAGE_SIZE_SHIFT) |
-	       (5 << DP_AUX_CH_CTL_PRECHARGE_2US_SHIFT) |
-	       (133 << DP_AUX_CH_CTL_BIT_CLOCK_2X_SHIFT) |
-	       DP_AUX_CH_CTL_TIME_OUT_ERROR |
-	       DP_AUX_CH_CTL_RECEIVE_ERROR);
-
-	/* Send the command and wait for it to complete */
-	I915_WRITE(ch_ctl, ctl);
-	(void) I915_READ(ch_ctl);
-	for (;;) {
-		udelay(100);
-		status = I915_READ(ch_ctl);
-		if ((status & DP_AUX_CH_CTL_SEND_BUSY) == 0)
+	aux_clock_divider = intel_hrawclk(dev) / 2;
+	/* Must try at least 3 times according to DP spec */
+	for (try = 0; try < 5; try++) {
+		/* Load the send data into the aux channel data registers */
+		for (i = 0; i < send_bytes; i += 4) {
+			uint32_t    d = pack_aux(send + i, send_bytes - i);;
+	
+			I915_WRITE(ch_data + i, d);
+		}
+	
+		ctl = (DP_AUX_CH_CTL_SEND_BUSY |
+		       DP_AUX_CH_CTL_TIME_OUT_400us |
+		       (send_bytes << DP_AUX_CH_CTL_MESSAGE_SIZE_SHIFT) |
+		       (5 << DP_AUX_CH_CTL_PRECHARGE_2US_SHIFT) |
+		       (aux_clock_divider << DP_AUX_CH_CTL_BIT_CLOCK_2X_SHIFT) |
+		       DP_AUX_CH_CTL_DONE |
+		       DP_AUX_CH_CTL_TIME_OUT_ERROR |
+		       DP_AUX_CH_CTL_RECEIVE_ERROR);
+	
+		/* Send the command and wait for it to complete */
+		I915_WRITE(ch_ctl, ctl);
+		(void) I915_READ(ch_ctl);
+		for (;;) {
+			udelay(100);
+			status = I915_READ(ch_ctl);
+			if ((status & DP_AUX_CH_CTL_SEND_BUSY) == 0)
+				break;
+		}
+	
+		/* Clear done status and any errors */
+		I915_WRITE(ch_ctl, (ctl |
+				DP_AUX_CH_CTL_DONE |
+				DP_AUX_CH_CTL_TIME_OUT_ERROR |
+				DP_AUX_CH_CTL_RECEIVE_ERROR));
+		(void) I915_READ(ch_ctl);
+		if ((status & DP_AUX_CH_CTL_TIME_OUT_ERROR) == 0)
 			break;
 	}
 
-	/* Clear done status and any errors */
-	I915_WRITE(ch_ctl, (ctl |
-			DP_AUX_CH_CTL_DONE |
-			DP_AUX_CH_CTL_TIME_OUT_ERROR |
-			DP_AUX_CH_CTL_RECEIVE_ERROR));
-	(void) I915_READ(ch_ctl);
-
 	if ((status & DP_AUX_CH_CTL_DONE) == 0) {
 		printk(KERN_ERR "dp_aux_ch not done status 0x%08x\n", status);
 		return -EBUSY;
-- 
cgit v1.1