2013-03-22 16:34:03 +02:00
/*
* Tegra host1x Command DMA
*
* 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/slab.h>
# include <linux/scatterlist.h>
# include <linux/dma-mapping.h>
2013-10-09 10:32:54 +02:00
# include "../cdma.h"
# include "../channel.h"
# include "../dev.h"
# include "../debug.h"
2013-03-22 16:34:03 +02:00
/*
2014-06-12 13:17:48 +02:00
* Put the restart at the end of pushbuffer memory
2013-03-22 16:34:03 +02:00
*/
static void push_buffer_init ( struct push_buffer * pb )
{
2016-12-14 13:16:14 +02:00
* ( u32 * ) ( pb - > mapped + pb - > size ) = host1x_opcode_restart ( 0 ) ;
2013-03-22 16:34:03 +02:00
}
/*
* Increment timedout buffer ' s syncpt via CPU .
*/
static void cdma_timeout_cpu_incr ( struct host1x_cdma * cdma , u32 getptr ,
u32 syncpt_incrs , u32 syncval , u32 nr_slots )
{
struct host1x * host1x = cdma_to_host1x ( cdma ) ;
struct push_buffer * pb = & cdma - > push_buffer ;
2016-06-22 16:44:07 +02:00
unsigned int i ;
2013-03-22 16:34:03 +02:00
for ( i = 0 ; i < syncpt_incrs ; i + + )
2013-05-29 13:26:08 +03:00
host1x_syncpt_incr ( cdma - > timeout . syncpt ) ;
2013-03-22 16:34:03 +02:00
/* after CPU incr, ensure shadow is up to date */
host1x_syncpt_load ( cdma - > timeout . syncpt ) ;
/* NOP all the PB slots */
while ( nr_slots - - ) {
2014-06-12 13:16:54 +02:00
u32 * p = ( u32 * ) ( pb - > mapped + getptr ) ;
2013-03-22 16:34:03 +02:00
* ( p + + ) = HOST1X_OPCODE_NOP ;
* ( p + + ) = HOST1X_OPCODE_NOP ;
2014-06-12 13:24:17 +02:00
dev_dbg ( host1x - > dev , " %s: NOP at %pad+%#x \n " , __func__ ,
2016-12-14 13:16:14 +02:00
& pb - > dma , getptr ) ;
getptr = ( getptr + 8 ) & ( pb - > size - 1 ) ;
2013-03-22 16:34:03 +02:00
}
2016-06-23 11:35:50 +02:00
2013-03-22 16:34:03 +02:00
wmb ( ) ;
}
/*
* Start channel DMA
*/
static void cdma_start ( struct host1x_cdma * cdma )
{
struct host1x_channel * ch = cdma_to_channel ( cdma ) ;
2019-02-01 14:28:25 +01:00
u64 start , end ;
2013-03-22 16:34:03 +02:00
if ( cdma - > running )
return ;
cdma - > last_pos = cdma - > push_buffer . pos ;
2019-02-01 14:28:25 +01:00
start = cdma - > push_buffer . dma ;
2019-02-01 14:28:29 +01:00
end = cdma - > push_buffer . size + 4 ;
2013-03-22 16:34:03 +02:00
host1x_ch_writel ( ch , HOST1X_CHANNEL_DMACTRL_DMASTOP ,
HOST1X_CHANNEL_DMACTRL ) ;
/* set base, put and end pointer */
2019-02-01 14:28:25 +01:00
host1x_ch_writel ( ch , lower_32_bits ( start ) , HOST1X_CHANNEL_DMASTART ) ;
# if HOST1X_HW >= 6
host1x_ch_writel ( ch , upper_32_bits ( start ) , HOST1X_CHANNEL_DMASTART_HI ) ;
# endif
2013-03-22 16:34:03 +02:00
host1x_ch_writel ( ch , cdma - > push_buffer . pos , HOST1X_CHANNEL_DMAPUT ) ;
2019-02-01 14:28:25 +01:00
# if HOST1X_HW >= 6
host1x_ch_writel ( ch , 0 , HOST1X_CHANNEL_DMAPUT_HI ) ;
# endif
host1x_ch_writel ( ch , lower_32_bits ( end ) , HOST1X_CHANNEL_DMAEND ) ;
# if HOST1X_HW >= 6
host1x_ch_writel ( ch , upper_32_bits ( end ) , HOST1X_CHANNEL_DMAEND_HI ) ;
# endif
2013-03-22 16:34:03 +02:00
/* reset GET */
host1x_ch_writel ( ch , HOST1X_CHANNEL_DMACTRL_DMASTOP |
HOST1X_CHANNEL_DMACTRL_DMAGETRST |
HOST1X_CHANNEL_DMACTRL_DMAINITGET ,
HOST1X_CHANNEL_DMACTRL ) ;
/* start the command DMA */
host1x_ch_writel ( ch , 0 , HOST1X_CHANNEL_DMACTRL ) ;
cdma - > running = true ;
}
/*
* Similar to cdma_start ( ) , but rather than starting from an idle
* state ( where DMA GET is set to DMA PUT ) , on a timeout we restore
* DMA GET from an explicit value ( so DMA may again be pending ) .
*/
static void cdma_timeout_restart ( struct host1x_cdma * cdma , u32 getptr )
{
struct host1x * host1x = cdma_to_host1x ( cdma ) ;
struct host1x_channel * ch = cdma_to_channel ( cdma ) ;
2019-02-01 14:28:25 +01:00
u64 start , end ;
2013-03-22 16:34:03 +02:00
if ( cdma - > running )
return ;
cdma - > last_pos = cdma - > push_buffer . pos ;
host1x_ch_writel ( ch , HOST1X_CHANNEL_DMACTRL_DMASTOP ,
HOST1X_CHANNEL_DMACTRL ) ;
2019-02-01 14:28:25 +01:00
start = cdma - > push_buffer . dma ;
2019-02-01 14:28:29 +01:00
end = cdma - > push_buffer . size + 4 ;
2019-02-01 14:28:25 +01:00
2013-03-22 16:34:03 +02:00
/* set base, end pointer (all of memory) */
2019-02-01 14:28:25 +01:00
host1x_ch_writel ( ch , lower_32_bits ( start ) , HOST1X_CHANNEL_DMASTART ) ;
# if HOST1X_HW >= 6
host1x_ch_writel ( ch , upper_32_bits ( start ) , HOST1X_CHANNEL_DMASTART_HI ) ;
# endif
host1x_ch_writel ( ch , lower_32_bits ( end ) , HOST1X_CHANNEL_DMAEND ) ;
# if HOST1X_HW >= 6
host1x_ch_writel ( ch , upper_32_bits ( end ) , HOST1X_CHANNEL_DMAEND_HI ) ;
# endif
2013-03-22 16:34:03 +02:00
/* set GET, by loading the value in PUT (then reset GET) */
host1x_ch_writel ( ch , getptr , HOST1X_CHANNEL_DMAPUT ) ;
host1x_ch_writel ( ch , HOST1X_CHANNEL_DMACTRL_DMASTOP |
HOST1X_CHANNEL_DMACTRL_DMAGETRST |
HOST1X_CHANNEL_DMACTRL_DMAINITGET ,
HOST1X_CHANNEL_DMACTRL ) ;
dev_dbg ( host1x - > dev ,
" %s: DMA GET 0x%x, PUT HW 0x%x / shadow 0x%x \n " , __func__ ,
host1x_ch_readl ( ch , HOST1X_CHANNEL_DMAGET ) ,
host1x_ch_readl ( ch , HOST1X_CHANNEL_DMAPUT ) ,
cdma - > last_pos ) ;
/* deassert GET reset and set PUT */
host1x_ch_writel ( ch , HOST1X_CHANNEL_DMACTRL_DMASTOP ,
HOST1X_CHANNEL_DMACTRL ) ;
host1x_ch_writel ( ch , cdma - > push_buffer . pos , HOST1X_CHANNEL_DMAPUT ) ;
/* start the command DMA */
host1x_ch_writel ( ch , 0 , HOST1X_CHANNEL_DMACTRL ) ;
cdma - > running = true ;
}
/*
* Kick channel DMA into action by writing its PUT offset ( if it has changed )
*/
static void cdma_flush ( struct host1x_cdma * cdma )
{
struct host1x_channel * ch = cdma_to_channel ( cdma ) ;
if ( cdma - > push_buffer . pos ! = cdma - > last_pos ) {
host1x_ch_writel ( ch , cdma - > push_buffer . pos ,
HOST1X_CHANNEL_DMAPUT ) ;
cdma - > last_pos = cdma - > push_buffer . pos ;
}
}
static void cdma_stop ( struct host1x_cdma * cdma )
{
struct host1x_channel * ch = cdma_to_channel ( cdma ) ;
mutex_lock ( & cdma - > lock ) ;
2016-06-23 11:35:50 +02:00
2013-03-22 16:34:03 +02:00
if ( cdma - > running ) {
host1x_cdma_wait_locked ( cdma , CDMA_EVENT_SYNC_QUEUE_EMPTY ) ;
host1x_ch_writel ( ch , HOST1X_CHANNEL_DMACTRL_DMASTOP ,
HOST1X_CHANNEL_DMACTRL ) ;
cdma - > running = false ;
}
2016-06-23 11:35:50 +02:00
2013-03-22 16:34:03 +02:00
mutex_unlock ( & cdma - > lock ) ;
}
2017-09-05 11:43:05 +03:00
static void cdma_hw_cmdproc_stop ( struct host1x * host , struct host1x_channel * ch ,
bool stop )
{
# if HOST1X_HW >= 6
host1x_ch_writel ( ch , stop ? 0x1 : 0x0 , HOST1X_CHANNEL_CMDPROC_STOP ) ;
# else
u32 cmdproc_stop = host1x_sync_readl ( host , HOST1X_SYNC_CMDPROC_STOP ) ;
if ( stop )
cmdproc_stop | = BIT ( ch - > id ) ;
else
cmdproc_stop & = ~ BIT ( ch - > id ) ;
host1x_sync_writel ( host , cmdproc_stop , HOST1X_SYNC_CMDPROC_STOP ) ;
# endif
}
static void cdma_hw_teardown ( struct host1x * host , struct host1x_channel * ch )
{
# if HOST1X_HW >= 6
host1x_ch_writel ( ch , 0x1 , HOST1X_CHANNEL_TEARDOWN ) ;
# else
host1x_sync_writel ( host , BIT ( ch - > id ) , HOST1X_SYNC_CH_TEARDOWN ) ;
# endif
}
2013-03-22 16:34:03 +02:00
/*
* Stops both channel ' s command processor and CDMA immediately .
* Also , tears down the channel and resets corresponding module .
*/
static void cdma_freeze ( struct host1x_cdma * cdma )
{
struct host1x * host = cdma_to_host1x ( cdma ) ;
struct host1x_channel * ch = cdma_to_channel ( cdma ) ;
if ( cdma - > torndown & & ! cdma - > running ) {
dev_warn ( host - > dev , " Already torn down \n " ) ;
return ;
}
dev_dbg ( host - > dev , " freezing channel (id %d) \n " , ch - > id ) ;
2017-09-05 11:43:05 +03:00
cdma_hw_cmdproc_stop ( host , ch , true ) ;
2013-03-22 16:34:03 +02:00
dev_dbg ( host - > dev , " %s: DMA GET 0x%x, PUT HW 0x%x / shadow 0x%x \n " ,
__func__ , host1x_ch_readl ( ch , HOST1X_CHANNEL_DMAGET ) ,
host1x_ch_readl ( ch , HOST1X_CHANNEL_DMAPUT ) ,
cdma - > last_pos ) ;
host1x_ch_writel ( ch , HOST1X_CHANNEL_DMACTRL_DMASTOP ,
HOST1X_CHANNEL_DMACTRL ) ;
2017-09-05 11:43:05 +03:00
cdma_hw_teardown ( host , ch ) ;
2013-03-22 16:34:03 +02:00
cdma - > running = false ;
cdma - > torndown = true ;
}
static void cdma_resume ( struct host1x_cdma * cdma , u32 getptr )
{
struct host1x * host1x = cdma_to_host1x ( cdma ) ;
struct host1x_channel * ch = cdma_to_channel ( cdma ) ;
dev_dbg ( host1x - > dev ,
2016-06-23 11:19:00 +02:00
" resuming channel (id %u, DMAGET restart = 0x%x) \n " ,
2013-03-22 16:34:03 +02:00
ch - > id , getptr ) ;
2017-09-05 11:43:05 +03:00
cdma_hw_cmdproc_stop ( host1x , ch , false ) ;
2013-03-22 16:34:03 +02:00
cdma - > torndown = false ;
cdma_timeout_restart ( cdma , getptr ) ;
}
/*
* If this timeout fires , it indicates the current sync_queue entry has
* exceeded its TTL and the userctx should be timed out and remaining
* submits already issued cleaned up ( future submits return an error ) .
*/
static void cdma_timeout_handler ( struct work_struct * work )
{
2017-09-05 11:43:05 +03:00
u32 syncpt_val ;
2013-03-22 16:34:03 +02:00
struct host1x_cdma * cdma ;
struct host1x * host1x ;
struct host1x_channel * ch ;
cdma = container_of ( to_delayed_work ( work ) , struct host1x_cdma ,
timeout . wq ) ;
host1x = cdma_to_host1x ( cdma ) ;
ch = cdma_to_channel ( cdma ) ;
2013-03-22 16:34:04 +02:00
host1x_debug_dump ( cdma_to_host1x ( cdma ) ) ;
2013-03-22 16:34:03 +02:00
mutex_lock ( & cdma - > lock ) ;
if ( ! cdma - > timeout . client ) {
dev_dbg ( host1x - > dev ,
" cdma_timeout: expired, but has no clientid \n " ) ;
mutex_unlock ( & cdma - > lock ) ;
return ;
}
/* stop processing to get a clean snapshot */
2017-09-05 11:43:05 +03:00
cdma_hw_cmdproc_stop ( host1x , ch , true ) ;
2013-03-22 16:34:03 +02:00
syncpt_val = host1x_syncpt_load ( cdma - > timeout . syncpt ) ;
/* has buffer actually completed? */
if ( ( s32 ) ( syncpt_val - cdma - > timeout . syncpt_val ) > = 0 ) {
dev_dbg ( host1x - > dev ,
" cdma_timeout: expired, but buffer had completed \n " ) ;
/* restore */
2017-09-05 11:43:05 +03:00
cdma_hw_cmdproc_stop ( host1x , ch , false ) ;
2013-03-22 16:34:03 +02:00
mutex_unlock ( & cdma - > lock ) ;
return ;
}
2016-06-23 11:19:00 +02:00
dev_warn ( host1x - > dev , " %s: timeout: %u (%s), HW thresh %d, done %d \n " ,
2016-06-23 11:35:50 +02:00
__func__ , cdma - > timeout . syncpt - > id , cdma - > timeout . syncpt - > name ,
syncpt_val , cdma - > timeout . syncpt_val ) ;
2013-03-22 16:34:03 +02:00
/* stop HW, resetting channel/module */
host1x_hw_cdma_freeze ( host1x , cdma ) ;
host1x_cdma_update_sync_queue ( cdma , ch - > dev ) ;
mutex_unlock ( & cdma - > lock ) ;
}
/*
* Init timeout resources
*/
2016-06-23 11:19:00 +02:00
static int cdma_timeout_init ( struct host1x_cdma * cdma , unsigned int syncpt )
2013-03-22 16:34:03 +02:00
{
INIT_DELAYED_WORK ( & cdma - > timeout . wq , cdma_timeout_handler ) ;
cdma - > timeout . initialized = true ;
return 0 ;
}
/*
* Clean up timeout resources
*/
static void cdma_timeout_destroy ( struct host1x_cdma * cdma )
{
if ( cdma - > timeout . initialized )
cancel_delayed_work ( & cdma - > timeout . wq ) ;
2016-06-23 11:35:50 +02:00
2013-03-22 16:34:03 +02:00
cdma - > timeout . initialized = false ;
}
static const struct host1x_cdma_ops host1x_cdma_ops = {
. start = cdma_start ,
. stop = cdma_stop ,
. flush = cdma_flush ,
. timeout_init = cdma_timeout_init ,
. timeout_destroy = cdma_timeout_destroy ,
. freeze = cdma_freeze ,
. resume = cdma_resume ,
. timeout_cpu_incr = cdma_timeout_cpu_incr ,
} ;
static const struct host1x_pushbuffer_ops host1x_pushbuffer_ops = {
. init = push_buffer_init ,
} ;