2013-03-22 18:34:01 +04:00
/*
* Tegra host1x Syncpoints
*
* Copyright ( c ) 2010 - 2013 , NVIDIA Corporation .
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms and conditions of the GNU General Public License ,
* version 2 , as published by the Free Software Foundation .
*
* This program is distributed in the hope it will be useful , but WITHOUT
* ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE . See the GNU General Public License for
* more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
# include <linux/module.h>
# include <linux/device.h>
# include <linux/slab.h>
# include <trace/events/host1x.h>
# include "syncpt.h"
# include "dev.h"
2013-03-22 18:34:02 +04:00
# include "intr.h"
2013-03-22 18:34:04 +04:00
# include "debug.h"
2013-03-22 18:34:02 +04:00
# define SYNCPT_CHECK_PERIOD (2 * HZ)
# define MAX_STUCK_CHECK_COUNT 15
2013-03-22 18:34:01 +04:00
2013-10-14 16:21:53 +04:00
static struct host1x_syncpt_base *
host1x_syncpt_base_request ( struct host1x * host )
{
struct host1x_syncpt_base * bases = host - > bases ;
unsigned int i ;
for ( i = 0 ; i < host - > info - > nb_bases ; i + + )
if ( ! bases [ i ] . requested )
break ;
if ( i > = host - > info - > nb_bases )
return NULL ;
bases [ i ] . requested = true ;
return & bases [ i ] ;
}
static void host1x_syncpt_base_free ( struct host1x_syncpt_base * base )
{
if ( base )
base - > requested = false ;
}
2013-10-14 16:21:52 +04:00
static struct host1x_syncpt * host1x_syncpt_alloc ( struct host1x * host ,
struct device * dev ,
unsigned long flags )
2013-03-22 18:34:01 +04:00
{
int i ;
struct host1x_syncpt * sp = host - > syncpt ;
char * name ;
for ( i = 0 ; i < host - > info - > nb_pts & & sp - > name ; i + + , sp + + )
;
2013-05-29 14:26:06 +04:00
if ( i > = host - > info - > nb_pts )
2013-03-22 18:34:01 +04:00
return NULL ;
2013-10-14 16:21:53 +04:00
if ( flags & HOST1X_SYNCPT_HAS_BASE ) {
sp - > base = host1x_syncpt_base_request ( host ) ;
if ( ! sp - > base )
return NULL ;
}
2013-03-22 18:34:01 +04:00
name = kasprintf ( GFP_KERNEL , " %02d-%s " , sp - > id ,
dev ? dev_name ( dev ) : NULL ) ;
if ( ! name )
return NULL ;
sp - > dev = dev ;
sp - > name = name ;
2013-10-14 16:21:52 +04:00
if ( flags & HOST1X_SYNCPT_CLIENT_MANAGED )
sp - > client_managed = true ;
else
sp - > client_managed = false ;
2013-03-22 18:34:01 +04:00
return sp ;
}
u32 host1x_syncpt_id ( struct host1x_syncpt * sp )
{
return sp - > id ;
}
2013-11-08 14:41:42 +04:00
EXPORT_SYMBOL ( host1x_syncpt_id ) ;
2013-03-22 18:34:01 +04:00
/*
* Updates the value sent to hardware .
*/
u32 host1x_syncpt_incr_max ( struct host1x_syncpt * sp , u32 incrs )
{
return ( u32 ) atomic_add_return ( incrs , & sp - > max_val ) ;
}
2014-02-20 02:48:36 +04:00
EXPORT_SYMBOL ( host1x_syncpt_incr_max ) ;
2013-03-22 18:34:01 +04:00
/*
* Write cached syncpoint and waitbase values to hardware .
*/
void host1x_syncpt_restore ( struct host1x * host )
{
struct host1x_syncpt * sp_base = host - > syncpt ;
u32 i ;
for ( i = 0 ; i < host1x_syncpt_nb_pts ( host ) ; i + + )
host1x_hw_syncpt_restore ( host , sp_base + i ) ;
for ( i = 0 ; i < host1x_syncpt_nb_bases ( host ) ; i + + )
host1x_hw_syncpt_restore_wait_base ( host , sp_base + i ) ;
wmb ( ) ;
}
/*
* Update the cached syncpoint and waitbase values by reading them
* from the registers .
*/
void host1x_syncpt_save ( struct host1x * host )
{
struct host1x_syncpt * sp_base = host - > syncpt ;
u32 i ;
for ( i = 0 ; i < host1x_syncpt_nb_pts ( host ) ; i + + ) {
if ( host1x_syncpt_client_managed ( sp_base + i ) )
host1x_hw_syncpt_load ( host , sp_base + i ) ;
else
WARN_ON ( ! host1x_syncpt_idle ( sp_base + i ) ) ;
}
for ( i = 0 ; i < host1x_syncpt_nb_bases ( host ) ; i + + )
host1x_hw_syncpt_load_wait_base ( host , sp_base + i ) ;
}
/*
* Updates the cached syncpoint value by reading a new value from the hardware
* register
*/
u32 host1x_syncpt_load ( struct host1x_syncpt * sp )
{
u32 val ;
val = host1x_hw_syncpt_load ( sp - > host , sp ) ;
trace_host1x_syncpt_load_min ( sp - > id , val ) ;
return val ;
}
/*
* Get the current syncpoint base
*/
u32 host1x_syncpt_load_wait_base ( struct host1x_syncpt * sp )
{
u32 val ;
host1x_hw_syncpt_load_wait_base ( sp - > host , sp ) ;
val = sp - > base_val ;
return val ;
}
/*
* Increment syncpoint value from cpu , updating cache
*/
2013-05-29 14:26:08 +04:00
int host1x_syncpt_incr ( struct host1x_syncpt * sp )
2013-03-22 18:34:01 +04:00
{
2013-05-29 14:26:08 +04:00
return host1x_hw_syncpt_cpu_incr ( sp - > host , sp ) ;
2013-03-22 18:34:01 +04:00
}
2013-11-08 14:41:42 +04:00
EXPORT_SYMBOL ( host1x_syncpt_incr ) ;
2013-03-22 18:34:01 +04:00
2013-03-22 18:34:02 +04:00
/*
* Updated sync point form hardware , and returns true if syncpoint is expired ,
* false if we may need to wait
*/
static bool syncpt_load_min_is_expired ( struct host1x_syncpt * sp , u32 thresh )
{
host1x_hw_syncpt_load ( sp - > host , sp ) ;
return host1x_syncpt_is_expired ( sp , thresh ) ;
}
/*
* Main entrypoint for syncpoint value waits .
*/
int host1x_syncpt_wait ( struct host1x_syncpt * sp , u32 thresh , long timeout ,
u32 * value )
{
DECLARE_WAIT_QUEUE_HEAD_ONSTACK ( wq ) ;
void * ref ;
struct host1x_waitlist * waiter ;
int err = 0 , check_count = 0 ;
u32 val ;
if ( value )
* value = 0 ;
/* first check cache */
if ( host1x_syncpt_is_expired ( sp , thresh ) ) {
if ( value )
* value = host1x_syncpt_load ( sp ) ;
return 0 ;
}
/* try to read from register */
val = host1x_hw_syncpt_load ( sp - > host , sp ) ;
if ( host1x_syncpt_is_expired ( sp , thresh ) ) {
if ( value )
* value = val ;
goto done ;
}
if ( ! timeout ) {
err = - EAGAIN ;
goto done ;
}
/* allocate a waiter */
waiter = kzalloc ( sizeof ( * waiter ) , GFP_KERNEL ) ;
if ( ! waiter ) {
err = - ENOMEM ;
goto done ;
}
/* schedule a wakeup when the syncpoint value is reached */
err = host1x_intr_add_action ( sp - > host , sp - > id , thresh ,
HOST1X_INTR_ACTION_WAKEUP_INTERRUPTIBLE ,
& wq , waiter , & ref ) ;
if ( err )
goto done ;
err = - EAGAIN ;
/* Caller-specified timeout may be impractically low */
if ( timeout < 0 )
timeout = LONG_MAX ;
/* wait for the syncpoint, or timeout, or signal */
while ( timeout ) {
long check = min_t ( long , SYNCPT_CHECK_PERIOD , timeout ) ;
int remain = wait_event_interruptible_timeout ( wq ,
syncpt_load_min_is_expired ( sp , thresh ) ,
check ) ;
if ( remain > 0 | | host1x_syncpt_is_expired ( sp , thresh ) ) {
if ( value )
* value = host1x_syncpt_load ( sp ) ;
err = 0 ;
break ;
}
if ( remain < 0 ) {
err = remain ;
break ;
}
timeout - = check ;
if ( timeout & & check_count < = MAX_STUCK_CHECK_COUNT ) {
dev_warn ( sp - > host - > dev ,
" %s: syncpoint id %d (%s) stuck waiting %d, timeout=%ld \n " ,
current - > comm , sp - > id , sp - > name ,
thresh , timeout ) ;
2013-03-22 18:34:04 +04:00
host1x_debug_dump_syncpts ( sp - > host ) ;
if ( check_count = = MAX_STUCK_CHECK_COUNT )
host1x_debug_dump ( sp - > host ) ;
2013-03-22 18:34:02 +04:00
check_count + + ;
}
}
host1x_intr_put_ref ( sp - > host , sp - > id , ref ) ;
done :
return err ;
}
EXPORT_SYMBOL ( host1x_syncpt_wait ) ;
/*
* Returns true if syncpoint is expired , false if we may need to wait
*/
bool host1x_syncpt_is_expired ( struct host1x_syncpt * sp , u32 thresh )
{
u32 current_val ;
u32 future_val ;
smp_rmb ( ) ;
current_val = ( u32 ) atomic_read ( & sp - > min_val ) ;
future_val = ( u32 ) atomic_read ( & sp - > max_val ) ;
/* Note the use of unsigned arithmetic here (mod 1<<32).
*
* c = current_val = min_val = the current value of the syncpoint .
* t = thresh = the value we are checking
* f = future_val = max_val = the value c will reach when all
* outstanding increments have completed .
*
* Note that c always chases f until it reaches f .
*
* Dtf = ( f - t )
* Dtc = ( c - t )
*
* Consider all cases :
*
* A ) . . . . . c . . t . . f . . . . . Dtf < Dtc need to wait
* B ) . . . . . c . . . . . f . . t . . Dtf > Dtc expired
* C ) . . t . . c . . . . . f . . . . . Dtf > Dtc expired ( Dct very large )
*
* Any case where f = = c : always expired ( for any t ) . Dtf = = Dcf
* Any case where t = = c : always expired ( for any f ) . Dtf > = Dtc ( because Dtc = = 0 )
* Any case where t = = f ! = c : always wait . Dtf < Dtc ( because Dtf = = 0 ,
* Dtc ! = 0 )
*
* Other cases :
*
* A ) . . . . . t . . f . . c . . . . . Dtf < Dtc need to wait
* A ) . . . . . f . . c . . t . . . . . Dtf < Dtc need to wait
* A ) . . . . . f . . t . . c . . . . . Dtf > Dtc expired
*
* So :
* Dtf > = Dtc implies EXPIRED ( return true )
* Dtf < Dtc implies WAIT ( return false )
*
* Note : If t is expired then we * cannot * wait on it . We would wait
* forever ( hang the system ) .
*
* Note : do NOT get clever and remove the - thresh from both sides . It
* is NOT the same .
*
* If future valueis zero , we have a client managed sync point . In that
* case we do a direct comparison .
*/
if ( ! host1x_syncpt_client_managed ( sp ) )
return future_val - thresh > = current_val - thresh ;
else
return ( s32 ) ( current_val - thresh ) > = 0 ;
}
2013-03-22 18:34:03 +04:00
/* remove a wait pointed to by patch_addr */
int host1x_syncpt_patch_wait ( struct host1x_syncpt * sp , void * patch_addr )
{
return host1x_hw_syncpt_patch_wait ( sp - > host , sp , patch_addr ) ;
}
2013-03-22 18:34:01 +04:00
int host1x_syncpt_init ( struct host1x * host )
{
2013-10-14 16:21:53 +04:00
struct host1x_syncpt_base * bases ;
2013-03-22 18:34:01 +04:00
struct host1x_syncpt * syncpt ;
int i ;
syncpt = devm_kzalloc ( host - > dev , sizeof ( * syncpt ) * host - > info - > nb_pts ,
2013-10-14 16:21:53 +04:00
GFP_KERNEL ) ;
2013-03-22 18:34:01 +04:00
if ( ! syncpt )
return - ENOMEM ;
2013-10-14 16:21:53 +04:00
bases = devm_kzalloc ( host - > dev , sizeof ( * bases ) * host - > info - > nb_bases ,
GFP_KERNEL ) ;
if ( ! bases )
return - ENOMEM ;
for ( i = 0 ; i < host - > info - > nb_pts ; i + + ) {
2013-03-22 18:34:01 +04:00
syncpt [ i ] . id = i ;
syncpt [ i ] . host = host ;
}
2013-10-14 16:21:53 +04:00
for ( i = 0 ; i < host - > info - > nb_bases ; i + + )
bases [ i ] . id = i ;
2013-03-22 18:34:01 +04:00
host - > syncpt = syncpt ;
2013-10-14 16:21:53 +04:00
host - > bases = bases ;
2013-03-22 18:34:01 +04:00
host1x_syncpt_restore ( host ) ;
2013-03-22 18:34:03 +04:00
/* Allocate sync point to use for clearing waits for expired fences */
2013-10-14 16:21:52 +04:00
host - > nop_sp = host1x_syncpt_alloc ( host , NULL , 0 ) ;
2013-03-22 18:34:03 +04:00
if ( ! host - > nop_sp )
return - ENOMEM ;
2013-03-22 18:34:01 +04:00
return 0 ;
}
struct host1x_syncpt * host1x_syncpt_request ( struct device * dev ,
2013-10-14 16:21:52 +04:00
unsigned long flags )
2013-03-22 18:34:01 +04:00
{
struct host1x * host = dev_get_drvdata ( dev - > parent ) ;
2013-10-14 16:21:52 +04:00
return host1x_syncpt_alloc ( host , dev , flags ) ;
2013-03-22 18:34:01 +04:00
}
2013-11-08 14:41:42 +04:00
EXPORT_SYMBOL ( host1x_syncpt_request ) ;
2013-03-22 18:34:01 +04:00
void host1x_syncpt_free ( struct host1x_syncpt * sp )
{
if ( ! sp )
return ;
2013-10-14 16:21:53 +04:00
host1x_syncpt_base_free ( sp - > base ) ;
2013-03-22 18:34:01 +04:00
kfree ( sp - > name ) ;
2013-10-14 16:21:53 +04:00
sp - > base = NULL ;
2013-03-22 18:34:01 +04:00
sp - > dev = NULL ;
sp - > name = NULL ;
2013-05-29 14:26:07 +04:00
sp - > client_managed = false ;
2013-03-22 18:34:01 +04:00
}
2013-11-08 14:41:42 +04:00
EXPORT_SYMBOL ( host1x_syncpt_free ) ;
2013-03-22 18:34:01 +04:00
void host1x_syncpt_deinit ( struct host1x * host )
{
int i ;
struct host1x_syncpt * sp = host - > syncpt ;
for ( i = 0 ; i < host - > info - > nb_pts ; i + + , sp + + )
kfree ( sp - > name ) ;
}
2013-09-24 18:30:32 +04:00
/*
* Read max . It indicates how many operations there are in queue , either in
* channel or in a software thread .
* */
u32 host1x_syncpt_read_max ( struct host1x_syncpt * sp )
{
smp_rmb ( ) ;
return ( u32 ) atomic_read ( & sp - > max_val ) ;
}
2013-11-08 14:41:42 +04:00
EXPORT_SYMBOL ( host1x_syncpt_read_max ) ;
2013-09-24 18:30:32 +04:00
/*
* Read min , which is a shadow of the current sync point value in hardware .
*/
u32 host1x_syncpt_read_min ( struct host1x_syncpt * sp )
{
smp_rmb ( ) ;
return ( u32 ) atomic_read ( & sp - > min_val ) ;
}
2013-11-08 14:41:42 +04:00
EXPORT_SYMBOL ( host1x_syncpt_read_min ) ;
2013-09-24 18:30:32 +04:00
2015-01-28 16:29:02 +03:00
u32 host1x_syncpt_read ( struct host1x_syncpt * sp )
{
return host1x_syncpt_load ( sp ) ;
}
EXPORT_SYMBOL ( host1x_syncpt_read ) ;
2013-03-22 18:34:01 +04:00
int host1x_syncpt_nb_pts ( struct host1x * host )
{
return host - > info - > nb_pts ;
}
int host1x_syncpt_nb_bases ( struct host1x * host )
{
return host - > info - > nb_bases ;
}
int host1x_syncpt_nb_mlocks ( struct host1x * host )
{
return host - > info - > nb_mlocks ;
}
struct host1x_syncpt * host1x_syncpt_get ( struct host1x * host , u32 id )
{
if ( host - > info - > nb_pts < id )
return NULL ;
return host - > syncpt + id ;
}
2013-11-08 14:41:42 +04:00
EXPORT_SYMBOL ( host1x_syncpt_get ) ;
2013-10-14 16:21:53 +04:00
struct host1x_syncpt_base * host1x_syncpt_get_base ( struct host1x_syncpt * sp )
{
return sp ? sp - > base : NULL ;
}
2013-11-08 14:41:42 +04:00
EXPORT_SYMBOL ( host1x_syncpt_get_base ) ;
2013-10-14 16:21:53 +04:00
u32 host1x_syncpt_base_id ( struct host1x_syncpt_base * base )
{
return base - > id ;
}
2013-11-08 14:41:42 +04:00
EXPORT_SYMBOL ( host1x_syncpt_base_id ) ;