dmaengine: dw: fix byte order of hw descriptor fields

If the DMA controller uses a different byte order than the host CPU,
the hardware linked list descriptor fields need to be byte-swapped.

This patch makes the driver write these fields using the same byte
order it uses for mmio accesses to the DMA engine. I do not know
if this is guaranteed to always be correct.

Signed-off-by: Mans Rullgard <mans@mansr.com>
Acked-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Vinod Koul <vinod.koul@intel.com>
This commit is contained in:
Mans Rullgard 2016-03-18 16:24:43 +02:00 committed by Vinod Koul
parent bb3450ad0e
commit df1f3a2305
2 changed files with 85 additions and 66 deletions

View file

@ -201,12 +201,12 @@ static inline void dwc_do_single_block(struct dw_dma_chan *dwc,
* Software emulation of LLP mode relies on interrupts to continue * Software emulation of LLP mode relies on interrupts to continue
* multi block transfer. * multi block transfer.
*/ */
ctllo = desc->lli.ctllo | DWC_CTLL_INT_EN; ctllo = lli_read(desc, ctllo) | DWC_CTLL_INT_EN;
channel_writel(dwc, SAR, desc->lli.sar); channel_writel(dwc, SAR, lli_read(desc, sar));
channel_writel(dwc, DAR, desc->lli.dar); channel_writel(dwc, DAR, lli_read(desc, dar));
channel_writel(dwc, CTL_LO, ctllo); channel_writel(dwc, CTL_LO, ctllo);
channel_writel(dwc, CTL_HI, desc->lli.ctlhi); channel_writel(dwc, CTL_HI, lli_read(desc, ctlhi));
channel_set_bit(dw, CH_EN, dwc->mask); channel_set_bit(dw, CH_EN, dwc->mask);
/* Move pointer to next descriptor */ /* Move pointer to next descriptor */
@ -424,7 +424,7 @@ static void dwc_scan_descriptors(struct dw_dma *dw, struct dw_dma_chan *dwc)
} }
/* Check first descriptors llp */ /* Check first descriptors llp */
if (desc->lli.llp == llp) { if (lli_read(desc, llp) == llp) {
/* This one is currently in progress */ /* This one is currently in progress */
dwc->residue -= dwc_get_sent(dwc); dwc->residue -= dwc_get_sent(dwc);
spin_unlock_irqrestore(&dwc->lock, flags); spin_unlock_irqrestore(&dwc->lock, flags);
@ -433,7 +433,7 @@ static void dwc_scan_descriptors(struct dw_dma *dw, struct dw_dma_chan *dwc)
dwc->residue -= desc->len; dwc->residue -= desc->len;
list_for_each_entry(child, &desc->tx_list, desc_node) { list_for_each_entry(child, &desc->tx_list, desc_node) {
if (child->lli.llp == llp) { if (lli_read(child, llp) == llp) {
/* Currently in progress */ /* Currently in progress */
dwc->residue -= dwc_get_sent(dwc); dwc->residue -= dwc_get_sent(dwc);
spin_unlock_irqrestore(&dwc->lock, flags); spin_unlock_irqrestore(&dwc->lock, flags);
@ -461,10 +461,14 @@ static void dwc_scan_descriptors(struct dw_dma *dw, struct dw_dma_chan *dwc)
spin_unlock_irqrestore(&dwc->lock, flags); spin_unlock_irqrestore(&dwc->lock, flags);
} }
static inline void dwc_dump_lli(struct dw_dma_chan *dwc, struct dw_lli *lli) static inline void dwc_dump_lli(struct dw_dma_chan *dwc, struct dw_desc *desc)
{ {
dev_crit(chan2dev(&dwc->chan), " desc: s0x%x d0x%x l0x%x c0x%x:%x\n", dev_crit(chan2dev(&dwc->chan), " desc: s0x%x d0x%x l0x%x c0x%x:%x\n",
lli->sar, lli->dar, lli->llp, lli->ctlhi, lli->ctllo); lli_read(desc, sar),
lli_read(desc, dar),
lli_read(desc, llp),
lli_read(desc, ctlhi),
lli_read(desc, ctllo));
} }
static void dwc_handle_error(struct dw_dma *dw, struct dw_dma_chan *dwc) static void dwc_handle_error(struct dw_dma *dw, struct dw_dma_chan *dwc)
@ -500,9 +504,9 @@ static void dwc_handle_error(struct dw_dma *dw, struct dw_dma_chan *dwc)
*/ */
dev_WARN(chan2dev(&dwc->chan), "Bad descriptor submitted for DMA!\n" dev_WARN(chan2dev(&dwc->chan), "Bad descriptor submitted for DMA!\n"
" cookie: %d\n", bad_desc->txd.cookie); " cookie: %d\n", bad_desc->txd.cookie);
dwc_dump_lli(dwc, &bad_desc->lli); dwc_dump_lli(dwc, bad_desc);
list_for_each_entry(child, &bad_desc->tx_list, desc_node) list_for_each_entry(child, &bad_desc->tx_list, desc_node)
dwc_dump_lli(dwc, &child->lli); dwc_dump_lli(dwc, child);
spin_unlock_irqrestore(&dwc->lock, flags); spin_unlock_irqrestore(&dwc->lock, flags);
@ -575,7 +579,7 @@ static void dwc_handle_cyclic(struct dw_dma *dw, struct dw_dma_chan *dwc,
dma_writel(dw, CLEAR.XFER, dwc->mask); dma_writel(dw, CLEAR.XFER, dwc->mask);
for (i = 0; i < dwc->cdesc->periods; i++) for (i = 0; i < dwc->cdesc->periods; i++)
dwc_dump_lli(dwc, &dwc->cdesc->desc[i]->lli); dwc_dump_lli(dwc, dwc->cdesc->desc[i]);
spin_unlock_irqrestore(&dwc->lock, flags); spin_unlock_irqrestore(&dwc->lock, flags);
} }
@ -734,25 +738,24 @@ dwc_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dest, dma_addr_t src,
if (!desc) if (!desc)
goto err_desc_get; goto err_desc_get;
desc->lli.sar = src + offset; lli_write(desc, sar, src + offset);
desc->lli.dar = dest + offset; lli_write(desc, dar, dest + offset);
desc->lli.ctllo = ctllo; lli_write(desc, ctllo, ctllo);
desc->lli.ctlhi = xfer_count; lli_write(desc, ctlhi, xfer_count);
desc->len = xfer_count << src_width; desc->len = xfer_count << src_width;
if (!first) { if (!first) {
first = desc; first = desc;
} else { } else {
prev->lli.llp = desc->txd.phys; lli_write(prev, llp, desc->txd.phys);
list_add_tail(&desc->desc_node, list_add_tail(&desc->desc_node, &first->tx_list);
&first->tx_list);
} }
prev = desc; prev = desc;
} }
if (flags & DMA_PREP_INTERRUPT) if (flags & DMA_PREP_INTERRUPT)
/* Trigger interrupt after last block */ /* Trigger interrupt after last block */
prev->lli.ctllo |= DWC_CTLL_INT_EN; lli_set(prev, ctllo, DWC_CTLL_INT_EN);
prev->lli.llp = 0; prev->lli.llp = 0;
first->txd.flags = flags; first->txd.flags = flags;
@ -822,9 +825,9 @@ dwc_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
if (!desc) if (!desc)
goto err_desc_get; goto err_desc_get;
desc->lli.sar = mem; lli_write(desc, sar, mem);
desc->lli.dar = reg; lli_write(desc, dar, reg);
desc->lli.ctllo = ctllo | DWC_CTLL_SRC_WIDTH(mem_width); lli_write(desc, ctllo, ctllo | DWC_CTLL_SRC_WIDTH(mem_width));
if ((len >> mem_width) > dwc->block_size) { if ((len >> mem_width) > dwc->block_size) {
dlen = dwc->block_size << mem_width; dlen = dwc->block_size << mem_width;
mem += dlen; mem += dlen;
@ -834,15 +837,14 @@ dwc_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
len = 0; len = 0;
} }
desc->lli.ctlhi = dlen >> mem_width; lli_write(desc, ctlhi, dlen >> mem_width);
desc->len = dlen; desc->len = dlen;
if (!first) { if (!first) {
first = desc; first = desc;
} else { } else {
prev->lli.llp = desc->txd.phys; lli_write(prev, llp, desc->txd.phys);
list_add_tail(&desc->desc_node, list_add_tail(&desc->desc_node, &first->tx_list);
&first->tx_list);
} }
prev = desc; prev = desc;
total_len += dlen; total_len += dlen;
@ -879,9 +881,9 @@ dwc_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
if (!desc) if (!desc)
goto err_desc_get; goto err_desc_get;
desc->lli.sar = reg; lli_write(desc, sar, reg);
desc->lli.dar = mem; lli_write(desc, dar, mem);
desc->lli.ctllo = ctllo | DWC_CTLL_DST_WIDTH(mem_width); lli_write(desc, ctllo, ctllo | DWC_CTLL_DST_WIDTH(mem_width));
if ((len >> reg_width) > dwc->block_size) { if ((len >> reg_width) > dwc->block_size) {
dlen = dwc->block_size << reg_width; dlen = dwc->block_size << reg_width;
mem += dlen; mem += dlen;
@ -890,15 +892,14 @@ dwc_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
dlen = len; dlen = len;
len = 0; len = 0;
} }
desc->lli.ctlhi = dlen >> reg_width; lli_write(desc, ctlhi, dlen >> reg_width);
desc->len = dlen; desc->len = dlen;
if (!first) { if (!first) {
first = desc; first = desc;
} else { } else {
prev->lli.llp = desc->txd.phys; lli_write(prev, llp, desc->txd.phys);
list_add_tail(&desc->desc_node, list_add_tail(&desc->desc_node, &first->tx_list);
&first->tx_list);
} }
prev = desc; prev = desc;
total_len += dlen; total_len += dlen;
@ -913,7 +914,7 @@ dwc_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
if (flags & DMA_PREP_INTERRUPT) if (flags & DMA_PREP_INTERRUPT)
/* Trigger interrupt after last block */ /* Trigger interrupt after last block */
prev->lli.ctllo |= DWC_CTLL_INT_EN; lli_set(prev, ctllo, DWC_CTLL_INT_EN);
prev->lli.llp = 0; prev->lli.llp = 0;
first->total_len = total_len; first->total_len = total_len;
@ -1400,50 +1401,50 @@ struct dw_cyclic_desc *dw_dma_cyclic_prep(struct dma_chan *chan,
switch (direction) { switch (direction) {
case DMA_MEM_TO_DEV: case DMA_MEM_TO_DEV:
desc->lli.dar = sconfig->dst_addr; lli_write(desc, dar, sconfig->dst_addr);
desc->lli.sar = buf_addr + (period_len * i); lli_write(desc, sar, buf_addr + period_len * i);
desc->lli.ctllo = (DWC_DEFAULT_CTLLO(chan) lli_write(desc, ctllo, (DWC_DEFAULT_CTLLO(chan)
| DWC_CTLL_DST_WIDTH(reg_width) | DWC_CTLL_DST_WIDTH(reg_width)
| DWC_CTLL_SRC_WIDTH(reg_width) | DWC_CTLL_SRC_WIDTH(reg_width)
| DWC_CTLL_DST_FIX | DWC_CTLL_DST_FIX
| DWC_CTLL_SRC_INC | DWC_CTLL_SRC_INC
| DWC_CTLL_INT_EN); | DWC_CTLL_INT_EN));
desc->lli.ctllo |= sconfig->device_fc ? lli_set(desc, ctllo, sconfig->device_fc ?
DWC_CTLL_FC(DW_DMA_FC_P_M2P) : DWC_CTLL_FC(DW_DMA_FC_P_M2P) :
DWC_CTLL_FC(DW_DMA_FC_D_M2P); DWC_CTLL_FC(DW_DMA_FC_D_M2P));
break; break;
case DMA_DEV_TO_MEM: case DMA_DEV_TO_MEM:
desc->lli.dar = buf_addr + (period_len * i); lli_write(desc, dar, buf_addr + period_len * i);
desc->lli.sar = sconfig->src_addr; lli_write(desc, sar, sconfig->src_addr);
desc->lli.ctllo = (DWC_DEFAULT_CTLLO(chan) lli_write(desc, ctllo, (DWC_DEFAULT_CTLLO(chan)
| DWC_CTLL_SRC_WIDTH(reg_width) | DWC_CTLL_SRC_WIDTH(reg_width)
| DWC_CTLL_DST_WIDTH(reg_width) | DWC_CTLL_DST_WIDTH(reg_width)
| DWC_CTLL_DST_INC | DWC_CTLL_DST_INC
| DWC_CTLL_SRC_FIX | DWC_CTLL_SRC_FIX
| DWC_CTLL_INT_EN); | DWC_CTLL_INT_EN));
desc->lli.ctllo |= sconfig->device_fc ? lli_set(desc, ctllo, sconfig->device_fc ?
DWC_CTLL_FC(DW_DMA_FC_P_P2M) : DWC_CTLL_FC(DW_DMA_FC_P_P2M) :
DWC_CTLL_FC(DW_DMA_FC_D_P2M); DWC_CTLL_FC(DW_DMA_FC_D_P2M));
break; break;
default: default:
break; break;
} }
desc->lli.ctlhi = (period_len >> reg_width); lli_write(desc, ctlhi, period_len >> reg_width);
cdesc->desc[i] = desc; cdesc->desc[i] = desc;
if (last) if (last)
last->lli.llp = desc->txd.phys; lli_write(last, llp, desc->txd.phys);
last = desc; last = desc;
} }
/* Let's make a cyclic list */ /* Let's make a cyclic list */
last->lli.llp = cdesc->desc[0]->txd.phys; lli_write(last, llp, cdesc->desc[0]->txd.phys);
dev_dbg(chan2dev(&dwc->chan), dev_dbg(chan2dev(&dwc->chan),
"cyclic prepared buf %pad len %zu period %zu periods %d\n", "cyclic prepared buf %pad len %zu period %zu periods %d\n",

View file

@ -308,26 +308,44 @@ static inline struct dw_dma *to_dw_dma(struct dma_device *ddev)
return container_of(ddev, struct dw_dma, dma); return container_of(ddev, struct dw_dma, dma);
} }
#ifdef CONFIG_DW_DMAC_BIG_ENDIAN_IO
typedef __be32 __dw32;
#else
typedef __le32 __dw32;
#endif
/* LLI == Linked List Item; a.k.a. DMA block descriptor */ /* LLI == Linked List Item; a.k.a. DMA block descriptor */
struct dw_lli { struct dw_lli {
/* values that are not changed by hardware */ /* values that are not changed by hardware */
u32 sar; __dw32 sar;
u32 dar; __dw32 dar;
u32 llp; /* chain to next lli */ __dw32 llp; /* chain to next lli */
u32 ctllo; __dw32 ctllo;
/* values that may get written back: */ /* values that may get written back: */
u32 ctlhi; __dw32 ctlhi;
/* sstat and dstat can snapshot peripheral register state. /* sstat and dstat can snapshot peripheral register state.
* silicon config may discard either or both... * silicon config may discard either or both...
*/ */
u32 sstat; __dw32 sstat;
u32 dstat; __dw32 dstat;
}; };
struct dw_desc { struct dw_desc {
/* FIRST values the hardware uses */ /* FIRST values the hardware uses */
struct dw_lli lli; struct dw_lli lli;
#ifdef CONFIG_DW_DMAC_BIG_ENDIAN_IO
#define lli_set(d, reg, v) ((d)->lli.reg |= cpu_to_be32(v))
#define lli_clear(d, reg, v) ((d)->lli.reg &= ~cpu_to_be32(v))
#define lli_read(d, reg) be32_to_cpu((d)->lli.reg)
#define lli_write(d, reg, v) ((d)->lli.reg = cpu_to_be32(v))
#else
#define lli_set(d, reg, v) ((d)->lli.reg |= cpu_to_le32(v))
#define lli_clear(d, reg, v) ((d)->lli.reg &= ~cpu_to_le32(v))
#define lli_read(d, reg) le32_to_cpu((d)->lli.reg)
#define lli_write(d, reg, v) ((d)->lli.reg = cpu_to_le32(v))
#endif
/* THEN values for driver housekeeping */ /* THEN values for driver housekeeping */
struct list_head desc_node; struct list_head desc_node;
struct list_head tx_list; struct list_head tx_list;