|
@@ -48,6 +48,9 @@ enum sh_dmae_desc_status {
|
|
|
*/
|
|
|
#define RS_DEFAULT (RS_DUAL)
|
|
|
|
|
|
+/* A bitmask with bits enough for enum sh_dmae_slave_chan_id */
|
|
|
+static unsigned long sh_dmae_slave_used[BITS_TO_LONGS(SHDMA_SLAVE_NUMBER)];
|
|
|
+
|
|
|
static void sh_dmae_chan_ld_cleanup(struct sh_dmae_chan *sh_chan, bool all);
|
|
|
|
|
|
#define SH_DMAC_CHAN_BASE(id) (dma_base_addr[id])
|
|
@@ -61,12 +64,6 @@ static u32 sh_dmae_readl(struct sh_dmae_chan *sh_dc, u32 reg)
|
|
|
return ctrl_inl(SH_DMAC_CHAN_BASE(sh_dc->id) + reg);
|
|
|
}
|
|
|
|
|
|
-static void dmae_init(struct sh_dmae_chan *sh_chan)
|
|
|
-{
|
|
|
- u32 chcr = RS_DEFAULT; /* default is DUAL mode */
|
|
|
- sh_dmae_writel(sh_chan, chcr, CHCR);
|
|
|
-}
|
|
|
-
|
|
|
/*
|
|
|
* Reset DMA controller
|
|
|
*
|
|
@@ -106,9 +103,8 @@ static bool dmae_is_busy(struct sh_dmae_chan *sh_chan)
|
|
|
}
|
|
|
|
|
|
static unsigned int ts_shift[] = TS_SHIFT;
|
|
|
-static inline unsigned int calc_xmit_shift(struct sh_dmae_chan *sh_chan)
|
|
|
+static inline unsigned int calc_xmit_shift(u32 chcr)
|
|
|
{
|
|
|
- u32 chcr = sh_dmae_readl(sh_chan, CHCR);
|
|
|
int cnt = ((chcr & CHCR_TS_LOW_MASK) >> CHCR_TS_LOW_SHIFT) |
|
|
|
((chcr & CHCR_TS_HIGH_MASK) >> CHCR_TS_HIGH_SHIFT);
|
|
|
|
|
@@ -119,7 +115,7 @@ static void dmae_set_reg(struct sh_dmae_chan *sh_chan, struct sh_dmae_regs *hw)
|
|
|
{
|
|
|
sh_dmae_writel(sh_chan, hw->sar, SAR);
|
|
|
sh_dmae_writel(sh_chan, hw->dar, DAR);
|
|
|
- sh_dmae_writel(sh_chan, hw->tcr >> calc_xmit_shift(sh_chan), TCR);
|
|
|
+ sh_dmae_writel(sh_chan, hw->tcr >> sh_chan->xmit_shift, TCR);
|
|
|
}
|
|
|
|
|
|
static void dmae_start(struct sh_dmae_chan *sh_chan)
|
|
@@ -127,7 +123,7 @@ static void dmae_start(struct sh_dmae_chan *sh_chan)
|
|
|
u32 chcr = sh_dmae_readl(sh_chan, CHCR);
|
|
|
|
|
|
chcr |= CHCR_DE | CHCR_IE;
|
|
|
- sh_dmae_writel(sh_chan, chcr, CHCR);
|
|
|
+ sh_dmae_writel(sh_chan, chcr & ~CHCR_TE, CHCR);
|
|
|
}
|
|
|
|
|
|
static void dmae_halt(struct sh_dmae_chan *sh_chan)
|
|
@@ -138,20 +134,27 @@ static void dmae_halt(struct sh_dmae_chan *sh_chan)
|
|
|
sh_dmae_writel(sh_chan, chcr, CHCR);
|
|
|
}
|
|
|
|
|
|
+static void dmae_init(struct sh_dmae_chan *sh_chan)
|
|
|
+{
|
|
|
+ u32 chcr = RS_DEFAULT; /* default is DUAL mode */
|
|
|
+ sh_chan->xmit_shift = calc_xmit_shift(chcr);
|
|
|
+ sh_dmae_writel(sh_chan, chcr, CHCR);
|
|
|
+}
|
|
|
+
|
|
|
static int dmae_set_chcr(struct sh_dmae_chan *sh_chan, u32 val)
|
|
|
{
|
|
|
/* When DMA was working, can not set data to CHCR */
|
|
|
if (dmae_is_busy(sh_chan))
|
|
|
return -EBUSY;
|
|
|
|
|
|
+ sh_chan->xmit_shift = calc_xmit_shift(val);
|
|
|
sh_dmae_writel(sh_chan, val, CHCR);
|
|
|
+
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
-#define DMARS1_ADDR 0x04
|
|
|
-#define DMARS2_ADDR 0x08
|
|
|
-#define DMARS_SHIFT 8
|
|
|
-#define DMARS_CHAN_MSK 0x01
|
|
|
+#define DMARS_SHIFT 8
|
|
|
+#define DMARS_CHAN_MSK 0x01
|
|
|
static int dmae_set_dmars(struct sh_dmae_chan *sh_chan, u16 val)
|
|
|
{
|
|
|
u32 addr;
|
|
@@ -163,29 +166,18 @@ static int dmae_set_dmars(struct sh_dmae_chan *sh_chan, u16 val)
|
|
|
if (sh_chan->id & DMARS_CHAN_MSK)
|
|
|
shift = DMARS_SHIFT;
|
|
|
|
|
|
- switch (sh_chan->id) {
|
|
|
- /* DMARS0 */
|
|
|
- case 0:
|
|
|
- case 1:
|
|
|
- addr = SH_DMARS_BASE;
|
|
|
- break;
|
|
|
- /* DMARS1 */
|
|
|
- case 2:
|
|
|
- case 3:
|
|
|
- addr = (SH_DMARS_BASE + DMARS1_ADDR);
|
|
|
- break;
|
|
|
- /* DMARS2 */
|
|
|
- case 4:
|
|
|
- case 5:
|
|
|
- addr = (SH_DMARS_BASE + DMARS2_ADDR);
|
|
|
- break;
|
|
|
- default:
|
|
|
+ if (sh_chan->id < 6)
|
|
|
+ /* DMA0RS0 - DMA0RS2 */
|
|
|
+ addr = SH_DMARS_BASE0 + (sh_chan->id / 2) * 4;
|
|
|
+#ifdef SH_DMARS_BASE1
|
|
|
+ else if (sh_chan->id < 12)
|
|
|
+ /* DMA1RS0 - DMA1RS2 */
|
|
|
+ addr = SH_DMARS_BASE1 + ((sh_chan->id - 6) / 2) * 4;
|
|
|
+#endif
|
|
|
+ else
|
|
|
return -EINVAL;
|
|
|
- }
|
|
|
|
|
|
- ctrl_outw((val << shift) |
|
|
|
- (ctrl_inw(addr) & (shift ? 0xFF00 : 0x00FF)),
|
|
|
- addr);
|
|
|
+ ctrl_outw((val << shift) | (ctrl_inw(addr) & (0xFF00 >> shift)), addr);
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
@@ -253,10 +245,53 @@ static struct sh_desc *sh_dmae_get_desc(struct sh_dmae_chan *sh_chan)
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
+static struct sh_dmae_slave_config *sh_dmae_find_slave(
|
|
|
+ struct sh_dmae_chan *sh_chan, enum sh_dmae_slave_chan_id slave_id)
|
|
|
+{
|
|
|
+ struct dma_device *dma_dev = sh_chan->common.device;
|
|
|
+ struct sh_dmae_device *shdev = container_of(dma_dev,
|
|
|
+ struct sh_dmae_device, common);
|
|
|
+ struct sh_dmae_pdata *pdata = &shdev->pdata;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ if ((unsigned)slave_id >= SHDMA_SLAVE_NUMBER)
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ for (i = 0; i < pdata->config_num; i++)
|
|
|
+ if (pdata->config[i].slave_id == slave_id)
|
|
|
+ return pdata->config + i;
|
|
|
+
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
static int sh_dmae_alloc_chan_resources(struct dma_chan *chan)
|
|
|
{
|
|
|
struct sh_dmae_chan *sh_chan = to_sh_chan(chan);
|
|
|
struct sh_desc *desc;
|
|
|
+ struct sh_dmae_slave *param = chan->private;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * This relies on the guarantee from dmaengine that alloc_chan_resources
|
|
|
+ * never runs concurrently with itself or free_chan_resources.
|
|
|
+ */
|
|
|
+ if (param) {
|
|
|
+ struct sh_dmae_slave_config *cfg;
|
|
|
+
|
|
|
+ cfg = sh_dmae_find_slave(sh_chan, param->slave_id);
|
|
|
+ if (!cfg)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (test_and_set_bit(param->slave_id, sh_dmae_slave_used))
|
|
|
+ return -EBUSY;
|
|
|
+
|
|
|
+ param->config = cfg;
|
|
|
+
|
|
|
+ dmae_set_dmars(sh_chan, cfg->mid_rid);
|
|
|
+ dmae_set_chcr(sh_chan, cfg->chcr);
|
|
|
+ } else {
|
|
|
+ if ((sh_dmae_readl(sh_chan, CHCR) & 0x700) != 0x400)
|
|
|
+ dmae_set_chcr(sh_chan, RS_DEFAULT);
|
|
|
+ }
|
|
|
|
|
|
spin_lock_bh(&sh_chan->desc_lock);
|
|
|
while (sh_chan->descs_allocated < NR_DESCS_PER_CHANNEL) {
|
|
@@ -289,10 +324,18 @@ static void sh_dmae_free_chan_resources(struct dma_chan *chan)
|
|
|
struct sh_desc *desc, *_desc;
|
|
|
LIST_HEAD(list);
|
|
|
|
|
|
+ dmae_halt(sh_chan);
|
|
|
+
|
|
|
/* Prepared and not submitted descriptors can still be on the queue */
|
|
|
if (!list_empty(&sh_chan->ld_queue))
|
|
|
sh_dmae_chan_ld_cleanup(sh_chan, true);
|
|
|
|
|
|
+ if (chan->private) {
|
|
|
+ /* The caller is holding dma_list_mutex */
|
|
|
+ struct sh_dmae_slave *param = chan->private;
|
|
|
+ clear_bit(param->slave_id, sh_dmae_slave_used);
|
|
|
+ }
|
|
|
+
|
|
|
spin_lock_bh(&sh_chan->desc_lock);
|
|
|
|
|
|
list_splice_init(&sh_chan->ld_free, &list);
|
|
@@ -304,7 +347,7 @@ static void sh_dmae_free_chan_resources(struct dma_chan *chan)
|
|
|
kfree(desc);
|
|
|
}
|
|
|
|
|
|
-/*
|
|
|
+/**
|
|
|
* sh_dmae_add_desc - get, set up and return one transfer descriptor
|
|
|
* @sh_chan: DMA channel
|
|
|
* @flags: DMA transfer flags
|
|
@@ -351,12 +394,14 @@ static struct sh_desc *sh_dmae_add_desc(struct sh_dmae_chan *sh_chan,
|
|
|
new->async_tx.cookie = -EINVAL;
|
|
|
}
|
|
|
|
|
|
- dev_dbg(sh_chan->dev, "chaining (%u/%u)@%x -> %x with %p, cookie %d\n",
|
|
|
+ dev_dbg(sh_chan->dev,
|
|
|
+ "chaining (%u/%u)@%x -> %x with %p, cookie %d, shift %d\n",
|
|
|
copy_size, *len, *src, *dest, &new->async_tx,
|
|
|
- new->async_tx.cookie);
|
|
|
+ new->async_tx.cookie, sh_chan->xmit_shift);
|
|
|
|
|
|
new->mark = DESC_PREPARED;
|
|
|
new->async_tx.flags = flags;
|
|
|
+ new->direction = direction;
|
|
|
|
|
|
*len -= copy_size;
|
|
|
if (direction == DMA_BIDIRECTIONAL || direction == DMA_TO_DEVICE)
|
|
@@ -465,6 +510,8 @@ static struct dma_async_tx_descriptor *sh_dmae_prep_memcpy(
|
|
|
if (!chan || !len)
|
|
|
return NULL;
|
|
|
|
|
|
+ chan->private = NULL;
|
|
|
+
|
|
|
sh_chan = to_sh_chan(chan);
|
|
|
|
|
|
sg_init_table(&sg, 1);
|
|
@@ -477,6 +524,44 @@ static struct dma_async_tx_descriptor *sh_dmae_prep_memcpy(
|
|
|
flags);
|
|
|
}
|
|
|
|
|
|
+static struct dma_async_tx_descriptor *sh_dmae_prep_slave_sg(
|
|
|
+ struct dma_chan *chan, struct scatterlist *sgl, unsigned int sg_len,
|
|
|
+ enum dma_data_direction direction, unsigned long flags)
|
|
|
+{
|
|
|
+ struct sh_dmae_slave *param;
|
|
|
+ struct sh_dmae_chan *sh_chan;
|
|
|
+
|
|
|
+ if (!chan)
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ sh_chan = to_sh_chan(chan);
|
|
|
+ param = chan->private;
|
|
|
+
|
|
|
+ /* Someone calling slave DMA on a public channel? */
|
|
|
+ if (!param || !sg_len) {
|
|
|
+ dev_warn(sh_chan->dev, "%s: bad parameter: %p, %d, %d\n",
|
|
|
+ __func__, param, sg_len, param ? param->slave_id : -1);
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * if (param != NULL), this is a successfully requested slave channel,
|
|
|
+ * therefore param->config != NULL too.
|
|
|
+ */
|
|
|
+ return sh_dmae_prep_sg(sh_chan, sgl, sg_len, ¶m->config->addr,
|
|
|
+ direction, flags);
|
|
|
+}
|
|
|
+
|
|
|
+static void sh_dmae_terminate_all(struct dma_chan *chan)
|
|
|
+{
|
|
|
+ struct sh_dmae_chan *sh_chan = to_sh_chan(chan);
|
|
|
+
|
|
|
+ if (!chan)
|
|
|
+ return;
|
|
|
+
|
|
|
+ sh_dmae_chan_ld_cleanup(sh_chan, true);
|
|
|
+}
|
|
|
+
|
|
|
static dma_async_tx_callback __ld_cleanup(struct sh_dmae_chan *sh_chan, bool all)
|
|
|
{
|
|
|
struct sh_desc *desc, *_desc;
|
|
@@ -508,7 +593,11 @@ static dma_async_tx_callback __ld_cleanup(struct sh_dmae_chan *sh_chan, bool all
|
|
|
cookie = tx->cookie;
|
|
|
|
|
|
if (desc->mark == DESC_COMPLETED && desc->chunks == 1) {
|
|
|
- BUG_ON(sh_chan->completed_cookie != desc->cookie - 1);
|
|
|
+ if (sh_chan->completed_cookie != desc->cookie - 1)
|
|
|
+ dev_dbg(sh_chan->dev,
|
|
|
+ "Completing cookie %d, expected %d\n",
|
|
|
+ desc->cookie,
|
|
|
+ sh_chan->completed_cookie + 1);
|
|
|
sh_chan->completed_cookie = desc->cookie;
|
|
|
}
|
|
|
|
|
@@ -581,7 +670,7 @@ static void sh_chan_xfer_ld_queue(struct sh_dmae_chan *sh_chan)
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- /* Find the first un-transfer desciptor */
|
|
|
+ /* Find the first not transferred desciptor */
|
|
|
list_for_each_entry(sd, &sh_chan->ld_queue, node)
|
|
|
if (sd->mark == DESC_SUBMITTED) {
|
|
|
/* Get the ld start address from ld_queue */
|
|
@@ -685,11 +774,14 @@ static void dmae_do_tasklet(unsigned long data)
|
|
|
struct sh_dmae_chan *sh_chan = (struct sh_dmae_chan *)data;
|
|
|
struct sh_desc *desc;
|
|
|
u32 sar_buf = sh_dmae_readl(sh_chan, SAR);
|
|
|
+ u32 dar_buf = sh_dmae_readl(sh_chan, DAR);
|
|
|
|
|
|
spin_lock(&sh_chan->desc_lock);
|
|
|
list_for_each_entry(desc, &sh_chan->ld_queue, node) {
|
|
|
- if ((desc->hw.sar + desc->hw.tcr) == sar_buf &&
|
|
|
- desc->mark == DESC_SUBMITTED) {
|
|
|
+ if (desc->mark == DESC_SUBMITTED &&
|
|
|
+ ((desc->direction == DMA_FROM_DEVICE &&
|
|
|
+ (desc->hw.dar + desc->hw.tcr) == dar_buf) ||
|
|
|
+ (desc->hw.sar + desc->hw.tcr) == sar_buf)) {
|
|
|
dev_dbg(sh_chan->dev, "done #%d@%p dst %u\n",
|
|
|
desc->async_tx.cookie, &desc->async_tx,
|
|
|
desc->hw.dar);
|
|
@@ -762,7 +854,7 @@ static int __devinit sh_dmae_chan_probe(struct sh_dmae_device *shdev, int id)
|
|
|
}
|
|
|
|
|
|
snprintf(new_sh_chan->dev_id, sizeof(new_sh_chan->dev_id),
|
|
|
- "sh-dmae%d", new_sh_chan->id);
|
|
|
+ "sh-dmae%d", new_sh_chan->id);
|
|
|
|
|
|
/* set up channel irq */
|
|
|
err = request_irq(irq, &sh_dmae_interrupt, irqflags,
|
|
@@ -773,11 +865,6 @@ static int __devinit sh_dmae_chan_probe(struct sh_dmae_device *shdev, int id)
|
|
|
goto err_no_irq;
|
|
|
}
|
|
|
|
|
|
- /* CHCR register control function */
|
|
|
- new_sh_chan->set_chcr = dmae_set_chcr;
|
|
|
- /* DMARS register control function */
|
|
|
- new_sh_chan->set_dmars = dmae_set_dmars;
|
|
|
-
|
|
|
shdev->chan[id] = new_sh_chan;
|
|
|
return 0;
|
|
|
|
|
@@ -848,12 +935,19 @@ static int __init sh_dmae_probe(struct platform_device *pdev)
|
|
|
INIT_LIST_HEAD(&shdev->common.channels);
|
|
|
|
|
|
dma_cap_set(DMA_MEMCPY, shdev->common.cap_mask);
|
|
|
+ dma_cap_set(DMA_SLAVE, shdev->common.cap_mask);
|
|
|
+
|
|
|
shdev->common.device_alloc_chan_resources
|
|
|
= sh_dmae_alloc_chan_resources;
|
|
|
shdev->common.device_free_chan_resources = sh_dmae_free_chan_resources;
|
|
|
shdev->common.device_prep_dma_memcpy = sh_dmae_prep_memcpy;
|
|
|
shdev->common.device_is_tx_complete = sh_dmae_is_complete;
|
|
|
shdev->common.device_issue_pending = sh_dmae_memcpy_issue_pending;
|
|
|
+
|
|
|
+ /* Compulsory for DMA_SLAVE fields */
|
|
|
+ shdev->common.device_prep_slave_sg = sh_dmae_prep_slave_sg;
|
|
|
+ shdev->common.device_terminate_all = sh_dmae_terminate_all;
|
|
|
+
|
|
|
shdev->common.dev = &pdev->dev;
|
|
|
/* Default transfer size of 32 bytes requires 32-byte alignment */
|
|
|
shdev->common.copy_align = 5;
|