nuttx/arch/arm/src/sama5/sam_dmac.c
Xiang Xiao a8a25cb09f arch: Remove xxx_sem[take|give] and call nxmuex_[lock|unlock] directly
Signed-off-by: Xiang Xiao <xiaoxiang@xiaomi.com>
2022-11-14 09:34:04 +09:00

2410 lines
68 KiB
C

/****************************************************************************
* arch/arm/src/sama5/sam_dmac.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <debug.h>
#include <errno.h>
#include <nuttx/irq.h>
#include <nuttx/arch.h>
#include <nuttx/mutex.h>
#include "arm_internal.h"
#include "sched/sched.h"
#include "chip.h"
#include "sam_dmac.h"
#include "sam_periphclks.h"
#include "sam_memories.h"
#include "hardware/sam_pmc.h"
#include "hardware/sam_dmac.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Configuration ************************************************************/
/* All of the currently supported SAMA5 chips support two DMA controllers
* of 8 DMA Channels each.
*/
#if SAM_NDMAC < 1
# undef CONFIG_SAMA5_DMAC1
# undef CONFIG_SAMA5_DMAC0
#elif SAM_NDMAC < 2
# undef CONFIG_SAMA5_DMAC1
#endif
/* Condition out the whole file unless DMA is selected in the configuration */
#if defined(CONFIG_SAMA5_DMAC0) || defined(CONFIG_SAMA5_DMAC1)
/* If SAMA5 DMA support is enabled, then OS DMA support should also be
* enabled
*/
#ifndef CONFIG_ARCH_DMA
# warning "SAMA5 DMA enabled but CONFIG_ARCH_DMA disabled"
#endif
/* Check the number of link list descriptors to allocate */
#ifndef CONFIG_SAMA5_NLLDESC
# define CONFIG_SAMA5_NLLDESC SAM_NDMACHAN
#endif
#if CONFIG_SAMA5_NLLDESC < SAM_NDMACHAN
# warning "At least SAM_NDMACHAN descriptors must be allocated"
# undef CONFIG_SAMA5_NLLDESC
# define CONFIG_SAMA5_NLLDESC SAM_NDMACHAN
#endif
/* Register values **********************************************************/
#define DMAC_CH_CTRLB_BOTHDSCR \
(DMAC_CH_CTRLB_SRCDSCR | DMAC_CH_CTRLB_DSTDSCR)
/****************************************************************************
* Private Types
****************************************************************************/
/* This structure maps a peripheral ID to an DMA channel */
struct sam_pidmap_s
{
uint8_t pid; /* Peripheral identifier */
uint8_t pchan; /* DMA channel */
};
/* This structure describes one DMA channel */
struct sam_dmach_s
{
#if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1)
uint8_t dmac; /* DMA controller number (0-1) */
#endif
uint8_t chan; /* DMA channel number (0-6) */
bool inuse; /* TRUE: The DMA channel is in use */
bool rx; /* TRUE: Peripheral to memory transfer */
uint32_t flags; /* DMA channel flags */
uint32_t base; /* DMA register channel base address */
uint32_t cfg; /* Pre-calculated CFG register for transfer */
dma_callback_t callback; /* Callback invoked when the DMA completes */
void *arg; /* Argument passed to callback function */
uint32_t rxaddr; /* RX memory address */
size_t rxsize; /* Size of RX memory region */
struct dma_linklist_s *llhead; /* DMA link list head */
struct dma_linklist_s *lltail; /* DMA link list head */
};
/* This structure describes the state of one DMA controller */
struct sam_dmac_s
{
/* These mutex protect the DMA channel and descriptor tables */
mutex_t chlock; /* Protects channel table */
sem_t dsem; /* Protects descriptor table */
uint32_t base; /* DMA register channel base address */
/* This array describes the available link list descriptors */
struct dma_linklist_s *desc;
/* This array describes each DMA channel */
struct sam_dmach_s *dmach;
};
/****************************************************************************
* Private Data
****************************************************************************/
/* CTRLA field lookups */
static const uint32_t g_srcwidth[4] =
{
DMAC_CH_CTRLA_SRCWIDTH_BYTE,
DMAC_CH_CTRLA_SRCWIDTH_HWORD,
DMAC_CH_CTRLA_SRCWIDTH_WORD,
DMAC_CH_CTRLA_SRCWIDTH_DWORD
};
static const uint32_t g_destwidth[4] =
{
DMAC_CH_CTRLA_DSTWIDTH_BYTE,
DMAC_CH_CTRLA_DSTWIDTH_HWORD,
DMAC_CH_CTRLA_DSTWIDTH_WORD,
DMAC_CH_CTRLA_DSTWIDTH_DWORD
};
static const uint32_t g_fifocfg[3] =
{
DMAC_CH_CFG_FIFOCFG_ALAP,
DMAC_CH_CFG_FIFOCFG_HALF,
DMAC_CH_CFG_FIFOCFG_ASAP
};
/* These tables map peripheral IDs to channels. A lookup is performed
* before each DMA transfer in order to map the peripheral IDs to the
* correct channel. This must be done because the channel can change with
* the direction of the transfer.
*/
#ifdef CONFIG_SAMA5_DMAC0
/* DMA controller 0, RX DMA: */
static const struct sam_pidmap_s g_dmac0_rxchan[] =
{
{ SAM_PID_HSMCI0, DMAC0_CH_HSMCI0 }, /* HSMCI0 Receive/transmit */
{ SAM_PID_SPI0, DMAC0_CH_SPI0_RX }, /* SPI0 Receive */
{ SAM_PID_USART0, DMAC0_CH_USART0_RX }, /* USART0 Receive */
{ SAM_PID_USART1, DMAC0_CH_USART1_RX }, /* USART1 Receive */
{ SAM_PID_TWI0, DMAC0_CH_TWI0_RX }, /* TWI0 Receive */
{ SAM_PID_TWI1, DMAC0_CH_TWI1_RX }, /* TWI1 Receive */
{ SAM_PID_UART0, DMAC0_CH_UART0_RX }, /* UART0 Receive */
{ SAM_PID_SSC0, DMAC0_CH_SSC0_RX }, /* SSC0 Receive */
{ SAM_PID_SMD, DMAC0_CH_SMD_RX }, /* SMD Receive */
};
#define NDMAC0_RXCHANNELS (sizeof(g_dmac0_rxchan) / sizeof(struct sam_pidmap_s))
/* DMA controller 0, TX DMA: */
static const struct sam_pidmap_s g_dmac0_txchan[] =
{
{ SAM_PID_HSMCI0, DMAC0_CH_HSMCI0 }, /* HSMCI0 Receive/transmit */
{ SAM_PID_SPI0, DMAC0_CH_SPI0_TX }, /* SPI0 Transmit */
{ SAM_PID_USART0, DMAC0_CH_USART0_TX }, /* USART0 Transmit */
{ SAM_PID_USART1, DMAC0_CH_USART1_TX }, /* USART1 Transmit */
{ SAM_PID_TWI0, DMAC0_CH_TWI0_TX }, /* TWI0 Transmit */
{ SAM_PID_TWI1, DMAC0_CH_TWI1_TX }, /* TWI1 Transmit */
{ SAM_PID_UART0, DMAC0_CH_UART0_TX }, /* UART0 Transmit */
{ SAM_PID_SSC0, DMAC0_CH_SSC0_TX }, /* SSC0 Transmit */
{ SAM_PID_SMD, DMAC0_CH_SMD_TX }, /* SMD Transmit */
};
#define NDMAC0_TXCHANNELS (sizeof(g_dmac0_txchan) / sizeof(struct sam_pidmap_s))
#endif
#ifdef CONFIG_SAMA5_DMAC1
/* DMA controller 1, RX DMA: */
static const struct sam_pidmap_s g_dmac1_rxchan[] =
{
{ SAM_PID_HSMCI1, DMAC1_CH_HSMCI1 }, /* HSMCI1 Receive/transmit */
{ SAM_PID_HSMCI2, DMAC1_CH_HSMCI2 }, /* HSMCI2 Receive/transmit */
{ SAM_PID_ADC, DMAC1_CH_ADC_RX }, /* ADC Receive */
{ SAM_PID_SSC1, DMAC1_CH_SSC1_RX }, /* SSC1 Receive */
{ SAM_PID_UART1, DMAC1_CH_UART1_RX }, /* UART1 Receive */
{ SAM_PID_USART2, DMAC1_CH_USART2_RX }, /* USART2 Receive */
{ SAM_PID_USART3, DMAC1_CH_USART3_RX }, /* USART3 Receive */
{ SAM_PID_TWI2, DMAC1_CH_TWI2_RX }, /* TWI2 Receive */
{ SAM_PID_DBGU, DMAC1_CH_DBGU_RX }, /* DBGU Receive */
{ SAM_PID_SPI1, DMAC1_CH_SPI1_RX }, /* SPI1 Receive */
{ SAM_PID_AES, DMAC1_CH_AES_RX }, /* AES Receive */
{ SAM_PID_TDES, DMAC1_CH_TDES_RX }, /* TDES Receive */
};
#define NDMAC1_RXCHANNELS (sizeof(g_dmac1_rxchan) / sizeof(struct sam_pidmap_s))
/* DMA controller 1, TX DMA: */
static const struct sam_pidmap_s g_dmac1_txchan[] =
{
{ SAM_PID_HSMCI1, DMAC1_CH_HSMCI1 }, /* HSMCI1 Receive/transmit */
{ SAM_PID_HSMCI2, DMAC1_CH_HSMCI2 }, /* HSMCI2 Receive/transmit */
{ SAM_PID_SSC1, DMAC1_CH_SSC1_TX }, /* SSC1 Transmit */
{ SAM_PID_UART1, DMAC1_CH_UART1_TX }, /* UART1 Transmit */
{ SAM_PID_USART2, DMAC1_CH_USART2_TX }, /* USART2 Transmit */
{ SAM_PID_USART3, DMAC1_CH_USART3_TX }, /* USART3 Transmit */
{ SAM_PID_TWI2, DMAC1_CH_TWI2_TX }, /* TWI2 Transmit */
{ SAM_PID_DBGU, DMAC1_CH_DBGU_TX }, /* DBGU Transmit */
{ SAM_PID_SPI1, DMAC1_CH_SPI1_TX }, /* SPI1 Transmit */
{ SAM_PID_SHA, DMAC1_CH_SHA_TX }, /* SHA Transmit */
{ SAM_PID_AES, DMAC1_CH_AES_TX }, /* AES Transmit */
{ SAM_PID_TDES, DMAC1_CH_TDES_TX }, /* TDES Transmit */
};
#define NDMAC1_TXCHANNELS (sizeof(g_dmac1_txchan) / sizeof(struct sam_pidmap_s))
#endif
#ifdef CONFIG_SAMA5_DMAC0
/* This array describes the available link list descriptors */
struct dma_linklist_s g_desc0[CONFIG_SAMA5_NLLDESC];
/* This array describes the state of each DMAC0 channel 0 */
static struct sam_dmach_s g_dmach0[SAM_NDMACHAN] =
{
#if SAM_NDMACHAN > 0
{
#if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1)
.dmac = 0,
#endif
.chan = 0,
.base = SAM_DMAC0_CH0_BASE,
},
#endif
#if SAM_NDMACHAN > 1
{
#if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1)
.dmac = 0,
#endif
.chan = 1,
.base = SAM_DMAC0_CH1_BASE,
},
#endif
#if SAM_NDMACHAN > 2
{
#if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1)
.dmac = 0,
#endif
.chan = 2,
.base = SAM_DMAC0_CH2_BASE,
},
#endif
#if SAM_NDMACHAN > 3
{
#if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1)
.dmac = 0,
#endif
.chan = 3,
.base = SAM_DMAC0_CH3_BASE,
},
#endif
#if SAM_NDMACHAN > 4
{
#if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1)
.dmac = 0,
#endif
.chan = 4,
.base = SAM_DMAC0_CH4_BASE,
},
#endif
#if SAM_NDMACHAN > 5
{
#if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1)
.dmac = 0,
#endif
.chan = 5,
.base = SAM_DMAC0_CH5_BASE,
},
#endif
#if SAM_NDMACHAN > 6
{
#if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1)
.dmac = 0,
#endif
.chan = 6,
.base = SAM_DMAC0_CH6_BASE,
},
#endif
#if SAM_NDMACHAN > 7
{
#if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1)
.dmac = 0,
#endif
.chan = 7,
.base = SAM_DMAC0_CH7_BASE,
}
#endif
};
/* This describes the overall state of DMA controller 0 */
static struct sam_dmac_s g_dmac0 =
{
.chlock = NXMUTEX_INITIALIZER,
.dsem = SEM_INITIALIZER(SAM_NDMACHAN),
/* DMAC 0 base address */
.base = SAM_DMAC0_VBASE,
/* This array describes the available link list descriptors */
.desc = g_desc0,
/* This array describes each DMA channel */
.dmach = g_dmach0,
};
#endif /* CONFIG_SAMA5_DMAC0 */
/* This array describes the state of DMA controller 1 */
#ifdef CONFIG_SAMA5_DMAC1
/* This array describes the available link list descriptors */
struct dma_linklist_s g_desc1[CONFIG_SAMA5_NLLDESC];
static struct sam_dmach_s g_dmach1[SAM_NDMACHAN] =
{
#if SAM_NDMACHAN > 0
{
#if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1)
.dmac = 1,
#endif
.chan = 0,
.base = SAM_DMAC1_CH0_BASE,
},
#endif
#if SAM_NDMACHAN > 1
{
#if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1)
.dmac = 1,
#endif
.chan = 1,
.base = SAM_DMAC1_CH1_BASE,
},
#endif
#if SAM_NDMACHAN > 2
{
#if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1)
.dmac = 1,
#endif
.chan = 2,
.base = SAM_DMAC1_CH2_BASE,
},
#endif
#if SAM_NDMACHAN > 3
{
#if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1)
.dmac = 1,
#endif
.chan = 3,
.base = SAM_DMAC1_CH3_BASE,
},
#endif
#if SAM_NDMACHAN > 4
{
#if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1)
.dmac = 1,
#endif
.chan = 4,
.base = SAM_DMAC1_CH4_BASE,
},
#endif
#if SAM_NDMACHAN > 5
{
#if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1)
.dmac = 1,
#endif
.chan = 5,
.base = SAM_DMAC1_CH5_BASE,
},
#endif
#if SAM_NDMACHAN > 6
{
#if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1)
.dmac = 1,
#endif
.chan = 6,
.base = SAM_DMAC1_CH6_BASE,
},
#endif
#if SAM_NDMACHAN > 7
{
#if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1)
.dmac = 1,
#endif
.chan = 7,
.base = SAM_DMAC1_CH7_BASE,
}
#endif
};
/* This describes the overall state of DMA controller 1 */
static struct sam_dmac_s g_dmac1 =
{
.chlock = NXMUTEX_INITIALIZER,
.dsem = SEM_INITIALIZER(SAM_NDMACHAN),
/* DMAC 0 base address */
.base = SAM_DMAC1_VBASE,
/* This array describes the available link list descriptors */
.desc = g_desc1,
/* This array describes each DMA channel */
.dmach = g_dmach1,
};
#endif /* CONFIG_SAMA5_DMAC1 */
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: sam_getdmac
*
* Description:
* Read a global DMAC register
*
****************************************************************************/
static inline uint32_t sam_getdmac(struct sam_dmac_s *dmac,
unsigned int offset)
{
return getreg32(dmac->base + offset);
}
/****************************************************************************
* Name: sam_putdmac
*
* Description:
* Write a value to a global DMAC register
*
****************************************************************************/
static inline void sam_putdmac(struct sam_dmac_s *dmac, uint32_t value,
unsigned int offset)
{
putreg32(value, dmac->base + offset);
}
/****************************************************************************
* Name: sam_getdmach
*
* Description:
* Read a DMAC channel register
*
****************************************************************************/
static inline uint32_t sam_getdmach(struct sam_dmach_s *dmach,
unsigned int offset)
{
return getreg32(dmach->base + offset);
}
/****************************************************************************
* Name: sam_putdmach
*
* Description:
* Write a value to a DMAC channel register
*
****************************************************************************/
static inline void sam_putdmach(struct sam_dmach_s *dmach, uint32_t value,
unsigned int offset)
{
putreg32(value, dmach->base + offset);
}
/****************************************************************************
* Name: sam_controller
*
* Description:
* Given a DMA channel instance, return a pointer to the parent DMA
* controller instance.
*
****************************************************************************/
static inline struct sam_dmac_s *sam_controller(struct sam_dmach_s *dmach)
{
#if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1)
return dmach->dmac ? &g_dmac1 : &g_dmac0;
#elif defined(CONFIG_SAMA5_DMAC0)
return &g_dmac0;
#else
return &g_dmac1;
#endif
}
/****************************************************************************
* Name: sam_fifocfg
*
* Description:
* Decode the FIFO config from the flags
*
****************************************************************************/
static inline uint32_t sam_fifocfg(struct sam_dmach_s *dmach)
{
unsigned int ndx;
ndx = (dmach->flags & DMACH_FLAG_FIFOCFG_MASK) >> DMACH_FLAG_FIFOCFG_SHIFT;
DEBUGASSERT(ndx < 3);
return g_fifocfg[ndx];
}
/****************************************************************************
* Name: sam_channel, sam_source_channel, and sam_sink_channel
*
* Description:
* Return the RX or TX channel associated with a PID. As a clarification:
*
* The source channel refers to the source of data for the DMA. This is,
* either (1) memory for a TX DMA or (2) a peripheral register for an RX
* DMA.
*
* The sink channel is the recipient of the DMA data. This is either
* (1) memory for an RX DMA, or (2) a peripheral register for a TX DMA.
*
****************************************************************************/
static uint8_t sam_channel(uint8_t pid, const struct sam_pidmap_s *table,
unsigned int nentries)
{
int i;
/* Search until either the entry with the matching PID is found or until
* all of the table entries have been examined without finding the PID.
*/
for (i = 0; i < nentries; i++, table++)
{
if (table->pid == pid)
{
return table->pchan;
}
}
dmaerr("ERROR: No channel found for pid %d\n", pid);
DEBUGPANIC();
return 0x3f;
}
static uint32_t sam_source_channel(struct sam_dmach_s *dmach, uint8_t pid,
bool isperiph)
{
const struct sam_pidmap_s *table;
unsigned int nentries;
if (!isperiph)
{
/* The source is memory, not a peripheral. */
return 0x3f;
}
else
#ifdef CONFIG_SAMA5_DMAC0
#ifdef CONFIG_SAMA5_DMAC1
if (dmach->dmac == 0)
#endif
{
/* Use the DMAC0 lookup table */
table = g_dmac0_rxchan;
nentries = NDMAC0_RXCHANNELS;
}
#endif
#ifdef CONFIG_SAMA5_DMAC1
#ifdef CONFIG_SAMA5_DMAC0
else
#endif
{
/* Use the DMAC1 lookup table */
table = g_dmac1_rxchan;
nentries = NDMAC1_RXCHANNELS;
}
#endif
return (uint32_t)sam_channel(pid, table, nentries);
}
static uint32_t sam_sink_channel(struct sam_dmach_s *dmach, uint8_t pid,
bool isperiph)
{
const struct sam_pidmap_s *table;
unsigned int nentries;
if (!isperiph)
{
/* The source is memory, not a peripheral. */
return 0x3f;
}
else
#ifdef CONFIG_SAMA5_DMAC0
#ifdef CONFIG_SAMA5_DMAC1
if (dmach->dmac == 0)
#endif
{
/* Use the DMAC0 lookup table */
table = g_dmac0_txchan;
nentries = NDMAC0_TXCHANNELS;
}
#endif
#ifdef CONFIG_SAMA5_DMAC1
#ifdef CONFIG_SAMA5_DMAC0
else
#endif
{
/* Use the DMAC1 lookup table */
table = g_dmac1_txchan;
nentries = NDMAC1_TXCHANNELS;
}
#endif
return (uint32_t)sam_channel(pid, table, nentries);
}
/****************************************************************************
* Name: sam_txcfg
*
* Description:
* Decode the flags to get the correct CFG register bit settings for
* a transmit (memory to peripheral) transfer.
*
****************************************************************************/
static inline uint32_t sam_txcfg(struct sam_dmach_s *dmach)
{
uint32_t regval;
unsigned int pid;
unsigned int pchan;
bool isperiph;
/* Set transfer (memory to peripheral) DMA channel configuration register */
regval = DMAC_CH_CFG_SOD;
pid = (dmach->flags & DMACH_FLAG_MEMPID_MASK) >>
DMACH_FLAG_MEMPID_SHIFT;
isperiph = ((dmach->flags & DMACH_FLAG_MEMISPERIPH) != 0);
pchan = sam_source_channel(dmach, pid, isperiph);
regval |= ((pchan & 0x0f) << DMAC_CH_CFG_SRCPER_SHIFT);
regval |= ((pchan & 0x30) << (DMAC_CH_CFG_SRCPERMSB_SHIFT - 4));
regval |= (dmach->flags & DMACH_FLAG_MEMH2SEL) != 0 ?
DMAC_CH_CFG_SRCH2SEL : 0;
pid = (dmach->flags & DMACH_FLAG_PERIPHPID_MASK) >>
DMACH_FLAG_PERIPHPID_SHIFT;
isperiph = ((dmach->flags & DMACH_FLAG_PERIPHISPERIPH) != 0);
pchan = sam_sink_channel(dmach, pid, isperiph);
regval |= ((pchan & 0x0f) << DMAC_CH_CFG_DSTPER_SHIFT);
regval |= ((pchan & 0x30) << (DMAC_CH_CFG_DSTPERMSB_SHIFT - 4));
regval |= (dmach->flags & DMACH_FLAG_PERIPHH2SEL) != 0 ?
DMAC_CH_CFG_DSTH2SEL : 0;
regval |= sam_fifocfg(dmach);
return regval;
}
/****************************************************************************
* Name: sam_rxcfg
*
* Description:
* Decode the flags to get the correct CFG register bit settings for
* a receive (peripheral to memory) transfer.
*
****************************************************************************/
static inline uint32_t sam_rxcfg(struct sam_dmach_s *dmach)
{
uint32_t regval;
unsigned int pid;
unsigned int pchan;
bool isperiph;
/* Set received (peripheral to memory) DMA channel config */
regval = DMAC_CH_CFG_SOD;
pid = (dmach->flags & DMACH_FLAG_PERIPHPID_MASK) >>
DMACH_FLAG_PERIPHPID_SHIFT;
isperiph = ((dmach->flags & DMACH_FLAG_PERIPHISPERIPH) != 0);
pchan = sam_source_channel(dmach, pid, isperiph);
regval |= ((pchan & 0x0f) << DMAC_CH_CFG_SRCPER_SHIFT);
regval |= ((pchan & 0x30) << (DMAC_CH_CFG_SRCPERMSB_SHIFT - 4));
regval |= (dmach->flags & DMACH_FLAG_PERIPHH2SEL) != 0 ?
DMAC_CH_CFG_SRCH2SEL : 0;
pid = (dmach->flags & DMACH_FLAG_MEMPID_MASK) >>
DMACH_FLAG_MEMPID_SHIFT;
isperiph = ((dmach->flags & DMACH_FLAG_MEMISPERIPH) != 0);
pchan = sam_sink_channel(dmach, pid, isperiph);
regval |= ((pchan & 0x0f) << DMAC_CH_CFG_DSTPER_SHIFT);
regval |= ((pchan & 0x30) << (DMAC_CH_CFG_DSTPERMSB_SHIFT - 4));
regval |= (dmach->flags & DMACH_FLAG_MEMH2SEL) != 0 ?
DMAC_CH_CFG_DSTH2SEL : 0;
regval |= sam_fifocfg(dmach);
return regval;
}
/****************************************************************************
* Name: sam_txctrlabits
*
* Description:
* Decode the flags to get the correct CTRLA register bit settings for
* a transmit (memory to peripheral) transfer. These are only the "fixed"
* CTRLA values and need to be updated with the actual transfer size before
* being written to CTRLA sam_txctrla).
*
****************************************************************************/
static inline uint32_t sam_txctrlabits(struct sam_dmach_s *dmach)
{
uint32_t regval;
unsigned int ndx;
unsigned int chunksize;
DEBUGASSERT(dmach);
/* Since this is a transmit, the source is described by the memory
* selections. Set the source width (memory width).
*/
ndx = (dmach->flags & DMACH_FLAG_MEMWIDTH_MASK) >>
DMACH_FLAG_MEMWIDTH_SHIFT;
DEBUGASSERT(ndx < 4);
regval = g_srcwidth[ndx];
/* Set the source chunk size (memory chunk size) */
chunksize = (dmach->flags & DMACH_FLAG_MEMCHUNKSIZE_MASK)
>> DMACH_FLAG_MEMCHUNKSIZE_SHIFT;
regval |= chunksize << DMAC_CH_CTRLA_SCSIZE_SHIFT;
/* Since this is a transmit, the destination is described by the peripheral
* selections. Set the destination width (peripheral width).
*/
ndx = (dmach->flags & DMACH_FLAG_PERIPHWIDTH_MASK) >>
DMACH_FLAG_PERIPHWIDTH_SHIFT;
DEBUGASSERT(ndx < 4);
regval |= g_destwidth[ndx];
/* Set the destination chunk size (peripheral chunk size) */
chunksize = (dmach->flags & DMACH_FLAG_PERIPHCHUNKSIZE_MASK)
>> DMACH_FLAG_PERIPHCHUNKSIZE_SHIFT;
regval |= chunksize << DMAC_CH_CTRLA_DCSIZE_SHIFT;
return regval;
}
/****************************************************************************
* Name: sam_maxtxtransfer
*
* Description:
* Maximum number of bytes that can be sent in on transfer
*
****************************************************************************/
static size_t sam_maxtxtransfer(struct sam_dmach_s *dmach)
{
unsigned int srcwidth;
size_t maxtransfer;
/* Get the maximum transfer size in bytes. BTSIZE is "the number of
* transfers to be performed, that is, for writes it refers to the number
* of source width transfers to perform when DMAC is flow controller. For
* Reads, BTSIZE refers to the number of transfers completed on the Source
* Interface. ..."
*/
srcwidth = (dmach->flags & DMACH_FLAG_MEMWIDTH_MASK)
>> DMACH_FLAG_MEMWIDTH_SHIFT;
switch (srcwidth)
{
default:
case 0: /* 8 bits, 1 byte */
maxtransfer = DMAC_CH_CTRLA_BTSIZE_MAX;
break;
case 1: /* 16 bits, 2 bytes */
maxtransfer = 2 * DMAC_CH_CTRLA_BTSIZE_MAX;
break;
case 2: /* 32 bits 4 bytes */
maxtransfer = 4 * DMAC_CH_CTRLA_BTSIZE_MAX;
break;
case 3: /* 64 bits, 8 bytes */
maxtransfer = 8 * DMAC_CH_CTRLA_BTSIZE_MAX;
break;
}
return maxtransfer;
}
/****************************************************************************
* Name: sam_ntxtransfers
*
* Description:
* Number of TX transfers via DMA
*
****************************************************************************/
static uint32_t sam_ntxtransfers(struct sam_dmach_s *dmach, uint32_t dmasize)
{
unsigned int srcwidth;
/* Adjust the source transfer size for the source chunk size (memory
* chunk size). BTSIZE is "the number of transfers to be performed, that
* is, for writes it refers to the number of source width transfers
* to perform when DMAC is flow controller. For Reads, BTSIZE refers to
* the number of transfers completed on the Source Interface. ..."
*/
srcwidth = (dmach->flags & DMACH_FLAG_MEMWIDTH_MASK)
>> DMACH_FLAG_MEMWIDTH_SHIFT;
switch (srcwidth)
{
default:
case 0: /* 8 bits, 1 byte */
break;
case 1: /* 16 bits, 2 bytes */
dmasize = (dmasize + 1) >> 1;
break;
case 2: /* 32 bits, 4 bytes */
dmasize = (dmasize + 3) >> 2;
break;
case 3: /* 64 bits, 8 bytes */
dmasize = (dmasize + 7) >> 3;
break;
}
return dmasize;
}
/****************************************************************************
* Name: sam_txctrla
*
* Description:
* 'OR' in the variable CTRLA bits
*
****************************************************************************/
static inline uint32_t sam_txctrla(struct sam_dmach_s *dmach,
uint32_t ctrla, uint32_t dmasize)
{
uint32_t ntransfers;
/* Set the buffer transfer size field. This is the number of transfers to
* be performed, that is, the number of source width transfers to perform.
*/
ntransfers = sam_ntxtransfers(dmach, dmasize);
DEBUGASSERT(ntransfers <= DMAC_CH_CTRLA_BTSIZE_MAX);
return (ctrla & ~DMAC_CH_CTRLA_BTSIZE_MASK) |
(ntransfers << DMAC_CH_CTRLA_BTSIZE_SHIFT);
}
/****************************************************************************
* Name: sam_rxctrlabits
*
* Description:
* Decode the flags to get the correct CTRLA register bit settings for
* a read (peripheral to memory) transfer. These are only the "fixed" CTRLA
* values and need to be updated with the actual transfer size before being
* written to CTRLA sam_rxctrla).
*
****************************************************************************/
static inline uint32_t sam_rxctrlabits(struct sam_dmach_s *dmach)
{
uint32_t regval;
unsigned int ndx;
unsigned int chunksize;
DEBUGASSERT(dmach);
/* Since this is a receive, the source is described by the peripheral
* selections. Set the source width (peripheral width).
*/
ndx = (dmach->flags & DMACH_FLAG_PERIPHWIDTH_MASK)
>> DMACH_FLAG_PERIPHWIDTH_SHIFT;
DEBUGASSERT(ndx < 4);
regval = g_srcwidth[ndx];
/* Set the source chunk size (peripheral chunk size) */
chunksize = (dmach->flags & DMACH_FLAG_PERIPHCHUNKSIZE_MASK)
>> DMACH_FLAG_PERIPHCHUNKSIZE_SHIFT;
regval |= chunksize << DMAC_CH_CTRLA_SCSIZE_SHIFT;
/* Since this is a receive, the destination is described by the memory
* selections. Set the destination width (memory width).
*/
ndx = (dmach->flags & DMACH_FLAG_MEMWIDTH_MASK)
>> DMACH_FLAG_MEMWIDTH_SHIFT;
DEBUGASSERT(ndx < 4);
regval |= g_destwidth[ndx];
/* Set the destination chunk size (memory chunk size) */
chunksize = (dmach->flags & DMACH_FLAG_MEMCHUNKSIZE_MASK)
>> DMACH_FLAG_MEMCHUNKSIZE_SHIFT;
regval |= chunksize << DMAC_CH_CTRLA_DCSIZE_SHIFT;
return regval;
}
/****************************************************************************
* Name: sam_maxrxtransfer
*
* Description:
* Maximum number of bytes that can be sent in on transfer
*
****************************************************************************/
static size_t sam_maxrxtransfer(struct sam_dmach_s *dmach)
{
unsigned int srcwidth;
size_t maxtransfer;
/* Get the maximum transfer size in bytes. BTSIZE is "the number of
* transfers to be performed, that is, for writes it refers to the number
* of source width transfers to perform when DMAC is flow controller. For
* Reads, BTSIZE refers to the number of transfers completed on the Source
* Interface. ..."
*/
srcwidth = (dmach->flags & DMACH_FLAG_PERIPHWIDTH_MASK)
>> DMACH_FLAG_PERIPHWIDTH_SHIFT;
switch (srcwidth)
{
default:
case 0: /* 8 bits, 1 byte */
maxtransfer = DMAC_CH_CTRLA_BTSIZE_MAX;
break;
case 1: /* 16 bits, 2 bytes */
maxtransfer = 2 * DMAC_CH_CTRLA_BTSIZE_MAX;
break;
case 2: /* 32 bits, 4 bytes */
maxtransfer = 4 * DMAC_CH_CTRLA_BTSIZE_MAX;
break;
case 3: /* 64 bits, 8 bytes */
maxtransfer = 8 * DMAC_CH_CTRLA_BTSIZE_MAX;
break;
}
return maxtransfer;
}
/****************************************************************************
* Name: sam_nrxtransfers
*
* Description:
* Number of RX transfers via DMA
*
****************************************************************************/
static uint32_t sam_nrxtransfers(struct sam_dmach_s *dmach, uint32_t dmasize)
{
unsigned int srcwidth;
/* Adjust the source transfer size for the source chunk size (peripheral
* chunk size). BTSIZE is "the number of transfers to be performed, that
* is, for writes it refers to the number of source width transfers
* to perform when DMAC is flow controller. For Reads, BTSIZE refers to
* the number of transfers completed on the Source Interface. ..."
*/
srcwidth = (dmach->flags & DMACH_FLAG_PERIPHWIDTH_MASK)
>> DMACH_FLAG_PERIPHWIDTH_SHIFT;
switch (srcwidth)
{
default:
case 0: /* 8 bits, 1 byte */
break;
case 1: /* 16 bits, 2 bytes */
dmasize = (dmasize + 1) >> 1;
break;
case 2: /* 32 bits, 4 bytes */
dmasize = (dmasize + 3) >> 2;
break;
case 3: /* 64 bits, 8 bytes */
dmasize = (dmasize + 7) >> 3;
break;
}
return dmasize;
}
/****************************************************************************
* Name: sam_rxctrla
*
* Description:
* 'OR' in the variable CTRLA bits
*
****************************************************************************/
static inline uint32_t sam_rxctrla(struct sam_dmach_s *dmach,
uint32_t ctrla, uint32_t dmasize)
{
uint32_t ntransfers;
/* Set the buffer transfer size field. This is the number of transfers to
* be performed, that is, the number of source width transfers to perform.
*/
ntransfers = sam_nrxtransfers(dmach, dmasize);
DEBUGASSERT(ntransfers <= DMAC_CH_CTRLA_BTSIZE_MAX);
return (ctrla & ~DMAC_CH_CTRLA_BTSIZE_MASK) |
(ntransfers << DMAC_CH_CTRLA_BTSIZE_SHIFT);
}
/****************************************************************************
* Name: sam_txctrlb
*
* Description:
* Decode the flags to get the correct CTRLB register bit settings for
* a transmit (memory to peripheral) transfer.
*
****************************************************************************/
static inline uint32_t sam_txctrlb(struct sam_dmach_s *dmach)
{
uint32_t regval;
unsigned int ahbif;
/* Assume that we will not be using the link list and disable the source
* and destination descriptors. The default will be single transfer mode.
*/
regval = DMAC_CH_CTRLB_BOTHDSCR | DMAC_CH_CTRLB_IEN;
/* Select flow control (even if the channel doesn't support it). The
* naming convention from TX is memory to peripheral, but that is really
* be determined by bits in the DMA flags.
*/
/* Is the memory source really a peripheral? */
if ((dmach->flags & DMACH_FLAG_MEMISPERIPH) != 0)
{
/* Yes.. is the peripheral destination also a peripheral? */
if ((dmach->flags & DMACH_FLAG_PERIPHISPERIPH) != 0)
{
/* Yes.. Use peripheral-to-peripheral flow control */
regval |= DMAC_CH_CTRLB_FC_P2P;
}
else
{
/* No.. Use peripheral-to-memory flow control */
regval |= DMAC_CH_CTRLB_FC_P2M;
}
}
else
{
/* No, the source is memory. Is the peripheral destination a
* peripheral
*/
if ((dmach->flags & DMACH_FLAG_PERIPHISPERIPH) != 0)
{
/* Yes.. Use memory-to-peripheral flow control */
regval |= DMAC_CH_CTRLB_FC_M2P;
}
else
{
/* No.. Use memory-to-memory flow control */
regval |= DMAC_CH_CTRLB_FC_M2M;
}
}
/* Source ABH layer */
ahbif = (dmach->flags & DMACH_FLAG_MEMAHB_MASK) >> DMACH_FLAG_MEMAHB_SHIFT;
regval |= (ahbif << DMAC_CH_CTRLB_SIF_SHIFT);
/* Select source address incrementing */
if ((dmach->flags & DMACH_FLAG_MEMINCREMENT) == 0)
{
regval |= DMAC_CH_CTRLB_SRCINCR_FIXED;
}
/* Destination ABH layer */
ahbif = (dmach->flags & DMACH_FLAG_PERIPHAHB_MASK) >>
DMACH_FLAG_PERIPHAHB_SHIFT;
regval |= (ahbif << DMAC_CH_CTRLB_DIF_SHIFT);
/* Select destination address incrementing */
if ((dmach->flags & DMACH_FLAG_PERIPHINCREMENT) == 0)
{
regval |= DMAC_CH_CTRLB_DSTINCR_FIXED;
}
return regval;
}
/****************************************************************************
* Name: sam_rxctrlb
*
* Description:
* Decode the flags to get the correct CTRLB register bit settings for
* a receive (peripheral to memory) transfer.
*
****************************************************************************/
static inline uint32_t sam_rxctrlb(struct sam_dmach_s *dmach)
{
uint32_t regval;
unsigned int ahbif;
/* Assume that we will not be using the link list and disable the source
* and destination descriptors. The default will be single transfer mode.
*/
regval = DMAC_CH_CTRLB_BOTHDSCR | DMAC_CH_CTRLB_IEN;
/* Select flow control (even if the channel doesn't support it). The
* naming convention from RX is peripheral to memory, but that is really
* be determined by bits in the DMA flags.
*/
/* Is the peripheral source really a peripheral? */
if ((dmach->flags & DMACH_FLAG_PERIPHISPERIPH) != 0)
{
/* Yes.. is the memory destination also a peripheral? */
if ((dmach->flags & DMACH_FLAG_MEMISPERIPH) != 0)
{
/* Yes.. Use peripheral-to-peripheral flow control */
regval |= DMAC_CH_CTRLB_FC_P2P;
}
else
{
/* No.. Use peripheral-to-memory flow control */
regval |= DMAC_CH_CTRLB_FC_P2M;
}
}
else
{
/* No, the peripheral source is memory. Is the memory destination
* a peripheral
*/
if ((dmach->flags & DMACH_FLAG_MEMISPERIPH) != 0)
{
/* Yes.. Use memory-to-peripheral flow control */
regval |= DMAC_CH_CTRLB_FC_M2P;
}
else
{
/* No.. Use memory-to-memory flow control */
regval |= DMAC_CH_CTRLB_FC_M2M;
}
}
/* Source ABH layer */
ahbif = (dmach->flags & DMACH_FLAG_PERIPHAHB_MASK) >>
DMACH_FLAG_PERIPHAHB_SHIFT;
regval |= (ahbif << DMAC_CH_CTRLB_SIF_SHIFT);
/* Select source address incrementing */
if ((dmach->flags & DMACH_FLAG_PERIPHINCREMENT) == 0)
{
regval |= DMAC_CH_CTRLB_SRCINCR_FIXED;
}
/* Destination ABH layer */
ahbif = (dmach->flags & DMACH_FLAG_MEMAHB_MASK) >> DMACH_FLAG_MEMAHB_SHIFT;
regval |= (ahbif << DMAC_CH_CTRLB_DIF_SHIFT);
/* Select address incrementing */
if ((dmach->flags & DMACH_FLAG_MEMINCREMENT) == 0)
{
regval |= DMAC_CH_CTRLB_DSTINCR_FIXED;
}
return regval;
}
/****************************************************************************
* Name: sam_allocdesc
*
* Description:
* Allocate and add one descriptor to the DMA channel's link list.
*
* NOTE: link list entries are freed by the DMA interrupt handler.
* However, since the setting/clearing of the 'in use' indication is
* atomic, no special actions need be performed. It would be a good thing
* to add logic to handle the case where all of the entries are exhausted
* and we could wait for some to be freed by the interrupt handler.
*
****************************************************************************/
static struct dma_linklist_s *
sam_allocdesc(struct sam_dmach_s *dmach, struct dma_linklist_s *prev,
uint32_t saddr, uint32_t daddr, uint32_t ctrla, uint32_t ctrlb)
{
struct sam_dmac_s *dmac = sam_controller(dmach);
struct dma_linklist_s *desc = NULL;
int i;
int ret;
/* Sanity check -- saddr == 0 is the indication that the link is unused.
* Obviously setting it to zero would break that usage.
*/
#ifdef CONFIG_DEBUG_FEATURES
if (saddr != 0)
#endif
{
/* Table a descriptor table semaphore count. When we get one, then
* there is at least one free descriptor in the table and it is ours.
*/
ret = nxsem_wait_uninterruptible(&dmac->dsem);
if (ret < 0)
{
return NULL;
}
/* Examine each link list entry to find an available one -- i.e., one
* with saddr == 0. That saddr field is set to zero by the DMA
* transfer complete interrupt handler. The following should be safe
* because that is an atomic operation.
*/
ret = nxmutex_lock(&dmac->chlock);
if (ret < 0)
{
nxsem_post(&dmac->dsem);
return NULL;
}
for (i = 0; i < CONFIG_SAMA5_NLLDESC; i++)
{
if (dmac->desc[i].saddr == 0)
{
/* We have it. Initialize the new link list entry */
desc = &dmac->desc[i];
desc->saddr = saddr; /* Source address */
desc->daddr = daddr; /* Destination address */
desc->ctrla = ctrla; /* Control A value */
desc->ctrlb = ctrlb; /* Control B value */
desc->dscr = 0; /* Next descriptor address */
/* And then hook it at the tail of the link list */
if (!prev)
{
/* There is no previous link. This is the new head of
* the list
*/
DEBUGASSERT(dmach->llhead == NULL &&
dmach->lltail == NULL);
dmach->llhead = desc;
}
else
{
DEBUGASSERT(dmach->llhead != NULL &&
dmach->lltail == prev);
/* When the second link is added to the list, that is the
* cue that we are going to do the link list transfer.
*
* Enable the source and destination descriptor in the link
* list entry just before this one. We assume that both
* source and destination buffers are non-continuous, but
* this should work even if that is not the case.
*/
prev->ctrlb &= ~DMAC_CH_CTRLB_BOTHDSCR;
/* Link the previous tail to the new tail.
* REVISIT: This assumes that the next description is
* fetched via AHB IF0.
*/
prev->dscr = (uint32_t)sam_physramaddr((uintptr_t)desc);
}
/* In any event, this is the new tail of the list. The source
* and destination descriptors must be disabled for the last
* entry in the link list.
*/
desc->ctrlb |= DMAC_CH_CTRLB_BOTHDSCR;
dmach->lltail = desc;
/* Assume that we will be doing multiple buffer transfers and
* that hardware will be accessing the descriptor via DMA.
*/
up_clean_dcache((uintptr_t)desc,
(uintptr_t)desc +
sizeof(struct dma_linklist_s));
break;
}
}
/* Because we hold a count from the counting mutex, the above
* search loop should always be successful.
*/
nxmutex_unlock(&dmac->chlock);
DEBUGASSERT(desc != NULL);
}
return desc;
}
/****************************************************************************
* Name: sam_freelinklist
*
* Description:
* Free all descriptors in the DMA channel's link list.
*
* NOTE: Called from the DMA interrupt handler.
*
****************************************************************************/
static void sam_freelinklist(struct sam_dmach_s *dmach)
{
struct sam_dmac_s *dmac = sam_controller(dmach);
struct dma_linklist_s *desc;
uintptr_t paddr;
/* Get the head of the link list and detach the link list from the DMA
* channel
*/
desc = dmach->llhead;
dmach->llhead = NULL;
dmach->lltail = NULL;
while (desc != NULL)
{
/* Valid, in-use descriptors never have saddr == 0 */
DEBUGASSERT(desc->saddr != 0);
/* Get the physical address of the next descriptor in the list */
paddr = desc->dscr;
/* Free the descriptor by simply nullifying it and bumping up the
* semaphore count.
*/
memset(desc, 0, sizeof(struct dma_linklist_s));
nxsem_post(&dmac->dsem);
/* Get the virtual address of the next descriptor in the list */
desc = (struct dma_linklist_s *)sam_virtramaddr(paddr);
}
}
/****************************************************************************
* Name: sam_txbuffer
*
* Description:
* Configure DMA for transmit of one buffer (memory to peripheral). This
* function may be called multiple times to handle large and/or dis-
* continuous transfers.
*
****************************************************************************/
static int sam_txbuffer(struct sam_dmach_s *dmach, uint32_t paddr,
uint32_t maddr, size_t nbytes)
{
uint32_t regval;
uint32_t ctrla;
uint32_t ctrlb;
/* If we are appending a buffer to a linklist, then re-use the CTRLA/B
* values. Otherwise, create them from the properties of the transfer.
*/
if (dmach->llhead)
{
regval = dmach->llhead->ctrla;
ctrlb = dmach->llhead->ctrlb;
}
else
{
regval = sam_txctrlabits(dmach);
ctrlb = sam_txctrlb(dmach);
}
ctrla = sam_txctrla(dmach, regval, nbytes);
/* Add the new link list entry */
if (!sam_allocdesc(dmach, dmach->lltail, maddr, paddr, ctrla, ctrlb))
{
return -ENOMEM;
}
/* Pre-calculate the transmit CFG register setting (it won't be used until
* the DMA is started).
*/
dmach->cfg = sam_txcfg(dmach);
return OK;
}
/****************************************************************************
* Name: sam_rxbuffer
*
* Description:
* Configure DMA for receipt of one buffer (peripheral to memory). This
* function may be called multiple times to handle large and/or dis-
* continuous transfers.
*
****************************************************************************/
static int sam_rxbuffer(struct sam_dmach_s *dmach, uint32_t paddr,
uint32_t maddr, size_t nbytes)
{
uint32_t regval;
uint32_t ctrla;
uint32_t ctrlb;
/* If we are appending a buffer to a linklist, then re-use the CTRLA/B
* values. Otherwise, create them from the properties of the transfer.
*/
if (dmach->llhead)
{
regval = dmach->llhead->ctrla;
ctrlb = dmach->llhead->ctrlb;
}
else
{
regval = sam_rxctrlabits(dmach);
ctrlb = sam_rxctrlb(dmach);
}
ctrla = sam_rxctrla(dmach, regval, nbytes);
/* Add the new link list entry */
if (!sam_allocdesc(dmach, dmach->lltail, paddr, maddr, ctrla, ctrlb))
{
return -ENOMEM;
}
/* Pre-calculate the receive CFG register setting (it won't be used until
* the DMA is started).
*/
dmach->cfg = sam_rxcfg(dmach);
return OK;
}
/****************************************************************************
* Name: sam_single
*
* Description:
* Start a single buffer DMA.
*
****************************************************************************/
static inline int sam_single(struct sam_dmach_s *dmach)
{
struct sam_dmac_s *dmac = sam_controller(dmach);
struct dma_linklist_s *llhead = dmach->llhead;
/* Clear any pending interrupts from any previous DMAC transfer by reading
* the interrupt status register.
*/
sam_getdmac(dmac, SAM_DMAC_EBCISR_OFFSET);
/* Write the starting source address in the SADDR register */
DEBUGASSERT(llhead != NULL && llhead->saddr != 0);
sam_putdmach(dmach, llhead->saddr, SAM_DMAC_CH_SADDR_OFFSET);
/* Write the starting destination address in the DADDR register */
sam_putdmach(dmach, llhead->daddr, SAM_DMAC_CH_DADDR_OFFSET);
/* Clear the next descriptor address */
sam_putdmach(dmach, 0, SAM_DMAC_CH_DSCR_OFFSET);
/* Set up the CTRLA register */
sam_putdmach(dmach, llhead->ctrla, SAM_DMAC_CH_CTRLA_OFFSET);
/* Set up the CTRLB register */
sam_putdmach(dmach, llhead->ctrlb, SAM_DMAC_CH_CTRLB_OFFSET);
/* Both the DST and SRC DSCR bits should be '1' in CTRLB */
DEBUGASSERT((llhead->ctrlb & DMAC_CH_CTRLB_BOTHDSCR) ==
DMAC_CH_CTRLB_BOTHDSCR);
/* Set up the CFG register */
sam_putdmach(dmach, dmach->cfg, SAM_DMAC_CH_CFG_OFFSET);
/* Enable the channel by writing a 1 to the CHER enable bit */
sam_putdmac(dmac, DMAC_CHER_ENA(dmach->chan), SAM_DMAC_CHER_OFFSET);
/* The DMA has been started. Once the transfer completes, hardware sets
* the interrupts and disables the channel. We will receive buffer
* complete and transfer complete interrupts.
*
* Enable error, buffer complete and transfer complete interrupts.
* (Since there is only a single buffer, we don't need the buffer
* complete interrupt).
*/
sam_putdmac(dmac, DMAC_EBC_CBTCINTS(dmach->chan), SAM_DMAC_EBCIER_OFFSET);
return OK;
}
/****************************************************************************
* Name: sam_multiple
*
* Description:
* Start a multiple buffer DMA.
*
****************************************************************************/
static inline int sam_multiple(struct sam_dmach_s *dmach)
{
struct sam_dmac_s *dmac = sam_controller(dmach);
struct dma_linklist_s *llhead = dmach->llhead;
DEBUGASSERT(llhead != NULL && llhead->saddr != 0);
/* Check the first and last CTRLB values */
DEBUGASSERT((llhead->ctrlb & DMAC_CH_CTRLB_BOTHDSCR) == 0);
DEBUGASSERT((dmach->lltail->ctrlb & DMAC_CH_CTRLB_BOTHDSCR) ==
DMAC_CH_CTRLB_BOTHDSCR);
/* Clear any pending interrupts from any previous DMAC transfer by reading
* the status register
*/
sam_getdmac(dmac, SAM_DMAC_EBCISR_OFFSET);
/* Set up the initial CTRLA register */
sam_putdmach(dmach, llhead->ctrla, SAM_DMAC_CH_CTRLA_OFFSET);
/* Set up the CTRLB register (will enable descriptors) */
sam_putdmach(dmach, llhead->ctrlb, SAM_DMAC_CH_CTRLB_OFFSET);
/* Write the channel configuration information into the CFG register */
sam_putdmach(dmach, dmach->cfg, SAM_DMAC_CH_CFG_OFFSET);
/* Program the DSCR register with the pointer to the firstlink list
* entry.
*/
sam_putdmach(dmach, (uint32_t)llhead, SAM_DMAC_CH_DSCR_OFFSET);
/* Finally, enable the channel by writing a 1 to the CHER enable */
sam_putdmac(dmac, DMAC_CHER_ENA(dmach->chan), SAM_DMAC_CHER_OFFSET);
/* As each buffer of data is transferred, the CTRLA register is written
* back into the link list entry. The CTRLA contains updated BTSIZE and
* DONE bits. Additionally, the CTRLA DONE bit is asserted when the
* buffer transfer has completed.
*
* The DMAC transfer continues until the CTRLB register disables the
* descriptor (DSCR bits) registers at the final buffer transfer.
*
* Enable error, buffer complete and transfer complete interrupts. We
* don't really need the buffer complete interrupts, but we will take them
* just to handle stall conditions.
*/
sam_putdmac(dmac, DMAC_EBC_CHANINTS(dmach->chan), SAM_DMAC_EBCIER_OFFSET);
return OK;
}
/****************************************************************************
* Name: sam_dmaterminate
*
* Description:
* Terminate the DMA transfer and disable the DMA channel
*
****************************************************************************/
static void sam_dmaterminate(struct sam_dmach_s *dmach, int result)
{
struct sam_dmac_s *dmac = sam_controller(dmach);
/* Disable all channel interrupts */
sam_putdmac(dmac, DMAC_EBC_CHANINTS(dmach->chan), SAM_DMAC_EBCIDR_OFFSET);
/* Disable the channel by writing one to the write-only channel disable
* register.
*/
sam_putdmac(dmac, DMAC_CHDR_DIS(dmach->chan), SAM_DMAC_CHDR_OFFSET);
/* Free the linklist */
sam_freelinklist(dmach);
/* If this was an RX DMA (peripheral-to-memory), then invalidate the cache
* to force reloads from memory.
*/
if (dmach->rx)
{
up_invalidate_dcache(dmach->rxaddr, dmach->rxaddr + dmach->rxsize);
}
/* Perform the DMA complete callback */
if (dmach->callback)
{
dmach->callback((DMA_HANDLE)dmach, dmach->arg, result);
}
dmach->callback = NULL;
dmach->arg = NULL;
}
/****************************************************************************
* Name: sam_dmac_interrupt
*
* Description:
* DMA interrupt handler
*
****************************************************************************/
static int sam_dmac_interrupt(int irq, void *context, void *arg)
{
struct sam_dmac_s *dmac = (struct sam_dmac_s *)arg;
struct sam_dmach_s *dmach;
unsigned int chndx;
uint32_t regval;
DEBUGASSERT(dmac != NULL);
/* Get the DMAC status register value. Ignore all masked interrupt
* status bits.
*/
regval = sam_getdmac(dmac, SAM_DMAC_EBCISR_OFFSET) &
sam_getdmac(dmac, SAM_DMAC_EBCIMR_OFFSET);
/* Check if the any transfer has completed or any errors have occurred. */
if (regval & DMAC_EBC_ALLCHANINTS)
{
/* Yes.. Check each bit to see which channel has interrupted */
for (chndx = 0; chndx < SAM_NDMACHAN; chndx++)
{
/* Are any interrupts pending for this channel? */
if ((regval & DMAC_EBC_CHANINTS(chndx)) != 0)
{
dmach = &dmac->dmach[chndx];
/* Yes.. Did an error occur? */
if ((regval & DMAC_EBC_ERR(chndx)) != 0)
{
/* Yes... Terminate the transfer with an error? */
dmaerr("ERROR: DMA failed: %08" PRIx32 "\n", regval);
sam_dmaterminate(dmach, -EIO);
}
/* Is the transfer complete? */
else if ((regval & DMAC_EBC_CBTC(chndx)) != 0)
{
/* Yes.. Terminate the transfer with success */
sam_dmaterminate(dmach, OK);
}
/* Otherwise, this must be a Buffer Transfer Complete (BTC)
* interrupt as part of a multiple buffer transfer.
*/
else /* if ((regval & DMAC_EBC_BTC(chndx)) != 0) */
{
/* Write the KEEPON field to clear the STALL states */
sam_putdmac(dmac, DMAC_CHER_KEEP(dmach->chan),
SAM_DMAC_CHER_OFFSET);
}
}
}
}
return OK;
}
/****************************************************************************
* Name: sam_dmainitialize
*
* Description:
* Initialize the DMA subsystem
*
* Returned Value:
* None
*
****************************************************************************/
void sam_dmainitialize(struct sam_dmac_s *dmac)
{
/* Disable all DMA interrupts */
sam_putdmac(dmac, DMAC_EBC_ALLINTS, SAM_DMAC_EBCIDR_OFFSET);
/* Disable all DMA channels */
sam_putdmac(dmac, DMAC_CHDR_DIS_ALL, SAM_DMAC_CHDR_OFFSET);
/* Enable the DMA controller */
sam_putdmac(dmac, DMAC_EN_ENABLE, SAM_DMAC_EN_OFFSET);
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: arm_dma_initialize
*
* Description:
* Initialize the DMA subsystem
*
* Returned Value:
* None
*
****************************************************************************/
void weak_function arm_dma_initialize(void)
{
#ifdef CONFIG_SAMA5_DMAC0
dmainfo("Initialize DMAC0\n");
/* Enable peripheral clock */
sam_dmac0_enableclk();
/* Attach DMA interrupt vector */
irq_attach(SAM_IRQ_DMAC0, sam_dmac_interrupt, &g_dmac0);
/* Initialize the controller */
sam_dmainitialize(&g_dmac0);
/* Enable the IRQ at the AIC (still disabled at the DMA controller) */
up_enable_irq(SAM_IRQ_DMAC0);
#endif
#ifdef CONFIG_SAMA5_DMAC1
dmainfo("Initialize DMAC1\n");
/* Enable peripheral clock */
sam_dmac1_enableclk();
/* Attach DMA interrupt vector */
irq_attach(SAM_IRQ_DMAC1, sam_dmac_interrupt, &g_dmac1);
/* Initialize the controller */
sam_dmainitialize(&g_dmac1);
/* Enable the IRQ at the AIC (still disabled at the DMA controller) */
up_enable_irq(SAM_IRQ_DMAC1);
#endif
}
/****************************************************************************
* Name: sam_dmachannel
*
* Allocate a DMA channel. This function sets aside a DMA channel then
* gives the caller exclusive access to the DMA channel.
*
* The naming convention in all of the DMA interfaces is that one side is
* the 'peripheral' and the other is 'memory'. However, the interface
* could still be used if, for example, both sides were memory although
* the naming would be awkward.
*
* Returned Value:
* If a DMA channel is available, this function returns a non-NULL, void*
* DMA channel handle. NULL is returned on any failure.
*
****************************************************************************/
DMA_HANDLE sam_dmachannel(uint8_t dmacno, uint32_t chflags)
{
struct sam_dmac_s *dmac;
struct sam_dmach_s *dmach;
unsigned int chndx;
int ret;
/* Pick the DMA controller */
#ifdef CONFIG_SAMA5_DMAC0
if (dmacno == 0)
{
dmac = &g_dmac0;
}
else
#endif
#ifdef CONFIG_SAMA5_DMAC1
if (dmacno == 1)
{
dmac = &g_dmac1;
}
else
#endif
{
dmaerr("ERROR: Bad DMAC number: %d\n", dmacno);
DEBUGPANIC();
return NULL;
}
/* Search for an available DMA channel with at least the requested FIFO
* size.
*/
dmach = NULL;
ret = nxmutex_lock(&dmac->chlock);
if (ret < 0)
{
return NULL;
}
for (chndx = 0; chndx < SAM_NDMACHAN; chndx++)
{
struct sam_dmach_s *candidate = &dmac->dmach[chndx];
if (!candidate->inuse)
{
dmach = candidate;
dmach->inuse = true;
/* Read the status register to clear any pending interrupts on the
* channel
*/
sam_getdmac(dmac, SAM_DMAC_EBCISR_OFFSET);
/* Disable the channel by writing one to the write-only channel
* disable register
*/
sam_putdmac(dmac, DMAC_CHDR_DIS(chndx), SAM_DMAC_CHDR_OFFSET);
/* Set the DMA channel flags. */
dmach->flags = chflags;
break;
}
}
nxmutex_unlock(&dmac->chlock);
/* Show the result of the allocation */
if (dmach)
{
dmainfo("DMAC%d CH%d: chflags: %08x returning dmach: %p\n",
(int)dmacno, dmach->chan, (int)chflags, dmach);
}
else
{
dmaerr("ERROR: Failed allocate DMAC%d channel\n", (int)dmacno);
}
return (DMA_HANDLE)dmach;
}
/****************************************************************************
* Name: sam_dmaconfig
*
* Description:
* There are two channel usage models: (1) The channel is allocated and
* configured in one step. This is the typical case where a DMA channel
* performs a constant role. The alternative is (2) where the DMA channel
* is reconfigured on the fly. In this case, the chflags provided to
* sam_dmachannel are not used and sam_dmaconfig() is called before each
* DMA to configure the DMA channel appropriately.
*
* Returned Value:
* None
*
****************************************************************************/
void sam_dmaconfig(DMA_HANDLE handle, uint32_t chflags)
{
struct sam_dmach_s *dmach = (struct sam_dmach_s *)handle;
/* Set the new DMA channel flags. */
dmach->flags = chflags;
#if defined(CONFIG_SAMA5_DMAC0) && defined(CONFIG_SAMA5_DMAC1)
dmainfo("DMAC%d CH%d: chflags: %08x\n",
dmach->dmac, dmach->chan, (int)chflags);
#elif defined(CONFIG_SAMA5_DMAC0)
dmainfo("DMAC0 CH%d: chflags: %08x\n",
dmach->chan, (int)chflags);
#else
dmainfo("DMAC1 CH%d: chflags: %08x\n",
dmach->chan, (int)chflags);
#endif
}
/****************************************************************************
* Name: sam_dmafree
*
* Description:
* Release a DMA channel. NOTE: The 'handle' used in this argument must
* NEVER be used again until sam_dmachannel() is called again to re-gain
* a valid handle.
*
* Returned Value:
* None
*
****************************************************************************/
void sam_dmafree(DMA_HANDLE handle)
{
struct sam_dmach_s *dmach = (struct sam_dmach_s *)handle;
dmainfo("dmach: %p\n", dmach);
DEBUGASSERT((dmach != NULL) && (dmach->inuse));
/* Mark the channel no longer in use. Clearing the inuse flag is an atomic
* operation and so should be safe.
*/
dmach->flags = 0;
dmach->inuse = false; /* No longer in use */
}
/****************************************************************************
* Name: sam_dmatxsetup
*
* Description:
* Configure DMA for transmit of one buffer (memory to peripheral). This
* function may be called multiple times to handle large and/or dis-
* continuous transfers. Calls to sam_dmatxsetup() and sam_dmarxsetup()
* must not be intermixed on the same transfer, however.
*
****************************************************************************/
int sam_dmatxsetup(DMA_HANDLE handle, uint32_t paddr, uint32_t maddr,
size_t nbytes)
{
struct sam_dmach_s *dmach = (struct sam_dmach_s *)handle;
size_t maxtransfer;
size_t remaining;
int ret = OK;
dmainfo("dmach: %p paddr: %08x maddr: %08x nbytes: %d\n",
dmach, (int)paddr, (int)maddr, (int)nbytes);
DEBUGASSERT(dmach);
dmainfo("llhead: %p lltail: %p\n", dmach->llhead, dmach->lltail);
/* The maximum transfer size in bytes depends upon the maximum number of
* transfers and the number of bytes per transfer.
*/
maxtransfer = sam_maxtxtransfer(dmach);
remaining = nbytes;
/* If this is a large transfer, break it up into smaller buffers */
while (remaining > maxtransfer)
{
/* Set up the maximum size transfer */
ret = sam_txbuffer(dmach, paddr, maddr, maxtransfer);
if (ret == OK)
{
/* Decrement the number of bytes left to transfer */
remaining -= maxtransfer;
/* Increment the memory & peripheral address (if it is appropriate
* to do so).
*/
if ((dmach->flags & DMACH_FLAG_PERIPHINCREMENT) != 0)
{
paddr += maxtransfer;
}
if ((dmach->flags & DMACH_FLAG_MEMINCREMENT) != 0)
{
maddr += maxtransfer;
}
}
}
/* Then set up the final buffer transfer */
if (ret == OK && remaining > 0)
{
ret = sam_txbuffer(dmach, paddr, maddr, remaining);
}
/* Save an indication so that the DMA interrupt completion logic will know
* that this was not an RX transfer.
*/
dmach->rx = false;
/* Clean caches associated with the DMA memory */
up_clean_dcache(maddr, maddr + nbytes);
return ret;
}
/****************************************************************************
* Name: sam_dmarxsetup
*
* Description:
* Configure DMA for receipt of one buffer (peripheral to memory). This
* function may be called multiple times to handle large and/or dis-
* continuous transfers. Calls to sam_dmatxsetup() and sam_dmarxsetup()
* must not be intermixed on the same transfer, however.
*
****************************************************************************/
int sam_dmarxsetup(DMA_HANDLE handle, uint32_t paddr, uint32_t maddr,
size_t nbytes)
{
struct sam_dmach_s *dmach = (struct sam_dmach_s *)handle;
size_t maxtransfer;
size_t remaining;
int ret = OK;
dmainfo("dmach: %p paddr: %08x maddr: %08x nbytes: %d\n",
dmach, (int)paddr, (int)maddr, (int)nbytes);
DEBUGASSERT(dmach);
dmainfo("llhead: %p lltail: %p\n", dmach->llhead, dmach->lltail);
/* The maximum transfer size in bytes depends upon the maximum number of
* transfers and the number of bytes per transfer.
*/
maxtransfer = sam_maxrxtransfer(dmach);
remaining = nbytes;
/* If this is a large transfer, break it up into smaller buffers */
while (remaining > maxtransfer)
{
/* Set up the maximum size transfer */
ret = sam_rxbuffer(dmach, paddr, maddr, maxtransfer);
if (ret == OK)
{
/* Decrement the number of bytes left to transfer */
remaining -= maxtransfer;
/* Increment the memory & peripheral address (if it is appropriate
* to do so).
*/
if ((dmach->flags & DMACH_FLAG_PERIPHINCREMENT) != 0)
{
paddr += maxtransfer;
}
if ((dmach->flags & DMACH_FLAG_MEMINCREMENT) != 0)
{
maddr += maxtransfer;
}
}
}
/* Then set up the final buffer transfer */
if (ret == OK && remaining > 0)
{
ret = sam_rxbuffer(dmach, paddr, maddr, remaining);
}
/* Save an indication so that the DMA interrupt completion logic will know
* that this was an RX transfer and will invalidate the cache.
*/
dmach->rx = true;
dmach->rxaddr = maddr;
dmach->rxsize = (dmach->flags & DMACH_FLAG_MEMINCREMENT) != 0 ?
nbytes : sizeof(uint32_t);
/* Clean caches associated with the DMA memory */
up_clean_dcache(maddr, maddr + nbytes);
return ret;
}
/****************************************************************************
* Name: sam_dmastart
*
* Description:
* Start the DMA transfer
*
****************************************************************************/
int sam_dmastart(DMA_HANDLE handle, dma_callback_t callback, void *arg)
{
struct sam_dmach_s *dmach = (struct sam_dmach_s *)handle;
int ret = -EINVAL;
dmainfo("dmach: %p callback: %p arg: %p\n", dmach, callback, arg);
DEBUGASSERT(dmach != NULL);
/* Verify that the DMA has been setup (i.e., at least one entry in the
* link list).
*/
if (dmach->llhead)
{
/* Save the callback info. This will be invoked whent the DMA
* completes
*/
dmach->callback = callback;
dmach->arg = arg;
/* Is this a single block transfer? Or a multiple block transfer? */
if (dmach->llhead == dmach->lltail)
{
ret = sam_single(dmach);
}
else
{
ret = sam_multiple(dmach);
}
}
return ret;
}
/****************************************************************************
* Name: sam_dmastop
*
* Description:
* Cancel the DMA. After sam_dmastop() is called, the DMA channel is
* reset and sam_dmarx/txsetup() must be called before sam_dmastart() can
* be called again
*
****************************************************************************/
void sam_dmastop(DMA_HANDLE handle)
{
struct sam_dmach_s *dmach = (struct sam_dmach_s *)handle;
irqstate_t flags;
dmainfo("dmach: %p\n", dmach);
DEBUGASSERT(dmach != NULL);
flags = enter_critical_section();
sam_dmaterminate(dmach, -EINTR);
leave_critical_section(flags);
}
/****************************************************************************
* Name: sam_dmasample
*
* Description:
* Sample DMA register contents
*
* Assumptions:
* - DMA handle allocated by sam_dmachannel()
*
****************************************************************************/
#ifdef CONFIG_DEBUG_DMA
void sam_dmasample(DMA_HANDLE handle, struct sam_dmaregs_s *regs)
{
struct sam_dmach_s *dmach = (struct sam_dmach_s *)handle;
struct sam_dmac_s *dmac = sam_controller(dmach);
irqstate_t flags;
/* Sample global registers. NOTE: reading EBCISR clears interrupts, but
* that should be okay IF interrupts are enabled when this function is
* called. But there is a race condition where this instrumentation could
* cause lost interrupts.
*/
flags = enter_critical_section();
regs->gcfg = sam_getdmac(dmac, SAM_DMAC_GCFG_OFFSET);
regs->en = sam_getdmac(dmac, SAM_DMAC_EN_OFFSET);
regs->sreq = sam_getdmac(dmac, SAM_DMAC_SREQ_OFFSET);
regs->creq = sam_getdmac(dmac, SAM_DMAC_CREQ_OFFSET);
regs->last = sam_getdmac(dmac, SAM_DMAC_LAST_OFFSET);
regs->ebcimr = sam_getdmac(dmac, SAM_DMAC_EBCIMR_OFFSET);
regs->ebcisr = sam_getdmac(dmac, SAM_DMAC_EBCISR_OFFSET);
regs->chsr = sam_getdmac(dmac, SAM_DMAC_CHSR_OFFSET);
regs->wpmr = sam_getdmac(dmac, SAM_DMAC_WPMR_OFFSET);
regs->wpsr = sam_getdmac(dmac, SAM_DMAC_WPSR_OFFSET);
/* Sample channel registers */
regs->saddr = sam_getdmach(dmach, SAM_DMAC_CH_SADDR_OFFSET);
regs->daddr = sam_getdmach(dmach, SAM_DMAC_CH_DADDR_OFFSET);
regs->dscr = sam_getdmach(dmach, SAM_DMAC_CH_DSCR_OFFSET);
regs->ctrla = sam_getdmach(dmach, SAM_DMAC_CH_CTRLA_OFFSET);
regs->ctrlb = sam_getdmach(dmach, SAM_DMAC_CH_CTRLB_OFFSET);
regs->cfg = sam_getdmach(dmach, SAM_DMAC_CH_CFG_OFFSET);
regs->spip = sam_getdmach(dmach, SAM_DMAC_CH_SPIP_OFFSET);
regs->dpip = sam_getdmach(dmach, SAM_DMAC_CH_DPIP_OFFSET);
leave_critical_section(flags);
}
#endif /* CONFIG_DEBUG_DMA */
/****************************************************************************
* Name: sam_dmadump
*
* Description:
* Dump previously sampled DMA register contents
*
* Assumptions:
* - DMA handle allocated by sam_dmachannel()
*
****************************************************************************/
#ifdef CONFIG_DEBUG_DMA
void sam_dmadump(DMA_HANDLE handle, const struct sam_dmaregs_s *regs,
const char *msg)
{
struct sam_dmach_s *dmach = (struct sam_dmach_s *)handle;
struct sam_dmac_s *dmac = sam_controller(dmach);
dmainfo("%s\n", msg);
dmainfo(" DMA Global Registers:\n");
dmainfo(" GCFG[%08x]: %08x\n",
dmac->base + SAM_DMAC_GCFG_OFFSET, regs->gcfg);
dmainfo(" EN[%08x]: %08x\n",
dmac->base + SAM_DMAC_EN_OFFSET, regs->en);
dmainfo(" SREQ[%08x]: %08x\n",
dmac->base + SAM_DMAC_SREQ_OFFSET, regs->sreq);
dmainfo(" CREQ[%08x]: %08x\n",
dmac->base + SAM_DMAC_CREQ_OFFSET, regs->creq);
dmainfo(" LAST[%08x]: %08x\n",
dmac->base + SAM_DMAC_LAST_OFFSET, regs->last);
dmainfo(" EBCIMR[%08x]: %08x\n",
dmac->base + SAM_DMAC_EBCIMR_OFFSET, regs->ebcimr);
dmainfo(" EBCISR[%08x]: %08x\n",
dmac->base + SAM_DMAC_EBCISR_OFFSET, regs->ebcisr);
dmainfo(" CHSR[%08x]: %08x\n",
dmac->base + SAM_DMAC_CHSR_OFFSET, regs->chsr);
dmainfo(" WPMR[%08x]: %08x\n",
dmac->base + SAM_DMAC_WPMR_OFFSET, regs->wpmr);
dmainfo(" WPSR[%08x]: %08x\n",
dmac->base + SAM_DMAC_WPSR_OFFSET, regs->wpsr);
dmainfo(" DMA Channel Registers:\n");
dmainfo(" SADDR[%08x]: %08x\n",
dmach->base + SAM_DMAC_CH_SADDR_OFFSET, regs->saddr);
dmainfo(" DADDR[%08x]: %08x\n",
dmach->base + SAM_DMAC_CH_DADDR_OFFSET, regs->daddr);
dmainfo(" DSCR[%08x]: %08x\n",
dmach->base + SAM_DMAC_CH_DSCR_OFFSET, regs->dscr);
dmainfo(" CTRLA[%08x]: %08x\n",
dmach->base + SAM_DMAC_CH_CTRLA_OFFSET, regs->ctrla);
dmainfo(" CTRLB[%08x]: %08x\n",
dmach->base + SAM_DMAC_CH_CTRLB_OFFSET, regs->ctrlb);
dmainfo(" CFG[%08x]: %08x\n",
dmach->base + SAM_DMAC_CH_CFG_OFFSET, regs->cfg);
dmainfo(" SPIP[%08x]: %08x\n",
dmach->base + SAM_DMAC_CH_SPIP_OFFSET, regs->spip);
dmainfo(" DPIP[%08x]: %08x\n",
dmach->base + SAM_DMAC_CH_DPIP_OFFSET, regs->dpip);
}
#endif /* CONFIG_DEBUG_DMA */
#endif /* CONFIG_SAMA5_DMAC0 || CONFIG_SAMA5_DMAC1 */