@ -10,6 +10,7 @@
* published by the Free Software Foundation .
*/
# include <linux/dma-mapping.h>
# include <linux/dmaengine.h>
# include <linux/interrupt.h>
# include <linux/list.h>
@ -40,6 +41,19 @@ struct rcar_dmac_xfer_chunk {
u32 size ;
} ;
/*
* struct rcar_dmac_hw_desc - Hardware descriptor for a transfer chunk
* @ sar : value of the SAR register ( source address )
* @ dar : value of the DAR register ( destination address )
* @ tcr : value of the TCR register ( transfer count )
*/
struct rcar_dmac_hw_desc {
u32 sar ;
u32 dar ;
u32 tcr ;
u32 reserved ;
} __attribute__ ( ( __packed__ ) ) ;
/*
* struct rcar_dmac_desc - R - Car Gen2 DMA Transfer Descriptor
* @ async_tx : base DMA asynchronous transaction descriptor
@ -49,6 +63,10 @@ struct rcar_dmac_xfer_chunk {
* @ node : entry in the channel ' s descriptors lists
* @ chunks : list of transfer chunks for this transfer
* @ running : the transfer chunk being currently processed
* @ nchunks : number of transfer chunks for this transfer
* @ hwdescs . mem : hardware descriptors memory for the transfer
* @ hwdescs . dma : device address of the hardware descriptors memory
* @ hwdescs . size : size of the hardware descriptors in bytes
* @ size : transfer size in bytes
* @ cyclic : when set indicates that the DMA transfer is cyclic
*/
@ -61,6 +79,13 @@ struct rcar_dmac_desc {
struct list_head node ;
struct list_head chunks ;
struct rcar_dmac_xfer_chunk * running ;
unsigned int nchunks ;
struct {
struct rcar_dmac_hw_desc * mem ;
dma_addr_t dma ;
size_t size ;
} hwdescs ;
unsigned int size ;
bool cyclic ;
@ -217,7 +242,8 @@ struct rcar_dmac {
# define RCAR_DMATSRB 0x0038
# define RCAR_DMACHCRB 0x001c
# define RCAR_DMACHCRB_DCNT(n) ((n) << 24)
# define RCAR_DMACHCRB_DPTR(n) ((n) << 16)
# define RCAR_DMACHCRB_DPTR_MASK (0xff << 16)
# define RCAR_DMACHCRB_DPTR_SHIFT 16
# define RCAR_DMACHCRB_DRST (1 << 15)
# define RCAR_DMACHCRB_DTS (1 << 8)
# define RCAR_DMACHCRB_SLM_NORMAL (0 << 4)
@ -289,30 +315,81 @@ static bool rcar_dmac_chan_is_busy(struct rcar_dmac_chan *chan)
static void rcar_dmac_chan_start_xfer ( struct rcar_dmac_chan * chan )
{
struct rcar_dmac_desc * desc = chan - > desc . running ;
struct rcar_dmac_xfer_chunk * chunk = desc - > running ;
dev_dbg ( chan - > chan . device - > dev ,
" chan%u: queue chunk %p: %u@%pad -> %pad \n " ,
chan - > index , chunk , chunk - > size , & chunk - > src_addr ,
& chunk - > dst_addr ) ;
u32 chcr = desc - > chcr ;
WARN_ON_ONCE ( rcar_dmac_chan_is_busy ( chan ) ) ;
if ( chan - > mid_rid > = 0 )
rcar_dmac_chan_write ( chan , RCAR_DMARS , chan - > mid_rid ) ;
if ( desc - > hwdescs . mem ) {
dev_dbg ( chan - > chan . device - > dev ,
" chan%u: queue desc %p: %u@%pad \n " ,
chan - > index , desc , desc - > nchunks , & desc - > hwdescs . dma ) ;
# ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
rcar_dmac_chan_write ( chan , RCAR_DMAFIXSAR , chunk - > src_addr > > 32 ) ;
rcar_dmac_chan_write ( chan , RCAR_DMAFIXDAR , chunk - > dst_addr > > 32 ) ;
rcar_dmac_chan_write ( chan , RCAR_DMAFIXDPBASE ,
desc - > hwdescs . dma > > 32 ) ;
# endif
rcar_dmac_chan_write ( chan , RCAR_DMASAR , chunk - > src_addr & 0xffffffff ) ;
rcar_dmac_chan_write ( chan , RCAR_DMADAR , chunk - > dst_addr & 0xffffffff ) ;
rcar_dmac_chan_write ( chan , RCAR_DMADPBASE ,
( desc - > hwdescs . dma & 0xfffffff0 ) |
RCAR_DMADPBASE_SEL ) ;
rcar_dmac_chan_write ( chan , RCAR_DMACHCRB ,
RCAR_DMACHCRB_DCNT ( desc - > nchunks - 1 ) |
RCAR_DMACHCRB_DRST ) ;
if ( chan - > mid_rid > = 0 )
rcar_dmac_chan_write ( chan , RCAR_DMARS , chan - > mid_rid ) ;
/*
* Program the descriptor stage interrupt to occur after the end
* of the first stage .
*/
rcar_dmac_chan_write ( chan , RCAR_DMADPCR , RCAR_DMADPCR_DIPT ( 1 ) ) ;
chcr | = RCAR_DMACHCR_RPT_SAR | RCAR_DMACHCR_RPT_DAR
| RCAR_DMACHCR_RPT_TCR | RCAR_DMACHCR_DPB ;
/*
* If the descriptor isn ' t cyclic enable normal descriptor mode
* and the transfer completion interrupt .
*/
if ( ! desc - > cyclic )
chcr | = RCAR_DMACHCR_DPM_ENABLED | RCAR_DMACHCR_IE ;
/*
* If the descriptor is cyclic and has a callback enable the
* descriptor stage interrupt in infinite repeat mode .
*/
else if ( desc - > async_tx . callback )
chcr | = RCAR_DMACHCR_DPM_INFINITE | RCAR_DMACHCR_DSIE ;
/*
* Otherwise just select infinite repeat mode without any
* interrupt .
*/
else
chcr | = RCAR_DMACHCR_DPM_INFINITE ;
} else {
struct rcar_dmac_xfer_chunk * chunk = desc - > running ;
rcar_dmac_chan_write ( chan , RCAR_DMATCR ,
chunk - > size > > desc - > xfer_shift ) ;
dev_dbg ( chan - > chan . device - > dev ,
" chan%u: queue chunk %p: %u@%pad -> %pad \n " ,
chan - > index , chunk , chunk - > size , & chunk - > src_addr ,
& chunk - > dst_addr ) ;
rcar_dmac_chan_write ( chan , RCAR_DMACHCR , desc - > chcr | RCAR_DMACHCR_DE |
RCAR_DMACHCR_IE ) ;
# ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
rcar_dmac_chan_write ( chan , RCAR_DMAFIXSAR ,
chunk - > src_addr > > 32 ) ;
rcar_dmac_chan_write ( chan , RCAR_DMAFIXDAR ,
chunk - > dst_addr > > 32 ) ;
# endif
rcar_dmac_chan_write ( chan , RCAR_DMASAR ,
chunk - > src_addr & 0xffffffff ) ;
rcar_dmac_chan_write ( chan , RCAR_DMADAR ,
chunk - > dst_addr & 0xffffffff ) ;
rcar_dmac_chan_write ( chan , RCAR_DMATCR ,
chunk - > size > > desc - > xfer_shift ) ;
chcr | = RCAR_DMACHCR_DPM_DISABLED | RCAR_DMACHCR_IE ;
}
rcar_dmac_chan_write ( chan , RCAR_DMACHCR , chcr | RCAR_DMACHCR_DE ) ;
}
static int rcar_dmac_init ( struct rcar_dmac * dmac )
@ -403,31 +480,58 @@ static int rcar_dmac_desc_alloc(struct rcar_dmac_chan *chan, gfp_t gfp)
* @ desc : the descriptor
*
* Put the descriptor and its transfer chunk descriptors back in the channel ' s
* free descriptors lists . The descriptor ' s chunk will be reinitialized to an
* empty list as a result .
* free descriptors lists , and free the hardware descriptors list memory . The
* descriptor ' s chunks list will be reinitialized to an empty list as a result .
*
* The descriptor must have been removed from the channel ' s done list before
* calling this function .
* The descriptor must have been removed from the channel ' s lists before calling
* this function .
*
* Locking : Must be called with the channel lock held .
* Locking : Must be called in non - atomic context .
*/
static void rcar_dmac_desc_put ( struct rcar_dmac_chan * chan ,
struct rcar_dmac_desc * desc )
{
if ( desc - > hwdescs . mem ) {
dma_free_coherent ( NULL , desc - > hwdescs . size , desc - > hwdescs . mem ,
desc - > hwdescs . dma ) ;
desc - > hwdescs . mem = NULL ;
}
spin_lock_irq ( & chan - > lock ) ;
list_splice_tail_init ( & desc - > chunks , & chan - > desc . chunks_free ) ;
list_add_tail ( & desc - > node , & chan - > desc . free ) ;
spin_unlock_irq ( & chan - > lock ) ;
}
static void rcar_dmac_desc_recycle_acked ( struct rcar_dmac_chan * chan )
{
struct rcar_dmac_desc * desc , * _desc ;
LIST_HEAD ( list ) ;
list_for_each_entry_safe ( desc , _desc , & chan - > desc . wait , node ) {
/*
* We have to temporarily move all descriptors from the wait list to a
* local list as iterating over the wait list , even with
* list_for_each_entry_safe , isn ' t safe if we release the channel lock
* around the rcar_dmac_desc_put ( ) call .
*/
spin_lock_irq ( & chan - > lock ) ;
list_splice_init ( & chan - > desc . wait , & list ) ;
spin_unlock_irq ( & chan - > lock ) ;
list_for_each_entry_safe ( desc , _desc , & list , node ) {
if ( async_tx_test_ack ( & desc - > async_tx ) ) {
list_del ( & desc - > node ) ;
rcar_dmac_desc_put ( chan , desc ) ;
}
}
if ( list_empty ( & list ) )
return ;
/* Put the remaining descriptors back in the wait list. */
spin_lock_irq ( & chan - > lock ) ;
list_splice ( & list , & chan - > desc . wait ) ;
spin_unlock_irq ( & chan - > lock ) ;
}
/*
@ -444,11 +548,11 @@ static struct rcar_dmac_desc *rcar_dmac_desc_get(struct rcar_dmac_chan *chan)
struct rcar_dmac_desc * desc ;
int ret ;
spin_lock_irq ( & chan - > lock ) ;
/* Recycle acked descriptors before attempting allocation. */
rcar_dmac_desc_recycle_acked ( chan ) ;
spin_lock_irq ( & chan - > lock ) ;
do {
if ( list_empty ( & chan - > desc . free ) ) {
/*
@ -547,6 +651,28 @@ rcar_dmac_xfer_chunk_get(struct rcar_dmac_chan *chan)
return chunk ;
}
static void rcar_dmac_alloc_hwdesc ( struct rcar_dmac_chan * chan ,
struct rcar_dmac_desc * desc )
{
struct rcar_dmac_xfer_chunk * chunk ;
struct rcar_dmac_hw_desc * hwdesc ;
size_t size = desc - > nchunks * sizeof ( * hwdesc ) ;
hwdesc = dma_alloc_coherent ( NULL , size , & desc - > hwdescs . dma , GFP_NOWAIT ) ;
if ( ! hwdesc )
return ;
desc - > hwdescs . mem = hwdesc ;
desc - > hwdescs . size = size ;
list_for_each_entry ( chunk , & desc - > chunks , node ) {
hwdesc - > sar = chunk - > src_addr ;
hwdesc - > dar = chunk - > dst_addr ;
hwdesc - > tcr = chunk - > size > > desc - > xfer_shift ;
hwdesc + + ;
}
}
/* -----------------------------------------------------------------------------
* Stop and reset
*/
@ -555,7 +681,8 @@ static void rcar_dmac_chan_halt(struct rcar_dmac_chan *chan)
{
u32 chcr = rcar_dmac_chan_read ( chan , RCAR_DMACHCR ) ;
chcr & = ~ ( RCAR_DMACHCR_IE | RCAR_DMACHCR_TE | RCAR_DMACHCR_DE ) ;
chcr & = ~ ( RCAR_DMACHCR_DSE | RCAR_DMACHCR_DSIE | RCAR_DMACHCR_IE |
RCAR_DMACHCR_TE | RCAR_DMACHCR_DE ) ;
rcar_dmac_chan_write ( chan , RCAR_DMACHCR , chcr ) ;
}
@ -666,8 +793,10 @@ rcar_dmac_chan_prep_sg(struct rcar_dmac_chan *chan, struct scatterlist *sgl,
struct rcar_dmac_xfer_chunk * chunk ;
struct rcar_dmac_desc * desc ;
struct scatterlist * sg ;
unsigned int nchunks = 0 ;
unsigned int max_chunk_size ;
unsigned int full_size = 0 ;
bool highmem = false ;
unsigned int i ;
desc = rcar_dmac_desc_get ( chan ) ;
@ -706,6 +835,14 @@ rcar_dmac_chan_prep_sg(struct rcar_dmac_chan *chan, struct scatterlist *sgl,
size = ALIGN ( dev_addr , 1ULL < < 32 ) - dev_addr ;
if ( mem_addr > > 32 ! = ( mem_addr + size - 1 ) > > 32 )
size = ALIGN ( mem_addr , 1ULL < < 32 ) - mem_addr ;
/*
* Check if either of the source or destination address
* can ' t be expressed in 32 bits . If so we can ' t use
* hardware descriptor lists .
*/
if ( dev_addr > > 32 | | mem_addr > > 32 )
highmem = true ;
# endif
chunk = rcar_dmac_xfer_chunk_get ( chan ) ;
@ -736,11 +873,26 @@ rcar_dmac_chan_prep_sg(struct rcar_dmac_chan *chan, struct scatterlist *sgl,
len - = size ;
list_add_tail ( & chunk - > node , & desc - > chunks ) ;
nchunks + + ;
}
}
desc - > nchunks = nchunks ;
desc - > size = full_size ;
/*
* Use hardware descriptor lists if possible when more than one chunk
* needs to be transferred ( otherwise they don ' t make much sense ) .
*
* The highmem check currently covers the whole transfer . As an
* optimization we could use descriptor lists for consecutive lowmem
* chunks and direct manual mode for highmem chunks . Whether the
* performance improvement would be significant enough compared to the
* additional complexity remains to be investigated .
*/
if ( ! highmem & & nchunks > 1 )
rcar_dmac_alloc_hwdesc ( chan , desc ) ;
return & desc - > async_tx ;
}
@ -940,8 +1092,10 @@ static unsigned int rcar_dmac_chan_get_residue(struct rcar_dmac_chan *chan,
dma_cookie_t cookie )
{
struct rcar_dmac_desc * desc = chan - > desc . running ;
struct rcar_dmac_xfer_chunk * running = NULL ;
struct rcar_dmac_xfer_chunk * chunk ;
unsigned int residue = 0 ;
unsigned int dptr = 0 ;
if ( ! desc )
return 0 ;
@ -954,9 +1108,23 @@ static unsigned int rcar_dmac_chan_get_residue(struct rcar_dmac_chan *chan,
if ( cookie ! = desc - > async_tx . cookie )
return desc - > size ;
/*
* In descriptor mode the descriptor running pointer is not maintained
* by the interrupt handler , find the running descriptor from the
* descriptor pointer field in the CHCRB register . In non - descriptor
* mode just use the running descriptor pointer .
*/
if ( desc - > hwdescs . mem ) {
dptr = ( rcar_dmac_chan_read ( chan , RCAR_DMACHCRB ) &
RCAR_DMACHCRB_DPTR_MASK ) > > RCAR_DMACHCRB_DPTR_SHIFT ;
WARN_ON ( dptr > = desc - > nchunks ) ;
} else {
running = desc - > running ;
}
/* Compute the size of all chunks still to be transferred. */
list_for_each_entry_reverse ( chunk , & desc - > chunks , node ) {
if ( chunk = = desc - > running )
if ( chunk = = running | | + + dptr = = desc - > nchunks )
break ;
residue + = chunk - > size ;
@ -1025,42 +1193,71 @@ done:
* IRQ handling
*/
static irqreturn_t rcar_dmac_isr_desc_stage_end ( struct rcar_dmac_chan * chan )
{
struct rcar_dmac_desc * desc = chan - > desc . running ;
unsigned int stage ;
if ( WARN_ON ( ! desc | | ! desc - > cyclic ) ) {
/*
* This should never happen , there should always be a running
* cyclic descriptor when a descriptor stage end interrupt is
* triggered . Warn and return .
*/
return IRQ_NONE ;
}
/* Program the interrupt pointer to the next stage. */
stage = ( rcar_dmac_chan_read ( chan , RCAR_DMACHCRB ) &
RCAR_DMACHCRB_DPTR_MASK ) > > RCAR_DMACHCRB_DPTR_SHIFT ;
rcar_dmac_chan_write ( chan , RCAR_DMADPCR , RCAR_DMADPCR_DIPT ( stage ) ) ;
return IRQ_WAKE_THREAD ;
}
static irqreturn_t rcar_dmac_isr_transfer_end ( struct rcar_dmac_chan * chan )
{
struct rcar_dmac_desc * desc = chan - > desc . running ;
struct rcar_dmac_xfer_chunk * chunk ;
irqreturn_t ret = IRQ_WAKE_THREAD ;
if ( WARN_ON_ONCE ( ! desc ) ) {
/*
* This should never happen , there should always be
* a running descriptor when a transfer ends . Warn and
* return .
* This should never happen , there should always be a running
* descriptor when a transfer end interrupt is triggered . Warn
* and return .
*/
return IRQ_NONE ;
}
/*
* If we haven ' t completed the last transfer chunk simply move to the
* next one . Only wake the IRQ thread if the transfer is cyclic .
* The transfer end interrupt isn ' t generated for each chunk when using
* descriptor mode . Only update the running chunk pointer in
* non - descriptor mode .
*/
chunk = desc - > running ;
if ( ! list_is_last ( & chunk - > node , & desc - > chunks ) ) {
desc - > running = list_next_entry ( chunk , node ) ;
if ( ! desc - > cyclic )
ret = IRQ_HANDLED ;
goto done ;
}
if ( ! desc - > hwdescs . mem ) {
/*
* If we haven ' t completed the last transfer chunk simply move
* to the next one . Only wake the IRQ thread if the transfer is
* cyclic .
*/
if ( ! list_is_last ( & desc - > running - > node , & desc - > chunks ) ) {
desc - > running = list_next_entry ( desc - > running , node ) ;
if ( ! desc - > cyclic )
ret = IRQ_HANDLED ;
goto done ;
}
/*
* We ' ve completed the last transfer chunk . If the transfer is cyclic ,
* move back to the first one .
*/
if ( desc - > cyclic ) {
desc - > running = list_first_entry ( & desc - > chunks ,
/*
* We ' ve completed the last transfer chunk . If the transfer is
* cyclic , move back to the first one .
*/
if ( desc - > cyclic ) {
desc - > running =
list_first_entry ( & desc - > chunks ,
struct rcar_dmac_xfer_chunk ,
node ) ;
goto done ;
goto done ;
}
}
/* The descriptor is complete, move it to the done list. */
@ -1083,6 +1280,7 @@ done:
static irqreturn_t rcar_dmac_isr_channel ( int irq , void * dev )
{
u32 mask = RCAR_DMACHCR_DSE | RCAR_DMACHCR_TE ;
struct rcar_dmac_chan * chan = dev ;
irqreturn_t ret = IRQ_NONE ;
u32 chcr ;
@ -1090,8 +1288,12 @@ static irqreturn_t rcar_dmac_isr_channel(int irq, void *dev)
spin_lock ( & chan - > lock ) ;
chcr = rcar_dmac_chan_read ( chan , RCAR_DMACHCR ) ;
rcar_dmac_chan_write ( chan , RCAR_DMACHCR ,
chcr & ~ ( RCAR_DMACHCR_TE | RCAR_DMACHCR_DE ) ) ;
if ( chcr & RCAR_DMACHCR_TE )
mask | = RCAR_DMACHCR_DE ;
rcar_dmac_chan_write ( chan , RCAR_DMACHCR , chcr & ~ mask ) ;
if ( chcr & RCAR_DMACHCR_DSE )
ret | = rcar_dmac_isr_desc_stage_end ( chan ) ;
if ( chcr & RCAR_DMACHCR_TE )
ret | = rcar_dmac_isr_transfer_end ( chan ) ;
@ -1148,11 +1350,11 @@ static irqreturn_t rcar_dmac_isr_channel_thread(int irq, void *dev)
list_add_tail ( & desc - > node , & chan - > desc . wait ) ;
}
spin_unlock_irq ( & chan - > lock ) ;
/* Recycle all acked descriptors. */
rcar_dmac_desc_recycle_acked ( chan ) ;
spin_unlock_irq ( & chan - > lock ) ;
return IRQ_HANDLED ;
}