forked from Minki/linux
dmaengine: sprd: Support DMA link-list mode
The Spreadtrum DMA can support the link-list transaction mode, which means DMA controller can do transaction one by one automatically once we linked these transaction by link-list register. Signed-off-by: Eric Long <eric.long@spreadtrum.com> Signed-off-by: Baolin Wang <baolin.wang@linaro.org> Signed-off-by: Vinod Koul <vkoul@kernel.org>
This commit is contained in:
parent
5b394b2ddf
commit
4ac6954647
@ -68,6 +68,7 @@
|
|||||||
|
|
||||||
/* SPRD_DMA_CHN_CFG register definition */
|
/* SPRD_DMA_CHN_CFG register definition */
|
||||||
#define SPRD_DMA_CHN_EN BIT(0)
|
#define SPRD_DMA_CHN_EN BIT(0)
|
||||||
|
#define SPRD_DMA_LINKLIST_EN BIT(4)
|
||||||
#define SPRD_DMA_WAIT_BDONE_OFFSET 24
|
#define SPRD_DMA_WAIT_BDONE_OFFSET 24
|
||||||
#define SPRD_DMA_DONOT_WAIT_BDONE 1
|
#define SPRD_DMA_DONOT_WAIT_BDONE 1
|
||||||
|
|
||||||
@ -103,7 +104,7 @@
|
|||||||
#define SPRD_DMA_REQ_MODE_MASK GENMASK(1, 0)
|
#define SPRD_DMA_REQ_MODE_MASK GENMASK(1, 0)
|
||||||
#define SPRD_DMA_FIX_SEL_OFFSET 21
|
#define SPRD_DMA_FIX_SEL_OFFSET 21
|
||||||
#define SPRD_DMA_FIX_EN_OFFSET 20
|
#define SPRD_DMA_FIX_EN_OFFSET 20
|
||||||
#define SPRD_DMA_LLIST_END_OFFSET 19
|
#define SPRD_DMA_LLIST_END BIT(19)
|
||||||
#define SPRD_DMA_FRG_LEN_MASK GENMASK(16, 0)
|
#define SPRD_DMA_FRG_LEN_MASK GENMASK(16, 0)
|
||||||
|
|
||||||
/* SPRD_DMA_CHN_BLK_LEN register definition */
|
/* SPRD_DMA_CHN_BLK_LEN register definition */
|
||||||
@ -164,6 +165,7 @@ struct sprd_dma_desc {
|
|||||||
struct sprd_dma_chn {
|
struct sprd_dma_chn {
|
||||||
struct virt_dma_chan vc;
|
struct virt_dma_chan vc;
|
||||||
void __iomem *chn_base;
|
void __iomem *chn_base;
|
||||||
|
struct sprd_dma_linklist linklist;
|
||||||
struct dma_slave_config slave_cfg;
|
struct dma_slave_config slave_cfg;
|
||||||
u32 chn_num;
|
u32 chn_num;
|
||||||
u32 dev_id;
|
u32 dev_id;
|
||||||
@ -582,7 +584,8 @@ static int sprd_dma_get_step(enum dma_slave_buswidth buswidth)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int sprd_dma_fill_desc(struct dma_chan *chan,
|
static int sprd_dma_fill_desc(struct dma_chan *chan,
|
||||||
struct sprd_dma_desc *sdesc,
|
struct sprd_dma_chn_hw *hw,
|
||||||
|
unsigned int sglen, int sg_index,
|
||||||
dma_addr_t src, dma_addr_t dst, u32 len,
|
dma_addr_t src, dma_addr_t dst, u32 len,
|
||||||
enum dma_transfer_direction dir,
|
enum dma_transfer_direction dir,
|
||||||
unsigned long flags,
|
unsigned long flags,
|
||||||
@ -590,7 +593,6 @@ static int sprd_dma_fill_desc(struct dma_chan *chan,
|
|||||||
{
|
{
|
||||||
struct sprd_dma_dev *sdev = to_sprd_dma_dev(chan);
|
struct sprd_dma_dev *sdev = to_sprd_dma_dev(chan);
|
||||||
struct sprd_dma_chn *schan = to_sprd_dma_chan(chan);
|
struct sprd_dma_chn *schan = to_sprd_dma_chan(chan);
|
||||||
struct sprd_dma_chn_hw *hw = &sdesc->chn_hw;
|
|
||||||
u32 req_mode = (flags >> SPRD_DMA_REQ_SHIFT) & SPRD_DMA_REQ_MODE_MASK;
|
u32 req_mode = (flags >> SPRD_DMA_REQ_SHIFT) & SPRD_DMA_REQ_MODE_MASK;
|
||||||
u32 int_mode = flags & SPRD_DMA_INT_MASK;
|
u32 int_mode = flags & SPRD_DMA_INT_MASK;
|
||||||
int src_datawidth, dst_datawidth, src_step, dst_step;
|
int src_datawidth, dst_datawidth, src_step, dst_step;
|
||||||
@ -670,12 +672,52 @@ static int sprd_dma_fill_desc(struct dma_chan *chan,
|
|||||||
temp |= (src_step & SPRD_DMA_TRSF_STEP_MASK) << SPRD_DMA_SRC_TRSF_STEP_OFFSET;
|
temp |= (src_step & SPRD_DMA_TRSF_STEP_MASK) << SPRD_DMA_SRC_TRSF_STEP_OFFSET;
|
||||||
hw->trsf_step = temp;
|
hw->trsf_step = temp;
|
||||||
|
|
||||||
|
/* link-list configuration */
|
||||||
|
if (schan->linklist.phy_addr) {
|
||||||
|
if (sg_index == sglen - 1)
|
||||||
|
hw->frg_len |= SPRD_DMA_LLIST_END;
|
||||||
|
|
||||||
|
hw->cfg |= SPRD_DMA_LINKLIST_EN;
|
||||||
|
|
||||||
|
/* link-list index */
|
||||||
|
temp = (sg_index + 1) % sglen;
|
||||||
|
/* Next link-list configuration's physical address offset */
|
||||||
|
temp = temp * sizeof(*hw) + SPRD_DMA_CHN_SRC_ADDR;
|
||||||
|
/*
|
||||||
|
* Set the link-list pointer point to next link-list
|
||||||
|
* configuration's physical address.
|
||||||
|
*/
|
||||||
|
hw->llist_ptr = schan->linklist.phy_addr + temp;
|
||||||
|
} else {
|
||||||
|
hw->llist_ptr = 0;
|
||||||
|
}
|
||||||
|
|
||||||
hw->frg_step = 0;
|
hw->frg_step = 0;
|
||||||
hw->src_blk_step = 0;
|
hw->src_blk_step = 0;
|
||||||
hw->des_blk_step = 0;
|
hw->des_blk_step = 0;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int sprd_dma_fill_linklist_desc(struct dma_chan *chan,
|
||||||
|
unsigned int sglen, int sg_index,
|
||||||
|
dma_addr_t src, dma_addr_t dst, u32 len,
|
||||||
|
enum dma_transfer_direction dir,
|
||||||
|
unsigned long flags,
|
||||||
|
struct dma_slave_config *slave_cfg)
|
||||||
|
{
|
||||||
|
struct sprd_dma_chn *schan = to_sprd_dma_chan(chan);
|
||||||
|
struct sprd_dma_chn_hw *hw;
|
||||||
|
|
||||||
|
if (!schan->linklist.virt_addr)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
hw = (struct sprd_dma_chn_hw *)(schan->linklist.virt_addr +
|
||||||
|
sg_index * sizeof(*hw));
|
||||||
|
|
||||||
|
return sprd_dma_fill_desc(chan, hw, sglen, sg_index, src, dst, len,
|
||||||
|
dir, flags, slave_cfg);
|
||||||
|
}
|
||||||
|
|
||||||
static struct dma_async_tx_descriptor *
|
static struct dma_async_tx_descriptor *
|
||||||
sprd_dma_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dest, dma_addr_t src,
|
sprd_dma_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dest, dma_addr_t src,
|
||||||
size_t len, unsigned long flags)
|
size_t len, unsigned long flags)
|
||||||
@ -744,10 +786,20 @@ sprd_dma_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
|
|||||||
u32 len = 0;
|
u32 len = 0;
|
||||||
int ret, i;
|
int ret, i;
|
||||||
|
|
||||||
/* TODO: now we only support one sg for each DMA configuration. */
|
if (!is_slave_direction(dir))
|
||||||
if (!is_slave_direction(dir) || sglen > 1)
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
if (context) {
|
||||||
|
struct sprd_dma_linklist *ll_cfg =
|
||||||
|
(struct sprd_dma_linklist *)context;
|
||||||
|
|
||||||
|
schan->linklist.phy_addr = ll_cfg->phy_addr;
|
||||||
|
schan->linklist.virt_addr = ll_cfg->virt_addr;
|
||||||
|
} else {
|
||||||
|
schan->linklist.phy_addr = 0;
|
||||||
|
schan->linklist.virt_addr = 0;
|
||||||
|
}
|
||||||
|
|
||||||
sdesc = kzalloc(sizeof(*sdesc), GFP_NOWAIT);
|
sdesc = kzalloc(sizeof(*sdesc), GFP_NOWAIT);
|
||||||
if (!sdesc)
|
if (!sdesc)
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -762,10 +814,25 @@ sprd_dma_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
|
|||||||
src = slave_cfg->src_addr;
|
src = slave_cfg->src_addr;
|
||||||
dst = sg_dma_address(sg);
|
dst = sg_dma_address(sg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The link-list mode needs at least 2 link-list
|
||||||
|
* configurations. If there is only one sg, it doesn't
|
||||||
|
* need to fill the link-list configuration.
|
||||||
|
*/
|
||||||
|
if (sglen < 2)
|
||||||
|
break;
|
||||||
|
|
||||||
|
ret = sprd_dma_fill_linklist_desc(chan, sglen, i, src, dst, len,
|
||||||
|
dir, flags, slave_cfg);
|
||||||
|
if (ret) {
|
||||||
|
kfree(sdesc);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = sprd_dma_fill_desc(chan, sdesc, src, dst, len, dir, flags,
|
ret = sprd_dma_fill_desc(chan, &sdesc->chn_hw, 0, 0, src, dst, len,
|
||||||
slave_cfg);
|
dir, flags, slave_cfg);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
kfree(sdesc);
|
kfree(sdesc);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -58,4 +58,73 @@ enum sprd_dma_int_type {
|
|||||||
SPRD_DMA_CFGERR_INT,
|
SPRD_DMA_CFGERR_INT,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* struct sprd_dma_linklist - DMA link-list address structure
|
||||||
|
* @virt_addr: link-list virtual address to configure link-list node
|
||||||
|
* @phy_addr: link-list physical address to link DMA transfer
|
||||||
|
*
|
||||||
|
* The Spreadtrum DMA controller supports the link-list mode, that means slaves
|
||||||
|
* can supply several groups configurations (each configuration represents one
|
||||||
|
* DMA transfer) saved in memory, and DMA controller will link these groups
|
||||||
|
* configurations by writing the physical address of each configuration into the
|
||||||
|
* link-list register.
|
||||||
|
*
|
||||||
|
* Just as shown below, the link-list pointer register will be pointed to the
|
||||||
|
* physical address of 'configuration 1', and the 'configuration 1' link-list
|
||||||
|
* pointer will be pointed to 'configuration 2', and so on.
|
||||||
|
* Once trigger the DMA transfer, the DMA controller will load 'configuration
|
||||||
|
* 1' to its registers automatically, after 'configuration 1' transaction is
|
||||||
|
* done, DMA controller will load 'configuration 2' automatically, until all
|
||||||
|
* DMA transactions are done.
|
||||||
|
*
|
||||||
|
* Note: The last link-list pointer should point to the physical address
|
||||||
|
* of 'configuration 1', which can avoid DMA controller loads incorrect
|
||||||
|
* configuration when the last configuration transaction is done.
|
||||||
|
*
|
||||||
|
* DMA controller linklist memory
|
||||||
|
* ====================== -----------------------
|
||||||
|
*| | | configuration 1 |<---
|
||||||
|
*| DMA controller | ------->| | |
|
||||||
|
*| | | | | |
|
||||||
|
*| | | | | |
|
||||||
|
*| | | | | |
|
||||||
|
*| linklist pointer reg |---- ----| linklist pointer | |
|
||||||
|
* ====================== | ----------------------- |
|
||||||
|
* | |
|
||||||
|
* | ----------------------- |
|
||||||
|
* | | configuration 2 | |
|
||||||
|
* --->| | |
|
||||||
|
* | | |
|
||||||
|
* | | |
|
||||||
|
* | | |
|
||||||
|
* ----| linklist pointer | |
|
||||||
|
* | ----------------------- |
|
||||||
|
* | |
|
||||||
|
* | ----------------------- |
|
||||||
|
* | | configuration 3 | |
|
||||||
|
* --->| | |
|
||||||
|
* | | |
|
||||||
|
* | . | |
|
||||||
|
* . |
|
||||||
|
* . |
|
||||||
|
* . |
|
||||||
|
* | . |
|
||||||
|
* | ----------------------- |
|
||||||
|
* | | configuration n | |
|
||||||
|
* --->| | |
|
||||||
|
* | | |
|
||||||
|
* | | |
|
||||||
|
* | | |
|
||||||
|
* | linklist pointer |----
|
||||||
|
* -----------------------
|
||||||
|
*
|
||||||
|
* To support the link-list mode, DMA slaves should allocate one segment memory
|
||||||
|
* from always-on IRAM or dma coherent memory to store these groups of DMA
|
||||||
|
* configuration, and pass the virtual and physical address to DMA controller.
|
||||||
|
*/
|
||||||
|
struct sprd_dma_linklist {
|
||||||
|
unsigned long virt_addr;
|
||||||
|
phys_addr_t phy_addr;
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
Reference in New Issue
Block a user