2009-06-05 14:42:42 +02:00
/*
* Copyright 2008 Advanced Micro Devices , Inc .
* Copyright 2008 Red Hat Inc .
* Copyright 2009 Jerome Glisse .
*
* Permission is hereby granted , free of charge , to any person obtaining a
* copy of this software and associated documentation files ( the " Software " ) ,
* to deal in the Software without restriction , including without limitation
* the rights to use , copy , modify , merge , publish , distribute , sublicense ,
* and / or sell copies of the Software , and to permit persons to whom the
* Software is furnished to do so , subject to the following conditions :
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software .
*
* THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND , EXPRESS OR
* IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY ,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT . IN NO EVENT SHALL
* THE COPYRIGHT HOLDER ( S ) OR AUTHOR ( S ) BE LIABLE FOR ANY CLAIM , DAMAGES OR
* OTHER LIABILITY , WHETHER IN AN ACTION OF CONTRACT , TORT OR OTHERWISE ,
* ARISING FROM , OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE .
*
* Authors : Dave Airlie
* Alex Deucher
* Jerome Glisse
*/
2012-10-02 18:01:07 +01:00
# include <drm/drmP.h>
# include <drm/drm_crtc_helper.h>
# include <drm/radeon_drm.h>
2009-06-05 14:42:42 +02:00
# include "radeon_reg.h"
# include "radeon.h"
# include "atom.h"
2012-05-17 01:33:30 +02:00
# define RADEON_WAIT_IDLE_TIMEOUT 200
2012-07-17 14:02:35 -04:00
/**
* 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 .
*/
2009-06-05 14:42:42 +02:00
irqreturn_t radeon_driver_irq_handler_kms ( DRM_IRQ_ARGS )
{
struct drm_device * dev = ( struct drm_device * ) arg ;
struct radeon_device * rdev = dev - > dev_private ;
return radeon_irq_process ( rdev ) ;
}
2009-12-04 16:56:37 -05:00
/*
* Handle hotplug events outside the interrupt handler proper .
*/
2012-07-17 14:02:35 -04:00
/**
* 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 .
*/
2009-12-04 16:56:37 -05:00
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 ;
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 ) ;
}
/* Just fire off a uevent and let userspace tell us what to do */
2010-05-07 06:42:51 +00:00
drm_helper_hpd_irq_event ( dev ) ;
2009-12-04 16:56:37 -05:00
}
2013-06-14 09:13:52 -04:00
/**
* 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 )
{
struct radeon_device * rdev = container_of ( work , struct radeon_device ,
reset_work ) ;
radeon_gpu_reset ( rdev ) ;
}
2012-07-17 14:02:35 -04:00
/**
* 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 .
*/
2009-06-05 14:42:42 +02:00
void radeon_driver_irq_preinstall_kms ( struct drm_device * dev )
{
struct radeon_device * rdev = dev - > dev_private ;
2012-05-17 01:33:30 +02:00
unsigned long irqflags ;
2009-06-05 14:42:42 +02:00
unsigned i ;
2012-05-17 01:33:30 +02:00
spin_lock_irqsave ( & rdev - > irq . lock , irqflags ) ;
2009-06-05 14:42:42 +02:00
/* Disable *all* interrupts */
2011-11-17 20:13:28 -05:00
for ( i = 0 ; i < RADEON_NUM_RINGS ; i + + )
2012-05-17 19:52:00 +02:00
atomic_set ( & rdev - > irq . ring_int [ i ] , 0 ) ;
2011-10-26 15:43:58 -04:00
for ( i = 0 ; i < RADEON_MAX_HPD_PINS ; i + + )
2010-03-16 17:08:06 -04:00
rdev - > irq . hpd [ i ] = false ;
2011-10-26 15:43:58 -04:00
for ( i = 0 ; i < RADEON_MAX_CRTCS ; i + + ) {
rdev - > irq . crtc_vblank_int [ i ] = false ;
2012-05-17 19:52:00 +02:00
atomic_set ( & rdev - > irq . pflip [ i ] , 0 ) ;
2012-03-30 08:59:57 -04:00
rdev - > irq . afmt [ i ] = false ;
2010-11-21 10:59:01 -05:00
}
2009-06-05 14:42:42 +02:00
radeon_irq_set ( rdev ) ;
2012-05-17 01:33:30 +02:00
spin_unlock_irqrestore ( & rdev - > irq . lock , irqflags ) ;
2009-06-05 14:42:42 +02:00
/* Clear bits */
radeon_irq_process ( rdev ) ;
}
2012-07-17 14:02:35 -04:00
/**
* 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 .
*/
2009-06-05 14:42:42 +02:00
int radeon_driver_irq_postinstall_kms ( struct drm_device * dev )
{
dev - > max_vblank_count = 0x001fffff ;
return 0 ;
}
2012-07-17 14:02:35 -04:00
/**
* radeon_driver_irq_uninstall_kms - drm irq uninstall callback
*
* @ dev : drm dev pointer
*
* This function disables all interrupt sources on the GPU ( all asics ) .
*/
2009-06-05 14:42:42 +02:00
void radeon_driver_irq_uninstall_kms ( struct drm_device * dev )
{
struct radeon_device * rdev = dev - > dev_private ;
2012-05-17 01:33:30 +02:00
unsigned long irqflags ;
2009-06-05 14:42:42 +02:00
unsigned i ;
if ( rdev = = NULL ) {
return ;
}
2012-05-17 01:33:30 +02:00
spin_lock_irqsave ( & rdev - > irq . lock , irqflags ) ;
2009-06-05 14:42:42 +02:00
/* Disable *all* interrupts */
2011-11-17 20:13:28 -05:00
for ( i = 0 ; i < RADEON_NUM_RINGS ; i + + )
2012-05-17 19:52:00 +02:00
atomic_set ( & rdev - > irq . ring_int [ i ] , 0 ) ;
2011-10-26 15:43:58 -04:00
for ( i = 0 ; i < RADEON_MAX_HPD_PINS ; i + + )
2010-01-07 15:39:14 +01:00
rdev - > irq . hpd [ i ] = false ;
2011-10-26 15:43:58 -04:00
for ( i = 0 ; i < RADEON_MAX_CRTCS ; i + + ) {
rdev - > irq . crtc_vblank_int [ i ] = false ;
2012-05-17 19:52:00 +02:00
atomic_set ( & rdev - > irq . pflip [ i ] , 0 ) ;
2012-03-30 08:59:57 -04:00
rdev - > irq . afmt [ i ] = false ;
2010-11-21 10:59:01 -05:00
}
2009-06-05 14:42:42 +02:00
radeon_irq_set ( rdev ) ;
2012-05-17 01:33:30 +02:00
spin_unlock_irqrestore ( & rdev - > irq . lock , irqflags ) ;
2009-06-05 14:42:42 +02:00
}
2012-07-17 14:02:35 -04:00
/**
* 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 .
*/
2011-10-25 14:58:49 -04:00
static bool radeon_msi_ok ( struct radeon_device * rdev )
{
/* RV370/RV380 was first asic with MSI support */
if ( rdev - > family < CHIP_RV380 )
return false ;
/* MSIs don't work on AGP */
if ( rdev - > flags & RADEON_IS_AGP )
return false ;
2011-11-01 14:20:30 -04:00
/* force MSI on */
if ( radeon_msi = = 1 )
return true ;
else if ( radeon_msi = = 0 )
return false ;
2011-10-25 15:11:08 -04:00
/* Quirks */
/* HP RS690 only seems to work with MSIs. */
if ( ( rdev - > pdev - > device = = 0x791f ) & &
( rdev - > pdev - > subsystem_vendor = = 0x103c ) & &
( rdev - > pdev - > subsystem_device = = 0x30c2 ) )
return true ;
2012-01-15 08:51:12 -05:00
/* Dell RS690 only seems to work with MSIs. */
if ( ( rdev - > pdev - > device = = 0x791f ) & &
( rdev - > pdev - > subsystem_vendor = = 0x1028 ) & &
( rdev - > pdev - > subsystem_device = = 0x01fc ) )
return true ;
2011-11-01 14:14:18 -04:00
/* Dell RS690 only seems to work with MSIs. */
if ( ( rdev - > pdev - > device = = 0x791f ) & &
( rdev - > pdev - > subsystem_vendor = = 0x1028 ) & &
( rdev - > pdev - > subsystem_device = = 0x01fd ) )
return true ;
2012-09-26 12:31:45 -04:00
/* 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 ;
2012-09-26 12:40:45 -04:00
/* try and enable MSIs by default on all RS690s */
if ( rdev - > family = = CHIP_RS690 )
return true ;
2012-04-13 11:14:50 +01:00
/* RV515 seems to have MSI issues where it loses
* MSI rearms occasionally . This leads to lockups and freezes .
* disable it by default .
*/
if ( rdev - > family = = CHIP_RV515 )
return false ;
2011-10-25 14:58:49 -04:00
if ( rdev - > flags & RADEON_IS_IGP ) {
/* APUs work fine with MSIs */
if ( rdev - > family > = CHIP_PALM )
return true ;
/* lots of IGPs have problems with MSIs */
return false ;
}
return true ;
}
2012-07-17 14:02:35 -04:00
/**
* 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 .
*/
2009-06-05 14:42:42 +02:00
int radeon_irq_kms_init ( struct radeon_device * rdev )
{
int r = 0 ;
2011-01-03 14:49:32 +01:00
INIT_WORK ( & rdev - > hotplug_work , radeon_hotplug_work_func ) ;
2012-03-30 08:59:57 -04:00
INIT_WORK ( & rdev - > audio_work , r600_audio_update_hdmi ) ;
2013-06-14 09:13:52 -04:00
INIT_WORK ( & rdev - > reset_work , radeon_irq_reset_work_func ) ;
2011-01-03 14:49:32 +01:00
2012-05-17 01:33:30 +02:00
spin_lock_init ( & rdev - > irq . lock ) ;
2010-03-16 17:08:06 -04:00
r = drm_vblank_init ( rdev - > ddev , rdev - > num_crtc ) ;
2009-06-05 14:42:42 +02:00
if ( r ) {
return r ;
}
2009-10-16 12:21:24 -04:00
/* enable msi */
rdev - > msi_enabled = 0 ;
2011-10-25 14:58:49 -04:00
if ( radeon_msi_ok ( rdev ) ) {
2009-10-16 12:21:24 -04:00
int ret = pci_enable_msi ( rdev - > pdev ) ;
2009-12-01 13:43:46 -05:00
if ( ! ret ) {
2009-10-16 12:21:24 -04:00
rdev - > msi_enabled = 1 ;
2010-08-12 18:05:34 -04:00
dev_info ( rdev - > dev , " radeon: using MSI. \n " ) ;
2009-12-01 13:43:46 -05:00
}
2009-10-16 12:21:24 -04:00
}
2009-06-05 14:42:42 +02:00
rdev - > irq . installed = true ;
2010-01-07 15:39:14 +01:00
r = drm_irq_install ( rdev - > ddev ) ;
if ( r ) {
rdev - > irq . installed = false ;
return r ;
}
2009-06-05 14:42:42 +02:00
DRM_INFO ( " radeon: irq initialized. \n " ) ;
return 0 ;
}
2012-07-17 14:02:35 -04:00
/**
2013-03-16 20:53:05 +09:00
* radeon_irq_kms_fini - tear down driver interrupt info
2012-07-17 14:02:35 -04:00
*
* @ rdev : radeon device pointer
*
* Tears down the work irq handlers , vblank handlers , MSIs , etc . ( all asics ) .
*/
2009-06-05 14:42:42 +02:00
void radeon_irq_kms_fini ( struct radeon_device * rdev )
{
2010-01-07 15:39:14 +01:00
drm_vblank_cleanup ( rdev - > ddev ) ;
2009-06-05 14:42:42 +02:00
if ( rdev - > irq . installed ) {
drm_irq_uninstall ( rdev - > ddev ) ;
2010-01-07 15:39:14 +01:00
rdev - > irq . installed = false ;
2009-10-16 12:21:24 -04:00
if ( rdev - > msi_enabled )
pci_disable_msi ( rdev - > pdev ) ;
2009-06-05 14:42:42 +02:00
}
2012-08-20 14:51:24 -07:00
flush_work ( & rdev - > hotplug_work ) ;
2009-06-05 14:42:42 +02:00
}
2009-12-01 16:04:56 +10:00
2012-07-17 14:02:35 -04:00
/**
* 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 .
*/
2011-11-17 20:13:28 -05:00
void radeon_irq_kms_sw_irq_get ( struct radeon_device * rdev , int ring )
2009-12-01 16:04:56 +10:00
{
unsigned long irqflags ;
2012-05-17 19:52:00 +02:00
if ( ! rdev - > ddev - > irq_enabled )
return ;
if ( atomic_inc_return ( & rdev - > irq . ring_int [ ring ] ) = = 1 ) {
spin_lock_irqsave ( & rdev - > irq . lock , irqflags ) ;
2009-12-01 16:04:56 +10:00
radeon_irq_set ( rdev ) ;
2012-05-17 19:52:00 +02:00
spin_unlock_irqrestore ( & rdev - > irq . lock , irqflags ) ;
2009-12-01 16:04:56 +10:00
}
}
2012-07-17 14:02:35 -04:00
/**
* 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 .
*/
2011-11-17 20:13:28 -05:00
void radeon_irq_kms_sw_irq_put ( struct radeon_device * rdev , int ring )
2009-12-01 16:04:56 +10:00
{
unsigned long irqflags ;
2012-05-17 19:52:00 +02:00
if ( ! rdev - > ddev - > irq_enabled )
return ;
if ( atomic_dec_and_test ( & rdev - > irq . ring_int [ ring ] ) ) {
spin_lock_irqsave ( & rdev - > irq . lock , irqflags ) ;
2009-12-01 16:04:56 +10:00
radeon_irq_set ( rdev ) ;
2012-05-17 19:52:00 +02:00
spin_unlock_irqrestore ( & rdev - > irq . lock , irqflags ) ;
2009-12-01 16:04:56 +10:00
}
}
2012-07-17 14:02:35 -04:00
/**
* 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 .
*/
2010-11-21 10:59:01 -05:00
void radeon_irq_kms_pflip_irq_get ( struct radeon_device * rdev , int crtc )
{
unsigned long irqflags ;
if ( crtc < 0 | | crtc > = rdev - > num_crtc )
return ;
2012-05-17 19:52:00 +02:00
if ( ! rdev - > ddev - > irq_enabled )
return ;
if ( atomic_inc_return ( & rdev - > irq . pflip [ crtc ] ) = = 1 ) {
spin_lock_irqsave ( & rdev - > irq . lock , irqflags ) ;
2010-11-21 10:59:01 -05:00
radeon_irq_set ( rdev ) ;
2012-05-17 19:52:00 +02:00
spin_unlock_irqrestore ( & rdev - > irq . lock , irqflags ) ;
2010-11-21 10:59:01 -05:00
}
}
2012-07-17 14:02:35 -04:00
/**
* 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 .
*/
2010-11-21 10:59:01 -05:00
void radeon_irq_kms_pflip_irq_put ( struct radeon_device * rdev , int crtc )
{
unsigned long irqflags ;
if ( crtc < 0 | | crtc > = rdev - > num_crtc )
return ;
2012-05-17 19:52:00 +02:00
if ( ! rdev - > ddev - > irq_enabled )
return ;
if ( atomic_dec_and_test ( & rdev - > irq . pflip [ crtc ] ) ) {
spin_lock_irqsave ( & rdev - > irq . lock , irqflags ) ;
2010-11-21 10:59:01 -05:00
radeon_irq_set ( rdev ) ;
2012-05-17 19:52:00 +02:00
spin_unlock_irqrestore ( & rdev - > irq . lock , irqflags ) ;
2010-11-21 10:59:01 -05:00
}
}
2012-07-17 14:02:35 -04:00
/**
* 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 ) .
*/
2012-05-17 01:33:30 +02:00
void radeon_irq_kms_enable_afmt ( struct radeon_device * rdev , int block )
{
unsigned long irqflags ;
2013-02-26 16:17:33 -05:00
if ( ! rdev - > ddev - > irq_enabled )
return ;
2012-05-17 01:33:30 +02:00
spin_lock_irqsave ( & rdev - > irq . lock , irqflags ) ;
rdev - > irq . afmt [ block ] = true ;
radeon_irq_set ( rdev ) ;
spin_unlock_irqrestore ( & rdev - > irq . lock , irqflags ) ;
}
2012-07-17 14:02:35 -04:00
/**
* 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 ) .
*/
2012-05-17 01:33:30 +02:00
void radeon_irq_kms_disable_afmt ( struct radeon_device * rdev , int block )
{
unsigned long irqflags ;
2013-02-26 16:17:33 -05:00
if ( ! rdev - > ddev - > irq_enabled )
return ;
2012-05-17 01:33:30 +02:00
spin_lock_irqsave ( & rdev - > irq . lock , irqflags ) ;
rdev - > irq . afmt [ block ] = false ;
radeon_irq_set ( rdev ) ;
spin_unlock_irqrestore ( & rdev - > irq . lock , irqflags ) ;
}
2012-07-17 14:02:35 -04:00
/**
* 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 ) .
*/
2012-05-17 01:33:30 +02:00
void radeon_irq_kms_enable_hpd ( struct radeon_device * rdev , unsigned hpd_mask )
{
unsigned long irqflags ;
int i ;
2013-02-26 16:17:33 -05:00
if ( ! rdev - > ddev - > irq_enabled )
return ;
2012-05-17 01:33:30 +02:00
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 ) ;
}
2012-07-17 14:02:35 -04:00
/**
* 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 ) .
*/
2012-05-17 01:33:30 +02:00
void radeon_irq_kms_disable_hpd ( struct radeon_device * rdev , unsigned hpd_mask )
{
unsigned long irqflags ;
int i ;
2013-02-26 16:17:33 -05:00
if ( ! rdev - > ddev - > irq_enabled )
return ;
2012-05-17 01:33:30 +02:00
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 ) ;
}