]> Pileus Git - ~andy/linux/commitdiff
drm/i915: irq-drive the dp aux communication
authorDaniel Vetter <daniel.vetter@ffwll.ch>
Sat, 1 Dec 2012 12:53:48 +0000 (13:53 +0100)
committerDaniel Vetter <daniel.vetter@ffwll.ch>
Thu, 6 Dec 2012 12:18:00 +0000 (13:18 +0100)
At least on the platforms that have a dp aux irq and also have it
enabled - vlvhsw should have one, too. But I don't have a machine to
test this on. Judging from docs there's no dp aux interrupt for gm45.

Also, I only have an ivb cpu edp machine, so the dp aux A code for
snb/ilk is untested.

For dpcd probing when nothing is connected it slashes about 5ms of cpu
time (cpu time is now negligible), which agrees with 3 * 5 400 usec
timeouts.

A previous version of this patch increases the time required to go
through the dp_detect cycle (which includes reading the edid) from
around 33 ms to around 40 ms. Experiments indicated that this is
purely due to the irq latency - the hw doesn't allow us to queue up
dp aux transactions and hence irq latency directly affects throughput.
gmbus is much better, there we have a 8 byte buffer, and we get the
irq once another 4 bytes can be queued up.

But by using the pm_qos interface to request the lowest possible cpu
wake-up latency this slowdown completely disappeared.

Since all our output detection logic is single-threaded with the
mode_config mutex right now anyway, I've decide not ot play fancy and
to just reuse the gmbus wait queue. But this would definitely prep the
way to run dp detection on different ports in parallel

v2: Add a timeout for dp aux transfers when using interrupts - the hw
_does_  prevent this with the hw-based 400 usec timeout, but if the
irq somehow doesn't arrive we're screwed. Lesson learned while
developing this ;-)

v3: While at it also convert the busy-loop to wait_for_atomic, so that
we don't run the risk of an infinite loop any more.

v4: Ensure we have the smallest possible irq latency by using the
pm_qos interface.

v5: Add a comment to the code to explain why we frob pm_qos. Suggested
by Chris Wilson.

v6: Disable dp irq for vlv, that's easier than trying to get at docs
and hw.

v7: Squash in a fix for Haswell that Paulo Zanoni tracked down - the
dp aux registers aren't at a fixed offset any more, but can be on the
PCH while the DP port is on the cpu die.

Reviewed-by: Imre Deak <imre.deak@intel.com> (v6)
Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
drivers/gpu/drm/i915/i915_dma.c
drivers/gpu/drm/i915/i915_drv.h
drivers/gpu/drm/i915/i915_irq.c
drivers/gpu/drm/i915/intel_dp.c

index 14d679ab52b71d3963376650c87c711ce96426f2..93630669c6dc63813fdb3c19fe520058eb455de6 100644 (file)
@@ -1737,6 +1737,7 @@ int i915_driver_unload(struct drm_device *dev)
        intel_teardown_mchbar(dev);
 
        destroy_workqueue(dev_priv->wq);
+       pm_qos_remove_request(&dev_priv->pm_qos);
 
        if (dev_priv->slab)
                kmem_cache_destroy(dev_priv->slab);
index 3843383e13ccbca09d221ef2b30a16d5499b1229..a00ee3da632fea81329fe6c5298f665282d1b2ee 100644 (file)
@@ -40,6 +40,7 @@
 #include <linux/backlight.h>
 #include <linux/intel-iommu.h>
 #include <linux/kref.h>
+#include <linux/pm_qos.h>
 
 /* General customization:
  */
@@ -665,6 +666,9 @@ typedef struct drm_i915_private {
        /* protects the irq masks */
        spinlock_t irq_lock;
 
+       /* To control wakeup latency, e.g. for irq-driven dp aux transfers. */
+       struct pm_qos_request pm_qos;
+
        /* DPIO indirect register protection */
        spinlock_t dpio_lock;
 
index 1e245e501e5a4cfaf5f30358cb3cc76b3b666549..58bb11b58796b7b302fd5c9f02d83b27f0b7755c 100644 (file)
@@ -536,7 +536,11 @@ static void gmbus_irq_handler(struct drm_device *dev)
 
 static void dp_aux_irq_handler(struct drm_device *dev)
 {
+       struct drm_i915_private *dev_priv = (drm_i915_private_t *) dev->dev_private;
+
        DRM_DEBUG_DRIVER("AUX channel interrupt\n");
+
+       wake_up_all(&dev_priv->gmbus_wait_queue);
 }
 
 static irqreturn_t valleyview_irq_handler(int irq, void *arg)
@@ -2732,6 +2736,8 @@ void intel_irq_init(struct drm_device *dev)
        setup_timer(&dev_priv->hangcheck_timer, i915_hangcheck_elapsed,
                    (unsigned long) dev);
 
+       pm_qos_add_request(&dev_priv->pm_qos, PM_QOS_CPU_DMA_LATENCY, 0);
+
        dev->driver->get_vblank_counter = i915_get_vblank_counter;
        dev->max_vblank_count = 0xffffff; /* only 24 bits of frame count */
        if (IS_G4X(dev) || INTEL_INFO(dev)->gen >= 5) {
index b51043e651b5dc3b3b5b8ca222e0e67b99f1de95..ada1b316195cdabb323508c23244c6d6dfc51772 100644 (file)
@@ -322,6 +322,48 @@ intel_dp_check_edp(struct intel_dp *intel_dp)
        }
 }
 
+static uint32_t
+intel_dp_aux_wait_done(struct intel_dp *intel_dp, bool has_aux_irq)
+{
+       struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp);
+       struct drm_device *dev = intel_dig_port->base.base.dev;
+       struct drm_i915_private *dev_priv = dev->dev_private;
+       uint32_t ch_ctl = intel_dp->output_reg + 0x10;
+       uint32_t status;
+       bool done;
+
+       if (IS_HASWELL(dev)) {
+               switch (intel_dig_port->port) {
+               case PORT_A:
+                       ch_ctl = DPA_AUX_CH_CTL;
+                       break;
+               case PORT_B:
+                       ch_ctl = PCH_DPB_AUX_CH_CTL;
+                       break;
+               case PORT_C:
+                       ch_ctl = PCH_DPC_AUX_CH_CTL;
+                       break;
+               case PORT_D:
+                       ch_ctl = PCH_DPD_AUX_CH_CTL;
+                       break;
+               default:
+                       BUG();
+               }
+       }
+
+#define C (((status = I915_READ(ch_ctl)) & DP_AUX_CH_CTL_SEND_BUSY) == 0)
+       if (has_aux_irq)
+               done = wait_event_timeout(dev_priv->gmbus_wait_queue, C, 10);
+       else
+               done = wait_for_atomic(C, 10) == 0;
+       if (!done)
+               DRM_ERROR("dp aux hw did not signal timeout (has irq: %i)!\n",
+                         has_aux_irq);
+#undef C
+
+       return status;
+}
+
 static int
 intel_dp_aux_ch(struct intel_dp *intel_dp,
                uint8_t *send, int send_bytes,
@@ -333,11 +375,17 @@ intel_dp_aux_ch(struct intel_dp *intel_dp,
        struct drm_i915_private *dev_priv = dev->dev_private;
        uint32_t ch_ctl = output_reg + 0x10;
        uint32_t ch_data = ch_ctl + 4;
-       int i;
-       int recv_bytes;
+       int i, ret, recv_bytes;
        uint32_t status;
        uint32_t aux_clock_divider;
        int try, precharge;
+       bool has_aux_irq = INTEL_INFO(dev)->gen >= 5 && !IS_VALLEYVIEW(dev);
+
+       /* dp aux is extremely sensitive to irq latency, hence request the
+        * lowest possible wakeup latency and so prevent the cpu from going into
+        * deep sleep states.
+        */
+       pm_qos_update_request(&dev_priv->pm_qos, 0);
 
        if (IS_HASWELL(dev)) {
                switch (intel_dig_port->port) {
@@ -400,7 +448,8 @@ intel_dp_aux_ch(struct intel_dp *intel_dp,
        if (try == 3) {
                WARN(1, "dp_aux_ch not started status 0x%08x\n",
                     I915_READ(ch_ctl));
-               return -EBUSY;
+               ret = -EBUSY;
+               goto out;
        }
 
        /* Must try at least 3 times according to DP spec */
@@ -413,6 +462,7 @@ intel_dp_aux_ch(struct intel_dp *intel_dp,
                /* Send the command and wait for it to complete */
                I915_WRITE(ch_ctl,
                           DP_AUX_CH_CTL_SEND_BUSY |
+                          (has_aux_irq ? DP_AUX_CH_CTL_INTERRUPT : 0) |
                           DP_AUX_CH_CTL_TIME_OUT_400us |
                           (send_bytes << DP_AUX_CH_CTL_MESSAGE_SIZE_SHIFT) |
                           (precharge << DP_AUX_CH_CTL_PRECHARGE_2US_SHIFT) |
@@ -420,12 +470,8 @@ intel_dp_aux_ch(struct intel_dp *intel_dp,
                           DP_AUX_CH_CTL_DONE |
                           DP_AUX_CH_CTL_TIME_OUT_ERROR |
                           DP_AUX_CH_CTL_RECEIVE_ERROR);
-               for (;;) {
-                       status = I915_READ(ch_ctl);
-                       if ((status & DP_AUX_CH_CTL_SEND_BUSY) == 0)
-                               break;
-                       udelay(100);
-               }
+
+               status = intel_dp_aux_wait_done(intel_dp, has_aux_irq);
 
                /* Clear done status and any errors */
                I915_WRITE(ch_ctl,
@@ -443,7 +489,8 @@ intel_dp_aux_ch(struct intel_dp *intel_dp,
 
        if ((status & DP_AUX_CH_CTL_DONE) == 0) {
                DRM_ERROR("dp_aux_ch not done status 0x%08x\n", status);
-               return -EBUSY;
+               ret = -EBUSY;
+               goto out;
        }
 
        /* Check for timeout or receive error.
@@ -451,14 +498,16 @@ intel_dp_aux_ch(struct intel_dp *intel_dp,
         */
        if (status & DP_AUX_CH_CTL_RECEIVE_ERROR) {
                DRM_ERROR("dp_aux_ch receive error status 0x%08x\n", status);
-               return -EIO;
+               ret = -EIO;
+               goto out;
        }
 
        /* Timeouts occur when the device isn't connected, so they're
         * "normal" -- don't fill the kernel log with these */
        if (status & DP_AUX_CH_CTL_TIME_OUT_ERROR) {
                DRM_DEBUG_KMS("dp_aux_ch timeout status 0x%08x\n", status);
-               return -ETIMEDOUT;
+               ret = -ETIMEDOUT;
+               goto out;
        }
 
        /* Unload any bytes sent back from the other side */
@@ -471,7 +520,11 @@ intel_dp_aux_ch(struct intel_dp *intel_dp,
                unpack_aux(I915_READ(ch_data + i),
                           recv + i, recv_bytes - i);
 
-       return recv_bytes;
+       ret = recv_bytes;
+out:
+       pm_qos_update_request(&dev_priv->pm_qos, PM_QOS_DEFAULT_VALUE);
+
+       return ret;
 }
 
 /* Write data to the aux channel in native mode */