2020-11-14 14:02:33 +00:00
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright ( c ) 2013 - 2015 , The Linux Foundation . All rights reserved .
*/
# include <linux/clk.h>
# include <linux/delay.h>
# include <linux/device.h>
# include <linux/dmaengine.h>
# include <linux/dma-mapping.h>
# include <linux/init.h>
# include <linux/interrupt.h>
# include <linux/io.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/of_address.h>
# include <linux/of_irq.h>
# include <linux/of_dma.h>
# include <linux/platform_device.h>
# include <linux/reset.h>
# include <linux/scatterlist.h>
# include <linux/slab.h>
# include "../dmaengine.h"
# include "../virt-dma.h"
/* ADM registers - calculated from channel number and security domain */
# define ADM_CHAN_MULTI 0x4
# define ADM_CI_MULTI 0x4
# define ADM_CRCI_MULTI 0x4
# define ADM_EE_MULTI 0x800
# define ADM_CHAN_OFFS(chan) (ADM_CHAN_MULTI * (chan))
# define ADM_EE_OFFS(ee) (ADM_EE_MULTI * (ee))
# define ADM_CHAN_EE_OFFS(chan, ee) (ADM_CHAN_OFFS(chan) + ADM_EE_OFFS(ee))
# define ADM_CHAN_OFFS(chan) (ADM_CHAN_MULTI * (chan))
# define ADM_CI_OFFS(ci) (ADM_CHAN_OFF(ci))
# define ADM_CH_CMD_PTR(chan, ee) (ADM_CHAN_EE_OFFS(chan, ee))
# define ADM_CH_RSLT(chan, ee) (0x40 + ADM_CHAN_EE_OFFS(chan, ee))
# define ADM_CH_FLUSH_STATE0(chan, ee) (0x80 + ADM_CHAN_EE_OFFS(chan, ee))
# define ADM_CH_STATUS_SD(chan, ee) (0x200 + ADM_CHAN_EE_OFFS(chan, ee))
# define ADM_CH_CONF(chan) (0x240 + ADM_CHAN_OFFS(chan))
# define ADM_CH_RSLT_CONF(chan, ee) (0x300 + ADM_CHAN_EE_OFFS(chan, ee))
# define ADM_SEC_DOMAIN_IRQ_STATUS(ee) (0x380 + ADM_EE_OFFS(ee))
# define ADM_CI_CONF(ci) (0x390 + (ci) * ADM_CI_MULTI)
# define ADM_GP_CTL 0x3d8
# define ADM_CRCI_CTL(crci, ee) (0x400 + (crci) * ADM_CRCI_MULTI + \
ADM_EE_OFFS ( ee ) )
/* channel status */
# define ADM_CH_STATUS_VALID BIT(1)
/* channel result */
# define ADM_CH_RSLT_VALID BIT(31)
# define ADM_CH_RSLT_ERR BIT(3)
# define ADM_CH_RSLT_FLUSH BIT(2)
# define ADM_CH_RSLT_TPD BIT(1)
/* channel conf */
# define ADM_CH_CONF_SHADOW_EN BIT(12)
# define ADM_CH_CONF_MPU_DISABLE BIT(11)
# define ADM_CH_CONF_PERM_MPU_CONF BIT(9)
# define ADM_CH_CONF_FORCE_RSLT_EN BIT(7)
# define ADM_CH_CONF_SEC_DOMAIN(ee) ((((ee) & 0x3) << 4) | (((ee) & 0x4) << 11))
/* channel result conf */
# define ADM_CH_RSLT_CONF_FLUSH_EN BIT(1)
# define ADM_CH_RSLT_CONF_IRQ_EN BIT(0)
/* CRCI CTL */
# define ADM_CRCI_CTL_MUX_SEL BIT(18)
# define ADM_CRCI_CTL_RST BIT(17)
/* CI configuration */
# define ADM_CI_RANGE_END(x) ((x) << 24)
# define ADM_CI_RANGE_START(x) ((x) << 16)
# define ADM_CI_BURST_4_WORDS BIT(2)
# define ADM_CI_BURST_8_WORDS BIT(3)
/* GP CTL */
# define ADM_GP_CTL_LP_EN BIT(12)
# define ADM_GP_CTL_LP_CNT(x) ((x) << 8)
/* Command pointer list entry */
# define ADM_CPLE_LP BIT(31)
# define ADM_CPLE_CMD_PTR_LIST BIT(29)
/* Command list entry */
# define ADM_CMD_LC BIT(31)
# define ADM_CMD_DST_CRCI(n) (((n) & 0xf) << 7)
# define ADM_CMD_SRC_CRCI(n) (((n) & 0xf) << 3)
# define ADM_CMD_TYPE_SINGLE 0x0
# define ADM_CMD_TYPE_BOX 0x3
# define ADM_CRCI_MUX_SEL BIT(4)
# define ADM_DESC_ALIGN 8
# define ADM_MAX_XFER (SZ_64K - 1)
# define ADM_MAX_ROWS (SZ_64K - 1)
# define ADM_MAX_CHANNELS 16
struct adm_desc_hw_box {
u32 cmd ;
u32 src_addr ;
u32 dst_addr ;
u32 row_len ;
u32 num_rows ;
u32 row_offset ;
} ;
struct adm_desc_hw_single {
u32 cmd ;
u32 src_addr ;
u32 dst_addr ;
u32 len ;
} ;
struct adm_async_desc {
struct virt_dma_desc vd ;
struct adm_device * adev ;
size_t length ;
enum dma_transfer_direction dir ;
dma_addr_t dma_addr ;
size_t dma_len ;
void * cpl ;
dma_addr_t cp_addr ;
u32 crci ;
u32 mux ;
u32 blk_size ;
} ;
struct adm_chan {
struct virt_dma_chan vc ;
struct adm_device * adev ;
/* parsed from DT */
u32 id ; /* channel id */
struct adm_async_desc * curr_txd ;
struct dma_slave_config slave ;
struct list_head node ;
int error ;
int initialized ;
} ;
static inline struct adm_chan * to_adm_chan ( struct dma_chan * common )
{
return container_of ( common , struct adm_chan , vc . chan ) ;
}
struct adm_device {
void __iomem * regs ;
struct device * dev ;
struct dma_device common ;
struct device_dma_parameters dma_parms ;
struct adm_chan * channels ;
u32 ee ;
struct clk * core_clk ;
struct clk * iface_clk ;
struct reset_control * clk_reset ;
struct reset_control * c0_reset ;
struct reset_control * c1_reset ;
struct reset_control * c2_reset ;
int irq ;
} ;
/**
* adm_free_chan - Frees dma resources associated with the specific channel
*
2020-11-26 18:46:02 +00:00
* @ chan : dma channel
2020-11-14 14:02:33 +00:00
*
2020-11-26 18:46:02 +00:00
* Free all allocated descriptors associated with this channel
2020-11-14 14:02:33 +00:00
*/
static void adm_free_chan ( struct dma_chan * chan )
{
/* free all queued descriptors */
vchan_free_chan_resources ( to_virt_chan ( chan ) ) ;
}
/**
* adm_get_blksize - Get block size from burst value
*
2020-11-26 18:46:02 +00:00
* @ burst : Burst size of transaction
2020-11-14 14:02:33 +00:00
*/
static int adm_get_blksize ( unsigned int burst )
{
int ret ;
switch ( burst ) {
case 16 :
case 32 :
case 64 :
case 128 :
ret = ffs ( burst > > 4 ) - 1 ;
break ;
case 192 :
ret = 4 ;
break ;
case 256 :
ret = 5 ;
break ;
default :
ret = - EINVAL ;
break ;
}
return ret ;
}
/**
* adm_process_fc_descriptors - Process descriptors for flow controlled xfers
*
* @ achan : ADM channel
* @ desc : Descriptor memory pointer
* @ sg : Scatterlist entry
* @ crci : CRCI value
* @ burst : Burst size of transaction
* @ direction : DMA transfer direction
*/
static void * adm_process_fc_descriptors ( struct adm_chan * achan , void * desc ,
struct scatterlist * sg , u32 crci ,
u32 burst ,
enum dma_transfer_direction direction )
{
struct adm_desc_hw_box * box_desc = NULL ;
struct adm_desc_hw_single * single_desc ;
u32 remainder = sg_dma_len ( sg ) ;
u32 rows , row_offset , crci_cmd ;
u32 mem_addr = sg_dma_address ( sg ) ;
u32 * incr_addr = & mem_addr ;
u32 * src , * dst ;
if ( direction = = DMA_DEV_TO_MEM ) {
crci_cmd = ADM_CMD_SRC_CRCI ( crci ) ;
row_offset = burst ;
src = & achan - > slave . src_addr ;
dst = & mem_addr ;
} else {
crci_cmd = ADM_CMD_DST_CRCI ( crci ) ;
row_offset = burst < < 16 ;
src = & mem_addr ;
dst = & achan - > slave . dst_addr ;
}
while ( remainder > = burst ) {
box_desc = desc ;
box_desc - > cmd = ADM_CMD_TYPE_BOX | crci_cmd ;
box_desc - > row_offset = row_offset ;
box_desc - > src_addr = * src ;
box_desc - > dst_addr = * dst ;
rows = remainder / burst ;
rows = min_t ( u32 , rows , ADM_MAX_ROWS ) ;
box_desc - > num_rows = rows < < 16 | rows ;
box_desc - > row_len = burst < < 16 | burst ;
* incr_addr + = burst * rows ;
remainder - = burst * rows ;
desc + = sizeof ( * box_desc ) ;
}
/* if leftover bytes, do one single descriptor */
if ( remainder ) {
single_desc = desc ;
single_desc - > cmd = ADM_CMD_TYPE_SINGLE | crci_cmd ;
single_desc - > len = remainder ;
single_desc - > src_addr = * src ;
single_desc - > dst_addr = * dst ;
desc + = sizeof ( * single_desc ) ;
if ( sg_is_last ( sg ) )
single_desc - > cmd | = ADM_CMD_LC ;
} else {
if ( box_desc & & sg_is_last ( sg ) )
box_desc - > cmd | = ADM_CMD_LC ;
}
return desc ;
}
/**
* adm_process_non_fc_descriptors - Process descriptors for non - fc xfers
*
* @ achan : ADM channel
* @ desc : Descriptor memory pointer
* @ sg : Scatterlist entry
* @ direction : DMA transfer direction
*/
static void * adm_process_non_fc_descriptors ( struct adm_chan * achan , void * desc ,
struct scatterlist * sg ,
enum dma_transfer_direction direction )
{
struct adm_desc_hw_single * single_desc ;
u32 remainder = sg_dma_len ( sg ) ;
u32 mem_addr = sg_dma_address ( sg ) ;
u32 * incr_addr = & mem_addr ;
u32 * src , * dst ;
if ( direction = = DMA_DEV_TO_MEM ) {
src = & achan - > slave . src_addr ;
dst = & mem_addr ;
} else {
src = & mem_addr ;
dst = & achan - > slave . dst_addr ;
}
do {
single_desc = desc ;
single_desc - > cmd = ADM_CMD_TYPE_SINGLE ;
single_desc - > src_addr = * src ;
single_desc - > dst_addr = * dst ;
single_desc - > len = ( remainder > ADM_MAX_XFER ) ?
ADM_MAX_XFER : remainder ;
remainder - = single_desc - > len ;
* incr_addr + = single_desc - > len ;
desc + = sizeof ( * single_desc ) ;
} while ( remainder ) ;
/* set last command if this is the end of the whole transaction */
if ( sg_is_last ( sg ) )
single_desc - > cmd | = ADM_CMD_LC ;
return desc ;
}
/**
* adm_prep_slave_sg - Prep slave sg transaction
*
* @ chan : dma channel
* @ sgl : scatter gather list
* @ sg_len : length of sg
* @ direction : DMA transfer direction
* @ flags : DMA flags
* @ context : transfer context ( unused )
*/
static struct dma_async_tx_descriptor * adm_prep_slave_sg ( struct dma_chan * chan ,
struct scatterlist * sgl ,
unsigned int sg_len ,
enum dma_transfer_direction direction ,
unsigned long flags ,
void * context )
{
struct adm_chan * achan = to_adm_chan ( chan ) ;
struct adm_device * adev = achan - > adev ;
struct adm_async_desc * async_desc ;
struct scatterlist * sg ;
dma_addr_t cple_addr ;
u32 i , burst ;
u32 single_count = 0 , box_count = 0 , crci = 0 ;
void * desc ;
u32 * cple ;
int blk_size = 0 ;
if ( ! is_slave_direction ( direction ) ) {
dev_err ( adev - > dev , " invalid dma direction \n " ) ;
return NULL ;
}
/*
* get burst value from slave configuration
*/
burst = ( direction = = DMA_MEM_TO_DEV ) ?
achan - > slave . dst_maxburst :
achan - > slave . src_maxburst ;
/* if using flow control, validate burst and crci values */
if ( achan - > slave . device_fc ) {
blk_size = adm_get_blksize ( burst ) ;
if ( blk_size < 0 ) {
dev_err ( adev - > dev , " invalid burst value: %d \n " ,
burst ) ;
return ERR_PTR ( - EINVAL ) ;
}
crci = achan - > slave . slave_id & 0xf ;
if ( ! crci | | achan - > slave . slave_id > 0x1f ) {
dev_err ( adev - > dev , " invalid crci value \n " ) ;
return ERR_PTR ( - EINVAL ) ;
}
}
/* iterate through sgs and compute allocation size of structures */
for_each_sg ( sgl , sg , sg_len , i ) {
if ( achan - > slave . device_fc ) {
box_count + = DIV_ROUND_UP ( sg_dma_len ( sg ) / burst ,
ADM_MAX_ROWS ) ;
if ( sg_dma_len ( sg ) % burst )
single_count + + ;
} else {
single_count + = DIV_ROUND_UP ( sg_dma_len ( sg ) ,
ADM_MAX_XFER ) ;
}
}
async_desc = kzalloc ( sizeof ( * async_desc ) , GFP_NOWAIT ) ;
if ( ! async_desc )
return ERR_PTR ( - ENOMEM ) ;
if ( crci )
async_desc - > mux = achan - > slave . slave_id & ADM_CRCI_MUX_SEL ?
ADM_CRCI_CTL_MUX_SEL : 0 ;
async_desc - > crci = crci ;
async_desc - > blk_size = blk_size ;
async_desc - > dma_len = single_count * sizeof ( struct adm_desc_hw_single ) +
box_count * sizeof ( struct adm_desc_hw_box ) +
sizeof ( * cple ) + 2 * ADM_DESC_ALIGN ;
async_desc - > cpl = kzalloc ( async_desc - > dma_len , GFP_NOWAIT ) ;
if ( ! async_desc - > cpl )
goto free ;
async_desc - > adev = adev ;
/* both command list entry and descriptors must be 8 byte aligned */
cple = PTR_ALIGN ( async_desc - > cpl , ADM_DESC_ALIGN ) ;
desc = PTR_ALIGN ( cple + 1 , ADM_DESC_ALIGN ) ;
for_each_sg ( sgl , sg , sg_len , i ) {
async_desc - > length + = sg_dma_len ( sg ) ;
if ( achan - > slave . device_fc )
desc = adm_process_fc_descriptors ( achan , desc , sg , crci ,
burst , direction ) ;
else
desc = adm_process_non_fc_descriptors ( achan , desc , sg ,
direction ) ;
}
async_desc - > dma_addr = dma_map_single ( adev - > dev , async_desc - > cpl ,
async_desc - > dma_len ,
DMA_TO_DEVICE ) ;
if ( dma_mapping_error ( adev - > dev , async_desc - > dma_addr ) )
goto free ;
cple_addr = async_desc - > dma_addr + ( ( void * ) cple - async_desc - > cpl ) ;
/* init cmd list */
dma_sync_single_for_cpu ( adev - > dev , cple_addr , sizeof ( * cple ) ,
DMA_TO_DEVICE ) ;
* cple = ADM_CPLE_LP ;
* cple | = ( async_desc - > dma_addr + ADM_DESC_ALIGN ) > > 3 ;
dma_sync_single_for_device ( adev - > dev , cple_addr , sizeof ( * cple ) ,
DMA_TO_DEVICE ) ;
return vchan_tx_prep ( & achan - > vc , & async_desc - > vd , flags ) ;
free :
kfree ( async_desc ) ;
return ERR_PTR ( - ENOMEM ) ;
}
/**
* adm_terminate_all - terminate all transactions on a channel
2020-11-26 18:46:02 +00:00
* @ chan : dma channel
2020-11-14 14:02:33 +00:00
*
* Dequeues and frees all transactions , aborts current transaction
* No callbacks are done
*
*/
static int adm_terminate_all ( struct dma_chan * chan )
{
struct adm_chan * achan = to_adm_chan ( chan ) ;
struct adm_device * adev = achan - > adev ;
unsigned long flags ;
LIST_HEAD ( head ) ;
spin_lock_irqsave ( & achan - > vc . lock , flags ) ;
vchan_get_all_descriptors ( & achan - > vc , & head ) ;
/* send flush command to terminate current transaction */
writel_relaxed ( 0x0 ,
adev - > regs + ADM_CH_FLUSH_STATE0 ( achan - > id , adev - > ee ) ) ;
spin_unlock_irqrestore ( & achan - > vc . lock , flags ) ;
vchan_dma_desc_free_list ( & achan - > vc , & head ) ;
return 0 ;
}
static int adm_slave_config ( struct dma_chan * chan , struct dma_slave_config * cfg )
{
struct adm_chan * achan = to_adm_chan ( chan ) ;
unsigned long flag ;
spin_lock_irqsave ( & achan - > vc . lock , flag ) ;
memcpy ( & achan - > slave , cfg , sizeof ( struct dma_slave_config ) ) ;
spin_unlock_irqrestore ( & achan - > vc . lock , flag ) ;
return 0 ;
}
/**
* adm_start_dma - start next transaction
2020-11-26 18:46:02 +00:00
* @ achan : ADM dma channel
2020-11-14 14:02:33 +00:00
*/
static void adm_start_dma ( struct adm_chan * achan )
{
struct virt_dma_desc * vd = vchan_next_desc ( & achan - > vc ) ;
struct adm_device * adev = achan - > adev ;
struct adm_async_desc * async_desc ;
lockdep_assert_held ( & achan - > vc . lock ) ;
if ( ! vd )
return ;
list_del ( & vd - > node ) ;
/* write next command list out to the CMD FIFO */
async_desc = container_of ( vd , struct adm_async_desc , vd ) ;
achan - > curr_txd = async_desc ;
/* reset channel error */
achan - > error = 0 ;
if ( ! achan - > initialized ) {
/* enable interrupts */
writel ( ADM_CH_CONF_SHADOW_EN |
ADM_CH_CONF_PERM_MPU_CONF |
ADM_CH_CONF_MPU_DISABLE |
ADM_CH_CONF_SEC_DOMAIN ( adev - > ee ) ,
adev - > regs + ADM_CH_CONF ( achan - > id ) ) ;
writel ( ADM_CH_RSLT_CONF_IRQ_EN | ADM_CH_RSLT_CONF_FLUSH_EN ,
adev - > regs + ADM_CH_RSLT_CONF ( achan - > id , adev - > ee ) ) ;
achan - > initialized = 1 ;
}
/* set the crci block size if this transaction requires CRCI */
if ( async_desc - > crci ) {
writel ( async_desc - > mux | async_desc - > blk_size ,
adev - > regs + ADM_CRCI_CTL ( async_desc - > crci , adev - > ee ) ) ;
}
/* make sure IRQ enable doesn't get reordered */
wmb ( ) ;
/* write next command list out to the CMD FIFO */
writel ( ALIGN ( async_desc - > dma_addr , ADM_DESC_ALIGN ) > > 3 ,
adev - > regs + ADM_CH_CMD_PTR ( achan - > id , adev - > ee ) ) ;
}
/**
* adm_dma_irq - irq handler for ADM controller
* @ irq : IRQ of interrupt
* @ data : callback data
*
* IRQ handler for the bam controller
*/
static irqreturn_t adm_dma_irq ( int irq , void * data )
{
struct adm_device * adev = data ;
u32 srcs , i ;
struct adm_async_desc * async_desc ;
unsigned long flags ;
srcs = readl_relaxed ( adev - > regs +
ADM_SEC_DOMAIN_IRQ_STATUS ( adev - > ee ) ) ;
for ( i = 0 ; i < ADM_MAX_CHANNELS ; i + + ) {
struct adm_chan * achan = & adev - > channels [ i ] ;
u32 status , result ;
if ( srcs & BIT ( i ) ) {
status = readl_relaxed ( adev - > regs +
ADM_CH_STATUS_SD ( i , adev - > ee ) ) ;
/* if no result present, skip */
if ( ! ( status & ADM_CH_STATUS_VALID ) )
continue ;
result = readl_relaxed ( adev - > regs +
ADM_CH_RSLT ( i , adev - > ee ) ) ;
/* no valid results, skip */
if ( ! ( result & ADM_CH_RSLT_VALID ) )
continue ;
/* flag error if transaction was flushed or failed */
if ( result & ( ADM_CH_RSLT_ERR | ADM_CH_RSLT_FLUSH ) )
achan - > error = 1 ;
spin_lock_irqsave ( & achan - > vc . lock , flags ) ;
async_desc = achan - > curr_txd ;
achan - > curr_txd = NULL ;
if ( async_desc ) {
vchan_cookie_complete ( & async_desc - > vd ) ;
/* kick off next DMA */
adm_start_dma ( achan ) ;
}
spin_unlock_irqrestore ( & achan - > vc . lock , flags ) ;
}
}
return IRQ_HANDLED ;
}
/**
* adm_tx_status - returns status of transaction
* @ chan : dma channel
* @ cookie : transaction cookie
* @ txstate : DMA transaction state
*
* Return status of dma transaction
*/
static enum dma_status adm_tx_status ( struct dma_chan * chan , dma_cookie_t cookie ,
struct dma_tx_state * txstate )
{
struct adm_chan * achan = to_adm_chan ( chan ) ;
struct virt_dma_desc * vd ;
enum dma_status ret ;
unsigned long flags ;
size_t residue = 0 ;
ret = dma_cookie_status ( chan , cookie , txstate ) ;
if ( ret = = DMA_COMPLETE | | ! txstate )
return ret ;
spin_lock_irqsave ( & achan - > vc . lock , flags ) ;
vd = vchan_find_desc ( & achan - > vc , cookie ) ;
if ( vd )
residue = container_of ( vd , struct adm_async_desc , vd ) - > length ;
spin_unlock_irqrestore ( & achan - > vc . lock , flags ) ;
/*
* residue is either the full length if it is in the issued list , or 0
* if it is in progress . We have no reliable way of determining
* anything inbetween
*/
dma_set_residue ( txstate , residue ) ;
if ( achan - > error )
return DMA_ERROR ;
return ret ;
}
/**
* adm_issue_pending - starts pending transactions
* @ chan : dma channel
*
* Issues all pending transactions and starts DMA
*/
static void adm_issue_pending ( struct dma_chan * chan )
{
struct adm_chan * achan = to_adm_chan ( chan ) ;
unsigned long flags ;
spin_lock_irqsave ( & achan - > vc . lock , flags ) ;
if ( vchan_issue_pending ( & achan - > vc ) & & ! achan - > curr_txd )
adm_start_dma ( achan ) ;
spin_unlock_irqrestore ( & achan - > vc . lock , flags ) ;
}
/**
* adm_dma_free_desc - free descriptor memory
* @ vd : virtual descriptor
*
*/
static void adm_dma_free_desc ( struct virt_dma_desc * vd )
{
struct adm_async_desc * async_desc = container_of ( vd ,
struct adm_async_desc , vd ) ;
dma_unmap_single ( async_desc - > adev - > dev , async_desc - > dma_addr ,
async_desc - > dma_len , DMA_TO_DEVICE ) ;
kfree ( async_desc - > cpl ) ;
kfree ( async_desc ) ;
}
static void adm_channel_init ( struct adm_device * adev , struct adm_chan * achan ,
u32 index )
{
achan - > id = index ;
achan - > adev = adev ;
vchan_init ( & achan - > vc , & adev - > common ) ;
achan - > vc . desc_free = adm_dma_free_desc ;
}
static int adm_dma_probe ( struct platform_device * pdev )
{
struct adm_device * adev ;
int ret ;
u32 i ;
adev = devm_kzalloc ( & pdev - > dev , sizeof ( * adev ) , GFP_KERNEL ) ;
if ( ! adev )
return - ENOMEM ;
adev - > dev = & pdev - > dev ;
adev - > regs = devm_platform_ioremap_resource ( pdev , 0 ) ;
if ( IS_ERR ( adev - > regs ) )
return PTR_ERR ( adev - > regs ) ;
adev - > irq = platform_get_irq ( pdev , 0 ) ;
if ( adev - > irq < 0 )
return adev - > irq ;
ret = of_property_read_u32 ( pdev - > dev . of_node , " qcom,ee " , & adev - > ee ) ;
if ( ret ) {
dev_err ( adev - > dev , " Execution environment unspecified \n " ) ;
return ret ;
}
adev - > core_clk = devm_clk_get ( adev - > dev , " core " ) ;
if ( IS_ERR ( adev - > core_clk ) )
return PTR_ERR ( adev - > core_clk ) ;
adev - > iface_clk = devm_clk_get ( adev - > dev , " iface " ) ;
if ( IS_ERR ( adev - > iface_clk ) )
return PTR_ERR ( adev - > iface_clk ) ;
adev - > clk_reset = devm_reset_control_get_exclusive ( & pdev - > dev , " clk " ) ;
if ( IS_ERR ( adev - > clk_reset ) ) {
dev_err ( adev - > dev , " failed to get ADM0 reset \n " ) ;
return PTR_ERR ( adev - > clk_reset ) ;
}
adev - > c0_reset = devm_reset_control_get_exclusive ( & pdev - > dev , " c0 " ) ;
if ( IS_ERR ( adev - > c0_reset ) ) {
dev_err ( adev - > dev , " failed to get ADM0 C0 reset \n " ) ;
return PTR_ERR ( adev - > c0_reset ) ;
}
adev - > c1_reset = devm_reset_control_get_exclusive ( & pdev - > dev , " c1 " ) ;
if ( IS_ERR ( adev - > c1_reset ) ) {
dev_err ( adev - > dev , " failed to get ADM0 C1 reset \n " ) ;
return PTR_ERR ( adev - > c1_reset ) ;
}
adev - > c2_reset = devm_reset_control_get_exclusive ( & pdev - > dev , " c2 " ) ;
if ( IS_ERR ( adev - > c2_reset ) ) {
dev_err ( adev - > dev , " failed to get ADM0 C2 reset \n " ) ;
return PTR_ERR ( adev - > c2_reset ) ;
}
ret = clk_prepare_enable ( adev - > core_clk ) ;
if ( ret ) {
dev_err ( adev - > dev , " failed to prepare/enable core clock \n " ) ;
return ret ;
}
ret = clk_prepare_enable ( adev - > iface_clk ) ;
if ( ret ) {
dev_err ( adev - > dev , " failed to prepare/enable iface clock \n " ) ;
goto err_disable_core_clk ;
}
reset_control_assert ( adev - > clk_reset ) ;
reset_control_assert ( adev - > c0_reset ) ;
reset_control_assert ( adev - > c1_reset ) ;
reset_control_assert ( adev - > c2_reset ) ;
udelay ( 2 ) ;
reset_control_deassert ( adev - > clk_reset ) ;
reset_control_deassert ( adev - > c0_reset ) ;
reset_control_deassert ( adev - > c1_reset ) ;
reset_control_deassert ( adev - > c2_reset ) ;
adev - > channels = devm_kcalloc ( adev - > dev , ADM_MAX_CHANNELS ,
sizeof ( * adev - > channels ) , GFP_KERNEL ) ;
if ( ! adev - > channels ) {
ret = - ENOMEM ;
goto err_disable_clks ;
}
/* allocate and initialize channels */
INIT_LIST_HEAD ( & adev - > common . channels ) ;
for ( i = 0 ; i < ADM_MAX_CHANNELS ; i + + )
adm_channel_init ( adev , & adev - > channels [ i ] , i ) ;
/* reset CRCIs */
for ( i = 0 ; i < 16 ; i + + )
writel ( ADM_CRCI_CTL_RST , adev - > regs +
ADM_CRCI_CTL ( i , adev - > ee ) ) ;
/* configure client interfaces */
writel ( ADM_CI_RANGE_START ( 0x40 ) | ADM_CI_RANGE_END ( 0xb0 ) |
ADM_CI_BURST_8_WORDS , adev - > regs + ADM_CI_CONF ( 0 ) ) ;
writel ( ADM_CI_RANGE_START ( 0x2a ) | ADM_CI_RANGE_END ( 0x2c ) |
ADM_CI_BURST_8_WORDS , adev - > regs + ADM_CI_CONF ( 1 ) ) ;
writel ( ADM_CI_RANGE_START ( 0x12 ) | ADM_CI_RANGE_END ( 0x28 ) |
ADM_CI_BURST_8_WORDS , adev - > regs + ADM_CI_CONF ( 2 ) ) ;
writel ( ADM_GP_CTL_LP_EN | ADM_GP_CTL_LP_CNT ( 0xf ) ,
adev - > regs + ADM_GP_CTL ) ;
ret = devm_request_irq ( adev - > dev , adev - > irq , adm_dma_irq ,
0 , " adm_dma " , adev ) ;
if ( ret )
goto err_disable_clks ;
platform_set_drvdata ( pdev , adev ) ;
adev - > common . dev = adev - > dev ;
adev - > common . dev - > dma_parms = & adev - > dma_parms ;
/* set capabilities */
dma_cap_zero ( adev - > common . cap_mask ) ;
dma_cap_set ( DMA_SLAVE , adev - > common . cap_mask ) ;
dma_cap_set ( DMA_PRIVATE , adev - > common . cap_mask ) ;
/* initialize dmaengine apis */
adev - > common . directions = BIT ( DMA_DEV_TO_MEM | DMA_MEM_TO_DEV ) ;
adev - > common . residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR ;
adev - > common . src_addr_widths = DMA_SLAVE_BUSWIDTH_4_BYTES ;
adev - > common . dst_addr_widths = DMA_SLAVE_BUSWIDTH_4_BYTES ;
adev - > common . device_free_chan_resources = adm_free_chan ;
adev - > common . device_prep_slave_sg = adm_prep_slave_sg ;
adev - > common . device_issue_pending = adm_issue_pending ;
adev - > common . device_tx_status = adm_tx_status ;
adev - > common . device_terminate_all = adm_terminate_all ;
adev - > common . device_config = adm_slave_config ;
ret = dma_async_device_register ( & adev - > common ) ;
if ( ret ) {
dev_err ( adev - > dev , " failed to register dma async device \n " ) ;
goto err_disable_clks ;
}
ret = of_dma_controller_register ( pdev - > dev . of_node ,
of_dma_xlate_by_chan_id ,
& adev - > common ) ;
if ( ret )
goto err_unregister_dma ;
return 0 ;
err_unregister_dma :
dma_async_device_unregister ( & adev - > common ) ;
err_disable_clks :
clk_disable_unprepare ( adev - > iface_clk ) ;
err_disable_core_clk :
clk_disable_unprepare ( adev - > core_clk ) ;
return ret ;
}
static int adm_dma_remove ( struct platform_device * pdev )
{
struct adm_device * adev = platform_get_drvdata ( pdev ) ;
struct adm_chan * achan ;
u32 i ;
of_dma_controller_free ( pdev - > dev . of_node ) ;
dma_async_device_unregister ( & adev - > common ) ;
for ( i = 0 ; i < ADM_MAX_CHANNELS ; i + + ) {
achan = & adev - > channels [ i ] ;
/* mask IRQs for this channel/EE pair */
writel ( 0 , adev - > regs + ADM_CH_RSLT_CONF ( achan - > id , adev - > ee ) ) ;
tasklet_kill ( & adev - > channels [ i ] . vc . task ) ;
adm_terminate_all ( & adev - > channels [ i ] . vc . chan ) ;
}
devm_free_irq ( adev - > dev , adev - > irq , adev ) ;
clk_disable_unprepare ( adev - > core_clk ) ;
clk_disable_unprepare ( adev - > iface_clk ) ;
return 0 ;
}
static const struct of_device_id adm_of_match [ ] = {
{ . compatible = " qcom,adm " , } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , adm_of_match ) ;
static struct platform_driver adm_dma_driver = {
. probe = adm_dma_probe ,
. remove = adm_dma_remove ,
. driver = {
. name = " adm-dma-engine " ,
. of_match_table = adm_of_match ,
} ,
} ;
module_platform_driver ( adm_dma_driver ) ;
MODULE_AUTHOR ( " Andy Gross <agross@codeaurora.org> " ) ;
MODULE_DESCRIPTION ( " QCOM ADM DMA engine driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;