diff options
Diffstat (limited to 'drivers/gpu/drm/radeon/radeon_irq_kms.c')
| -rw-r--r-- | drivers/gpu/drm/radeon/radeon_irq_kms.c | 283 | 
1 files changed, 251 insertions, 32 deletions
diff --git a/drivers/gpu/drm/radeon/radeon_irq_kms.c b/drivers/gpu/drm/radeon/radeon_irq_kms.c index 5df58d1aba0..afaa1727abd 100644 --- a/drivers/gpu/drm/radeon/radeon_irq_kms.c +++ b/drivers/gpu/drm/radeon/radeon_irq_kms.c @@ -32,6 +32,17 @@  #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,94 @@ 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; +		atomic_set(&rdev->irq.ring_int[i], 0);  	rdev->irq.gui_idle = false;  	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; +		atomic_set(&rdev->irq.ring_int[i], 0);  	rdev->irq.gui_idle = false;  	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 */ @@ -166,17 +221,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 +261,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 +280,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 +344,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 +370,121 @@ 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); +} + +/** + * radeon_irq_kms_wait_gui_idle - waits for drawing engine to be idle + * + * @rdev: radeon device pointer + * + * Enabled the GUI idle interrupt and waits for it to fire (r6xx+). + * This is currently used to make sure the 3D engine is idle for power + * management, but should be replaces with proper fence waits. + * GUI idle interrupts don't work very well on pre-r6xx hw and it also + * does not take into account other aspects of the chip that may be busy. + * DO NOT USE GOING FORWARD. + */ +int radeon_irq_kms_wait_gui_idle(struct radeon_device *rdev) +{ +	unsigned long irqflags; +	int r; + +	spin_lock_irqsave(&rdev->irq.lock, irqflags); +	rdev->irq.gui_idle = true; +	radeon_irq_set(rdev); +	spin_unlock_irqrestore(&rdev->irq.lock, irqflags); + +	r = wait_event_timeout(rdev->irq.idle_queue, radeon_gui_idle(rdev), +			       msecs_to_jiffies(RADEON_WAIT_IDLE_TIMEOUT)); + +	spin_lock_irqsave(&rdev->irq.lock, irqflags); +	rdev->irq.gui_idle = false; +	radeon_irq_set(rdev); +	spin_unlock_irqrestore(&rdev->irq.lock, irqflags); +	return r; +}  |