drm/radeon: Add MSI quirk for gateway RS690
[pandora-kernel.git] / drivers / gpu / drm / radeon / radeon_irq_kms.c
index 5df58d1..2da4465 100644 (file)
 #include "radeon.h"
 #include "atom.h"
 
+#define RADEON_WAIT_IDLE_TIMEOUT 200
+
+/**
+ * radeon_driver_irq_handler_kms - irq handler for KMS
+ *
+ * @DRM_IRQ_ARGS: args
+ *
+ * This is the irq handler for the radeon KMS driver (all asics).
+ * radeon_irq_process is a macro that points to the per-asic
+ * irq handler callback.
+ */
 irqreturn_t radeon_driver_irq_handler_kms(DRM_IRQ_ARGS)
 {
        struct drm_device *dev = (struct drm_device *) arg;
@@ -43,6 +54,17 @@ irqreturn_t radeon_driver_irq_handler_kms(DRM_IRQ_ARGS)
 /*
  * Handle hotplug events outside the interrupt handler proper.
  */
+/**
+ * radeon_hotplug_work_func - display hotplug work handler
+ *
+ * @work: work struct
+ *
+ * This is the hot plug event work handler (all asics).
+ * The work gets scheduled from the irq handler if there
+ * was a hot plug interrupt.  It walks the connector table
+ * and calls the hotplug handler for each one, then sends
+ * a drm hotplug event to alert userspace.
+ */
 static void radeon_hotplug_work_func(struct work_struct *work)
 {
        struct radeon_device *rdev = container_of(work, struct radeon_device,
@@ -59,61 +81,92 @@ static void radeon_hotplug_work_func(struct work_struct *work)
        drm_helper_hpd_irq_event(dev);
 }
 
+/**
+ * radeon_driver_irq_preinstall_kms - drm irq preinstall callback
+ *
+ * @dev: drm dev pointer
+ *
+ * Gets the hw ready to enable irqs (all asics).
+ * This function disables all interrupt sources on the GPU.
+ */
 void radeon_driver_irq_preinstall_kms(struct drm_device *dev)
 {
        struct radeon_device *rdev = dev->dev_private;
+       unsigned long irqflags;
        unsigned i;
 
+       spin_lock_irqsave(&rdev->irq.lock, irqflags);
        /* Disable *all* interrupts */
        for (i = 0; i < RADEON_NUM_RINGS; i++)
-               rdev->irq.sw_int[i] = false;
-       rdev->irq.gui_idle = false;
+               atomic_set(&rdev->irq.ring_int[i], 0);
        for (i = 0; i < RADEON_MAX_HPD_PINS; i++)
                rdev->irq.hpd[i] = false;
        for (i = 0; i < RADEON_MAX_CRTCS; i++) {
                rdev->irq.crtc_vblank_int[i] = false;
-               rdev->irq.pflip[i] = false;
+               atomic_set(&rdev->irq.pflip[i], 0);
                rdev->irq.afmt[i] = false;
        }
        radeon_irq_set(rdev);
+       spin_unlock_irqrestore(&rdev->irq.lock, irqflags);
        /* Clear bits */
        radeon_irq_process(rdev);
 }
 
+/**
+ * radeon_driver_irq_postinstall_kms - drm irq preinstall callback
+ *
+ * @dev: drm dev pointer
+ *
+ * Handles stuff to be done after enabling irqs (all asics).
+ * Returns 0 on success.
+ */
 int radeon_driver_irq_postinstall_kms(struct drm_device *dev)
 {
-       struct radeon_device *rdev = dev->dev_private;
-       unsigned i;
-
        dev->max_vblank_count = 0x001fffff;
-       for (i = 0; i < RADEON_NUM_RINGS; i++)
-               rdev->irq.sw_int[i] = true;
-       radeon_irq_set(rdev);
        return 0;
 }
 
+/**
+ * radeon_driver_irq_uninstall_kms - drm irq uninstall callback
+ *
+ * @dev: drm dev pointer
+ *
+ * This function disables all interrupt sources on the GPU (all asics).
+ */
 void radeon_driver_irq_uninstall_kms(struct drm_device *dev)
 {
        struct radeon_device *rdev = dev->dev_private;
+       unsigned long irqflags;
        unsigned i;
 
        if (rdev == NULL) {
                return;
        }
+       spin_lock_irqsave(&rdev->irq.lock, irqflags);
        /* Disable *all* interrupts */
        for (i = 0; i < RADEON_NUM_RINGS; i++)
-               rdev->irq.sw_int[i] = false;
-       rdev->irq.gui_idle = false;
+               atomic_set(&rdev->irq.ring_int[i], 0);
        for (i = 0; i < RADEON_MAX_HPD_PINS; i++)
                rdev->irq.hpd[i] = false;
        for (i = 0; i < RADEON_MAX_CRTCS; i++) {
                rdev->irq.crtc_vblank_int[i] = false;
-               rdev->irq.pflip[i] = false;
+               atomic_set(&rdev->irq.pflip[i], 0);
                rdev->irq.afmt[i] = false;
        }
        radeon_irq_set(rdev);
+       spin_unlock_irqrestore(&rdev->irq.lock, irqflags);
 }
 
+/**
+ * radeon_msi_ok - asic specific msi checks
+ *
+ * @rdev: radeon device pointer
+ *
+ * Handles asic specific MSI checks to determine if
+ * MSIs should be enabled on a particular chip (all asics).
+ * Returns true if MSIs should be enabled, false if MSIs
+ * should not be enabled.
+ */
 static bool radeon_msi_ok(struct radeon_device *rdev)
 {
        /* RV370/RV380 was first asic with MSI support */
@@ -149,6 +202,12 @@ static bool radeon_msi_ok(struct radeon_device *rdev)
            (rdev->pdev->subsystem_device == 0x01fd))
                return true;
 
+       /* Gateway RS690 only seems to work with MSIs. */
+       if ((rdev->pdev->device == 0x791f) &&
+           (rdev->pdev->subsystem_vendor == 0x107b) &&
+           (rdev->pdev->subsystem_device == 0x0185))
+               return true;
+
        /* RV515 seems to have MSI issues where it loses
         * MSI rearms occasionally. This leads to lockups and freezes.
         * disable it by default.
@@ -166,17 +225,22 @@ static bool radeon_msi_ok(struct radeon_device *rdev)
        return true;
 }
 
+/**
+ * radeon_irq_kms_init - init driver interrupt info
+ *
+ * @rdev: radeon device pointer
+ *
+ * Sets up the work irq handlers, vblank init, MSIs, etc. (all asics).
+ * Returns 0 for success, error for failure.
+ */
 int radeon_irq_kms_init(struct radeon_device *rdev)
 {
-       int i;
        int r = 0;
 
        INIT_WORK(&rdev->hotplug_work, radeon_hotplug_work_func);
        INIT_WORK(&rdev->audio_work, r600_audio_update_hdmi);
 
-       spin_lock_init(&rdev->irq.sw_lock);
-       for (i = 0; i < rdev->num_crtc; i++)
-               spin_lock_init(&rdev->irq.pflip_lock[i]);
+       spin_lock_init(&rdev->irq.lock);
        r = drm_vblank_init(rdev->ddev, rdev->num_crtc);
        if (r) {
                return r;
@@ -201,6 +265,13 @@ int radeon_irq_kms_init(struct radeon_device *rdev)
        return 0;
 }
 
+/**
+ * radeon_irq_kms_fini - tear down driver interrrupt info
+ *
+ * @rdev: radeon device pointer
+ *
+ * Tears down the work irq handlers, vblank handlers, MSIs, etc. (all asics).
+ */
 void radeon_irq_kms_fini(struct radeon_device *rdev)
 {
        drm_vblank_cleanup(rdev->ddev);
@@ -213,31 +284,63 @@ void radeon_irq_kms_fini(struct radeon_device *rdev)
        flush_work_sync(&rdev->hotplug_work);
 }
 
+/**
+ * radeon_irq_kms_sw_irq_get - enable software interrupt
+ *
+ * @rdev: radeon device pointer
+ * @ring: ring whose interrupt you want to enable
+ *
+ * Enables the software interrupt for a specific ring (all asics).
+ * The software interrupt is generally used to signal a fence on
+ * a particular ring.
+ */
 void radeon_irq_kms_sw_irq_get(struct radeon_device *rdev, int ring)
 {
        unsigned long irqflags;
 
-       spin_lock_irqsave(&rdev->irq.sw_lock, irqflags);
-       if (rdev->ddev->irq_enabled && (++rdev->irq.sw_refcount[ring] == 1)) {
-               rdev->irq.sw_int[ring] = true;
+       if (!rdev->ddev->irq_enabled)
+               return;
+
+       if (atomic_inc_return(&rdev->irq.ring_int[ring]) == 1) {
+               spin_lock_irqsave(&rdev->irq.lock, irqflags);
                radeon_irq_set(rdev);
+               spin_unlock_irqrestore(&rdev->irq.lock, irqflags);
        }
-       spin_unlock_irqrestore(&rdev->irq.sw_lock, irqflags);
 }
 
+/**
+ * radeon_irq_kms_sw_irq_put - disable software interrupt
+ *
+ * @rdev: radeon device pointer
+ * @ring: ring whose interrupt you want to disable
+ *
+ * Disables the software interrupt for a specific ring (all asics).
+ * The software interrupt is generally used to signal a fence on
+ * a particular ring.
+ */
 void radeon_irq_kms_sw_irq_put(struct radeon_device *rdev, int ring)
 {
        unsigned long irqflags;
 
-       spin_lock_irqsave(&rdev->irq.sw_lock, irqflags);
-       BUG_ON(rdev->ddev->irq_enabled && rdev->irq.sw_refcount[ring] <= 0);
-       if (rdev->ddev->irq_enabled && (--rdev->irq.sw_refcount[ring] == 0)) {
-               rdev->irq.sw_int[ring] = false;
+       if (!rdev->ddev->irq_enabled)
+               return;
+
+       if (atomic_dec_and_test(&rdev->irq.ring_int[ring])) {
+               spin_lock_irqsave(&rdev->irq.lock, irqflags);
                radeon_irq_set(rdev);
+               spin_unlock_irqrestore(&rdev->irq.lock, irqflags);
        }
-       spin_unlock_irqrestore(&rdev->irq.sw_lock, irqflags);
 }
 
+/**
+ * radeon_irq_kms_pflip_irq_get - enable pageflip interrupt
+ *
+ * @rdev: radeon device pointer
+ * @crtc: crtc whose interrupt you want to enable
+ *
+ * Enables the pageflip interrupt for a specific crtc (all asics).
+ * For pageflips we use the vblank interrupt source.
+ */
 void radeon_irq_kms_pflip_irq_get(struct radeon_device *rdev, int crtc)
 {
        unsigned long irqflags;
@@ -245,14 +348,25 @@ void radeon_irq_kms_pflip_irq_get(struct radeon_device *rdev, int crtc)
        if (crtc < 0 || crtc >= rdev->num_crtc)
                return;
 
-       spin_lock_irqsave(&rdev->irq.pflip_lock[crtc], irqflags);
-       if (rdev->ddev->irq_enabled && (++rdev->irq.pflip_refcount[crtc] == 1)) {
-               rdev->irq.pflip[crtc] = true;
+       if (!rdev->ddev->irq_enabled)
+               return;
+
+       if (atomic_inc_return(&rdev->irq.pflip[crtc]) == 1) {
+               spin_lock_irqsave(&rdev->irq.lock, irqflags);
                radeon_irq_set(rdev);
+               spin_unlock_irqrestore(&rdev->irq.lock, irqflags);
        }
-       spin_unlock_irqrestore(&rdev->irq.pflip_lock[crtc], irqflags);
 }
 
+/**
+ * radeon_irq_kms_pflip_irq_put - disable pageflip interrupt
+ *
+ * @rdev: radeon device pointer
+ * @crtc: crtc whose interrupt you want to disable
+ *
+ * Disables the pageflip interrupt for a specific crtc (all asics).
+ * For pageflips we use the vblank interrupt source.
+ */
 void radeon_irq_kms_pflip_irq_put(struct radeon_device *rdev, int crtc)
 {
        unsigned long irqflags;
@@ -260,12 +374,90 @@ void radeon_irq_kms_pflip_irq_put(struct radeon_device *rdev, int crtc)
        if (crtc < 0 || crtc >= rdev->num_crtc)
                return;
 
-       spin_lock_irqsave(&rdev->irq.pflip_lock[crtc], irqflags);
-       BUG_ON(rdev->ddev->irq_enabled && rdev->irq.pflip_refcount[crtc] <= 0);
-       if (rdev->ddev->irq_enabled && (--rdev->irq.pflip_refcount[crtc] == 0)) {
-               rdev->irq.pflip[crtc] = false;
+       if (!rdev->ddev->irq_enabled)
+               return;
+
+       if (atomic_dec_and_test(&rdev->irq.pflip[crtc])) {
+               spin_lock_irqsave(&rdev->irq.lock, irqflags);
                radeon_irq_set(rdev);
+               spin_unlock_irqrestore(&rdev->irq.lock, irqflags);
        }
-       spin_unlock_irqrestore(&rdev->irq.pflip_lock[crtc], irqflags);
+}
+
+/**
+ * radeon_irq_kms_enable_afmt - enable audio format change interrupt
+ *
+ * @rdev: radeon device pointer
+ * @block: afmt block whose interrupt you want to enable
+ *
+ * Enables the afmt change interrupt for a specific afmt block (all asics).
+ */
+void radeon_irq_kms_enable_afmt(struct radeon_device *rdev, int block)
+{
+       unsigned long irqflags;
+
+       spin_lock_irqsave(&rdev->irq.lock, irqflags);
+       rdev->irq.afmt[block] = true;
+       radeon_irq_set(rdev);
+       spin_unlock_irqrestore(&rdev->irq.lock, irqflags);
+
+}
+
+/**
+ * radeon_irq_kms_disable_afmt - disable audio format change interrupt
+ *
+ * @rdev: radeon device pointer
+ * @block: afmt block whose interrupt you want to disable
+ *
+ * Disables the afmt change interrupt for a specific afmt block (all asics).
+ */
+void radeon_irq_kms_disable_afmt(struct radeon_device *rdev, int block)
+{
+       unsigned long irqflags;
+
+       spin_lock_irqsave(&rdev->irq.lock, irqflags);
+       rdev->irq.afmt[block] = false;
+       radeon_irq_set(rdev);
+       spin_unlock_irqrestore(&rdev->irq.lock, irqflags);
+}
+
+/**
+ * radeon_irq_kms_enable_hpd - enable hotplug detect interrupt
+ *
+ * @rdev: radeon device pointer
+ * @hpd_mask: mask of hpd pins you want to enable.
+ *
+ * Enables the hotplug detect interrupt for a specific hpd pin (all asics).
+ */
+void radeon_irq_kms_enable_hpd(struct radeon_device *rdev, unsigned hpd_mask)
+{
+       unsigned long irqflags;
+       int i;
+
+       spin_lock_irqsave(&rdev->irq.lock, irqflags);
+       for (i = 0; i < RADEON_MAX_HPD_PINS; ++i)
+               rdev->irq.hpd[i] |= !!(hpd_mask & (1 << i));
+       radeon_irq_set(rdev);
+       spin_unlock_irqrestore(&rdev->irq.lock, irqflags);
+}
+
+/**
+ * radeon_irq_kms_disable_hpd - disable hotplug detect interrupt
+ *
+ * @rdev: radeon device pointer
+ * @hpd_mask: mask of hpd pins you want to disable.
+ *
+ * Disables the hotplug detect interrupt for a specific hpd pin (all asics).
+ */
+void radeon_irq_kms_disable_hpd(struct radeon_device *rdev, unsigned hpd_mask)
+{
+       unsigned long irqflags;
+       int i;
+
+       spin_lock_irqsave(&rdev->irq.lock, irqflags);
+       for (i = 0; i < RADEON_MAX_HPD_PINS; ++i)
+               rdev->irq.hpd[i] &= !(hpd_mask & (1 << i));
+       radeon_irq_set(rdev);
+       spin_unlock_irqrestore(&rdev->irq.lock, irqflags);
 }