2010-05-24 07:28:19 +04:00
/* linux/drivers/dma/pl330.c
*
* Copyright ( C ) 2010 Samsung Electronics Co . Ltd .
* Jaswinder Singh < jassi . brar @ samsung . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*/
# include <linux/io.h>
# include <linux/init.h>
# include <linux/slab.h>
# include <linux/module.h>
# include <linux/dmaengine.h>
# include <linux/interrupt.h>
# include <linux/amba/bus.h>
# include <linux/amba/pl330.h>
# define NR_DEFAULT_DESC 16
enum desc_status {
/* In the DMAC pool */
FREE ,
/*
* Allocted to some channel during prep_xxx
* Also may be sitting on the work_list .
*/
PREP ,
/*
* Sitting on the work_list and already submitted
* to the PL330 core . Not more than two descriptors
* of a channel can be BUSY at any time .
*/
BUSY ,
/*
* Sitting on the channel work_list but xfer done
* by PL330 core
*/
DONE ,
} ;
struct dma_pl330_chan {
/* Schedule desc completion */
struct tasklet_struct task ;
/* DMA-Engine Channel */
struct dma_chan chan ;
/* Last completed cookie */
dma_cookie_t completed ;
/* List of to be xfered descriptors */
struct list_head work_list ;
/* Pointer to the DMAC that manages this channel,
* NULL if the channel is available to be acquired .
* As the parent , this DMAC also provides descriptors
* to the channel .
*/
struct dma_pl330_dmac * dmac ;
/* To protect channel manipulation */
spinlock_t lock ;
/* Token of a hardware channel thread of PL330 DMAC
* NULL if the channel is available to be acquired .
*/
void * pl330_chid ;
} ;
struct dma_pl330_dmac {
struct pl330_info pif ;
/* DMA-Engine Device */
struct dma_device ddma ;
/* Pool of descriptors available for the DMAC's channels */
struct list_head desc_pool ;
/* To protect desc_pool manipulation */
spinlock_t pool_lock ;
/* Peripheral channels connected to this DMAC */
struct dma_pl330_chan peripherals [ 0 ] ; /* keep at end */
} ;
struct dma_pl330_desc {
/* To attach to a queue as child */
struct list_head node ;
/* Descriptor for the DMA Engine API */
struct dma_async_tx_descriptor txd ;
/* Xfer for PL330 core */
struct pl330_xfer px ;
struct pl330_reqcfg rqcfg ;
struct pl330_req req ;
enum desc_status status ;
/* The channel which currently holds this desc */
struct dma_pl330_chan * pchan ;
} ;
static inline struct dma_pl330_chan *
to_pchan ( struct dma_chan * ch )
{
if ( ! ch )
return NULL ;
return container_of ( ch , struct dma_pl330_chan , chan ) ;
}
static inline struct dma_pl330_desc *
to_desc ( struct dma_async_tx_descriptor * tx )
{
return container_of ( tx , struct dma_pl330_desc , txd ) ;
}
static inline void free_desc_list ( struct list_head * list )
{
struct dma_pl330_dmac * pdmac ;
struct dma_pl330_desc * desc ;
struct dma_pl330_chan * pch ;
unsigned long flags ;
if ( list_empty ( list ) )
return ;
/* Finish off the work list */
list_for_each_entry ( desc , list , node ) {
dma_async_tx_callback callback ;
void * param ;
/* All desc in a list belong to same channel */
pch = desc - > pchan ;
callback = desc - > txd . callback ;
param = desc - > txd . callback_param ;
if ( callback )
callback ( param ) ;
desc - > pchan = NULL ;
}
pdmac = pch - > dmac ;
spin_lock_irqsave ( & pdmac - > pool_lock , flags ) ;
list_splice_tail_init ( list , & pdmac - > desc_pool ) ;
spin_unlock_irqrestore ( & pdmac - > pool_lock , flags ) ;
}
static inline void fill_queue ( struct dma_pl330_chan * pch )
{
struct dma_pl330_desc * desc ;
int ret ;
list_for_each_entry ( desc , & pch - > work_list , node ) {
/* If already submitted */
if ( desc - > status = = BUSY )
break ;
ret = pl330_submit_req ( pch - > pl330_chid ,
& desc - > req ) ;
if ( ! ret ) {
desc - > status = BUSY ;
break ;
} else if ( ret = = - EAGAIN ) {
/* QFull or DMAC Dying */
break ;
} else {
/* Unacceptable request */
desc - > status = DONE ;
dev_err ( pch - > dmac - > pif . dev , " %s:%d Bad Desc(%d) \n " ,
__func__ , __LINE__ , desc - > txd . cookie ) ;
tasklet_schedule ( & pch - > task ) ;
}
}
}
static void pl330_tasklet ( unsigned long data )
{
struct dma_pl330_chan * pch = ( struct dma_pl330_chan * ) data ;
struct dma_pl330_desc * desc , * _dt ;
unsigned long flags ;
LIST_HEAD ( list ) ;
spin_lock_irqsave ( & pch - > lock , flags ) ;
/* Pick up ripe tomatoes */
list_for_each_entry_safe ( desc , _dt , & pch - > work_list , node )
if ( desc - > status = = DONE ) {
pch - > completed = desc - > txd . cookie ;
list_move_tail ( & desc - > node , & list ) ;
}
/* Try to submit a req imm. next to the last completed cookie */
fill_queue ( pch ) ;
/* Make sure the PL330 Channel thread is active */
pl330_chan_ctrl ( pch - > pl330_chid , PL330_OP_START ) ;
spin_unlock_irqrestore ( & pch - > lock , flags ) ;
free_desc_list ( & list ) ;
}
static void dma_pl330_rqcb ( void * token , enum pl330_op_err err )
{
struct dma_pl330_desc * desc = token ;
struct dma_pl330_chan * pch = desc - > pchan ;
unsigned long flags ;
/* If desc aborted */
if ( ! pch )
return ;
spin_lock_irqsave ( & pch - > lock , flags ) ;
desc - > status = DONE ;
spin_unlock_irqrestore ( & pch - > lock , flags ) ;
tasklet_schedule ( & pch - > task ) ;
}
static int pl330_alloc_chan_resources ( struct dma_chan * chan )
{
struct dma_pl330_chan * pch = to_pchan ( chan ) ;
struct dma_pl330_dmac * pdmac = pch - > dmac ;
unsigned long flags ;
spin_lock_irqsave ( & pch - > lock , flags ) ;
pch - > completed = chan - > cookie = 1 ;
pch - > pl330_chid = pl330_request_channel ( & pdmac - > pif ) ;
if ( ! pch - > pl330_chid ) {
spin_unlock_irqrestore ( & pch - > lock , flags ) ;
return 0 ;
}
tasklet_init ( & pch - > task , pl330_tasklet , ( unsigned long ) pch ) ;
spin_unlock_irqrestore ( & pch - > lock , flags ) ;
return 1 ;
}
static int pl330_control ( struct dma_chan * chan , enum dma_ctrl_cmd cmd , unsigned long arg )
{
struct dma_pl330_chan * pch = to_pchan ( chan ) ;
struct dma_pl330_desc * desc ;
unsigned long flags ;
/* Only supports DMA_TERMINATE_ALL */
if ( cmd ! = DMA_TERMINATE_ALL )
return - ENXIO ;
spin_lock_irqsave ( & pch - > lock , flags ) ;
/* FLUSH the PL330 Channel thread */
pl330_chan_ctrl ( pch - > pl330_chid , PL330_OP_FLUSH ) ;
/* Mark all desc done */
list_for_each_entry ( desc , & pch - > work_list , node )
desc - > status = DONE ;
spin_unlock_irqrestore ( & pch - > lock , flags ) ;
pl330_tasklet ( ( unsigned long ) pch ) ;
return 0 ;
}
static void pl330_free_chan_resources ( struct dma_chan * chan )
{
struct dma_pl330_chan * pch = to_pchan ( chan ) ;
unsigned long flags ;
spin_lock_irqsave ( & pch - > lock , flags ) ;
tasklet_kill ( & pch - > task ) ;
pl330_release_channel ( pch - > pl330_chid ) ;
pch - > pl330_chid = NULL ;
spin_unlock_irqrestore ( & pch - > lock , flags ) ;
}
static enum dma_status
pl330_tx_status ( struct dma_chan * chan , dma_cookie_t cookie ,
struct dma_tx_state * txstate )
{
struct dma_pl330_chan * pch = to_pchan ( chan ) ;
dma_cookie_t last_done , last_used ;
int ret ;
last_done = pch - > completed ;
last_used = chan - > cookie ;
ret = dma_async_is_complete ( cookie , last_done , last_used ) ;
dma_set_tx_state ( txstate , last_done , last_used , 0 ) ;
return ret ;
}
static void pl330_issue_pending ( struct dma_chan * chan )
{
pl330_tasklet ( ( unsigned long ) to_pchan ( chan ) ) ;
}
/*
* We returned the last one of the circular list of descriptor ( s )
* from prep_xxx , so the argument to submit corresponds to the last
* descriptor of the list .
*/
static dma_cookie_t pl330_tx_submit ( struct dma_async_tx_descriptor * tx )
{
struct dma_pl330_desc * desc , * last = to_desc ( tx ) ;
struct dma_pl330_chan * pch = to_pchan ( tx - > chan ) ;
dma_cookie_t cookie ;
unsigned long flags ;
spin_lock_irqsave ( & pch - > lock , flags ) ;
/* Assign cookies to all nodes */
cookie = tx - > chan - > cookie ;
while ( ! list_empty ( & last - > node ) ) {
desc = list_entry ( last - > node . next , struct dma_pl330_desc , node ) ;
if ( + + cookie < 0 )
cookie = 1 ;
desc - > txd . cookie = cookie ;
list_move_tail ( & desc - > node , & pch - > work_list ) ;
}
if ( + + cookie < 0 )
cookie = 1 ;
last - > txd . cookie = cookie ;
list_add_tail ( & last - > node , & pch - > work_list ) ;
tx - > chan - > cookie = cookie ;
spin_unlock_irqrestore ( & pch - > lock , flags ) ;
return cookie ;
}
static inline void _init_desc ( struct dma_pl330_desc * desc )
{
desc - > pchan = NULL ;
desc - > req . x = & desc - > px ;
desc - > req . token = desc ;
desc - > rqcfg . swap = SWAP_NO ;
desc - > rqcfg . privileged = 0 ;
desc - > rqcfg . insnaccess = 0 ;
desc - > rqcfg . scctl = SCCTRL0 ;
desc - > rqcfg . dcctl = DCCTRL0 ;
desc - > req . cfg = & desc - > rqcfg ;
desc - > req . xfer_cb = dma_pl330_rqcb ;
desc - > txd . tx_submit = pl330_tx_submit ;
INIT_LIST_HEAD ( & desc - > node ) ;
}
/* Returns the number of descriptors added to the DMAC pool */
int add_desc ( struct dma_pl330_dmac * pdmac , gfp_t flg , int count )
{
struct dma_pl330_desc * desc ;
unsigned long flags ;
int i ;
if ( ! pdmac )
return 0 ;
desc = kmalloc ( count * sizeof ( * desc ) , flg ) ;
if ( ! desc )
return 0 ;
spin_lock_irqsave ( & pdmac - > pool_lock , flags ) ;
for ( i = 0 ; i < count ; i + + ) {
_init_desc ( & desc [ i ] ) ;
list_add_tail ( & desc [ i ] . node , & pdmac - > desc_pool ) ;
}
spin_unlock_irqrestore ( & pdmac - > pool_lock , flags ) ;
return count ;
}
static struct dma_pl330_desc *
pluck_desc ( struct dma_pl330_dmac * pdmac )
{
struct dma_pl330_desc * desc = NULL ;
unsigned long flags ;
if ( ! pdmac )
return NULL ;
spin_lock_irqsave ( & pdmac - > pool_lock , flags ) ;
if ( ! list_empty ( & pdmac - > desc_pool ) ) {
desc = list_entry ( pdmac - > desc_pool . next ,
struct dma_pl330_desc , node ) ;
list_del_init ( & desc - > node ) ;
desc - > status = PREP ;
desc - > txd . callback = NULL ;
}
spin_unlock_irqrestore ( & pdmac - > pool_lock , flags ) ;
return desc ;
}
static struct dma_pl330_desc * pl330_get_desc ( struct dma_pl330_chan * pch )
{
struct dma_pl330_dmac * pdmac = pch - > dmac ;
struct dma_pl330_peri * peri = pch - > chan . private ;
struct dma_pl330_desc * desc ;
/* Pluck one desc from the pool of DMAC */
desc = pluck_desc ( pdmac ) ;
/* If the DMAC pool is empty, alloc new */
if ( ! desc ) {
if ( ! add_desc ( pdmac , GFP_ATOMIC , 1 ) )
return NULL ;
/* Try again */
desc = pluck_desc ( pdmac ) ;
if ( ! desc ) {
dev_err ( pch - > dmac - > pif . dev ,
" %s:%d ALERT! \n " , __func__ , __LINE__ ) ;
return NULL ;
}
}
/* Initialize the descriptor */
desc - > pchan = pch ;
desc - > txd . cookie = 0 ;
async_tx_ack ( & desc - > txd ) ;
desc - > req . rqtype = peri - > rqtype ;
desc - > req . peri = peri - > peri_id ;
dma_async_tx_descriptor_init ( & desc - > txd , & pch - > chan ) ;
return desc ;
}
static inline void fill_px ( struct pl330_xfer * px ,
dma_addr_t dst , dma_addr_t src , size_t len )
{
px - > next = NULL ;
px - > bytes = len ;
px - > dst_addr = dst ;
px - > src_addr = src ;
}
static struct dma_pl330_desc *
__pl330_prep_dma_memcpy ( struct dma_pl330_chan * pch , dma_addr_t dst ,
dma_addr_t src , size_t len )
{
struct dma_pl330_desc * desc = pl330_get_desc ( pch ) ;
if ( ! desc ) {
dev_err ( pch - > dmac - > pif . dev , " %s:%d Unable to fetch desc \n " ,
__func__ , __LINE__ ) ;
return NULL ;
}
/*
* Ideally we should lookout for reqs bigger than
* those that can be programmed with 256 bytes of
* MC buffer , but considering a req size is seldom
* going to be word - unaligned and more than 200 MB ,
* we take it easy .
* Also , should the limit is reached we ' d rather
* have the platform increase MC buffer size than
* complicating this API driver .
*/
fill_px ( & desc - > px , dst , src , len ) ;
return desc ;
}
/* Call after fixing burst size */
static inline int get_burst_len ( struct dma_pl330_desc * desc , size_t len )
{
struct dma_pl330_chan * pch = desc - > pchan ;
struct pl330_info * pi = & pch - > dmac - > pif ;
int burst_len ;
burst_len = pi - > pcfg . data_bus_width / 8 ;
burst_len * = pi - > pcfg . data_buf_dep ;
burst_len > > = desc - > rqcfg . brst_size ;
/* src/dst_burst_len can't be more than 16 */
if ( burst_len > 16 )
burst_len = 16 ;
while ( burst_len > 1 ) {
if ( ! ( len % ( burst_len < < desc - > rqcfg . brst_size ) ) )
break ;
burst_len - - ;
}
return burst_len ;
}
static struct dma_async_tx_descriptor *
pl330_prep_dma_memcpy ( struct dma_chan * chan , dma_addr_t dst ,
dma_addr_t src , size_t len , unsigned long flags )
{
struct dma_pl330_desc * desc ;
struct dma_pl330_chan * pch = to_pchan ( chan ) ;
struct dma_pl330_peri * peri = chan - > private ;
struct pl330_info * pi ;
int burst ;
if ( unlikely ( ! pch | | ! len | | ! peri ) )
return NULL ;
if ( peri - > rqtype ! = MEMTOMEM )
return NULL ;
pi = & pch - > dmac - > pif ;
desc = __pl330_prep_dma_memcpy ( pch , dst , src , len ) ;
if ( ! desc )
return NULL ;
desc - > rqcfg . src_inc = 1 ;
desc - > rqcfg . dst_inc = 1 ;
/* Select max possible burst size */
burst = pi - > pcfg . data_bus_width / 8 ;
while ( burst > 1 ) {
if ( ! ( len % burst ) )
break ;
burst / = 2 ;
}
desc - > rqcfg . brst_size = 0 ;
while ( burst ! = ( 1 < < desc - > rqcfg . brst_size ) )
desc - > rqcfg . brst_size + + ;
desc - > rqcfg . brst_len = get_burst_len ( desc , len ) ;
desc - > txd . flags = flags ;
return & desc - > txd ;
}
static struct dma_async_tx_descriptor *
pl330_prep_slave_sg ( struct dma_chan * chan , struct scatterlist * sgl ,
unsigned int sg_len , enum dma_data_direction direction ,
unsigned long flg )
{
struct dma_pl330_desc * first , * desc = NULL ;
struct dma_pl330_chan * pch = to_pchan ( chan ) ;
struct dma_pl330_peri * peri = chan - > private ;
struct scatterlist * sg ;
unsigned long flags ;
int i , burst_size ;
dma_addr_t addr ;
if ( unlikely ( ! pch | | ! sgl | | ! sg_len ) )
return NULL ;
/* Make sure the direction is consistent */
if ( ( direction = = DMA_TO_DEVICE & &
peri - > rqtype ! = MEMTODEV ) | |
( direction = = DMA_FROM_DEVICE & &
peri - > rqtype ! = DEVTOMEM ) ) {
dev_err ( pch - > dmac - > pif . dev , " %s:%d Invalid Direction \n " ,
__func__ , __LINE__ ) ;
return NULL ;
}
addr = peri - > fifo_addr ;
burst_size = peri - > burst_sz ;
first = NULL ;
for_each_sg ( sgl , sg , sg_len , i ) {
desc = pl330_get_desc ( pch ) ;
if ( ! desc ) {
struct dma_pl330_dmac * pdmac = pch - > dmac ;
dev_err ( pch - > dmac - > pif . dev ,
" %s:%d Unable to fetch desc \n " ,
__func__ , __LINE__ ) ;
if ( ! first )
return NULL ;
spin_lock_irqsave ( & pdmac - > pool_lock , flags ) ;
while ( ! list_empty ( & first - > node ) ) {
desc = list_entry ( first - > node . next ,
struct dma_pl330_desc , node ) ;
list_move_tail ( & desc - > node , & pdmac - > desc_pool ) ;
}
list_move_tail ( & first - > node , & pdmac - > desc_pool ) ;
spin_unlock_irqrestore ( & pdmac - > pool_lock , flags ) ;
return NULL ;
}
if ( ! first )
first = desc ;
else
list_add_tail ( & desc - > node , & first - > node ) ;
if ( direction = = DMA_TO_DEVICE ) {
desc - > rqcfg . src_inc = 1 ;
desc - > rqcfg . dst_inc = 0 ;
fill_px ( & desc - > px ,
addr , sg_dma_address ( sg ) , sg_dma_len ( sg ) ) ;
} else {
desc - > rqcfg . src_inc = 0 ;
desc - > rqcfg . dst_inc = 1 ;
fill_px ( & desc - > px ,
sg_dma_address ( sg ) , addr , sg_dma_len ( sg ) ) ;
}
desc - > rqcfg . brst_size = burst_size ;
desc - > rqcfg . brst_len = 1 ;
}
/* Return the last desc in the chain */
desc - > txd . flags = flg ;
return & desc - > txd ;
}
static irqreturn_t pl330_irq_handler ( int irq , void * data )
{
if ( pl330_update ( data ) )
return IRQ_HANDLED ;
else
return IRQ_NONE ;
}
static int __devinit
2011-02-19 18:55:00 +03:00
pl330_probe ( struct amba_device * adev , const struct amba_id * id )
2010-05-24 07:28:19 +04:00
{
struct dma_pl330_platdata * pdat ;
struct dma_pl330_dmac * pdmac ;
struct dma_pl330_chan * pch ;
struct pl330_info * pi ;
struct dma_device * pd ;
struct resource * res ;
int i , ret , irq ;
pdat = adev - > dev . platform_data ;
if ( ! pdat | | ! pdat - > nr_valid_peri ) {
dev_err ( & adev - > dev , " platform data missing \n " ) ;
return - ENODEV ;
}
/* Allocate a new DMAC and its Channels */
pdmac = kzalloc ( pdat - > nr_valid_peri * sizeof ( * pch )
+ sizeof ( * pdmac ) , GFP_KERNEL ) ;
if ( ! pdmac ) {
dev_err ( & adev - > dev , " unable to allocate mem \n " ) ;
return - ENOMEM ;
}
pi = & pdmac - > pif ;
pi - > dev = & adev - > dev ;
pi - > pl330_data = NULL ;
pi - > mcbufsz = pdat - > mcbuf_sz ;
res = & adev - > res ;
request_mem_region ( res - > start , resource_size ( res ) , " dma-pl330 " ) ;
pi - > base = ioremap ( res - > start , resource_size ( res ) ) ;
if ( ! pi - > base ) {
ret = - ENXIO ;
goto probe_err1 ;
}
irq = adev - > irq [ 0 ] ;
ret = request_irq ( irq , pl330_irq_handler , 0 ,
dev_name ( & adev - > dev ) , pi ) ;
if ( ret )
goto probe_err2 ;
ret = pl330_add ( pi ) ;
if ( ret )
goto probe_err3 ;
INIT_LIST_HEAD ( & pdmac - > desc_pool ) ;
spin_lock_init ( & pdmac - > pool_lock ) ;
/* Create a descriptor pool of default size */
if ( ! add_desc ( pdmac , GFP_KERNEL , NR_DEFAULT_DESC ) )
dev_warn ( & adev - > dev , " unable to allocate desc \n " ) ;
pd = & pdmac - > ddma ;
INIT_LIST_HEAD ( & pd - > channels ) ;
/* Initialize channel parameters */
for ( i = 0 ; i < pdat - > nr_valid_peri ; i + + ) {
struct dma_pl330_peri * peri = & pdat - > peri [ i ] ;
pch = & pdmac - > peripherals [ i ] ;
switch ( peri - > rqtype ) {
case MEMTOMEM :
dma_cap_set ( DMA_MEMCPY , pd - > cap_mask ) ;
break ;
case MEMTODEV :
case DEVTOMEM :
dma_cap_set ( DMA_SLAVE , pd - > cap_mask ) ;
break ;
default :
dev_err ( & adev - > dev , " DEVTODEV Not Supported \n " ) ;
continue ;
}
INIT_LIST_HEAD ( & pch - > work_list ) ;
spin_lock_init ( & pch - > lock ) ;
pch - > pl330_chid = NULL ;
pch - > chan . private = peri ;
pch - > chan . device = pd ;
pch - > chan . chan_id = i ;
pch - > dmac = pdmac ;
/* Add the channel to the DMAC list */
pd - > chancnt + + ;
list_add_tail ( & pch - > chan . device_node , & pd - > channels ) ;
}
pd - > dev = & adev - > dev ;
pd - > device_alloc_chan_resources = pl330_alloc_chan_resources ;
pd - > device_free_chan_resources = pl330_free_chan_resources ;
pd - > device_prep_dma_memcpy = pl330_prep_dma_memcpy ;
pd - > device_tx_status = pl330_tx_status ;
pd - > device_prep_slave_sg = pl330_prep_slave_sg ;
pd - > device_control = pl330_control ;
pd - > device_issue_pending = pl330_issue_pending ;
ret = dma_async_device_register ( pd ) ;
if ( ret ) {
dev_err ( & adev - > dev , " unable to register DMAC \n " ) ;
goto probe_err4 ;
}
amba_set_drvdata ( adev , pdmac ) ;
dev_info ( & adev - > dev ,
" Loaded driver for PL330 DMAC-%d \n " , adev - > periphid ) ;
dev_info ( & adev - > dev ,
" \t DBUFF-%ux%ubytes Num_Chans-%u Num_Peri-%u Num_Events-%u \n " ,
pi - > pcfg . data_buf_dep ,
pi - > pcfg . data_bus_width / 8 , pi - > pcfg . num_chan ,
pi - > pcfg . num_peri , pi - > pcfg . num_events ) ;
return 0 ;
probe_err4 :
pl330_del ( pi ) ;
probe_err3 :
free_irq ( irq , pi ) ;
probe_err2 :
iounmap ( pi - > base ) ;
probe_err1 :
release_mem_region ( res - > start , resource_size ( res ) ) ;
kfree ( pdmac ) ;
return ret ;
}
static int __devexit pl330_remove ( struct amba_device * adev )
{
struct dma_pl330_dmac * pdmac = amba_get_drvdata ( adev ) ;
struct dma_pl330_chan * pch , * _p ;
struct pl330_info * pi ;
struct resource * res ;
int irq ;
if ( ! pdmac )
return 0 ;
amba_set_drvdata ( adev , NULL ) ;
/* Idle the DMAC */
list_for_each_entry_safe ( pch , _p , & pdmac - > ddma . channels ,
chan . device_node ) {
/* Remove the channel */
list_del ( & pch - > chan . device_node ) ;
/* Flush the channel */
pl330_control ( & pch - > chan , DMA_TERMINATE_ALL , 0 ) ;
pl330_free_chan_resources ( & pch - > chan ) ;
}
pi = & pdmac - > pif ;
pl330_del ( pi ) ;
irq = adev - > irq [ 0 ] ;
free_irq ( irq , pi ) ;
iounmap ( pi - > base ) ;
res = & adev - > res ;
release_mem_region ( res - > start , resource_size ( res ) ) ;
kfree ( pdmac ) ;
return 0 ;
}
static struct amba_id pl330_ids [ ] = {
{
. id = 0x00041330 ,
. mask = 0x000fffff ,
} ,
{ 0 , 0 } ,
} ;
static struct amba_driver pl330_driver = {
. drv = {
. owner = THIS_MODULE ,
. name = " dma-pl330 " ,
} ,
. id_table = pl330_ids ,
. probe = pl330_probe ,
. remove = pl330_remove ,
} ;
static int __init pl330_init ( void )
{
return amba_driver_register ( & pl330_driver ) ;
}
module_init ( pl330_init ) ;
static void __exit pl330_exit ( void )
{
amba_driver_unregister ( & pl330_driver ) ;
return ;
}
module_exit ( pl330_exit ) ;
MODULE_AUTHOR ( " Jaswinder Singh <jassi.brar@samsung.com> " ) ;
MODULE_DESCRIPTION ( " API Driver for PL330 DMAC " ) ;
MODULE_LICENSE ( " GPL " ) ;