/****************************************************************************
 * arch/arm/src/efm32/efm32_dma.c
 *
 *   Copyright (C) 2014 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <semaphore.h>
#include <assert.h>
#include <errno.h>

#include <nuttx/arch.h>

#include "up_arch.h"
#include "chip/efm32_cmu.h"
#include "chip/efm32_dma.h"
#include "efm32_dma.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#define ALIGN_MASK(s)   ((1 << s) - 1)
#define ALIGN_DOWN(v,m) ((v) & ~m)
#define ALIGN_UP(v,m)   (((v) + (m)) & ~m)

/****************************************************************************
 * Public Types
 ****************************************************************************/
/* This structure describes one DMA channel */

struct dma_channel_s
{
  uint8_t chan;                  /* DMA channel number (0-EFM32_DMA_NCHANNELS) */
  bool inuse;                    /* TRUE: The DMA channel is in use */
  dma_config_t config;           /* Current configuration */
  dma_callback_t callback;       /* Callback invoked when the DMA completes */
  void *arg;                     /* Argument passed to callback function */
};

/* This structure describes the state of the DMA controller */

struct dma_controller_s
{
  sem_t exclsem;                 /* Protects channel table */
  sem_t chansem;                 /* Count of free channels */
};

/****************************************************************************
 * Private Data
 ****************************************************************************/

/* This is the overall state of the DMA controller */

static struct dma_controller_s g_dmac;

/* This is the array of all DMA channels */

static struct dma_channel_s g_dmach[EFM32_DMA_NCHANNELS];

/* This array describes the available channel control data structures.  Each
 * structure must be aligned to a 256 address boundary.  The last 8 or 9
 * bits of address are provided by the DMA controller:
 *
 * 8-Channels:
 *   Bit 7:    Selects the alternate descriptor list
 *   Bits 4-6: The DMA channel (0-7)
 *   Bits 2-3: Selects the descriptor field
 *   Bits 0-1: Always zero
 *
 * 12-Channels:
 *   Bit 8:    Selects the alternate descriptor list
 *   Bits 4-7: The DMA channel (0-11)
 *   Bits 2-3: Selects the descriptor field
 *   Bits 0-1: Always zero
 */

#if EFM32_DMA_NCHANNELS <= 8
#  define DESC_TABLE_SIZE  8
#  define DESC_TABLE_ALIGN 256 /* 2*8*16 */
#elif EFM32_DMA_NCHANNELS <= 16
#  define DESC_TABLE_SIZE  16
#  define DESC_TABLE_ALIGN 512 /* 2*16*16 */
#else
#  error Unknown descriptor table size
#endif

#ifdef CONFIG_EFM32_DMA_ALTDSEC
static struct dma_descriptor_s g_descriptors[DESC_TABLE_SIZE + EFM32_DMA_NCHANNELS]
  __attribute__((aligned(DESC_TABLE_ALIGN)));
#else
static struct dma_descriptor_s g_descriptors[EFM32_DMA_NCHANNELS]
  __attribute__((aligned(DESC_TABLE_ALIGN)));
#endif

/****************************************************************************
 * Public Data
 ****************************************************************************/

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: efm32_set_chctrl
 *
 * Description:
 *  Set the channel control register
 *
 ****************************************************************************/

static void efm32_set_chctrl(struct dma_channel_s *dmach, dma_config_t config)
{
  uintptr_t regaddr;
  uint32_t decoded;
  uint32_t regval;

  decoded  = (uint32_t)(config & EFM32_DMA_SIGSEL_MASK) >> EFM32_DMA_SIGSEL_SHIFT;
  regval   = (decoded << _DMA_CH_CTRL_SIGSEL_SHIFT);
  decoded  = (uint32_t)(config & EFM32_DMA_SOURCSEL_MASK) >> EFM32_DMA_SOURCSEL_SHIFT;
  regval  |= (decoded << _DMA_CH_CTRL_SOURCESEL_SHIFT);

  regaddr = EFM32_DMA_CHn_CTRL(dmach->chan);
  putreg32(regval, regaddr);
}

/****************************************************************************
 * Name: efm32_align_shift
 *
 * Description:
 *  Set the channel control register
 *
 ****************************************************************************/

static inline unsigned int efm32_align_shift(dma_config_t config)
{
  unsigned int shift;

  shift = (config & EFM32_DMA_XFERSIZE_MASK) >> EFM32_DMA_XFERSIZE_SHIFT;
  DEBUGASSERT(shift != 3);
  return shift;
}

/****************************************************************************
 * Name: efm32_get_descriptor
 *
 * Description:
 *  Get the address of the primary or alternate descriptor assigned to the
 *  DMA channel
 *
 ****************************************************************************/

static inline struct dma_descriptor_s *
efm32_get_descriptor(struct dma_channel_s *dmach, bool alt)
{
  uintptr_t base = alt ? getreg32(EFM32_DMA_ALTCTRLBASE) :
                         getreg32(EFM32_DMA_CTRLBASE);
  return ((struct dma_descriptor_s *)base) + dmach->chan;
}

/****************************************************************************
 * Name: efm32_dmac_interrupt
 *
 * Description:
 *  DMA interrupt handler
 *
 ****************************************************************************/

static int efm32_dmac_interrupt(int irq, void *context)
{
  struct dma_channel_s *dmach;
  unsigned int chndx;
  uint32_t pending;
  uint32_t bit;

  /* Get the set of pending, unmasked global XDMAC interrupts */

  pending = getreg32(EFM32_DMA_IF) & getreg32(EFM32_DMA_IEN);
  putreg32(pending, EFM32_DMA_IFC);

  /* Check each bit to see which channel(s) have interrupted */

  for (chndx = 0; chndx < EFM32_DMA_NCHANNELS && pending != 0; chndx++)
    {
      /* Are any interrupts pending for this channel? */

      bit = 1 << chndx;
      if ((pending & bit) != 0)
        {
          dmach = &g_dmach[chndx];

          /* Put the DMA channel in the stopped state */

          efm32_dmastop((DMA_HANDLE)dmach);

          /* Call the DMA completion callback */

          if (dmach->callback)
            {
              dmach->callback((DMA_HANDLE)dmach, OK, dmach->arg);
              dmach->callback = NULL;
            }

          dmach->arg = NULL;

          /* Clear the bit in the sampled set of pending interrupts */

          pending &= !bit;
        }
    }

  return OK;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: up_dmainitialize
 *
 * Description:
 *   Initialize the DMA subsystem
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

void weak_function up_dmainitialize(void)
{
  uint32_t regval;
  int i;

  dmallvdbg("Initialize XDMAC0\n");

  /* Initialize the channel list  */

  sem_init(&g_dmac.exclsem, 0, 1);
  sem_init(&g_dmac.chansem, 0, EFM32_DMA_NCHANNELS);

  for (i = 0; i < EFM32_DMA_NCHANNELS; i++)
    {
      g_dmach[i].chan = i;
    }

  /* Enable clocking to the DMA module.  DMA is clocked by HFCORECLK. */

  regval  = getreg32(EFM32_CMU_HFCORECLKEN0);
  regval |= CMU_HFCORECLKEN0_DMA;
  putreg32(regval, EFM32_CMU_HFCORECLKEN0);

  /* Set the control base addresses.  Note: EFM32_DMA_ALTCTRLBASE
   * is a read-only register that provides the location where hardware
   * will obtain the alternative descriptors.
   */

  putreg32((uint32_t)g_descriptors, EFM32_DMA_CTRLBASE);

  /* Attach DMA interrupt vector */

  (void)irq_attach(EFM32_IRQ_DMA, efm32_dmac_interrupt);

  /* Enable the DMA controller */

  putreg32(DMA_CONFIG_EN, EFM32_DMA_CONFIG);

  /* Enable the IRQ at the AIC (still disabled at the DMA controller) */

  up_enable_irq(EFM32_IRQ_DMA);
}

/****************************************************************************
 * Name: efm32_dmachannel
 *
 * Description:
 *   Allocate a DMA channel.  This function gives the caller mutually
 *   exclusive access to a DMA channel.
 *
 *   If no DMA channel is available, then efm32_dmachannel() will wait
 *   until the holder of a channel relinquishes the channel by calling
 *   efm32_dmafree().
 *
 * Input parameters:
 *   None
 *
 * Returned Value:
 *   This function ALWAYS returns a non-NULL, void* DMA channel handle.
 *
 * Assumptions:
 *   - The caller can wait for a DMA channel to be freed if it is not
 *     available.
 *
 ****************************************************************************/

DMA_HANDLE efm32_dmachannel(void)
{
  struct dma_channel_s *dmach;
  unsigned int chndx;
  uint32_t bit;

  /* Take a count from from the channel counting semaphore.  We may block
   * if there are no free channels.  When we get the count, then we can
   * be assured that a channel is available in the channel list and is
   * reserved for us.
   */

  while (sem_wait(&g_dmac.chansem) < 0)
    {
      /* sem_wait should fail only if it is awakened by a a signal */

      DEBUGASSERT(errno == EINTR);
    }

  /* Get exclusive access to the DMA channel list */

  while (sem_wait(&g_dmac.exclsem) < 0)
    {
      /* sem_wait should fail only if it is awakened by a a signal */

      DEBUGASSERT(errno == EINTR);
    }

  /* Search for an available DMA channel */

  for (chndx = 0, dmach = NULL; chndx < EFM32_DMA_NCHANNELS; chndx++)
    {
      struct dma_channel_s *candidate = &g_dmach[chndx];
      if (!candidate->inuse)
        {
          dmach         = candidate;
          dmach->inuse  = true;

          /* Clear any pending channel interrupts */

          bit = 1 << chndx;
          putreg32(bit, EFM32_DMA_IFC);

          /* Disable the channel */

          putreg32(bit, EFM32_DMA_CHENC);
          break;
        }
    }

  sem_post(&g_dmac.exclsem);

  /* Since we have reserved a DMA descriptor by taking a count from chansem,
   * it would be a serious logic failure if we could not find a free channel
   * for our use.
   */

  DEBUGASSERT(dmach);
  return (DMA_HANDLE)dmach;
}

/****************************************************************************
 * Name: efm32_dmafree
 *
 * Description:
 *   Release a DMA channel.  If another thread is waiting for this DMA channel
 *   in a call to efm32_dmachannel, then this function will re-assign the
 *   DMA channel to that thread and wake it up.  NOTE:  The 'handle' used
 *   in this argument must NEVER be used again until efm32_dmachannel() is
 *   called again to re-gain access to the channel.
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *   - The caller holds the DMA channel.
 *   - There is no DMA in progress
 *
 ****************************************************************************/

void efm32_dmafree(DMA_HANDLE handle)
{
  struct dma_channel_s *dmach = (struct dma_channel_s *)handle;

  DEBUGASSERT(dmach != NULL && dmach->inuse);
  dmavdbg("DMA channel %d\n", dmach->chan);

  /* Disable the channel */

  putreg32(1 << dmach->chan, EFM32_DMA_CHENC);

  /* Mark the channel no longer in use.  Clearing the in-use flag is an atomic
   * operation and so should be safe.
   */

  dmach->inuse = false;

  /* And increment the count of free channels... possibly waking up a
   * thread that may be waiting for a channel.
   */

  sem_post(&g_dmac.chansem);
}

/****************************************************************************
 * Name: efm32_rxdmasetup
 *
 * Description:
 *   Configure an RX (peripheral-to-memory) DMA before starting the transfer.
 *
 * Input Parameters:
 *   paddr  - Peripheral address (source)
 *   maddr  - Memory address (destination)
 *   nbytes - Number of bytes to transfer.  Must be an even multiple of the
 *            configured transfer size.
 *   config - Channel configuration selections
 *
 ****************************************************************************/

void efm32_rxdmasetup(DMA_HANDLE handle, uintptr_t paddr, uintptr_t maddr,
                      size_t nbytes, dma_config_t config)
{
  struct dma_channel_s *dmach = (struct dma_channel_s *)handle;
  struct dma_descriptor_s *desc;
  unsigned int xfersize;
  unsigned int shift;
  uint32_t regval;
  uint32_t incr;
  uint32_t mask;

  DEBUGASSERT(dmach != NULL && dmach->inuse);

  /* Get the properly alignment shift and mask */

  shift = efm32_align_shift(config);
  mask  = ALIGN_MASK(shift);

  /* Make sure that the number of bytes we are asked to transfer is a multiple
   * of the transfer size.
   */

  xfersize = (1 << shift);
  nbytes   = ALIGN_DOWN(nbytes, mask);
  DEBUGASSERT(nbytes > 0);

  /* Save the configuration (for efm32_dmastart()). */

  dmach->config = config;

  /* Configure for the selected peripheral */

  efm32_set_chctrl(dmach, config);

  /* Configure the primary channel descriptor */

  desc         = efm32_get_descriptor(dmach, false);
  desc->srcend = (uint32_t *)paddr;
  desc->dstend = (uint32_t *)(maddr + nbytes - xfersize);

  /* No source increment, destination increments according to transfer size.
   * No privileges.  Arbitrate after each transfer. Default priority.
   */

  regval = DMA_CTRL_SRC_INC_NONE | DMA_CTRL_DST_PROT_NON_PRIVILEGED |
           DMA_CTRL_SRC_PROT_NON_PRIVILEGED | DMA_CTRL_R_POWER_1 |
           (0 << _DMA_CTRL_NEXT_USEBURST_SHIFT) | _DMA_CTRL_CYCLE_CTRL_BASIC;

  switch (shift)
    {
    default:
    case 0: /* Byte transfer */
      regval |= DMA_CTRL_DST_SIZE_BYTE | DMA_CTRL_SRC_SIZE_BYTE;
      incr    = DMA_CTRL_DST_INC_BYTE;
      break;

    case 1: /* Half word transfer */
      regval |= DMA_CTRL_DST_SIZE_HALFWORD | DMA_CTRL_SRC_SIZE_HALFWORD;
      incr    = DMA_CTRL_DST_INC_HALFWORD;
      break;

    case 2: /* Word transfer */
      regval |=  DMA_CTRL_DST_SIZE_WORD | DMA_CTRL_SRC_SIZE_WORD;
      incr    = DMA_CTRL_DST_INC_WORD;
      break;
    }

  /* Do we need to increment the memory address? */

  if ((config & EFM32_DMA_MEMINCR_MASK) == EFM32_DMA_MEMINCR)
    {
      regval |= incr;
    }

  /* Set the number of transfers (minus 1) */

  DEBUGASSERT((nbytes >> shift) < 1024);
  regval |= ((nbytes >> shift) - 1) << _DMA_CTRL_N_MINUS_1_SHIFT;
  desc->ctrl = regval;
  desc->user = 0;
}

/****************************************************************************
 * Name: efm32_txdmasetup
 *
 * Description:
 *   Configure an TX (memory-to-memory) DMA before starting the transfer.
 *
 * Input Parameters:
 *   paddr  - Peripheral address (destination)
 *   maddr  - Memory address (source)
 *   nbytes - Number of bytes to transfer.  Must be an even multiple of the
 *            configured transfer size.
 *   config - Channel configuration selections
 *
 ****************************************************************************/

void efm32_txdmasetup(DMA_HANDLE handle, uintptr_t paddr, uintptr_t maddr,
                      size_t nbytes, dma_config_t config)
{
  struct dma_channel_s *dmach = (struct dma_channel_s *)handle;
  struct dma_descriptor_s *desc;
  unsigned int xfersize;
  unsigned int shift;
  uint32_t regval;
  uint32_t incr;
  uint32_t mask;

  DEBUGASSERT(dmach != NULL && dmach->inuse);

  /* Get the properly alignment shift and mask */

  shift = efm32_align_shift(config);
  mask  = ALIGN_MASK(shift);

  /* Make sure that the number of bytes we are asked to transfer is a multiple
   * of the transfer size.
   */

  xfersize = (1 << shift);
  nbytes   = ALIGN_DOWN(nbytes, mask);
  DEBUGASSERT(nbytes > 0);

  /* Save the configuration (for efm32_dmastart()). */

  dmach->config = config;

  /* Configure for the selected peripheral */

  efm32_set_chctrl(dmach, config);

  /* Configure the primary channel descriptor */

  desc         = efm32_get_descriptor(dmach, false);
  desc->srcend = (uint32_t *)(maddr + nbytes - xfersize);
  desc->dstend = (uint32_t *)paddr;

  /* No destination increment, source increments according to transfer size.
   * No privileges.  Arbitrate after each transfer. Default priority.
   */

  regval = DMA_CTRL_DST_INC_NONE | DMA_CTRL_DST_PROT_NON_PRIVILEGED |
           DMA_CTRL_SRC_PROT_NON_PRIVILEGED | DMA_CTRL_R_POWER_1 |
           (0 << _DMA_CTRL_NEXT_USEBURST_SHIFT) | _DMA_CTRL_CYCLE_CTRL_BASIC;

  switch (shift)
    {
    default:
    case 0: /* Byte transfer */
      regval |= DMA_CTRL_DST_SIZE_BYTE | DMA_CTRL_SRC_SIZE_BYTE;
      incr    = DMA_CTRL_SRC_INC_BYTE;
      break;

    case 1: /* Half word transfer */
      regval |= DMA_CTRL_DST_SIZE_HALFWORD | DMA_CTRL_SRC_SIZE_HALFWORD;
      incr    = DMA_CTRL_SRC_INC_HALFWORD;
      break;

    case 2: /* Word transfer */
      regval |= DMA_CTRL_DST_SIZE_WORD | DMA_CTRL_SRC_SIZE_WORD;
      incr    = DMA_CTRL_SRC_INC_WORD;
      break;
    }

  /* Do we need to increment the memory address? */

  if ((config & EFM32_DMA_MEMINCR_MASK) == EFM32_DMA_MEMINCR)
    {
      regval |= incr;
    }

  /* Set the number of transfers (minus 1) */

  DEBUGASSERT((nbytes >> shift) < 1024);
  regval |= ((nbytes >> shift) - 1) << _DMA_CTRL_N_MINUS_1_SHIFT;
  desc->ctrl = regval;
  desc->user = 0;
}

/****************************************************************************
 * Name: efm32_dmastart
 *
 * Description:
 *   Start the DMA transfer
 *
 * Assumptions:
 *   - DMA handle allocated by efm32_dmachannel()
 *   - No DMA in progress
 *
 ****************************************************************************/

void efm32_dmastart(DMA_HANDLE handle, dma_callback_t callback, void *arg)
{
  struct dma_channel_s *dmach = (struct dma_channel_s *)handle;
  irqstate_t flags;
  uint32_t regval;
  uint32_t bit;

  DEBUGASSERT(dmach && dmach->inuse && dmach->desc);

  /* Save the DMA complete callback info */

  dmach->callback = callback;
  dmach->arg       = arg;

  /* Finish configuring the channel */

  bit = 1 << dmach->chan;
  if ((dmach->config & EFM32_DMA_SINGLE_MASK) == EFM32_DMA_BUFFER_FULL)
    {
      /* Disable the single requests for the channel (i.e. do not react to data
       * available, wait for buffer full)
       */

      putreg32(bit, EFM32_DMA_CHUSEBURSTS);

      /* Enable buffer-full requests for the channel */

      putreg32(bit, EFM32_DMA_CHREQMASKC);
    }
  else
    {
      /* Enable the single requests for the channel */

      putreg32(bit, EFM32_DMA_CHUSEBURSTC);

      /* Disable buffer-full requests for the channel */

      putreg32(bit, EFM32_DMA_CHREQMASKS);
    }

  /* Use the primary data structure for the channel */

  putreg32(bit, EFM32_DMA_CHALTC);

  /* Enable DMA completion interrupts */

  flags   = irqsave();
  regval  = getreg32(EFM32_DMA_IEN);
  regval |= bit;
  putreg32(regval, EFM32_DMA_IEN);

  /* Enable the channel */

  putreg32(bit, EFM32_DMA_CHENS);
  irqrestore(flags);
}

/****************************************************************************
 * Name: efm32_dmastop
 *
 * Description:
 *   Cancel the DMA.  After efm32_dmastop() is called, the DMA channel is
 *   reset and efm32_dmasetup() must be called before efm32_dmastart() can be
 *   called again
 *
 * Assumptions:
 *   - DMA handle allocated by efm32_dmachannel()
 *
 ****************************************************************************/

void efm32_dmastop(DMA_HANDLE handle)
{
  struct dma_channel_s *dmach = (struct dma_channel_s *)handle;
  irqstate_t flags;
  uint32_t regval;
  uint32_t bit;

  DEBUGASSERT(dmach);
  bit = 1 << dmach->chan;

  /* Disable the channel */

  flags = irqsave();
  putreg32(bit, EFM32_DMA_CHENC);

  /* Disable Channel interrupts */

  regval  = getreg32(EFM32_DMA_IEN);
  regval |= bit;
  putreg32(regval, EFM32_DMA_IEN);
  irqrestore(flags);
}

/****************************************************************************
 * Name: efm32_dmasample
 *
 * Description:
 *   Sample DMA register contents
 *
 * Assumptions:
 *   - DMA handle allocated by efm32_dmachannel()
 *
 ****************************************************************************/

#ifdef CONFIG_DEBUG_DMA
void efm32_dmasample(DMA_HANDLE handle, struct efm32_dmaregs_s *regs)
{
  struct dma_channel_s *dmach = (struct dma_channel_s *)handle;
  uintptr_t regaddr;
  irqstate_t flags;

  /* Sample DMA registers. */

  flags              = irqsave();

  regs->status       = getreg32(EFM32_DMA_STATUS);
  regs->ctrlbase     = getreg32(EFM32_DMA_CTRLBASE);
  regs->altctrlbase  = getreg32(EFM32_DMA_ALTCTRLBASE);
  regs->chwaitstatus = getreg32(EFM32_DMA_CHWAITSTATUS);
  regs->chusebursts  = getreg32(EFM32_DMA_CHUSEBURSTS);
  regs->chreqmasks   = getreg32(EFM32_DMA_CHREQMASKS);
  regs->chens        = getreg32(EFM32_DMA_CHENS);
  regs->chalts       = getreg32(EFM32_DMA_CHALTS);
  regs->chpris       = getreg32(EFM32_DMA_CHPRIS);
  regs->errorc       = getreg32(EFM32_DMA_ERRORC);
  regs->chreqstatus  = getreg32(EFM32_DMA_CHREQSTATUS);
  regs->chsreqstatus = getreg32(EFM32_DMA_CHSREQSTATUS);
  regs->ifr          = getreg32(EFM32_DMA_IF);
  regs->ien          = getreg32(EFM32_DMA_IEN);
#if defined(CONFIG_EFM32_EFM32GG)
  regs->ctrl         = getreg32(EFM32_DMA_CTRL);
  regs->rds          = getreg32(EFM32_DMA_RDS);
  regs->loop0        = getreg32(EFM32_DMA_LOOP0);
  regs->loop1        = getreg32(EFM32_DMA_LOOP1);
  regs->rect0        = getreg32(EFM32_DMA_RECT0);
#endif

  /* Sample channel control register */

  regaddr            = EFM32_DMA_CHn_CTRL(dmach->chan)
  regs->chnctrl      = getreg32(regaddr);

  irqrestore(flags);
}
#endif

/****************************************************************************
 * Name: efm32_dmadump
 *
 * Description:
 *   Dump previously sampled DMA register contents
 *
 * Assumptions:
 *   - DMA handle allocated by efm32_dmachannel()
 *
 ****************************************************************************/

#ifdef CONFIG_DEBUG_DMA
void efm32_dmadump(DMA_HANDLE handle, const struct efm32_dmaregs_s *regs,
                   const char *msg)
{
  struct dma_channel_s *dmach = (struct dma_channel_s *)handle;

  dmadbg("%s\n", msg);
  dmadbg("  DMA Registers:\n");
  dmadbg("        STATUS: %08x\n", regs->status);
  dmadbg("      CTRLBASE: %08x\n", regs->ctrlbase);
  dmadbg("   ALTCTRLBASE: %08x\n", regs->altctrlbase);
  dmadbg("  CHWAITSTATUS: %08x\n", regs->chwaitstatus);
  dmadbg("   CHUSEBURSTS: %08x\n", regs->chusebursts);
  dmadbg("    CHREQMASKS: %08x\n", regs->chreqmasks);
  dmadbg("         CHENS: %08x\n", regs->chens);
  dmadbg("        CHALTS: %08x\n", regs->chalts);
  dmadbg("        CHPRIS: %08x\n", regs->chpris);
  dmadbg("        ERRORC: %08x\n", regs->errorc);
  dmadbg("   CHREQSTATUS: %08x\n", regs->chreqstatus);
  dmadbg("  CHSREQSTATUS: %08x\n", regs->chsreqstatus);
  dmadbg("           IEN: %08x\n", regs->ien);
#if defined(CONFIG_EFM32_EFM32GG)
  dmadbg("          CTRL: %08x\n", regs->ctrl);
  dmadbg("           RDS: %08x\n", regs->rds);
  dmadbg("         LOOP0: %08x\n", regs->loop0);
  dmadbg("         LOOP1: %08x\n", regs->loop1);
  dmadbg("         RECT0: %08x\n", regs->rect0);
#endif
  dmadbg("  DMA Channel %d Registers:\n", dmach->chan);
  dmadbg("        CHCTRL: %08x\n", regs->chnctrl);
}
#endif
