summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/radeon/radeon_irq_kms.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/radeon/radeon_irq_kms.c')
-rw-r--r--drivers/gpu/drm/radeon/radeon_irq_kms.c212
1 files changed, 164 insertions, 48 deletions
diff --git a/drivers/gpu/drm/radeon/radeon_irq_kms.c b/drivers/gpu/drm/radeon/radeon_irq_kms.c
index bcdefd1dcd43..9961251b44ba 100644
--- a/drivers/gpu/drm/radeon/radeon_irq_kms.c
+++ b/drivers/gpu/drm/radeon/radeon_irq_kms.c
@@ -25,30 +25,41 @@
* Alex Deucher
* Jerome Glisse
*/
-#include <drm/drmP.h>
-#include <drm/drm_crtc_helper.h>
+
+#include <linux/pci.h>
+#include <linux/pm_runtime.h>
+
+#include <drm/drm_device.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
#include <drm/radeon_drm.h>
-#include "radeon_reg.h"
-#include "radeon.h"
+
#include "atom.h"
+#include "radeon.h"
+#include "radeon_kms.h"
+#include "radeon_reg.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)
+static irqreturn_t radeon_driver_irq_handler_kms(int irq, void *arg)
{
struct drm_device *dev = (struct drm_device *) arg;
struct radeon_device *rdev = dev->dev_private;
+ irqreturn_t ret;
- return radeon_irq_process(rdev);
+ ret = radeon_irq_process(rdev);
+ if (ret == IRQ_HANDLED)
+ pm_runtime_mark_last_busy(dev->dev);
+ return ret;
}
/*
@@ -68,34 +79,36 @@ irqreturn_t radeon_driver_irq_handler_kms(DRM_IRQ_ARGS)
static void radeon_hotplug_work_func(struct work_struct *work)
{
struct radeon_device *rdev = container_of(work, struct radeon_device,
- hotplug_work);
- struct drm_device *dev = rdev->ddev;
+ hotplug_work.work);
+ struct drm_device *dev = rdev_to_drm(rdev);
struct drm_mode_config *mode_config = &dev->mode_config;
struct drm_connector *connector;
- if (mode_config->num_connector) {
- list_for_each_entry(connector, &mode_config->connector_list, head)
- radeon_connector_hotplug(connector);
- }
+ /* we can race here at startup, some boards seem to trigger
+ * hotplug irqs when they shouldn't. */
+ if (!rdev->mode_info.mode_config_initialized)
+ return;
+
+ mutex_lock(&mode_config->mutex);
+ list_for_each_entry(connector, &mode_config->connector_list, head)
+ radeon_connector_hotplug(connector);
+ mutex_unlock(&mode_config->mutex);
/* Just fire off a uevent and let userspace tell us what to do */
drm_helper_hpd_irq_event(dev);
}
-/**
- * radeon_irq_reset_work_func - execute gpu reset
- *
- * @work: work struct
- *
- * Execute scheduled gpu reset (cayman+).
- * This function is called when the irq handler
- * thinks we need a gpu reset.
- */
-static void radeon_irq_reset_work_func(struct work_struct *work)
+static void radeon_dp_work_func(struct work_struct *work)
{
struct radeon_device *rdev = container_of(work, struct radeon_device,
- reset_work);
+ dp_work);
+ struct drm_device *dev = rdev_to_drm(rdev);
+ struct drm_mode_config *mode_config = &dev->mode_config;
+ struct drm_connector *connector;
- radeon_gpu_reset(rdev);
+ mutex_lock(&mode_config->mutex);
+ list_for_each_entry(connector, &mode_config->connector_list, head)
+ radeon_connector_hotplug(connector);
+ mutex_unlock(&mode_config->mutex);
}
/**
@@ -106,7 +119,7 @@ static void radeon_irq_reset_work_func(struct work_struct *work)
* 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)
+static void radeon_driver_irq_preinstall_kms(struct drm_device *dev)
{
struct radeon_device *rdev = dev->dev_private;
unsigned long irqflags;
@@ -138,9 +151,15 @@ void radeon_driver_irq_preinstall_kms(struct drm_device *dev)
* Handles stuff to be done after enabling irqs (all asics).
* Returns 0 on success.
*/
-int radeon_driver_irq_postinstall_kms(struct drm_device *dev)
+static int radeon_driver_irq_postinstall_kms(struct drm_device *dev)
{
- dev->max_vblank_count = 0x001fffff;
+ struct radeon_device *rdev = dev->dev_private;
+
+ if (ASIC_IS_AVIVO(rdev))
+ dev->max_vblank_count = 0x00ffffff;
+ else
+ dev->max_vblank_count = 0x001fffff;
+
return 0;
}
@@ -151,7 +170,7 @@ int radeon_driver_irq_postinstall_kms(struct drm_device *dev)
*
* This function disables all interrupt sources on the GPU (all asics).
*/
-void radeon_driver_irq_uninstall_kms(struct drm_device *dev)
+static void radeon_driver_irq_uninstall_kms(struct drm_device *dev)
{
struct radeon_device *rdev = dev->dev_private;
unsigned long irqflags;
@@ -176,6 +195,36 @@ void radeon_driver_irq_uninstall_kms(struct drm_device *dev)
spin_unlock_irqrestore(&rdev->irq.lock, irqflags);
}
+static int radeon_irq_install(struct radeon_device *rdev, int irq)
+{
+ struct drm_device *dev = rdev_to_drm(rdev);
+ int ret;
+
+ if (irq == IRQ_NOTCONNECTED)
+ return -ENOTCONN;
+
+ radeon_driver_irq_preinstall_kms(dev);
+
+ /* PCI devices require shared interrupts. */
+ ret = request_irq(irq, radeon_driver_irq_handler_kms,
+ IRQF_SHARED, dev->driver->name, dev);
+ if (ret)
+ return ret;
+
+ radeon_driver_irq_postinstall_kms(dev);
+
+ return 0;
+}
+
+static void radeon_irq_uninstall(struct radeon_device *rdev)
+{
+ struct drm_device *dev = rdev_to_drm(rdev);
+ struct pci_dev *pdev = to_pci_dev(dev->dev);
+
+ radeon_driver_irq_uninstall_kms(dev);
+ free_irq(pdev->irq, dev);
+}
+
/**
* radeon_msi_ok - asic specific msi checks
*
@@ -196,6 +245,16 @@ static bool radeon_msi_ok(struct radeon_device *rdev)
if (rdev->flags & RADEON_IS_AGP)
return false;
+ /*
+ * Older chips have a HW limitation, they can only generate 40 bits
+ * of address for "64-bit" MSIs which breaks on some platforms, notably
+ * IBM POWER servers, so we limit them
+ */
+ if (rdev->family < CHIP_BONAIRE) {
+ dev_info(rdev->dev, "radeon: MSI limited to 32-bit\n");
+ rdev->pdev->no_64bit_msi = 1;
+ }
+
/* force MSI on */
if (radeon_msi == 1)
return true;
@@ -260,15 +319,16 @@ int radeon_irq_kms_init(struct radeon_device *rdev)
{
int r = 0;
- INIT_WORK(&rdev->hotplug_work, radeon_hotplug_work_func);
- INIT_WORK(&rdev->audio_work, r600_audio_update_hdmi);
- INIT_WORK(&rdev->reset_work, radeon_irq_reset_work_func);
-
spin_lock_init(&rdev->irq.lock);
- r = drm_vblank_init(rdev->ddev, rdev->num_crtc);
+
+ /* Disable vblank irqs aggressively for power-saving */
+ rdev_to_drm(rdev)->vblank_disable_immediate = true;
+
+ r = drm_vblank_init(rdev_to_drm(rdev), rdev->num_crtc);
if (r) {
return r;
}
+
/* enable msi */
rdev->msi_enabled = 0;
@@ -279,12 +339,19 @@ int radeon_irq_kms_init(struct radeon_device *rdev)
dev_info(rdev->dev, "radeon: using MSI.\n");
}
}
+
+ INIT_DELAYED_WORK(&rdev->hotplug_work, radeon_hotplug_work_func);
+ INIT_WORK(&rdev->dp_work, radeon_dp_work_func);
+ INIT_WORK(&rdev->audio_work, r600_audio_update_hdmi);
+
rdev->irq.installed = true;
- r = drm_irq_install(rdev->ddev);
+ r = radeon_irq_install(rdev, rdev->pdev->irq);
if (r) {
rdev->irq.installed = false;
+ flush_delayed_work(&rdev->hotplug_work);
return r;
}
+
DRM_INFO("radeon: irq initialized.\n");
return 0;
}
@@ -298,14 +365,13 @@ int radeon_irq_kms_init(struct radeon_device *rdev)
*/
void radeon_irq_kms_fini(struct radeon_device *rdev)
{
- drm_vblank_cleanup(rdev->ddev);
if (rdev->irq.installed) {
- drm_irq_uninstall(rdev->ddev);
+ radeon_irq_uninstall(rdev);
rdev->irq.installed = false;
if (rdev->msi_enabled)
pci_disable_msi(rdev->pdev);
+ flush_delayed_work(&rdev->hotplug_work);
}
- flush_work(&rdev->hotplug_work);
}
/**
@@ -322,7 +388,7 @@ void radeon_irq_kms_sw_irq_get(struct radeon_device *rdev, int ring)
{
unsigned long irqflags;
- if (!rdev->ddev->irq_enabled)
+ if (!rdev->irq.installed)
return;
if (atomic_inc_return(&rdev->irq.ring_int[ring]) == 1) {
@@ -333,6 +399,21 @@ void radeon_irq_kms_sw_irq_get(struct radeon_device *rdev, int ring)
}
/**
+ * radeon_irq_kms_sw_irq_get_delayed - 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.
+ */
+bool radeon_irq_kms_sw_irq_get_delayed(struct radeon_device *rdev, int ring)
+{
+ return atomic_inc_return(&rdev->irq.ring_int[ring]) == 1;
+}
+
+/**
* radeon_irq_kms_sw_irq_put - disable software interrupt
*
* @rdev: radeon device pointer
@@ -346,7 +427,7 @@ void radeon_irq_kms_sw_irq_put(struct radeon_device *rdev, int ring)
{
unsigned long irqflags;
- if (!rdev->ddev->irq_enabled)
+ if (!rdev->irq.installed)
return;
if (atomic_dec_and_test(&rdev->irq.ring_int[ring])) {
@@ -372,7 +453,7 @@ void radeon_irq_kms_pflip_irq_get(struct radeon_device *rdev, int crtc)
if (crtc < 0 || crtc >= rdev->num_crtc)
return;
- if (!rdev->ddev->irq_enabled)
+ if (!rdev->irq.installed)
return;
if (atomic_inc_return(&rdev->irq.pflip[crtc]) == 1) {
@@ -398,7 +479,7 @@ void radeon_irq_kms_pflip_irq_put(struct radeon_device *rdev, int crtc)
if (crtc < 0 || crtc >= rdev->num_crtc)
return;
- if (!rdev->ddev->irq_enabled)
+ if (!rdev->irq.installed)
return;
if (atomic_dec_and_test(&rdev->irq.pflip[crtc])) {
@@ -420,7 +501,7 @@ void radeon_irq_kms_enable_afmt(struct radeon_device *rdev, int block)
{
unsigned long irqflags;
- if (!rdev->ddev->irq_enabled)
+ if (!rdev->irq.installed)
return;
spin_lock_irqsave(&rdev->irq.lock, irqflags);
@@ -442,7 +523,7 @@ void radeon_irq_kms_disable_afmt(struct radeon_device *rdev, int block)
{
unsigned long irqflags;
- if (!rdev->ddev->irq_enabled)
+ if (!rdev->irq.installed)
return;
spin_lock_irqsave(&rdev->irq.lock, irqflags);
@@ -464,7 +545,7 @@ void radeon_irq_kms_enable_hpd(struct radeon_device *rdev, unsigned hpd_mask)
unsigned long irqflags;
int i;
- if (!rdev->ddev->irq_enabled)
+ if (!rdev->irq.installed)
return;
spin_lock_irqsave(&rdev->irq.lock, irqflags);
@@ -487,7 +568,7 @@ void radeon_irq_kms_disable_hpd(struct radeon_device *rdev, unsigned hpd_mask)
unsigned long irqflags;
int i;
- if (!rdev->ddev->irq_enabled)
+ if (!rdev->irq.installed)
return;
spin_lock_irqsave(&rdev->irq.lock, irqflags);
@@ -497,3 +578,38 @@ void radeon_irq_kms_disable_hpd(struct radeon_device *rdev, unsigned hpd_mask)
spin_unlock_irqrestore(&rdev->irq.lock, irqflags);
}
+/**
+ * radeon_irq_kms_set_irq_n_enabled - helper for updating interrupt enable registers
+ *
+ * @rdev: radeon device pointer
+ * @reg: the register to write to enable/disable interrupts
+ * @mask: the mask that enables the interrupts
+ * @enable: whether to enable or disable the interrupt register
+ * @name: the name of the interrupt register to print to the kernel log
+ * @n: the number of the interrupt register to print to the kernel log
+ *
+ * Helper for updating the enable state of interrupt registers. Checks whether
+ * or not the interrupt matches the enable state we want. If it doesn't, then
+ * we update it and print a debugging message to the kernel log indicating the
+ * new state of the interrupt register.
+ *
+ * Used for updating sequences of interrupts registers like HPD1, HPD2, etc.
+ */
+void radeon_irq_kms_set_irq_n_enabled(struct radeon_device *rdev,
+ u32 reg, u32 mask,
+ bool enable, const char *name, unsigned n)
+{
+ u32 tmp = RREG32(reg);
+
+ /* Interrupt state didn't change */
+ if (!!(tmp & mask) == enable)
+ return;
+
+ if (enable) {
+ DRM_DEBUG("%s%d interrupts enabled\n", name, n);
+ WREG32(reg, tmp |= mask);
+ } else {
+ DRM_DEBUG("%s%d interrupts disabled\n", name, n);
+ WREG32(reg, tmp & ~mask);
+ }
+}