2013-04-08 12:41:29 +02:00
/*
* Copyright 2011 Advanced Micro Devices , Inc .
* All Rights Reserved .
*
* 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 , sub license , 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 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 NON - INFRINGEMENT . IN NO EVENT SHALL
* THE COPYRIGHT HOLDERS , AUTHORS AND / OR ITS SUPPLIERS 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 .
*
* The above copyright notice and this permission notice ( including the
* next paragraph ) shall be included in all copies or substantial portions
* of the Software .
*
*/
/*
* Authors :
* Christian König < deathsimple @ vodafone . de >
*/
# include <linux/firmware.h>
# include <linux/module.h>
# include <drm/drmP.h>
# include <drm/drm.h>
# include "radeon.h"
# include "r600d.h"
2013-04-18 15:25:59 +02:00
/* 1 second timeout */
# define UVD_IDLE_TIMEOUT_MS 1000
2013-04-08 12:41:29 +02:00
/* Firmware Names */
2013-04-16 22:11:37 +02:00
# define FIRMWARE_R600 "radeon / R600_uvd.bin"
# define FIRMWARE_RS780 "radeon / RS780_uvd.bin"
# define FIRMWARE_RV770 "radeon / RV770_uvd.bin"
2013-04-08 12:41:29 +02:00
# define FIRMWARE_RV710 "radeon / RV710_uvd.bin"
# define FIRMWARE_CYPRESS "radeon / CYPRESS_uvd.bin"
# define FIRMWARE_SUMO "radeon / SUMO_uvd.bin"
# define FIRMWARE_TAHITI "radeon / TAHITI_uvd.bin"
2013-04-09 13:39:21 -04:00
# define FIRMWARE_BONAIRE "radeon / BONAIRE_uvd.bin"
2013-04-08 12:41:29 +02:00
2013-04-16 22:11:37 +02:00
MODULE_FIRMWARE ( FIRMWARE_R600 ) ;
MODULE_FIRMWARE ( FIRMWARE_RS780 ) ;
MODULE_FIRMWARE ( FIRMWARE_RV770 ) ;
2013-04-08 12:41:29 +02:00
MODULE_FIRMWARE ( FIRMWARE_RV710 ) ;
MODULE_FIRMWARE ( FIRMWARE_CYPRESS ) ;
MODULE_FIRMWARE ( FIRMWARE_SUMO ) ;
MODULE_FIRMWARE ( FIRMWARE_TAHITI ) ;
2013-04-09 13:39:21 -04:00
MODULE_FIRMWARE ( FIRMWARE_BONAIRE ) ;
2013-04-08 12:41:29 +02:00
2013-04-18 15:25:59 +02:00
static void radeon_uvd_idle_work_handler ( struct work_struct * work ) ;
2013-04-08 12:41:29 +02:00
int radeon_uvd_init ( struct radeon_device * rdev )
{
unsigned long bo_size ;
const char * fw_name ;
int i , r ;
2013-04-18 15:25:59 +02:00
INIT_DELAYED_WORK ( & rdev - > uvd . idle_work , radeon_uvd_idle_work_handler ) ;
2013-04-08 12:41:29 +02:00
switch ( rdev - > family ) {
2013-04-16 22:11:37 +02:00
case CHIP_RV610 :
case CHIP_RV630 :
case CHIP_RV670 :
case CHIP_RV620 :
case CHIP_RV635 :
fw_name = FIRMWARE_R600 ;
break ;
case CHIP_RS780 :
case CHIP_RS880 :
fw_name = FIRMWARE_RS780 ;
break ;
case CHIP_RV770 :
fw_name = FIRMWARE_RV770 ;
break ;
2013-04-08 12:41:29 +02:00
case CHIP_RV710 :
case CHIP_RV730 :
case CHIP_RV740 :
fw_name = FIRMWARE_RV710 ;
break ;
case CHIP_CYPRESS :
case CHIP_HEMLOCK :
case CHIP_JUNIPER :
case CHIP_REDWOOD :
case CHIP_CEDAR :
fw_name = FIRMWARE_CYPRESS ;
break ;
case CHIP_SUMO :
case CHIP_SUMO2 :
case CHIP_PALM :
case CHIP_CAYMAN :
case CHIP_BARTS :
case CHIP_TURKS :
case CHIP_CAICOS :
fw_name = FIRMWARE_SUMO ;
break ;
case CHIP_TAHITI :
case CHIP_VERDE :
case CHIP_PITCAIRN :
case CHIP_ARUBA :
2014-01-20 11:25:35 -05:00
case CHIP_OLAND :
2013-04-08 12:41:29 +02:00
fw_name = FIRMWARE_TAHITI ;
break ;
2013-04-09 13:39:21 -04:00
case CHIP_BONAIRE :
case CHIP_KABINI :
case CHIP_KAVERI :
2013-09-24 11:08:17 -04:00
case CHIP_HAWAII :
2014-04-30 18:40:50 -04:00
case CHIP_MULLINS :
2013-04-09 13:39:21 -04:00
fw_name = FIRMWARE_BONAIRE ;
break ;
2013-04-08 12:41:29 +02:00
default :
return - EINVAL ;
}
2013-08-05 14:10:55 +02:00
r = request_firmware ( & rdev - > uvd_fw , fw_name , rdev - > dev ) ;
2013-04-08 12:41:29 +02:00
if ( r ) {
dev_err ( rdev - > dev , " radeon_uvd: Can't load firmware \" %s \" \n " ,
fw_name ) ;
return r ;
}
2013-08-05 14:10:55 +02:00
bo_size = RADEON_GPU_PAGE_ALIGN ( rdev - > uvd_fw - > size + 8 ) +
2014-08-22 14:25:55 +02:00
RADEON_UVD_STACK_SIZE + RADEON_UVD_HEAP_SIZE +
RADEON_GPU_PAGE_SIZE ;
2013-04-08 12:41:29 +02:00
r = radeon_bo_create ( rdev , bo_size , PAGE_SIZE , true ,
2014-09-18 14:11:56 +02:00
RADEON_GEM_DOMAIN_VRAM , 0 , NULL ,
NULL , & rdev - > uvd . vcpu_bo ) ;
2013-04-08 12:41:29 +02:00
if ( r ) {
dev_err ( rdev - > dev , " (%d) failed to allocate UVD bo \n " , r ) ;
return r ;
}
2013-07-12 10:18:09 -04:00
r = radeon_bo_reserve ( rdev - > uvd . vcpu_bo , false ) ;
if ( r ) {
radeon_bo_unref ( & rdev - > uvd . vcpu_bo ) ;
dev_err ( rdev - > dev , " (%d) failed to reserve UVD bo \n " , r ) ;
2013-04-08 12:41:29 +02:00
return r ;
2013-07-12 10:18:09 -04:00
}
2013-04-08 12:41:29 +02:00
2013-07-12 10:18:09 -04:00
r = radeon_bo_pin ( rdev - > uvd . vcpu_bo , RADEON_GEM_DOMAIN_VRAM ,
& rdev - > uvd . gpu_addr ) ;
if ( r ) {
radeon_bo_unreserve ( rdev - > uvd . vcpu_bo ) ;
radeon_bo_unref ( & rdev - > uvd . vcpu_bo ) ;
dev_err ( rdev - > dev , " (%d) UVD bo pin failed \n " , r ) ;
return r ;
}
2013-04-08 12:41:29 +02:00
2013-07-12 10:18:09 -04:00
r = radeon_bo_kmap ( rdev - > uvd . vcpu_bo , & rdev - > uvd . cpu_addr ) ;
if ( r ) {
dev_err ( rdev - > dev , " (%d) UVD map failed \n " , r ) ;
2013-04-08 12:41:29 +02:00
return r ;
2013-07-12 10:18:09 -04:00
}
radeon_bo_unreserve ( rdev - > uvd . vcpu_bo ) ;
2013-04-08 12:41:29 +02:00
for ( i = 0 ; i < RADEON_MAX_UVD_HANDLES ; + + i ) {
atomic_set ( & rdev - > uvd . handles [ i ] , 0 ) ;
rdev - > uvd . filp [ i ] = NULL ;
2013-08-05 12:41:20 -04:00
rdev - > uvd . img_size [ i ] = 0 ;
2013-04-08 12:41:29 +02:00
}
return 0 ;
}
void radeon_uvd_fini ( struct radeon_device * rdev )
{
int r ;
if ( rdev - > uvd . vcpu_bo = = NULL )
2013-07-12 10:18:09 -04:00
return ;
2013-04-08 12:41:29 +02:00
r = radeon_bo_reserve ( rdev - > uvd . vcpu_bo , false ) ;
if ( ! r ) {
radeon_bo_kunmap ( rdev - > uvd . vcpu_bo ) ;
radeon_bo_unpin ( rdev - > uvd . vcpu_bo ) ;
radeon_bo_unreserve ( rdev - > uvd . vcpu_bo ) ;
}
2013-07-12 10:18:09 -04:00
radeon_bo_unref ( & rdev - > uvd . vcpu_bo ) ;
2013-08-05 14:10:55 +02:00
2014-02-26 19:22:47 -05:00
radeon_ring_fini ( rdev , & rdev - > ring [ R600_RING_TYPE_UVD_INDEX ] ) ;
2013-08-05 14:10:55 +02:00
release_firmware ( rdev - > uvd_fw ) ;
2013-04-08 12:41:29 +02:00
}
2013-07-12 10:18:09 -04:00
int radeon_uvd_suspend ( struct radeon_device * rdev )
2013-04-08 12:41:29 +02:00
{
2015-05-07 15:19:25 +02:00
int i , r ;
2013-04-08 12:41:29 +02:00
if ( rdev - > uvd . vcpu_bo = = NULL )
2013-07-12 10:18:09 -04:00
return 0 ;
2013-04-08 12:41:29 +02:00
2015-05-07 15:19:25 +02:00
for ( i = 0 ; i < RADEON_MAX_UVD_HANDLES ; + + i ) {
uint32_t handle = atomic_read ( & rdev - > uvd . handles [ i ] ) ;
if ( handle ! = 0 ) {
struct radeon_fence * fence ;
2013-08-05 14:10:55 +02:00
2015-05-07 15:19:25 +02:00
radeon_uvd_note_usage ( rdev ) ;
2013-08-05 14:10:55 +02:00
2015-05-07 15:19:25 +02:00
r = radeon_uvd_get_destroy_msg ( rdev ,
R600_RING_TYPE_UVD_INDEX , handle , & fence ) ;
if ( r ) {
DRM_ERROR ( " Error destroying UVD (%d)! \n " , r ) ;
continue ;
}
2013-08-05 14:10:55 +02:00
2015-05-07 15:19:25 +02:00
radeon_fence_wait ( fence , false ) ;
radeon_fence_unref ( & fence ) ;
2013-08-05 14:10:55 +02:00
2015-05-07 15:19:25 +02:00
rdev - > uvd . filp [ i ] = NULL ;
atomic_set ( & rdev - > uvd . handles [ i ] , 0 ) ;
}
}
2013-04-08 12:41:29 +02:00
2013-07-12 10:18:09 -04:00
return 0 ;
}
2013-06-06 17:51:21 -04:00
2013-07-12 10:18:09 -04:00
int radeon_uvd_resume ( struct radeon_device * rdev )
{
2013-08-05 14:10:55 +02:00
unsigned size ;
void * ptr ;
2013-07-12 10:18:09 -04:00
if ( rdev - > uvd . vcpu_bo = = NULL )
return - EINVAL ;
2013-04-08 12:41:29 +02:00
2013-08-05 14:10:55 +02:00
memcpy ( rdev - > uvd . cpu_addr , rdev - > uvd_fw - > data , rdev - > uvd_fw - > size ) ;
size = radeon_bo_size ( rdev - > uvd . vcpu_bo ) ;
size - = rdev - > uvd_fw - > size ;
ptr = rdev - > uvd . cpu_addr ;
ptr + = rdev - > uvd_fw - > size ;
2015-05-07 15:19:25 +02:00
memset ( ptr , 0 , size ) ;
2013-04-08 12:41:29 +02:00
return 0 ;
}
2014-08-21 12:18:12 +02:00
void radeon_uvd_force_into_uvd_segment ( struct radeon_bo * rbo ,
uint32_t allowed_domains )
2013-04-08 12:41:29 +02:00
{
2014-08-27 13:16:04 +02:00
int i ;
for ( i = 0 ; i < rbo - > placement . num_placement ; + + i ) {
rbo - > placements [ i ] . fpfn = 0 > > PAGE_SHIFT ;
rbo - > placements [ i ] . lpfn = ( 256 * 1024 * 1024 ) > > PAGE_SHIFT ;
}
2014-08-21 12:18:12 +02:00
/* If it must be in VRAM it must be in the first segment as well */
if ( allowed_domains = = RADEON_GEM_DOMAIN_VRAM )
return ;
/* abort if we already have more than one placement */
if ( rbo - > placement . num_placement > 1 )
return ;
/* add another 256MB segment */
rbo - > placements [ 1 ] = rbo - > placements [ 0 ] ;
rbo - > placements [ 1 ] . fpfn + = ( 256 * 1024 * 1024 ) > > PAGE_SHIFT ;
rbo - > placements [ 1 ] . lpfn + = ( 256 * 1024 * 1024 ) > > PAGE_SHIFT ;
rbo - > placement . num_placement + + ;
rbo - > placement . num_busy_placement + + ;
2013-04-08 12:41:29 +02:00
}
void radeon_uvd_free_handles ( struct radeon_device * rdev , struct drm_file * filp )
{
int i , r ;
for ( i = 0 ; i < RADEON_MAX_UVD_HANDLES ; + + i ) {
2013-08-05 14:10:56 +02:00
uint32_t handle = atomic_read ( & rdev - > uvd . handles [ i ] ) ;
if ( handle ! = 0 & & rdev - > uvd . filp [ i ] = = filp ) {
2013-04-08 12:41:29 +02:00
struct radeon_fence * fence ;
2013-10-30 12:56:04 +01:00
radeon_uvd_note_usage ( rdev ) ;
2013-04-08 12:41:29 +02:00
r = radeon_uvd_get_destroy_msg ( rdev ,
R600_RING_TYPE_UVD_INDEX , handle , & fence ) ;
if ( r ) {
DRM_ERROR ( " Error destroying UVD (%d)! \n " , r ) ;
continue ;
}
radeon_fence_wait ( fence , false ) ;
radeon_fence_unref ( & fence ) ;
rdev - > uvd . filp [ i ] = NULL ;
atomic_set ( & rdev - > uvd . handles [ i ] , 0 ) ;
}
}
}
static int radeon_uvd_cs_msg_decode ( uint32_t * msg , unsigned buf_sizes [ ] )
{
unsigned stream_type = msg [ 4 ] ;
unsigned width = msg [ 6 ] ;
unsigned height = msg [ 7 ] ;
unsigned dpb_size = msg [ 9 ] ;
unsigned pitch = msg [ 28 ] ;
unsigned width_in_mb = width / 16 ;
unsigned height_in_mb = ALIGN ( height / 16 , 2 ) ;
unsigned image_size , tmp , min_dpb_size ;
image_size = width * height ;
image_size + = image_size / 2 ;
image_size = ALIGN ( image_size , 1024 ) ;
switch ( stream_type ) {
case 0 : /* H264 */
/* reference picture buffer */
min_dpb_size = image_size * 17 ;
/* macroblock context buffer */
min_dpb_size + = width_in_mb * height_in_mb * 17 * 192 ;
/* IT surface buffer */
min_dpb_size + = width_in_mb * height_in_mb * 32 ;
break ;
case 1 : /* VC1 */
/* reference picture buffer */
min_dpb_size = image_size * 3 ;
/* CONTEXT_BUFFER */
min_dpb_size + = width_in_mb * height_in_mb * 128 ;
/* IT surface buffer */
min_dpb_size + = width_in_mb * 64 ;
/* DB surface buffer */
min_dpb_size + = width_in_mb * 128 ;
/* BP */
tmp = max ( width_in_mb , height_in_mb ) ;
min_dpb_size + = ALIGN ( tmp * 7 * 16 , 64 ) ;
break ;
case 3 : /* MPEG2 */
/* reference picture buffer */
min_dpb_size = image_size * 3 ;
break ;
case 4 : /* MPEG4 */
/* reference picture buffer */
min_dpb_size = image_size * 3 ;
/* CM */
min_dpb_size + = width_in_mb * height_in_mb * 64 ;
/* IT surface buffer */
min_dpb_size + = ALIGN ( width_in_mb * height_in_mb * 32 , 64 ) ;
break ;
default :
DRM_ERROR ( " UVD codec not handled %d! \n " , stream_type ) ;
return - EINVAL ;
}
if ( width > pitch ) {
DRM_ERROR ( " Invalid UVD decoding target pitch! \n " ) ;
return - EINVAL ;
}
if ( dpb_size < min_dpb_size ) {
DRM_ERROR ( " Invalid dpb_size in UVD message (%d / %d)! \n " ,
dpb_size , min_dpb_size ) ;
return - EINVAL ;
}
buf_sizes [ 0x1 ] = dpb_size ;
buf_sizes [ 0x2 ] = image_size ;
return 0 ;
}
2015-05-07 15:19:24 +02:00
static int radeon_uvd_validate_codec ( struct radeon_cs_parser * p ,
unsigned stream_type )
{
switch ( stream_type ) {
case 0 : /* H264 */
case 1 : /* VC1 */
/* always supported */
return 0 ;
case 3 : /* MPEG2 */
case 4 : /* MPEG4 */
/* only since UVD 3 */
if ( p - > rdev - > family > = CHIP_PALM )
return 0 ;
/* fall through */
default :
DRM_ERROR ( " UVD codec not supported by hardware %d! \n " ,
stream_type ) ;
return - EINVAL ;
}
}
2013-04-08 12:41:29 +02:00
static int radeon_uvd_cs_msg ( struct radeon_cs_parser * p , struct radeon_bo * bo ,
unsigned offset , unsigned buf_sizes [ ] )
{
int32_t * msg , msg_type , handle ;
2013-08-05 12:41:20 -04:00
unsigned img_size = 0 ;
2014-04-02 17:14:48 +02:00
struct fence * f ;
2013-04-08 12:41:29 +02:00
void * ptr ;
int i , r ;
if ( offset & 0x3F ) {
DRM_ERROR ( " UVD messages must be 64 byte aligned! \n " ) ;
return - EINVAL ;
}
2014-04-02 17:14:48 +02:00
f = reservation_object_get_excl ( bo - > tbo . resv ) ;
if ( f ) {
r = radeon_fence_wait ( ( struct radeon_fence * ) f , false ) ;
2013-08-11 21:27:56 +02:00
if ( r ) {
DRM_ERROR ( " Failed waiting for UVD message (%d)! \n " , r ) ;
return r ;
}
}
2013-04-08 12:41:29 +02:00
r = radeon_bo_kmap ( bo , & ptr ) ;
2013-08-05 14:10:57 +02:00
if ( r ) {
DRM_ERROR ( " Failed mapping the UVD message (%d)! \n " , r ) ;
2013-04-08 12:41:29 +02:00
return r ;
2013-08-05 14:10:57 +02:00
}
2013-04-08 12:41:29 +02:00
msg = ptr + offset ;
msg_type = msg [ 1 ] ;
handle = msg [ 2 ] ;
if ( handle = = 0 ) {
DRM_ERROR ( " Invalid UVD handle! \n " ) ;
return - EINVAL ;
}
2015-05-07 15:19:23 +02:00
switch ( msg_type ) {
case 0 :
/* it's a create msg, calc image size (width * height) */
img_size = msg [ 7 ] * msg [ 8 ] ;
2015-05-07 15:19:24 +02:00
r = radeon_uvd_validate_codec ( p , msg [ 4 ] ) ;
2015-05-07 15:19:23 +02:00
radeon_bo_kunmap ( bo ) ;
2015-05-07 15:19:24 +02:00
if ( r )
return r ;
2015-05-07 15:19:23 +02:00
/* try to alloc a new handle */
for ( i = 0 ; i < RADEON_MAX_UVD_HANDLES ; + + i ) {
if ( atomic_read ( & p - > rdev - > uvd . handles [ i ] ) = = handle ) {
DRM_ERROR ( " Handle 0x%x already in use! \n " , handle ) ;
return - EINVAL ;
}
if ( ! atomic_cmpxchg ( & p - > rdev - > uvd . handles [ i ] , 0 , handle ) ) {
p - > rdev - > uvd . filp [ i ] = p - > filp ;
p - > rdev - > uvd . img_size [ i ] = img_size ;
return 0 ;
}
}
DRM_ERROR ( " No more free UVD handles! \n " ) ;
return - EINVAL ;
case 1 :
2015-05-07 15:19:24 +02:00
/* it's a decode msg, validate codec and calc buffer sizes */
r = radeon_uvd_validate_codec ( p , msg [ 4 ] ) ;
if ( ! r )
r = radeon_uvd_cs_msg_decode ( msg , buf_sizes ) ;
2013-04-08 12:41:29 +02:00
radeon_bo_kunmap ( bo ) ;
if ( r )
return r ;
2015-05-07 15:19:23 +02:00
/* validate the handle */
for ( i = 0 ; i < RADEON_MAX_UVD_HANDLES ; + + i ) {
if ( atomic_read ( & p - > rdev - > uvd . handles [ i ] ) = = handle ) {
if ( p - > rdev - > uvd . filp [ i ] ! = p - > filp ) {
DRM_ERROR ( " UVD handle collision detected! \n " ) ;
return - EINVAL ;
}
return 0 ;
}
}
DRM_ERROR ( " Invalid UVD handle 0x%x! \n " , handle ) ;
return - ENOENT ;
case 2 :
2013-04-08 12:41:29 +02:00
/* it's a destroy msg, free the handle */
for ( i = 0 ; i < RADEON_MAX_UVD_HANDLES ; + + i )
atomic_cmpxchg ( & p - > rdev - > uvd . handles [ i ] , handle , 0 ) ;
radeon_bo_kunmap ( bo ) ;
return 0 ;
2015-05-07 15:19:23 +02:00
default :
2013-04-08 12:41:29 +02:00
2015-05-07 15:19:23 +02:00
DRM_ERROR ( " Illegal UVD message type (%d)! \n " , msg_type ) ;
return - EINVAL ;
2013-04-08 12:41:29 +02:00
}
2015-05-07 15:19:23 +02:00
BUG ( ) ;
2013-04-08 12:41:29 +02:00
return - EINVAL ;
}
static int radeon_uvd_cs_reloc ( struct radeon_cs_parser * p ,
int data0 , int data1 ,
2013-08-05 14:10:57 +02:00
unsigned buf_sizes [ ] , bool * has_msg_cmd )
2013-04-08 12:41:29 +02:00
{
struct radeon_cs_chunk * relocs_chunk ;
2014-11-27 14:48:42 +01:00
struct radeon_bo_list * reloc ;
2013-04-08 12:41:29 +02:00
unsigned idx , cmd , offset ;
uint64_t start , end ;
int r ;
2014-12-03 15:53:24 +01:00
relocs_chunk = p - > chunk_relocs ;
2013-04-08 12:41:29 +02:00
offset = radeon_get_ib_value ( p , data0 ) ;
idx = radeon_get_ib_value ( p , data1 ) ;
if ( idx > = relocs_chunk - > length_dw ) {
DRM_ERROR ( " Relocs at %d after relocations chunk end %d ! \n " ,
idx , relocs_chunk - > length_dw ) ;
return - EINVAL ;
}
2014-12-03 15:46:49 +01:00
reloc = & p - > relocs [ ( idx / 4 ) ] ;
2014-03-03 12:38:08 +01:00
start = reloc - > gpu_offset ;
2013-04-08 12:41:29 +02:00
end = start + radeon_bo_size ( reloc - > robj ) ;
start + = offset ;
p - > ib . ptr [ data0 ] = start & 0xFFFFFFFF ;
p - > ib . ptr [ data1 ] = start > > 32 ;
cmd = radeon_get_ib_value ( p , p - > idx ) > > 1 ;
if ( cmd < 0x4 ) {
2014-04-28 09:40:22 -04:00
if ( end < = start ) {
DRM_ERROR ( " invalid reloc offset %X! \n " , offset ) ;
return - EINVAL ;
}
2013-04-08 12:41:29 +02:00
if ( ( end - start ) < buf_sizes [ cmd ] ) {
2013-08-05 14:10:57 +02:00
DRM_ERROR ( " buffer (%d) to small (%d / %d)! \n " , cmd ,
2013-04-08 12:41:29 +02:00
( unsigned ) ( end - start ) , buf_sizes [ cmd ] ) ;
return - EINVAL ;
}
} else if ( cmd ! = 0x100 ) {
DRM_ERROR ( " invalid UVD command %X! \n " , cmd ) ;
return - EINVAL ;
}
2013-12-20 17:48:54 +01:00
if ( ( start > > 28 ) ! = ( ( end - 1 ) > > 28 ) ) {
2013-04-14 12:45:43 +02:00
DRM_ERROR ( " reloc %LX-%LX crossing 256MB boundary! \n " ,
start , end ) ;
return - EINVAL ;
2013-04-08 12:41:29 +02:00
}
2013-10-15 20:12:03 +02:00
/* TODO: is this still necessary on NI+ ? */
if ( ( cmd = = 0 | | cmd = = 0x3 ) & &
2013-04-14 12:45:43 +02:00
( start > > 28 ) ! = ( p - > rdev - > uvd . gpu_addr > > 28 ) ) {
DRM_ERROR ( " msg/fb buffer %LX-%LX out of 256MB segment! \n " ,
2013-04-08 12:41:29 +02:00
start , end ) ;
return - EINVAL ;
}
2013-04-14 12:45:43 +02:00
if ( cmd = = 0 ) {
2013-08-05 14:10:57 +02:00
if ( * has_msg_cmd ) {
DRM_ERROR ( " More than one message in a UVD-IB! \n " ) ;
return - EINVAL ;
}
* has_msg_cmd = true ;
2013-04-14 12:45:43 +02:00
r = radeon_uvd_cs_msg ( p , reloc - > robj , offset , buf_sizes ) ;
if ( r )
return r ;
2013-08-05 14:10:57 +02:00
} else if ( ! * has_msg_cmd ) {
DRM_ERROR ( " Message needed before other commands are send! \n " ) ;
return - EINVAL ;
2013-04-14 12:45:43 +02:00
}
2013-04-08 12:41:29 +02:00
return 0 ;
}
static int radeon_uvd_cs_reg ( struct radeon_cs_parser * p ,
struct radeon_cs_packet * pkt ,
int * data0 , int * data1 ,
2013-08-05 14:10:57 +02:00
unsigned buf_sizes [ ] ,
bool * has_msg_cmd )
2013-04-08 12:41:29 +02:00
{
int i , r ;
p - > idx + + ;
for ( i = 0 ; i < = pkt - > count ; + + i ) {
switch ( pkt - > reg + i * 4 ) {
case UVD_GPCOM_VCPU_DATA0 :
* data0 = p - > idx ;
break ;
case UVD_GPCOM_VCPU_DATA1 :
* data1 = p - > idx ;
break ;
case UVD_GPCOM_VCPU_CMD :
2013-08-05 14:10:57 +02:00
r = radeon_uvd_cs_reloc ( p , * data0 , * data1 ,
buf_sizes , has_msg_cmd ) ;
2013-04-08 12:41:29 +02:00
if ( r )
return r ;
break ;
case UVD_ENGINE_CNTL :
break ;
default :
DRM_ERROR ( " Invalid reg 0x%X! \n " ,
pkt - > reg + i * 4 ) ;
return - EINVAL ;
}
p - > idx + + ;
}
return 0 ;
}
int radeon_uvd_cs_parse ( struct radeon_cs_parser * p )
{
struct radeon_cs_packet pkt ;
int r , data0 = 0 , data1 = 0 ;
2013-08-05 14:10:57 +02:00
/* does the IB has a msg command */
bool has_msg_cmd = false ;
2013-04-08 12:41:29 +02:00
/* minimum buffer sizes */
unsigned buf_sizes [ ] = {
[ 0x00000000 ] = 2048 ,
[ 0x00000001 ] = 32 * 1024 * 1024 ,
[ 0x00000002 ] = 2048 * 1152 * 3 ,
[ 0x00000003 ] = 2048 ,
} ;
2014-12-03 15:53:24 +01:00
if ( p - > chunk_ib - > length_dw % 16 ) {
2013-04-08 12:41:29 +02:00
DRM_ERROR ( " UVD IB length (%d) not 16 dwords aligned! \n " ,
2014-12-03 15:53:24 +01:00
p - > chunk_ib - > length_dw ) ;
2013-04-08 12:41:29 +02:00
return - EINVAL ;
}
2014-12-03 15:53:24 +01:00
if ( p - > chunk_relocs = = NULL ) {
2013-04-08 12:41:29 +02:00
DRM_ERROR ( " No relocation chunk ! \n " ) ;
return - EINVAL ;
}
do {
r = radeon_cs_packet_parse ( p , & pkt , p - > idx ) ;
if ( r )
return r ;
switch ( pkt . type ) {
case RADEON_PACKET_TYPE0 :
2013-08-05 14:10:57 +02:00
r = radeon_uvd_cs_reg ( p , & pkt , & data0 , & data1 ,
buf_sizes , & has_msg_cmd ) ;
2013-04-08 12:41:29 +02:00
if ( r )
return r ;
break ;
case RADEON_PACKET_TYPE2 :
p - > idx + = pkt . count + 2 ;
break ;
default :
DRM_ERROR ( " Unknown packet type %d ! \n " , pkt . type ) ;
return - EINVAL ;
}
2014-12-03 15:53:24 +01:00
} while ( p - > idx < p - > chunk_ib - > length_dw ) ;
2013-08-05 14:10:57 +02:00
if ( ! has_msg_cmd ) {
DRM_ERROR ( " UVD-IBs need a msg command! \n " ) ;
return - EINVAL ;
}
2013-04-08 12:41:29 +02:00
return 0 ;
}
static int radeon_uvd_send_msg ( struct radeon_device * rdev ,
2014-08-22 14:25:55 +02:00
int ring , uint64_t addr ,
2013-04-08 12:41:29 +02:00
struct radeon_fence * * fence )
{
struct radeon_ib ib ;
int i , r ;
2013-10-30 12:56:05 +01:00
r = radeon_ib_get ( rdev , ring , & ib , NULL , 64 ) ;
2013-06-27 13:48:17 +02:00
if ( r )
2014-08-22 14:25:55 +02:00
return r ;
2013-04-08 12:41:29 +02:00
ib . ptr [ 0 ] = PACKET0 ( UVD_GPCOM_VCPU_DATA0 , 0 ) ;
ib . ptr [ 1 ] = addr ;
ib . ptr [ 2 ] = PACKET0 ( UVD_GPCOM_VCPU_DATA1 , 0 ) ;
ib . ptr [ 3 ] = addr > > 32 ;
ib . ptr [ 4 ] = PACKET0 ( UVD_GPCOM_VCPU_CMD , 0 ) ;
ib . ptr [ 5 ] = 0 ;
for ( i = 6 ; i < 16 ; + + i )
ib . ptr [ i ] = PACKET2 ( 0 ) ;
ib . length_dw = 16 ;
2014-08-18 17:34:55 +09:00
r = radeon_ib_schedule ( rdev , & ib , NULL , false ) ;
2013-04-08 12:41:29 +02:00
if ( fence )
* fence = radeon_fence_ref ( ib . fence ) ;
radeon_ib_free ( rdev , & ib ) ;
2013-06-27 13:48:17 +02:00
return r ;
2013-04-08 12:41:29 +02:00
}
/* multiple fence commands without any stream commands in between can
crash the vcpu so just try to emmit a dummy create / destroy msg to
avoid this */
int radeon_uvd_get_create_msg ( struct radeon_device * rdev , int ring ,
uint32_t handle , struct radeon_fence * * fence )
{
2014-08-22 14:25:55 +02:00
/* we use the last page of the vcpu bo for the UVD message */
uint64_t offs = radeon_bo_size ( rdev - > uvd . vcpu_bo ) -
RADEON_GPU_PAGE_SIZE ;
2013-04-08 12:41:29 +02:00
2014-08-22 14:25:55 +02:00
uint32_t * msg = rdev - > uvd . cpu_addr + offs ;
uint64_t addr = rdev - > uvd . gpu_addr + offs ;
2013-04-08 12:41:29 +02:00
2014-08-22 14:25:55 +02:00
int r , i ;
2013-04-08 12:41:29 +02:00
2014-08-22 14:25:55 +02:00
r = radeon_bo_reserve ( rdev - > uvd . vcpu_bo , true ) ;
if ( r )
2013-04-08 12:41:29 +02:00
return r ;
/* stitch together an UVD create msg */
2013-06-07 10:04:54 -04:00
msg [ 0 ] = cpu_to_le32 ( 0x00000de4 ) ;
msg [ 1 ] = cpu_to_le32 ( 0x00000000 ) ;
msg [ 2 ] = cpu_to_le32 ( handle ) ;
msg [ 3 ] = cpu_to_le32 ( 0x00000000 ) ;
msg [ 4 ] = cpu_to_le32 ( 0x00000000 ) ;
msg [ 5 ] = cpu_to_le32 ( 0x00000000 ) ;
msg [ 6 ] = cpu_to_le32 ( 0x00000000 ) ;
msg [ 7 ] = cpu_to_le32 ( 0x00000780 ) ;
msg [ 8 ] = cpu_to_le32 ( 0x00000440 ) ;
msg [ 9 ] = cpu_to_le32 ( 0x00000000 ) ;
msg [ 10 ] = cpu_to_le32 ( 0x01b37000 ) ;
2013-04-08 12:41:29 +02:00
for ( i = 11 ; i < 1024 ; + + i )
2013-06-07 10:04:54 -04:00
msg [ i ] = cpu_to_le32 ( 0x0 ) ;
2013-04-08 12:41:29 +02:00
2014-08-22 14:25:55 +02:00
r = radeon_uvd_send_msg ( rdev , ring , addr , fence ) ;
radeon_bo_unreserve ( rdev - > uvd . vcpu_bo ) ;
return r ;
2013-04-08 12:41:29 +02:00
}
int radeon_uvd_get_destroy_msg ( struct radeon_device * rdev , int ring ,
uint32_t handle , struct radeon_fence * * fence )
{
2014-08-22 14:25:55 +02:00
/* we use the last page of the vcpu bo for the UVD message */
uint64_t offs = radeon_bo_size ( rdev - > uvd . vcpu_bo ) -
RADEON_GPU_PAGE_SIZE ;
2013-04-08 12:41:29 +02:00
2014-08-22 14:25:55 +02:00
uint32_t * msg = rdev - > uvd . cpu_addr + offs ;
uint64_t addr = rdev - > uvd . gpu_addr + offs ;
2013-04-08 12:41:29 +02:00
2014-08-22 14:25:55 +02:00
int r , i ;
2013-04-08 12:41:29 +02:00
2014-08-22 14:25:55 +02:00
r = radeon_bo_reserve ( rdev - > uvd . vcpu_bo , true ) ;
if ( r )
2013-04-08 12:41:29 +02:00
return r ;
/* stitch together an UVD destroy msg */
2013-06-07 10:04:54 -04:00
msg [ 0 ] = cpu_to_le32 ( 0x00000de4 ) ;
msg [ 1 ] = cpu_to_le32 ( 0x00000002 ) ;
msg [ 2 ] = cpu_to_le32 ( handle ) ;
msg [ 3 ] = cpu_to_le32 ( 0x00000000 ) ;
2013-04-08 12:41:29 +02:00
for ( i = 4 ; i < 1024 ; + + i )
2013-06-07 10:04:54 -04:00
msg [ i ] = cpu_to_le32 ( 0x0 ) ;
2013-04-08 12:41:29 +02:00
2014-08-22 14:25:55 +02:00
r = radeon_uvd_send_msg ( rdev , ring , addr , fence ) ;
radeon_bo_unreserve ( rdev - > uvd . vcpu_bo ) ;
return r ;
2013-04-08 12:41:29 +02:00
}
2013-04-18 15:25:59 +02:00
2013-08-05 12:41:20 -04:00
/**
* radeon_uvd_count_handles - count number of open streams
*
* @ rdev : radeon_device pointer
* @ sd : number of SD streams
* @ hd : number of HD streams
*
* Count the number of open SD / HD streams as a hint for power mangement
*/
static void radeon_uvd_count_handles ( struct radeon_device * rdev ,
unsigned * sd , unsigned * hd )
{
unsigned i ;
* sd = 0 ;
* hd = 0 ;
for ( i = 0 ; i < RADEON_MAX_UVD_HANDLES ; + + i ) {
if ( ! atomic_read ( & rdev - > uvd . handles [ i ] ) )
continue ;
if ( rdev - > uvd . img_size [ i ] > = 720 * 576 )
+ + ( * hd ) ;
else
+ + ( * sd ) ;
}
}
2013-04-18 15:25:59 +02:00
static void radeon_uvd_idle_work_handler ( struct work_struct * work )
{
struct radeon_device * rdev =
container_of ( work , struct radeon_device , uvd . idle_work . work ) ;
2013-06-21 15:12:57 -04:00
if ( radeon_fence_count_emitted ( rdev , R600_RING_TYPE_UVD_INDEX ) = = 0 ) {
if ( ( rdev - > pm . pm_method = = PM_METHOD_DPM ) & & rdev - > pm . dpm_enabled ) {
2014-01-10 16:05:05 +01:00
radeon_uvd_count_handles ( rdev , & rdev - > pm . dpm . sd ,
& rdev - > pm . dpm . hd ) ;
2013-07-24 12:12:49 -04:00
radeon_dpm_enable_uvd ( rdev , false ) ;
2013-06-21 15:12:57 -04:00
} else {
radeon_set_uvd_clocks ( rdev , 0 , 0 ) ;
}
} else {
2013-04-18 15:25:59 +02:00
schedule_delayed_work ( & rdev - > uvd . idle_work ,
msecs_to_jiffies ( UVD_IDLE_TIMEOUT_MS ) ) ;
2013-06-21 15:12:57 -04:00
}
2013-04-18 15:25:59 +02:00
}
void radeon_uvd_note_usage ( struct radeon_device * rdev )
{
2013-07-24 12:12:49 -04:00
bool streams_changed = false ;
2013-04-18 15:25:59 +02:00
bool set_clocks = ! cancel_delayed_work_sync ( & rdev - > uvd . idle_work ) ;
set_clocks & = schedule_delayed_work ( & rdev - > uvd . idle_work ,
msecs_to_jiffies ( UVD_IDLE_TIMEOUT_MS ) ) ;
2013-07-24 12:12:49 -04:00
if ( ( rdev - > pm . pm_method = = PM_METHOD_DPM ) & & rdev - > pm . dpm_enabled ) {
unsigned hd = 0 , sd = 0 ;
radeon_uvd_count_handles ( rdev , & sd , & hd ) ;
if ( ( rdev - > pm . dpm . sd ! = sd ) | |
( rdev - > pm . dpm . hd ! = hd ) ) {
rdev - > pm . dpm . sd = sd ;
rdev - > pm . dpm . hd = hd ;
2014-06-07 11:31:25 -04:00
/* disable this for now */
/*streams_changed = true;*/
2013-07-24 12:12:49 -04:00
}
}
if ( set_clocks | | streams_changed ) {
2013-06-21 15:12:57 -04:00
if ( ( rdev - > pm . pm_method = = PM_METHOD_DPM ) & & rdev - > pm . dpm_enabled ) {
2013-07-24 12:12:49 -04:00
radeon_dpm_enable_uvd ( rdev , true ) ;
2013-06-21 15:12:57 -04:00
} else {
radeon_set_uvd_clocks ( rdev , 53300 , 40000 ) ;
}
}
2013-04-18 15:25:59 +02:00
}
2013-04-29 11:55:02 +02:00
static unsigned radeon_uvd_calc_upll_post_div ( unsigned vco_freq ,
unsigned target_freq ,
unsigned pd_min ,
unsigned pd_even )
{
unsigned post_div = vco_freq / target_freq ;
/* adjust to post divider minimum value */
if ( post_div < pd_min )
post_div = pd_min ;
/* we alway need a frequency less than or equal the target */
if ( ( vco_freq / post_div ) > target_freq )
post_div + = 1 ;
/* post dividers above a certain value must be even */
if ( post_div > pd_even & & post_div % 2 )
post_div + = 1 ;
return post_div ;
}
/**
* radeon_uvd_calc_upll_dividers - calc UPLL clock dividers
*
* @ rdev : radeon_device pointer
* @ vclk : wanted VCLK
* @ dclk : wanted DCLK
* @ vco_min : minimum VCO frequency
* @ vco_max : maximum VCO frequency
* @ fb_factor : factor to multiply vco freq with
* @ fb_mask : limit and bitmask for feedback divider
* @ pd_min : post divider minimum
* @ pd_max : post divider maximum
* @ pd_even : post divider must be even above this value
* @ optimal_fb_div : resulting feedback divider
* @ optimal_vclk_div : resulting vclk post divider
* @ optimal_dclk_div : resulting dclk post divider
*
* Calculate dividers for UVDs UPLL ( R6xx - SI , except APUs ) .
* Returns zero on success - EINVAL on error .
*/
int radeon_uvd_calc_upll_dividers ( struct radeon_device * rdev ,
unsigned vclk , unsigned dclk ,
unsigned vco_min , unsigned vco_max ,
unsigned fb_factor , unsigned fb_mask ,
unsigned pd_min , unsigned pd_max ,
unsigned pd_even ,
unsigned * optimal_fb_div ,
unsigned * optimal_vclk_div ,
unsigned * optimal_dclk_div )
{
unsigned vco_freq , ref_freq = rdev - > clock . spll . reference_freq ;
/* start off with something large */
unsigned optimal_score = ~ 0 ;
/* loop through vco from low to high */
vco_min = max ( max ( vco_min , vclk ) , dclk ) ;
for ( vco_freq = vco_min ; vco_freq < = vco_max ; vco_freq + = 100 ) {
uint64_t fb_div = ( uint64_t ) vco_freq * fb_factor ;
unsigned vclk_div , dclk_div , score ;
do_div ( fb_div , ref_freq ) ;
/* fb div out of range ? */
if ( fb_div > fb_mask )
break ; /* it can oly get worse */
fb_div & = fb_mask ;
/* calc vclk divider with current vco freq */
vclk_div = radeon_uvd_calc_upll_post_div ( vco_freq , vclk ,
pd_min , pd_even ) ;
if ( vclk_div > pd_max )
break ; /* vco is too big, it has to stop */
/* calc dclk divider with current vco freq */
dclk_div = radeon_uvd_calc_upll_post_div ( vco_freq , dclk ,
pd_min , pd_even ) ;
if ( vclk_div > pd_max )
break ; /* vco is too big, it has to stop */
/* calc score with current vco freq */
score = vclk - ( vco_freq / vclk_div ) + dclk - ( vco_freq / dclk_div ) ;
/* determine if this vco setting is better than current optimal settings */
if ( score < optimal_score ) {
* optimal_fb_div = fb_div ;
* optimal_vclk_div = vclk_div ;
* optimal_dclk_div = dclk_div ;
optimal_score = score ;
if ( optimal_score = = 0 )
break ; /* it can't get better than this */
}
}
/* did we found a valid setup ? */
if ( optimal_score = = ~ 0 )
return - EINVAL ;
return 0 ;
}
int radeon_uvd_send_upll_ctlreq ( struct radeon_device * rdev ,
unsigned cg_upll_func_cntl )
{
unsigned i ;
/* make sure UPLL_CTLREQ is deasserted */
WREG32_P ( cg_upll_func_cntl , 0 , ~ UPLL_CTLREQ_MASK ) ;
mdelay ( 10 ) ;
/* assert UPLL_CTLREQ */
WREG32_P ( cg_upll_func_cntl , UPLL_CTLREQ_MASK , ~ UPLL_CTLREQ_MASK ) ;
/* wait for CTLACK and CTLACK2 to get asserted */
for ( i = 0 ; i < 100 ; + + i ) {
uint32_t mask = UPLL_CTLACK_MASK | UPLL_CTLACK2_MASK ;
if ( ( RREG32 ( cg_upll_func_cntl ) & mask ) = = mask )
break ;
mdelay ( 10 ) ;
}
/* deassert UPLL_CTLREQ */
WREG32_P ( cg_upll_func_cntl , 0 , ~ UPLL_CTLREQ_MASK ) ;
if ( i = = 100 ) {
DRM_ERROR ( " Timeout setting UVD clocks! \n " ) ;
return - ETIMEDOUT ;
}
return 0 ;
}