581 lines
18 KiB
C
581 lines
18 KiB
C
|
/***************************************************************************
|
||
|
* __________ __ ___.
|
||
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||
|
* \/ \/ \/ \/ \/
|
||
|
* $Id$
|
||
|
*
|
||
|
* Copyright (C) 2014 by Cástor Muñoz
|
||
|
*
|
||
|
* 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.
|
||
|
*
|
||
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||
|
* KIND, either express or implied.
|
||
|
*
|
||
|
****************************************************************************/
|
||
|
#include "config.h"
|
||
|
#include <stddef.h>
|
||
|
#include "system.h"
|
||
|
#include "pl080.h"
|
||
|
#include "panic.h"
|
||
|
|
||
|
/*
|
||
|
* ARM PrimeCell PL080 Multiple Master DMA controller
|
||
|
*/
|
||
|
|
||
|
/*#define PANIC_DEBUG*/
|
||
|
#ifdef PANIC_DEBUG
|
||
|
void dmac_ch_panicf(const char *fn, struct dmac_ch* ch)
|
||
|
{
|
||
|
char *err = NULL;
|
||
|
|
||
|
if (!ch)
|
||
|
err = "NULL channel";
|
||
|
else if (!ch->dmac)
|
||
|
err = "NULL ch->dmac";
|
||
|
else if (ch->dmac->ch_l[ch->prio] != ch)
|
||
|
err = "not initialized channel";
|
||
|
|
||
|
if (err)
|
||
|
panicf("%s(): <%d> %s", fn, ch ? (int)ch->prio : -1, err);
|
||
|
}
|
||
|
#define PANIC_DEBUG_CHANNEL(ch) dmac_ch_panicf(__func__,(ch))
|
||
|
#else
|
||
|
#define PANIC_DEBUG_CHANNEL(ch) {}
|
||
|
#endif
|
||
|
|
||
|
/* task helpers */
|
||
|
static inline struct dmac_tsk *dmac_ch_tsk_by_idx(
|
||
|
struct dmac_ch *ch, uint32_t idx)
|
||
|
{
|
||
|
return ch->tskbuf + (idx & ch->tskbuf_mask);
|
||
|
}
|
||
|
#define CH_TSK_TOP(ch) dmac_ch_tsk_by_idx((ch), (ch)->tasks_queued)
|
||
|
#define CH_TSK_TAIL(ch) dmac_ch_tsk_by_idx((ch), (ch)->tasks_done)
|
||
|
|
||
|
static inline bool dmac_ch_task_queue_empty(struct dmac_ch *ch)
|
||
|
{
|
||
|
return (ch->tasks_done == ch->tasks_queued);
|
||
|
}
|
||
|
|
||
|
/* enable/disable DMA controller */
|
||
|
static inline void dmac_hw_enable(struct dmac *dmac)
|
||
|
{
|
||
|
DMACCONFIG(dmac->baddr) |= DMACCONFIG_E_BIT;
|
||
|
}
|
||
|
|
||
|
static inline void dmac_hw_disable(struct dmac *dmac)
|
||
|
{
|
||
|
DMACCONFIG(dmac->baddr) &= ~DMACCONFIG_E_BIT;
|
||
|
}
|
||
|
|
||
|
/* enable/disable DMA channel */
|
||
|
static inline void dmac_ch_enable(struct dmac_ch *ch)
|
||
|
{
|
||
|
DMACCxCONFIG(ch->baddr) |= DMACCxCONFIG_E_BIT;
|
||
|
}
|
||
|
|
||
|
static inline void dmac_ch_disable(struct dmac_ch *ch)
|
||
|
{
|
||
|
uint32_t baddr = ch->baddr;
|
||
|
|
||
|
/* Disable the channel, clears the FIFO after
|
||
|
completing current AHB transfer */
|
||
|
DMACCxCONFIG(baddr) &= ~DMACCxCONFIG_E_BIT;
|
||
|
/* Wait for it to go inactive */
|
||
|
while (DMACCxCONFIG(baddr) & DMACCxCONFIG_A_BIT);
|
||
|
}
|
||
|
|
||
|
#if 0
|
||
|
static void dmac_ch_halt(struct dmac_ch *ch)
|
||
|
{
|
||
|
uint32_t baddr = ch->baddr;
|
||
|
|
||
|
/* Halt the channel, ignores subsequent DMA requests,
|
||
|
the contents of the FIFO are drained */
|
||
|
DMACCxCONFIG(baddr) |= DMACCxCONFIG_H_BIT;
|
||
|
/* Wait for it to go inactive */
|
||
|
while (DMACCxCONFIG(baddr) & DMACCxCONFIG_A_BIT);
|
||
|
/* Disable channel and restore Halt bit */
|
||
|
DMACCxCONFIG(baddr) &= ~(DMACCxCONFIG_H_BIT | DMACCxCONFIG_E_BIT);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/* launch next task in queue */
|
||
|
static void ICODE_ATTR dmac_ch_run(struct dmac_ch *ch)
|
||
|
{
|
||
|
struct dmac *dmac = ch->dmac;
|
||
|
|
||
|
if (!dmac->ch_run_status)
|
||
|
dmac_hw_enable(dmac);
|
||
|
dmac->ch_run_status |= (1 << ch->prio);
|
||
|
|
||
|
/* Clear any pending interrupts leftover from previous operation */
|
||
|
/*DMACINTTCCLR(dmac->baddr) = (1 << ch->prio);*/ /* not needed */
|
||
|
|
||
|
/* copy whole LLI to HW registers */
|
||
|
*DMACCxLLI(ch->baddr) = *(CH_TSK_TAIL(ch)->start_lli);
|
||
|
|
||
|
dmac_ch_enable(ch);
|
||
|
}
|
||
|
|
||
|
static void ICODE_ATTR dmac_ch_abort(struct dmac_ch* ch)
|
||
|
{
|
||
|
struct dmac *dmac = ch->dmac;
|
||
|
|
||
|
dmac_ch_disable(ch);
|
||
|
|
||
|
/* Clear any pending interrupt */
|
||
|
DMACINTTCCLR(dmac->baddr) = (1 << ch->prio);
|
||
|
|
||
|
dmac->ch_run_status &= ~(1 << ch->prio);
|
||
|
if (!dmac->ch_run_status)
|
||
|
dmac_hw_disable(dmac);
|
||
|
}
|
||
|
|
||
|
/* ISR */
|
||
|
static inline void dmac_ch_callback(struct dmac_ch *ch)
|
||
|
{
|
||
|
PANIC_DEBUG_CHANNEL(ch);
|
||
|
|
||
|
/* backup current task cb_data */
|
||
|
void *cb_data = CH_TSK_TAIL(ch)->cb_data;
|
||
|
|
||
|
/* mark current task as finished (resources can be reused) */
|
||
|
ch->tasks_done++;
|
||
|
|
||
|
/* launch next DMA task */
|
||
|
if (ch->queue_mode == QUEUE_NORMAL)
|
||
|
if (!dmac_ch_task_queue_empty(ch))
|
||
|
dmac_ch_run(ch);
|
||
|
|
||
|
/* run user callback, new tasks could be launched/queued here */
|
||
|
if (ch->cb_fn)
|
||
|
ch->cb_fn(cb_data);
|
||
|
|
||
|
/* disable DMA channel if there are no running tasks */
|
||
|
if (dmac_ch_task_queue_empty(ch))
|
||
|
dmac_ch_abort(ch);
|
||
|
}
|
||
|
|
||
|
void ICODE_ATTR dmac_callback(struct dmac *dmac)
|
||
|
{
|
||
|
#ifdef PANIC_DEBUG
|
||
|
if (!dmac)
|
||
|
panicf("dmac_callback(): NULL dmac");
|
||
|
#endif
|
||
|
|
||
|
unsigned int ch_n;
|
||
|
uint32_t baddr = dmac->baddr;
|
||
|
uint32_t intsts = DMACINTSTS(baddr);
|
||
|
|
||
|
/* Lowest channel index is serviced first */
|
||
|
for (ch_n = 0; ch_n < DMAC_CH_COUNT; ch_n++) {
|
||
|
if ((intsts & (1 << ch_n))) {
|
||
|
if (DMACINTERRSTS(baddr) & (1 << ch_n))
|
||
|
panicf("DMA ch%d: HW error", ch_n);
|
||
|
|
||
|
/* clear terminal count interrupt */
|
||
|
DMACINTTCCLR(baddr) = (1 << ch_n);
|
||
|
|
||
|
dmac_ch_callback(dmac->ch_l[ch_n]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* API
|
||
|
*/
|
||
|
void dmac_open(struct dmac *dmac)
|
||
|
{
|
||
|
uint32_t baddr = dmac->baddr;
|
||
|
int ch_n;
|
||
|
|
||
|
dmac_hw_enable(dmac);
|
||
|
|
||
|
DMACCONFIG(baddr) = ((dmac->m1 & DMACCONFIG_M1_MSK) << DMACCONFIG_M1_POS)
|
||
|
| ((dmac->m2 & DMACCONFIG_M2_MSK) << DMACCONFIG_M2_POS);
|
||
|
|
||
|
for (ch_n = 0; ch_n < DMAC_CH_COUNT; ch_n++) {
|
||
|
DMACCxCONFIG(DMAC_CH_BASE(baddr, ch_n)) = 0; /* disable channel */
|
||
|
dmac->ch_l[ch_n] = NULL;
|
||
|
}
|
||
|
dmac->ch_run_status = 0;
|
||
|
|
||
|
/* clear channel interrupts */
|
||
|
DMACINTTCCLR(baddr) = 0xff;
|
||
|
DMACINTERRCLR(baddr) = 0xff;
|
||
|
|
||
|
dmac_hw_disable(dmac);
|
||
|
}
|
||
|
|
||
|
void dmac_ch_init(struct dmac_ch *ch, struct dmac_ch_cfg *cfg)
|
||
|
{
|
||
|
#ifdef PANIC_DEBUG
|
||
|
if (!ch)
|
||
|
panicf("%s(): NULL channel", __func__);
|
||
|
else if (!ch->dmac)
|
||
|
panicf("%s(): NULL ch->dmac", __func__);
|
||
|
else if (ch->dmac->ch_l[ch->prio])
|
||
|
panicf("%s(): channel %d already initilized", __func__, ch->prio);
|
||
|
uint32_t align_mask = (1 << MIN(cfg->swidth, cfg->dwidth)) - 1;
|
||
|
if (ch->cfg->lli_xfer_max_count & align_mask)
|
||
|
panicf("%s(): bad bus width: sw=%u dw=%u max_cnt=%u", __func__,
|
||
|
cfg->swidth, cfg->dwidth, ch->cfg->lli_xfer_max_count);
|
||
|
#endif
|
||
|
|
||
|
struct dmac *dmac = ch->dmac;
|
||
|
int ch_n = ch->prio;
|
||
|
|
||
|
dmac->ch_l[ch_n] = ch;
|
||
|
|
||
|
ch->baddr = DMAC_CH_BASE(dmac->baddr, ch_n);
|
||
|
ch->llibuf_top = ch->llibuf;
|
||
|
ch->tasks_queued = 0;
|
||
|
ch->tasks_done = 0;
|
||
|
ch->cfg = cfg;
|
||
|
|
||
|
ch->control =
|
||
|
((cfg->sbsize & DMACCxCONTROL_SBSIZE_MSK) << DMACCxCONTROL_SBSIZE_POS) |
|
||
|
((cfg->dbsize & DMACCxCONTROL_DBSIZE_MSK) << DMACCxCONTROL_DBSIZE_POS) |
|
||
|
((cfg->swidth & DMACCxCONTROL_SWIDTH_MSK) << DMACCxCONTROL_SWIDTH_POS) |
|
||
|
((cfg->dwidth & DMACCxCONTROL_DWIDTH_MSK) << DMACCxCONTROL_DWIDTH_POS) |
|
||
|
((cfg->sbus & DMACCxCONTROL_S_MSK) << DMACCxCONTROL_S_POS) |
|
||
|
((cfg->dbus & DMACCxCONTROL_D_MSK) << DMACCxCONTROL_D_POS) |
|
||
|
((cfg->sinc & DMACCxCONTROL_SI_MSK) << DMACCxCONTROL_SI_POS) |
|
||
|
((cfg->dinc & DMACCxCONTROL_DI_MSK) << DMACCxCONTROL_DI_POS) |
|
||
|
((cfg->prot & DMACCxCONTROL_PROT_MSK) << DMACCxCONTROL_PROT_POS);
|
||
|
|
||
|
/* flow control notes:
|
||
|
* - currently only master modes are supported (FLOWCNTRL_x_DMA).
|
||
|
* - must use DMAC_PERI_NONE when srcperi and/or dstperi are memory.
|
||
|
*/
|
||
|
uint32_t flowcntrl = (((cfg->srcperi != DMAC_PERI_NONE) << 1) |
|
||
|
(cfg->dstperi != DMAC_PERI_NONE)) << DMACCxCONFIG_FLOWCNTRL_POS;
|
||
|
|
||
|
DMACCxCONFIG(ch->baddr) =
|
||
|
((cfg->srcperi & DMACCxCONFIG_SRCPERI_MSK) << DMACCxCONFIG_SRCPERI_POS) |
|
||
|
((cfg->dstperi & DMACCxCONFIG_DESTPERI_MSK) << DMACCxCONFIG_DESTPERI_POS) |
|
||
|
flowcntrl | DMACCxCONFIG_IE_BIT | DMACCxCONFIG_ITC_BIT;
|
||
|
}
|
||
|
|
||
|
void dmac_ch_lock_int(struct dmac_ch *ch)
|
||
|
{
|
||
|
PANIC_DEBUG_CHANNEL(ch);
|
||
|
|
||
|
int flags = disable_irq_save();
|
||
|
DMACCxCONFIG(ch->baddr) &= ~DMACCxCONFIG_ITC_BIT;
|
||
|
restore_irq(flags);
|
||
|
}
|
||
|
|
||
|
void dmac_ch_unlock_int(struct dmac_ch *ch)
|
||
|
{
|
||
|
PANIC_DEBUG_CHANNEL(ch);
|
||
|
|
||
|
int flags = disable_irq_save();
|
||
|
DMACCxCONFIG(ch->baddr) |= DMACCxCONFIG_ITC_BIT;
|
||
|
restore_irq(flags);
|
||
|
}
|
||
|
|
||
|
/* 1D->2D DMA transfers:
|
||
|
*
|
||
|
* srcaddr: aaaaaaaaaaabbbbbbbbbbbccccccc
|
||
|
* <- size ->
|
||
|
* <- width -><- width -><- r ->
|
||
|
*
|
||
|
* dstaddr: aaaaaaaaaaa.....
|
||
|
* dstaddr + stride: bbbbbbbbbbb.....
|
||
|
* dstaddr + 2*stride: ccccccc.........
|
||
|
* <- stride ->
|
||
|
* <- width ->
|
||
|
*
|
||
|
* 1D->1D DMA transfers:
|
||
|
*
|
||
|
* If 'width'=='stride', uses 'lli_xfer_max_count' for LLI count.
|
||
|
*
|
||
|
* Queue modes:
|
||
|
*
|
||
|
* QUEUE_NORMAL: each task in the queue is launched using a new
|
||
|
* DMA transfer once previous task is finished.
|
||
|
*
|
||
|
* QUEUE_LINK: when a task is queued, it is linked with the last
|
||
|
* queued task, creating a single continuous DMA transfer. New
|
||
|
* tasks must be queued while the channel is running, otherwise
|
||
|
* the continuous DMA transfer will be broken.
|
||
|
*
|
||
|
* Misc notes:
|
||
|
*
|
||
|
* Arguments 'size', 'width' and 'stride' are in bytes.
|
||
|
*
|
||
|
* Maximum supported 'width' depends on bus 'swidth' size, it is:
|
||
|
* maximum width = DMAC_LLI_MAX_COUNT << swidth
|
||
|
*
|
||
|
* User must supply 'srcaddr', 'dstaddr', 'width', 'size', 'stride'
|
||
|
* and 'lli_xfer_max_count' aligned to configured source and
|
||
|
* destination bus widths, otherwise transfers will be internally
|
||
|
* aligned by DMA hardware.
|
||
|
*/
|
||
|
#define LLI_COUNT(lli) ((lli)->control & DMACCxCONTROL_COUNT_MSK)
|
||
|
#define LNK2LLI(link) ((struct dmac_lli*) ((link) & ~3))
|
||
|
|
||
|
static inline void drain_write_buffer(void)
|
||
|
{
|
||
|
asm volatile (
|
||
|
"mcr p15, 0, %0, c7, c10, 4\n"
|
||
|
: : "r"(0));
|
||
|
}
|
||
|
|
||
|
static inline void clean_dcache_line(void volatile *addr)
|
||
|
{
|
||
|
asm volatile (
|
||
|
"mcr p15, 0, %0, c7, c10, 1\n" /* clean d-cache line by MVA */
|
||
|
: : "r"((uint32_t)addr & ~(CACHEALIGN_SIZE - 1)));
|
||
|
}
|
||
|
|
||
|
void ICODE_ATTR dmac_ch_queue_2d(
|
||
|
struct dmac_ch *ch, void *srcaddr, void *dstaddr,
|
||
|
size_t size, size_t width, size_t stride, void *cb_data)
|
||
|
{
|
||
|
#ifdef PANIC_DEBUG
|
||
|
PANIC_DEBUG_CHANNEL(ch);
|
||
|
uint32_t align = (1 << MIN(ch->cfg->swidth, ch->cfg->dwidth)) - 1;
|
||
|
if (((uint32_t)srcaddr | (uint32_t)dstaddr | size | width | stride) & align)
|
||
|
panicf("dmac_ch_queue_2d(): %d,%p,%p,%u,%u,%u: bad alignment?",
|
||
|
ch->prio, srcaddr, dstaddr, size, width, stride);
|
||
|
#endif
|
||
|
|
||
|
struct dmac_tsk *tsk;
|
||
|
unsigned int srcinc, dstinc;
|
||
|
uint32_t control, llibuf_idx;
|
||
|
struct dmac_lli volatile *lli, *next_lli;
|
||
|
|
||
|
/* get and fill new task */
|
||
|
tsk = CH_TSK_TOP(ch);
|
||
|
tsk->start_lli = ch->llibuf_top;
|
||
|
tsk->size = size;
|
||
|
tsk->cb_data = cb_data;
|
||
|
|
||
|
/* use maximum LLI transfer count for 1D->1D transfers */
|
||
|
if (width == stride)
|
||
|
width = stride = ch->cfg->lli_xfer_max_count << ch->cfg->swidth;
|
||
|
|
||
|
srcinc = (ch->cfg->sinc) ? stride : 0;
|
||
|
dstinc = (ch->cfg->dinc) ? width : 0;
|
||
|
|
||
|
size >>= ch->cfg->swidth;
|
||
|
width >>= ch->cfg->swidth;
|
||
|
|
||
|
/* fill LLI circular buffer */
|
||
|
control = ch->control | width;
|
||
|
lli = ch->llibuf_top;
|
||
|
llibuf_idx = lli - ch->llibuf;
|
||
|
|
||
|
while (1)
|
||
|
{
|
||
|
llibuf_idx = (llibuf_idx + 1) & ch->llibuf_mask;
|
||
|
next_lli = ch->llibuf + llibuf_idx;
|
||
|
|
||
|
lli->srcaddr = srcaddr;
|
||
|
lli->dstaddr = dstaddr;
|
||
|
|
||
|
if (size <= width)
|
||
|
break;
|
||
|
|
||
|
lli->link = (uint32_t)next_lli | ch->llibuf_bus;
|
||
|
lli->control = control;
|
||
|
|
||
|
srcaddr += srcinc;
|
||
|
dstaddr += dstinc;
|
||
|
size -= width;
|
||
|
|
||
|
/* clean dcache after completing a line */
|
||
|
if (((uint32_t)next_lli & (CACHEALIGN_SIZE - 1)) == 0)
|
||
|
clean_dcache_line(lli);
|
||
|
|
||
|
lli = next_lli;
|
||
|
}
|
||
|
/* last lli, enable terminal count interrupt */
|
||
|
lli->link = 0;
|
||
|
lli->control = ch->control | size | DMACCxCONTROL_I_BIT;
|
||
|
clean_dcache_line(lli);
|
||
|
drain_write_buffer();
|
||
|
|
||
|
tsk->end_lli = lli;
|
||
|
|
||
|
/* previous code is not protected against IRQs, it is fine to
|
||
|
enter the DMA interrupt handler while an application is
|
||
|
queuing a task, but the aplication must be protected when
|
||
|
doing concurrent queueing. */
|
||
|
|
||
|
int flags = disable_irq_save();
|
||
|
|
||
|
ch->llibuf_top = next_lli;
|
||
|
|
||
|
/* queue new task, launch it if it is the only queued task */
|
||
|
if (ch->tasks_done == ch->tasks_queued++)
|
||
|
{
|
||
|
dmac_ch_run(ch);
|
||
|
}
|
||
|
else if (ch->queue_mode == QUEUE_LINK)
|
||
|
{
|
||
|
uint32_t baddr = ch->baddr;
|
||
|
uint32_t link, hw_link;
|
||
|
|
||
|
link = (uint32_t)tsk->start_lli | ch->llibuf_bus;
|
||
|
hw_link = DMACCxLINK(baddr);
|
||
|
|
||
|
/* if it is a direct HW link, do it ASAP */
|
||
|
if (!hw_link) {
|
||
|
DMACCxLINK(baddr) = link;
|
||
|
/* check if the link was successful */
|
||
|
link = DMACCxLINK(baddr); /* dummy read for delay */
|
||
|
if (!(DMACCxCONFIG(baddr) & DMACCxCONFIG_E_BIT))
|
||
|
panicf("DMA ch%d: link error", ch->prio);
|
||
|
}
|
||
|
|
||
|
/* Locate the LLI where the new task must be linked. Link it even
|
||
|
if it was a direct HW link, dmac_ch_get_info() needs it. */
|
||
|
lli = dmac_ch_tsk_by_idx(ch, ch->tasks_queued-2)->end_lli;
|
||
|
lli->link = link;
|
||
|
clean_dcache_line(lli);
|
||
|
drain_write_buffer();
|
||
|
|
||
|
/* If the updated LLI was loaded by the HW while it was being
|
||
|
modified, verify that the HW link is correct. */
|
||
|
if (LNK2LLI(hw_link) == lli) {
|
||
|
uint32_t cur_hw_link = DMACCxLINK(baddr);
|
||
|
if ((cur_hw_link != hw_link) && (cur_hw_link != link))
|
||
|
DMACCxLINK(baddr) = link;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
restore_irq(flags);
|
||
|
}
|
||
|
|
||
|
void dmac_ch_stop(struct dmac_ch* ch)
|
||
|
{
|
||
|
PANIC_DEBUG_CHANNEL(ch);
|
||
|
|
||
|
int flags = disable_irq_save();
|
||
|
dmac_ch_abort(ch);
|
||
|
ch->tasks_done = ch->tasks_queued; /* clear queue */
|
||
|
restore_irq(flags);
|
||
|
}
|
||
|
|
||
|
bool dmac_ch_running(struct dmac_ch *ch)
|
||
|
{
|
||
|
PANIC_DEBUG_CHANNEL(ch);
|
||
|
|
||
|
int flags = disable_irq_save();
|
||
|
bool running = !dmac_ch_task_queue_empty(ch);
|
||
|
restore_irq(flags);
|
||
|
return running;
|
||
|
}
|
||
|
|
||
|
/* returns source or destination address of the actual LLI transfer,
|
||
|
remaining bytes for current task, and total remaining bytes */
|
||
|
void *dmac_ch_get_info(struct dmac_ch *ch, size_t *bytes, size_t *t_bytes)
|
||
|
{
|
||
|
PANIC_DEBUG_CHANNEL(ch);
|
||
|
|
||
|
void *cur_addr = NULL;
|
||
|
size_t count = 0, t_count = 0;
|
||
|
|
||
|
int flags = disable_irq_save();
|
||
|
|
||
|
if (!dmac_ch_task_queue_empty(ch))
|
||
|
{
|
||
|
struct dmac_lli volatile *cur_lli;
|
||
|
struct dmac_tsk *tsk;
|
||
|
uint32_t cur_task; /* index */
|
||
|
uint32_t baddr = ch->baddr;
|
||
|
|
||
|
/* Read DMA transfer progress:
|
||
|
*
|
||
|
* The recommended procedure (stop channel -> read progress ->
|
||
|
* relaunch channel) is problematic for real time transfers,
|
||
|
* specially when fast sample rates are combined with small
|
||
|
* pheripheral FIFOs.
|
||
|
*
|
||
|
* An experimental method is used, it is based on the results
|
||
|
* observed when reading the LLI registers at the instant they
|
||
|
* are being updated by the HW (using s5l8702, 2:1 CPU/AHB
|
||
|
* clock ratio):
|
||
|
* - SRCADDR may return erroneous/corrupted data
|
||
|
* - LINK and COUNT always returns valid data
|
||
|
* - it seems that HW internally updates LINK and COUNT
|
||
|
* 'atomically', this means that reading twice using the
|
||
|
* sequence LINK1->COUNT1->LINK2->COUNT2:
|
||
|
* if LINK1 == LINK2 then COUNT1 is consistent with LINK
|
||
|
* if LINK1 <> LINK2 then COUNT2 is consistent with LINK2
|
||
|
*/
|
||
|
uint32_t link, link2, control, control2;
|
||
|
|
||
|
/* HW read sequence */
|
||
|
link = DMACCxLINK(baddr);
|
||
|
control = DMACCxCONTROL(baddr);
|
||
|
link2 = DMACCxLINK(baddr);
|
||
|
control2 = DMACCxCONTROL(baddr);
|
||
|
|
||
|
if (link != link2) {
|
||
|
link = link2;
|
||
|
control = control2;
|
||
|
}
|
||
|
|
||
|
count = control & DMACCxCONTROL_COUNT_MSK; /* HW count */
|
||
|
|
||
|
cur_task = ch->tasks_done;
|
||
|
|
||
|
/* In QUEUE_LINK mode, when the task has just finished and is
|
||
|
* waiting to enter the interrupt handler, the readed HW data
|
||
|
* may correspont to the next linked task. Check it and update
|
||
|
* real cur_task accordly.
|
||
|
*/
|
||
|
struct dmac_lli *next_start_lli = LNK2LLI(
|
||
|
dmac_ch_tsk_by_idx(ch, cur_task)->end_lli->link);
|
||
|
if (next_start_lli && (next_start_lli->link == link))
|
||
|
cur_task++;
|
||
|
|
||
|
tsk = dmac_ch_tsk_by_idx(ch, cur_task);
|
||
|
|
||
|
/* get previous to next LLI in the circular buffer */
|
||
|
cur_lli = (link) ? ch->llibuf + (ch->llibuf_mask &
|
||
|
(LNK2LLI(link) - ch->llibuf - 1)) : tsk->end_lli;
|
||
|
|
||
|
/* Calculate current address, choose destination address when
|
||
|
* dest increment is set (usually MEMMEM or PERIMEM transfers),
|
||
|
* otherwise use source address (usually MEMPERI transfers).
|
||
|
*/
|
||
|
void *start_addr;
|
||
|
if (ch->control & (1 << DMACCxCONTROL_DI_POS)) {
|
||
|
cur_addr = cur_lli->dstaddr;
|
||
|
start_addr = tsk->start_lli->dstaddr;
|
||
|
}
|
||
|
else {
|
||
|
cur_addr = cur_lli->srcaddr;
|
||
|
start_addr = tsk->start_lli->srcaddr;
|
||
|
}
|
||
|
cur_addr += (LLI_COUNT(cur_lli) - count) << ch->cfg->swidth;
|
||
|
|
||
|
/* calculate bytes for current task */
|
||
|
count = tsk->size - (cur_addr - start_addr);
|
||
|
|
||
|
/* count bytes for the remaining tasks */
|
||
|
if (t_bytes)
|
||
|
while (++cur_task != ch->tasks_queued)
|
||
|
t_count += dmac_ch_tsk_by_idx(ch, cur_task)->size;
|
||
|
}
|
||
|
|
||
|
restore_irq(flags);
|
||
|
|
||
|
if (bytes) *bytes = count;
|
||
|
if (t_bytes) *t_bytes = count + t_count;
|
||
|
|
||
|
return cur_addr;
|
||
|
}
|