/****************************************************************************
 * arch/arm/src/s32k3xx/s32k3xx_edma.c
 *
 *   Copyright (C) 2019, 2021 Gregory Nutt. All rights reserved.
 *   Copyright 2022 NXP
 *   Authors: Gregory Nutt <gnutt@nuttx.org>
 *            David Sidrane <david.sidrane@nscdg.com>
 *            Peter van der Perk <peter.vanderperk@nxp.com>
 *
 * This file was leveraged from the NuttX S32K1 port.  Portions of that eDMA
 * logic derived from NXP sample code which has a compatible BSD 3-clause
 * license:
 *
 *   Copyright (c) 2015, Freescale Semiconductor, Inc.
 *   Copyright 2016-2017 NXP
 *   All rights reserved
 *
 * 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 <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/queue.h>
#include <nuttx/spinlock.h>
#include <nuttx/mutex.h>
#include <nuttx/semaphore.h>

#include "arm_internal.h"
#include "sched/sched.h"

#include "chip.h"
#include "hardware/s32k3xx_edma.h"
#include "hardware/s32k3xx_dmamux.h"
#include "s32k3xx_edma.h"
#include "s32k3xx_clocknames.h"
#include "s32k3xx_periphclocks.h"

#ifdef CONFIG_S32K3XX_EDMA

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

/* TCD Alignment.
 *
 * eDMA TCDs must be aligned with the D-Cache line boundaries to facilitate
 * cache operations on the TCDs when the D-Cache is enabled.
 *
 * NOTE:  The TCDs are 32-bytes in length.  We implicitly assume that the
 * D-Cache line size is also 32-bits.  Otherwise, padding would be required
 * at the ends of the TCDS and buffers to protect data after the end of from
 * invalidation.
 */

#ifdef CONFIG_ARMV7M_DCACHE
#  define EDMA_ALIGN  ARMV7M_DCACHE_LINESIZE
#else
/* 32 byte alignment for TCDs is required for scatter gather */

#define EDMA_ALIGN        32
#endif

#define EDMA_ALIGN_MASK   (EDMA_ALIGN - 1)
#define EDMA_ALIGN_UP(n)  (((n) + EDMA_ALIGN_MASK) & ~EDMA_ALIGN_MASK)

#ifdef CONFIG_S32K3XX_DTCM_HEAP
#  define DTCM_BACKDOOR_OFFSET 0x1000000
#endif

/****************************************************************************
 * Private Types
 ****************************************************************************/

/* State of a DMA channel */

enum s32k3xx_dmastate_e
{
  S32K3XX_DMA_IDLE = 0,             /* No DMA in progress */
  S32K3XX_DMA_CONFIGURED,           /* DMA configured, but not yet started */
  S32K3XX_DMA_ACTIVE                /* DMA has been started and is in progress */
};

/* This structure describes one DMA channel */

struct s32k3xx_dmach_s
{
  uint8_t chan;                   /* DMA channel number (0-S32K3XX_EDMA_NCHANNELS) */
  bool inuse;                     /* true: The DMA channel is in use */
  uint8_t dmamux;                 /* The DMAMUX channel selection */
  uint8_t state;                  /* Channel state.  See enum s32k3xx_dmastate_e */
  uint32_t flags;                 /* DMA channel flags */
  edma_callback_t callback;       /* Callback invoked when the DMA completes */
  void *arg;                      /* Argument passed to callback function */

#if CONFIG_S32K3XX_EDMA_NTCD > 0
  /* That TCD list is linked through the DLAST SGA field.  The first transfer
   * to be performed is at the head of the list.  Subsequent TCDs are added
   * at the tail of the list.
   */

  struct s32k3xx_edmatcd_s *head;   /* First TCD in the list */
  struct s32k3xx_edmatcd_s *tail;   /* Last TCD in the list */
#endif
};

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

struct s32k3xx_edma_s
{
  /* These mutex protect the DMA channel and descriptor tables */

  mutex_t chlock;                 /* Protects channel table */
#if CONFIG_S32K3XX_EDMA_NTCD > 0
  sem_t dsem;                     /* Supports wait for free descriptors */
#endif

  /* This array describes each DMA channel */

  struct s32k3xx_dmach_s dmach[S32K3XX_EDMA_NCHANNELS];
};

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

uintptr_t const S32K3XX_EDMA_TCD[S32K3XX_EDMA_NCHANNELS] =
{
  S32K3XX_EDMA_CH0_CSR,
  S32K3XX_EDMA_CH1_CSR,
  S32K3XX_EDMA_CH2_CSR,
  S32K3XX_EDMA_CH3_CSR,
  S32K3XX_EDMA_CH4_CSR,
  S32K3XX_EDMA_CH5_CSR,
  S32K3XX_EDMA_CH6_CSR,
  S32K3XX_EDMA_CH7_CSR,
  S32K3XX_EDMA_CH8_CSR,
  S32K3XX_EDMA_CH9_CSR,
  S32K3XX_EDMA_CH10_CSR,
  S32K3XX_EDMA_CH11_CSR,
  S32K3XX_EDMA_CH12_CSR,
  S32K3XX_EDMA_CH13_CSR,
  S32K3XX_EDMA_CH14_CSR,
  S32K3XX_EDMA_CH15_CSR,
  S32K3XX_EDMA_CH16_CSR,
  S32K3XX_EDMA_CH17_CSR,
  S32K3XX_EDMA_CH18_CSR,
  S32K3XX_EDMA_CH19_CSR,
  S32K3XX_EDMA_CH20_CSR,
  S32K3XX_EDMA_CH21_CSR,
  S32K3XX_EDMA_CH22_CSR,
  S32K3XX_EDMA_CH23_CSR,
  S32K3XX_EDMA_CH24_CSR,
  S32K3XX_EDMA_CH25_CSR,
  S32K3XX_EDMA_CH26_CSR,
  S32K3XX_EDMA_CH27_CSR,
  S32K3XX_EDMA_CH28_CSR,
  S32K3XX_EDMA_CH29_CSR,
  S32K3XX_EDMA_CH30_CSR,
  S32K3XX_EDMA_CH31_CSR
};

/* The state of the eDMA */

static struct s32k3xx_edma_s g_edma =
{
  .chlock = NXMUTEX_INITIALIZER,
#if CONFIG_S32K3XX_EDMA_NTCD > 0
  .dsem = SEM_INITIALIZER(CONFIG_S32K3XX_EDMA_NTCD),
#endif
};

#if CONFIG_S32K3XX_EDMA_NTCD > 0
/* This is a singly-linked list of free TCDs */

static sq_queue_t g_tcd_free;

/* This is a pool of pre-allocated TCDs */

static struct s32k3xx_edmatcd_s g_tcd_pool[CONFIG_S32K3XX_EDMA_NTCD]
              aligned_data(EDMA_ALIGN);
#endif

const struct peripheral_clock_config_s edma_clockconfig[] =
{
#if S32K3XX_EDMA_NCHANNELS > 0
  {
    .clkname = EDMA_TCD0_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 1
  {
    .clkname = EDMA_TCD1_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 2
  {
    .clkname = EDMA_TCD2_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 3
  {
    .clkname = EDMA_TCD3_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 4
  {
    .clkname = EDMA_TCD4_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 5
  {
    .clkname = EDMA_TCD5_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 6
  {
    .clkname = EDMA_TCD6_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 7
  {
    .clkname = EDMA_TCD7_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 8
  {
    .clkname = EDMA_TCD8_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 9
  {
    .clkname = EDMA_TCD9_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 10
  {
    .clkname = EDMA_TCD10_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 11
  {
    .clkname = EDMA_TCD11_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 12
  {
    .clkname = EDMA_TCD12_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 13
  {
    .clkname = EDMA_TCD13_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 14
  {
    .clkname = EDMA_TCD14_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 15
  {
    .clkname = EDMA_TCD15_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 16
  {
    .clkname = EDMA_TCD16_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 17
  {
    .clkname = EDMA_TCD17_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 18
  {
    .clkname = EDMA_TCD18_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 19
  {
    .clkname = EDMA_TCD19_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 20
  {
    .clkname = EDMA_TCD20_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 21
  {
    .clkname = EDMA_TCD21_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 22
  {
    .clkname = EDMA_TCD22_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 23
  {
    .clkname = EDMA_TCD23_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 24
  {
    .clkname = EDMA_TCD24_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 25
  {
    .clkname = EDMA_TCD25_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 26
  {
    .clkname = EDMA_TCD26_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 27
  {
    .clkname = EDMA_TCD27_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 28
  {
    .clkname = EDMA_TCD28_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 29
  {
    .clkname = EDMA_TCD29_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 30
  {
    .clkname = EDMA_TCD30_CLK,
    .clkgate = true,
  },
#endif
#if S32K3XX_EDMA_NCHANNELS > 31
  {
    .clkname = EDMA_TCD31_CLK,
    .clkgate = true,
  },
#endif
};

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

#ifdef CONFIG_S32K3XX_DTCM_HEAP
extern uint8_t DTCM_BASE_ADDR[];
extern uint8_t DTCM_END_ADDR[];
#endif

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

/****************************************************************************
 * Name: s32k3xx_tcd_alloc
 *
 * Description:
 *   Allocate an in-memory, TCD
 *
 ****************************************************************************/

#if CONFIG_S32K3XX_EDMA_NTCD > 0
static struct s32k3xx_edmatcd_s *s32k3xx_tcd_alloc(void)
{
  struct s32k3xx_edmatcd_s *tcd;
  irqstate_t flags;

  /* Take the 'dsem'.  When we hold the the 'dsem', then we know that one
   * TCD is reserved for us in the free list.
   *
   * NOTE: We use a critical section here because we may block waiting for
   * the 'dsem'.  The critical section will be suspended while we are
   * waiting.
   */

  flags = enter_critical_section();
  nxsem_wait_uninterruptible(&g_edma.dsem);

  /* Now there should be a TCD in the free list reserved just for us */

  tcd = (struct s32k3xx_edmatcd_s *)sq_remfirst(&g_tcd_free);
  DEBUGASSERT(tcd != NULL);

  leave_critical_section(flags);
  return tcd;
}
#endif

/****************************************************************************
 * Name: s32k3xx_tcd_free
 *
 * Description:
 *   Free an in-memory, TCD
 *
 ****************************************************************************/

#if CONFIG_S32K3XX_EDMA_NTCD > 0
static void s32k3xx_tcd_free(struct s32k3xx_edmatcd_s *tcd)
{
  irqstate_t flags;

  /* Add the the TCD to the end of the free list and post the 'dsem',
   * possibly waking up another thread that might be waiting for
   * a TCD.
   */

  flags = spin_lock_irqsave(NULL);
  sq_addlast((sq_entry_t *)tcd, &g_tcd_free);
  nxsem_post(&g_edma.dsem);
  spin_unlock_irqrestore(NULL, flags);
}
#endif

/****************************************************************************
 * Name: s32k3xx_tcd_initialize()
 *
 * Description:
 *   Initialize the TCD free list from the pool of pre-allocated TCDs.
 *
 * Assumptions:
 *   Called early in the initialization sequence so no special protection is
 *   necessary.
 *
 ****************************************************************************/

#if CONFIG_S32K3XX_EDMA_NTCD > 0
static inline void s32k3xx_tcd_initialize(void)
{
  sq_entry_t *tcd;
  int i;

  /* Add each pre-allocated TCD to the tail of the TCD free list */

  sq_init(&g_tcd_free);
  for (i = 0; i < CONFIG_S32K3XX_EDMA_NTCD; i++)
    {
      tcd = (sq_entry_t *)&g_tcd_pool[i];
      sq_addlast(tcd, &g_tcd_free);
    }
}
#endif

/****************************************************************************
 * Name: s32k3xx_tcd_chanlink
 *
 * Description:
 *   This function configures either a minor link or a major link. The minor
 *   link means the channel link is triggered every time CITER decreases by 1
 *   The major link means that the channel link  is triggered when the CITER
 *   is exhausted.
 *
 *   NOTE: Users should ensure that DONE flag is cleared before calling this
 *   interface, or the configuration is invalid.
 *
 * Input Parameters:
 *   tcd  - Point to the TCD structure.
 *   type - Channel link type.
 *   chan - The linked channel number.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#ifdef CONFIG_S32K3XX_EDMA_ELINK
static inline void s32k3xx_tcd_chanlink(uint8_t flags,
                                        struct s32k3xx_dmach_s *linkch,
                                        struct s32k3xx_edmatcd_s *tcd)
{
  uint16_t regval16;

  flags &= EDMA_CONFIG_LINKTYPE_MASK;

  if (linkch == NULL || flags == EDMA_CONFIG_LINKTYPE_LINKNONE)
    {
      /* No link or no link channel provided */

      /* Disable minor links */

      /* Disable major link */

      tcd->csr   &= ~EDMA_TCD_CSR_MAJORELINK;
    }
  else if (flags == EDMA_CONFIG_LINKTYPE_MINORLINK) /* Minor link config */
    {
      /* Enable minor link */

      tcd->citer |= EDMA_TCD_CITER_ELINK;
      tcd->biter |= EDMA_TCD_BITER_ELINK;

      /* Set linked channel */

      regval16    = tcd->citer;
      regval16   &= ~EDMA_TCD_CITER_LINKCH_MASK;
      regval16   |= EDMA_TCD_CITER_LINKCH(linkch->chan);
      tcd->citer  = regval16;

      regval16    = tcd->biter;
      regval16   &= ~EDMA_TCD_BITER_LINKCH_MASK;
      regval16   |= EDMA_TCD_BITER_LINKCH(linkch->chan);
      tcd->biter  = regval16;
    }
  else /* if (flags == EDMA_CONFIG_LINKTYPE_MAJORLINK)  Major link config */
    {
      /* Enable major link */

      regval16    = tcd->csr;
      regval16   |= EDMA_TCD_CSR_MAJORELINK;
      tcd->csr    = regval16;

      /* Set major linked channel */

      regval16   &= ~EDMA_TCD_CSR_MAJORLINKCH_MASK;
      regval16   |=  EDMA_TCD_CSR_MAJORLINKCH(linkch->chan);
      tcd->csr    = regval16;
    }
}
#endif

/****************************************************************************
 * Name: s32k3xx_tcd_configure
 *
 * Description:
 *  Configure all TCD registers to the specified values.  'tcd' is an
 *  'overlay' that may refer either to either the TCD register set or to an
 *  in-memory TCD structure.
 *
 ****************************************************************************/

static inline void s32k3xx_tcd_configure(struct s32k3xx_edmatcd_s *tcd,
                            const struct s32k3xx_edma_xfrconfig_s *config)
{
  tcd->saddr    = config->saddr;
  tcd->soff     = config->soff;
  tcd->attr     = EDMA_TCD_ATTR_SSIZE(config->ssize) |  /* Transfer Attributes */
                  EDMA_TCD_ATTR_DSIZE(config->dsize);
#ifdef CONFIG_S32K3XX_EDMA_MOD
  tcd->attr     |= EDMA_TCD_ATTR_SMOD(config->smod) |  /* Transfer Attributes */
                   EDMA_TCD_ATTR_DMOD(config->dmod);
#endif
  tcd->nbytes   = config->nbytes;
  tcd->slast    = config->flags & EDMA_CONFIG_LOOPSRC ? -config->iter : 0;
  tcd->daddr    = config->daddr;
  tcd->doff     = config->doff;
  tcd->citer    = config->iter & EDMA_TCD_CITER_MASK;
  tcd->biter    = config->iter & EDMA_TCD_BITER_MASK;
  tcd->csr      = config->flags & EDMA_CONFIG_LOOP_MASK ?
                                  0 : EDMA_TCD_CSR_DREQ;
  tcd->csr     |= config->flags & EDMA_CONFIG_INTHALF ?
                                  EDMA_TCD_CSR_INTHALF : 0;
  tcd->dlastsga = config->flags & EDMA_CONFIG_LOOPDEST ? -config->iter : 0;

#ifdef CONFIG_S32K3XX_DTCM_HEAP
  /* Remap address to backdoor address for eDMA */

  if (tcd->saddr >= (uint32_t)DTCM_BASE_ADDR &&
      tcd->saddr < (uint32_t)DTCM_END_ADDR)
    {
        tcd->saddr += DTCM_BACKDOOR_OFFSET;
    }

  if (tcd->daddr >= (uint32_t)DTCM_BASE_ADDR &&
      tcd->daddr < (uint32_t)DTCM_END_ADDR)
    {
        tcd->daddr += DTCM_BACKDOOR_OFFSET;
    }
#endif

  /* And special case flags */

#ifdef CONFIG_S32K3XX_EDMA_ELINK
  /* Configure major/minor link mapping */

  s32k3xx_tcd_chanlink(config->flags,
                       (struct s32k3xx_dmach_s *)config->linkch,
                       tcd);
#endif
}

/****************************************************************************
 * Name: s32k3xx_tcd_instantiate
 *
 * Description:
 *   Copy an in-memory TCD into eDMA channel TCD registers
 *
 ****************************************************************************/

#if CONFIG_S32K3XX_EDMA_NTCD > 0
static void s32k3xx_tcd_instantiate(struct s32k3xx_dmach_s *dmach,
                                    const struct s32k3xx_edmatcd_s *tcd)
{
  uintptr_t base = S32K3XX_EDMA_TCD[dmach->chan];

  /* Push tcd into hardware TCD register */

  /* Clear DONE bit first, otherwise ESG cannot be set */

  putreg16(0,             base + S32K3XX_EDMA_TCD_CSR_OFFSET);

  putreg32(tcd->saddr,    base + S32K3XX_EDMA_TCD_SADDR_OFFSET);
  putreg16(tcd->soff,     base + S32K3XX_EDMA_TCD_SOFF_OFFSET);
  putreg16(tcd->attr,     base + S32K3XX_EDMA_TCD_ATTR_OFFSET);
  putreg32(tcd->nbytes,   base + S32K3XX_EDMA_TCD_NBYTES_OFFSET);
  putreg32(tcd->slast,    base + S32K3XX_EDMA_TCD_SLAST_SDA_OFFSET);
  putreg32(tcd->daddr,    base + S32K3XX_EDMA_TCD_DADDR_OFFSET);
  putreg16(tcd->doff,     base + S32K3XX_EDMA_TCD_DOFF_OFFSET);
  putreg16(tcd->citer,    base + S32K3XX_EDMA_TCD_CITER_OFFSET);
  putreg32(tcd->dlastsga, base + S32K3XX_EDMA_TCD_DLAST_SGA_OFFSET);

  putreg16(tcd->csr,      base + S32K3XX_EDMA_TCD_CSR_OFFSET);

  putreg16(tcd->biter,    base + S32K3XX_EDMA_TCD_BITER_OFFSET);
}
#endif

/****************************************************************************
 * Name: s32k3xx_dmaterminate
 *
 * Description:
 *   Terminate the DMA transfer and disable the DMA channel
 *
 ****************************************************************************/

static void s32k3xx_dmaterminate(struct s32k3xx_dmach_s *dmach, int result)
{
#if CONFIG_S32K3XX_EDMA_NTCD > 0
  struct s32k3xx_edmatcd_s *tcd;
  struct s32k3xx_edmatcd_s *next;
#endif
  uint8_t chan;

  chan            = dmach->chan;

  /* Disable channel IRQ requests */

  putreg32(EDMA_CH_INT, S32K3XX_EDMA_TCD[chan] + S32K3XX_EDMA_CH_INT_OFFSET);

  /* Clear CSR to disable channel. Because if the given channel started,
   * transfer CSR will be not zero. Because if it is the last transfer, DREQ
   * will be set.  If not, ESG will be set.
   */

  putreg32(0, S32K3XX_EDMA_TCD[chan] + S32K3XX_EDMA_CH_CSR_OFFSET);

  putreg16(0, S32K3XX_EDMA_TCD[chan] + S32K3XX_EDMA_TCD_CSR_OFFSET);

  /* Cancel next TCD transfer. */

  putreg32(0, S32K3XX_EDMA_TCD[chan] + S32K3XX_EDMA_TCD_DLAST_SGA_OFFSET);

#if CONFIG_S32K3XX_EDMA_NTCD > 0
  /* Return all allocated TCDs to the free list */

  for (tcd = dmach->head; tcd != NULL; tcd = next)
    {
      /* If channel looped to itself we are done
       * if not continue to free tcds in chain
       */

       next = dmach->flags & EDMA_CONFIG_LOOPDEST ?
              NULL : (struct s32k3xx_edmatcd_s *)tcd->dlastsga;

       s32k3xx_tcd_free(tcd);
    }

  dmach->head = NULL;
  dmach->tail = NULL;
#endif

  /* Perform the DMA complete callback */

  if (dmach->callback)
    {
      dmach->callback((DMACH_HANDLE)dmach, dmach->arg, true, result);
    }

  dmach->callback = NULL;
  dmach->arg      = NULL;
  dmach->state    = S32K3XX_DMA_IDLE;
}

/****************************************************************************
 * Name: s32k3xx_edma_interrupt
 *
 * Description:
 *   DMA interrupt handler.  This function clears the channel major
 *   interrupt flag and calls the callback function if it is not NULL.
 *
 *   NOTE:  For the case using TCD queue, when the major iteration count is
 *   exhausted, additional operations are performed.  These include the
 *   final address adjustments and reloading of the BITER field into the
 *   CITER.  Assertion of an optional interrupt request also occurs at this
 *   time, as does a possible fetch of a new TCD from memory using the
 *   scatter/gather address pointer included in the descriptor (if scatter/
 *   gather is enabled).
 *
 ****************************************************************************/

static int s32k3xx_edma_interrupt(int irq, void *context, void *arg)
{
  struct s32k3xx_dmach_s *dmach;

  uint32_t  regval32;
  uint32_t  errval32;
  uint8_t   chan;
  int       result;

  /* 'arg' should the DMA channel instance. */

  dmach = (struct s32k3xx_dmach_s *)arg;
  DEBUGASSERT(dmach != NULL);

  chan  = dmach->chan;
  DEBUGASSERT(chan < S32K3XX_EDMA_NCHANNELS && dmach == &g_edma.dmach[chan]);

  /* Get the eDMA Error Status register value. */

  errval32  = getreg32(S32K3XX_EDMA_TCD[chan] +
                      S32K3XX_EDMA_CH_ES_OFFSET);

  if (errval32 & EDMA_CH_ES_ERR)
    {
      DEBUGASSERT(dmach->state == S32K3XX_DMA_ACTIVE);

      /* Clear the error */

      putreg32(EDMA_CH_ES_ERR, S32K3XX_EDMA_TCD[chan] +
                      S32K3XX_EDMA_CH_ES_OFFSET);

      /* Clear the pending eDMA channel interrupt */

      putreg32(EDMA_CH_INT, S32K3XX_EDMA_TCD[chan] +
               S32K3XX_EDMA_CH_INT_OFFSET);

      s32k3xx_dmaterminate(dmach, -EIO);
      return OK;
    }

  /* Check for an eDMA pending interrupt on this channel */

  regval32 = getreg32(S32K3XX_EDMA_INT);
  if ((regval32 & EDMA_INT(chan)) != 0)
    {
      /* An interrupt is pending.
       * This should only happen if the channel is active.
       */

      DEBUGASSERT(dmach->state == S32K3XX_DMA_ACTIVE);

      /* Clear the pending eDMA channel interrupt */

      putreg32(EDMA_CH_INT, S32K3XX_EDMA_TCD[chan] +
               S32K3XX_EDMA_CH_INT_OFFSET);

      /* Get the eDMA TCD Control and Status register value. */

      regval32  = getreg32(S32K3XX_EDMA_TCD[chan] +
                          S32K3XX_EDMA_CH_CSR_OFFSET);

      /* Check if transfer has finished. */

      if ((regval32 & EDMA_CH_CSR_DONE) != 0)
        {
          /* Clear the pending DONE interrupt status. */

          regval32 |= EDMA_CH_CSR_DONE;
          putreg32(regval32, S32K3XX_EDMA_TCD[chan] +
                   S32K3XX_EDMA_CH_CSR_OFFSET);
          result = OK;
        }
      else
        {
#if CONFIG_S32K3XX_EDMA_NTCD > 0
          /* Perform the half or end-of-major-cycle DMA callback */

          if (dmach->callback != NULL)
            {
              dmach->callback((DMACH_HANDLE)dmach, dmach->arg,
                              false, OK);
            }

          return OK;
#else
          /* Otherwise the interrupt was not expected! */

          DEBUGPANIC();
          result = -EPIPE;
#endif
        }

      /* Terminate the transfer when it is done. */

      if ((dmach->flags & EDMA_CONFIG_LOOP_MASK) == 0)
        {
          s32k3xx_dmaterminate(dmach, result);
        }
      else if (dmach->callback != NULL)
        {
          dmach->callback((DMACH_HANDLE)dmach, dmach->arg,
                          true, result);
        }
    }

  return OK;
}

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

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

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

  dmainfo("Initialize eDMA\n");

  /* Enable clocking for DMA */

  s32k3xx_periphclocks(sizeof(edma_clockconfig) /
                       sizeof(struct peripheral_clock_config_s),
                       edma_clockconfig);

  /* Enable clocking for the DMA mux */

  /* FIXME */

  /* Configure the eDMA controller */

  regval = getreg32(S32K3XX_EDMA_CSR);
  regval &= ~(EDMA_CSR_EDBG | EDMA_CSR_ERCA | EDMA_CSR_HAE | EDMA_CSR_GCLC |
              EDMA_CSR_GMRC);

#ifdef CONFIG_S32K3XX_EDMA_EDBG
  regval |= EDMA_CSR_EDBG;   /* Enable Debug */
#endif
#ifdef CONFIG_S32K3XX_EDMA_ERCA
  regval |= EDMA_CSR_ERCA;   /* Enable Round Robin Channel Arbitration */
#endif
#ifdef CONFIG_S32K3XX_EDMA_ERGA
  regval |= EDMA_CSR_ERGA;   /* Enable Round Robin Group Arbitration */
#endif
#ifdef CONFIG_S32K3XX_EDMA_HOE
  regval |= EDMA_CSR_HAE;    /* Halt On Error */
#endif
#ifdef CONFIG_S32K3XX_EDMA_CLM
  regval |= EDMA_CSR_GCLC;    /* Continuous Link Mode / Global Channel Linking Control */
#endif
#ifdef CONFIG_S32K3XX_EDMA_EMLIM
  regval |= EDMA_CSR_GMRC;   /* Enable Minor Loop Mapping / Global Master ID Replication Control */
#endif

  putreg32(regval, S32K3XX_EDMA_CSR);

  /* Initialize data structures */

  for (i = 0; i < S32K3XX_EDMA_NCHANNELS; i++)
    {
      g_edma.dmach[i].chan = i;
    }

#if CONFIG_S32K3XX_EDMA_NTCD > 0
  /* Initialize the list of free TCDs from the pool of pre-allocated TCDs. */

  s32k3xx_tcd_initialize();
#endif

  /* Attach DMA interrupt vectors. */

  for (i = 0; i < S32K3XX_EDMA_NCHANNELS; i++)
    {
      irq_attach(S32K3XX_IRQ_DMA_CH0 + i,
                 s32k3xx_edma_interrupt, &g_edma.dmach[i]);
    }

  /* Disable all DMA channel interrupts at the eDMA controller */

  for (i = 0; i < S32K3XX_EDMA_NCHANNELS; i++)
    {
      /* Disable all DMA channels and DMA channel interrupts */

      putreg32(0, S32K3XX_EDMA_TCD[i] + S32K3XX_EDMA_CH_CSR_OFFSET);

      /* Set all TCD CSR, biter and citer entries to 0 so that
       * will be 0 when DONE is not set so that s32k3xx_dmach_getcount
       * reports 0.
       */

      putreg16(0, S32K3XX_EDMA_TCD[i] + S32K3XX_EDMA_TCD_CSR_OFFSET);
      putreg16(0, S32K3XX_EDMA_TCD[i] + S32K3XX_EDMA_TCD_CITER_OFFSET);
      putreg16(0, S32K3XX_EDMA_TCD[i] + S32K3XX_EDMA_TCD_BITER_OFFSET);
    }

  /* Clear all pending DMA channel interrupts */

  putreg32(0xffffffff, S32K3XX_EDMA_INT);

  /* Enable the channel interrupts at the NVIC (still disabled at the eDMA
   * controller).
   */

  for (i = 0; i < S32K3XX_EDMA_NCHANNELS; i++)
    {
      up_enable_irq(S32K3XX_IRQ_DMA_CH0 + i);
    }
}

/****************************************************************************
 * Name: s32k3xx_dmach_alloc
 *
 *   Allocate a DMA channel.  This function sets aside a DMA channel,
 *   initializes the DMAMUX for the channel, then gives the caller exclusive
 *   access to the DMA channel.
 *
 * Input Parameters:
 *
 *   dmamux - DMAMUX configuration see DMAMUX channel configuration register
 *            bit-field definitions in hardware/s32k3xx_dmamux.h.
 *            Settings include:
 *
 *            DMAMUX_CHCFG_SOURCE     Chip-specific DMA source (required)
 *            DMAMUX_CHCFG_TRIG       DMA Channel Trigger Enable (optional)
 *            DMAMUX_CHCFG_ENBL       DMA Mux Channel Enable (required)
 *
 *            A value of zero will disable the DMAMUX channel.
 *   dchpri - DCHPRI channel priority configuration.  See DCHPRI channel
 *            configuration register bit-field definitions in
 *            hardware/s32k3xx_edma.h.  Meaningful settings include:
 *
 *            EDMA_DCHPRI_CHPRI       Channel Arbitration Priority
 *            DCHPRI_DPA              Disable Preempt Ability
 *            DCHPRI_ECP              Enable Channel Preemption
 *
 *            The power-on default, 0x05, is a reasonable choice.
 *
 * Returned Value:
 *   If a DMA channel is available, this function returns a non-NULL, void*
 *   DMA channel handle.  NULL is returned on any failure.
 *
 ****************************************************************************/

DMACH_HANDLE s32k3xx_dmach_alloc(uint16_t dmamux, uint8_t dchpri)
{
  struct s32k3xx_dmach_s *dmach;
  unsigned int chndx;
  unsigned int offset = 0;
  int ret;

  if ((dmamux & DMAMUX_CHCFG_DMAMUX1) == DMAMUX_CHCFG_DMAMUX1)
    {
      offset = 16;
    }

  /* Search for an available DMA channel */

  dmach = NULL;
  ret = nxmutex_lock(&g_edma.chlock);
  if (ret < 0)
    {
      return NULL;
    }

  for (chndx = offset; chndx < S32K3XX_EDMA_NCHANNELS / 2 + offset; chndx++)
    {
      struct s32k3xx_dmach_s *candidate = &g_edma.dmach[chndx];

      if (!candidate->inuse)
        {
          dmach         = candidate;
          dmach->inuse  = true;
          dmach->state  = S32K3XX_DMA_IDLE;
          dmach->dmamux = dmamux & DMAMUX_CHCFG_MASK;

          /* Clear any pending interrupts on the channel */

          DEBUGASSERT(chndx == dmach->chan);
          putreg32(0, S32K3XX_EDMA_TCD[chndx] + S32K3XX_EDMA_CH_CSR_OFFSET);

          /* Make sure that the channel is disabled. */

          putreg32(EDMA_CH_INT, S32K3XX_EDMA_TCD[dmach->chan] +
                   S32K3XX_EDMA_CH_INT_OFFSET);

          /* Disable the associated DMAMUX for now */

          if (chndx < 16)
            {
              dmainfo("CH%d: MUX0: %p\n", chndx,
                      (void *)S32K3XX_DMAMUX0_CHCFG(chndx));
              putreg8(0, S32K3XX_DMAMUX0_CHCFG(chndx));
            }
          else
            {
              dmainfo("CH%d: MUX1: %p\n", chndx,
                      (void *)S32K3XX_DMAMUX1_CHCFG(chndx - 16));
              putreg8(0, S32K3XX_DMAMUX1_CHCFG(chndx - 16));
            }
          break;
        }
    }

  nxmutex_unlock(&g_edma.chlock);

  /* Show the result of the allocation */

  if (dmach != NULL)
    {
      dmainfo("CH%d: returning dmach: %p\n", dmach->chan, dmach);
    }
  else
    {
      dmaerr("ERROR: Failed allocate eDMA channel\n");
    }

  return (DMACH_HANDLE)dmach;
}

/****************************************************************************
 * Name: s32k3xx_dmach_free
 *
 * Description:
 *   Release a DMA channel.  NOTE:  The 'handle' used in this argument must
 *   NEVER be used again until s32k3xx_dmach_alloc() is called again to
 *   re-gain a valid handle.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

void s32k3xx_dmach_free(DMACH_HANDLE handle)
{
  struct s32k3xx_dmach_s *dmach = (struct s32k3xx_dmach_s *)handle;

  dmainfo("dmach: %p\n", dmach);
  DEBUGASSERT(dmach != NULL && dmach->inuse &&
              dmach->state != S32K3XX_DMA_ACTIVE);

  /* 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 */
  dmach->state = S32K3XX_DMA_IDLE;        /* Better not be active! */

  /* Make sure that the channel is disabled. */

  putreg32(EDMA_CH_INT, S32K3XX_EDMA_TCD[dmach->chan] +
           S32K3XX_EDMA_CH_INT_OFFSET);

  /* Disable the associated DMAMUX */

  if (dmach->chan < 16)
    {
      putreg8(0, S32K3XX_DMAMUX0_CHCFG(dmach->chan));
    }
  else
    {
      putreg8(0, S32K3XX_DMAMUX1_CHCFG(dmach->chan - 16));
    }
}

/****************************************************************************
 * Name: s32k3xx_dmach_xfrsetup
 *
 * Description:
 *   This function adds the eDMA transfer to the DMA sequence.  The request
 *   is setup according to the content of the transfer configuration
 *   structure. For "normal" DMA, s32k3xx_dmach_xfrsetup is called only once.
 *   Scatter/gather DMA is accomplished by calling this function repeatedly,
 *   once for each transfer in the sequence.  Scatter/gather DMA processing
 *   is enabled automatically when the second transfer configuration is
 *   received.
 *
 *   This function may be called multiple times to handle multiple,
 *   discontinuous transfers (scatter-gather)
 *
 * Input Parameters:
 *   handle - DMA channel handle created by s32k3xx_dmach_alloc()
 *   config - A DMA transfer configuration instance, populated by the
 *            The content of 'config' describes the transfer
 *
 * Returned Value
 *   Zero (OK) is returned on success; a negated errno value is returned on
 *   any failure.
 *
 ****************************************************************************/

int s32k3xx_dmach_xfrsetup(DMACH_HANDLE *handle,
                           const struct s32k3xx_edma_xfrconfig_s *config)
{
  struct s32k3xx_dmach_s *dmach = (struct s32k3xx_dmach_s *)handle;
#if CONFIG_S32K3XX_EDMA_NTCD > 0
  struct s32k3xx_edmatcd_s *tcd;
  struct s32k3xx_edmatcd_s *prev;
  uint16_t mask = config->flags & EDMA_CONFIG_INTMAJOR ? 0 :
                                  EDMA_TCD_CSR_INTMAJOR;
#else
  uint32_t regval32;
#endif
  uint16_t regval16;

  DEBUGASSERT(dmach != NULL);
  dmainfo("dmach%u: %p config: %p\n", dmach->chan, dmach, config);

  dmach->flags  = config->flags;

#if CONFIG_S32K3XX_EDMA_NTCD > 0
  /* Scatter/gather DMA is supported */

  /* Allocate a TCD, waiting if necessary */

  tcd = s32k3xx_tcd_alloc();

  /* Configure current TCD block transfer. */

  s32k3xx_tcd_configure(tcd, config);

  /* Enable the interrupt when the major iteration count completes for this
   * TCD.  For "normal" DMAs, this will correspond to the DMA DONE
   * interrupt; for scatter gather DMAs, multiple interrupts will be
   * generated with the final being the DONE interrupt.
   */

  tcd->csr |= EDMA_TCD_CSR_INTMAJOR;

  /* Is this the first descriptor in the list? */

  if (dmach->head == NULL)
    {
      /* Yes.. add it to the list */

      dmach->head  = tcd;
      dmach->tail  = tcd;

      /* And instantiate the first TCD in the DMA channel TCD registers. */

      s32k3xx_tcd_instantiate(dmach, tcd);
    }
  else
    {
      /* Cannot mix transfer types */

      if (dmach->flags & EDMA_CONFIG_LOOP_MASK)
        {
          s32k3xx_tcd_free(tcd);
          return -EINVAL;
        }

      /* Chain from previous descriptor in the list. */

      /* Enable scatter/gather feature in the previous TCD. */

      prev           = dmach->tail;
      regval16       = prev->csr;
      regval16      &= ~(EDMA_TCD_CSR_DREQ | mask);
      regval16      |= EDMA_TCD_CSR_ESG;
      prev->csr      = regval16;

      prev->dlastsga = (uint32_t)tcd;
      dmach->tail    = tcd;

      /* Clean cache associated with the previous TCD memory */

      up_clean_dcache((uintptr_t)prev,
                      (uintptr_t)prev + sizeof(struct s32k3xx_edmatcd_s));

      /* Check if the TCD block in the DMA channel registers is the same as
       * the previous previous TCD.  This can happen if the previous TCD was
       * the first TCD and has already be loaded into the TCD registers.
       */

      if (dmach->head == prev)
        {
          /* Enable scatter/gather also in the TCD registers. */

          regval16  = getreg16(S32K3XX_EDMA_TCD[dmach->chan]
                               + S32K3XX_EDMA_TCD_CSR_OFFSET);
          regval16 &= ~(EDMA_TCD_CSR_DREQ | mask);
          regval16 |= EDMA_TCD_CSR_ESG;
          putreg16(regval16, S32K3XX_EDMA_TCD[dmach->chan]
                   + S32K3XX_EDMA_TCD_CSR_OFFSET);

          putreg32((uint32_t)tcd, S32K3XX_EDMA_TCD[dmach->chan]
                   + S32K3XX_EDMA_TCD_DLAST_SGA_OFFSET);
        }
    }

  /* Clean cache associated with the TCD memory */

  up_clean_dcache((uintptr_t)tcd,
                  (uintptr_t)tcd + sizeof(struct s32k3xx_edmatcd_s));
#else
  /* Scatter/gather DMA is NOT supported */

  /* Check if eDMA is busy: if the channel has started transfer, CSR will be
   * non-zero.
   */

  regval32  = getreg32(S32K3XX_EDMA_TCD[dmach->chan]
                               + S32K3XX_EDMA_CH_CSR_OFFSET);

  if (regval32 != 0 && (regval32 & EDMA_CH_CSR_DONE) == 0)
    {
      return -EBUSY;
    }

  /* Configure channel TCD registers to the values specified in config. */

  s32k3xx_tcd_configure((struct s32k3xx_edmatcd_s *)
                        S32K3XX_EDMA_TCD[dmach->chan] +
                        S32K3XX_EDMA_TCD_SADDR_OFFSET, config);

  /* Enable the DONE interrupt when the major iteration count completes. */

  modifyreg16(S32K3XX_EDMA_TCD[dmach->chan]
                   + S32K3XX_EDMA_TCD_CSR_OFFSET, 0,
              EDMA_TCD_CSR_INTMAJOR);
#endif

  /* Set the DMAMUX source and enable and optional trigger */

  if (dmach->chan < 16)
    {
      putreg8(dmach->dmamux, S32K3XX_DMAMUX0_CHCFG(dmach->chan));
    }
  else
    {
      putreg8(dmach->dmamux, S32K3XX_DMAMUX1_CHCFG(dmach->chan - 16));
    }

  dmach->state = S32K3XX_DMA_CONFIGURED;
  return OK;
}

/****************************************************************************
 * Name: s32k3xx_dmach_start
 *
 * Description:
 *   Start the DMA transfer.  This function should be called after the final
 *   call to s32k3xx_dmach_xfrsetup() in order to avoid race conditions.
 *
 *   At the conclusion of each major DMA loop, a callback to the user
 *   provided function is made:  |For "normal" DMAs, this will correspond to
 *   the DMA DONE interrupt; for scatter gather DMAs,
 *   this will be generated with the final TCD.
 *
 *   At the conclusion of the DMA, the DMA channel is reset, all TCDs are
 *   freed, and the callback function is called with the the success/fail
 *   result of the DMA.
 *
 *   NOTE: On Rx DMAs (peripheral-to-memory or memory-to-memory), it is
 *   necessary to invalidate the destination memory.  That is not done
 *   automatically by the DMA module.  Invalidation of the destination memory
 *   regions is the responsibility of the caller.
 *
 * Input Parameters:
 *   handle   - DMA channel handle created by s32k3xx_dmach_alloc()
 *   callback - The callback to be invoked when the DMA is completes or is
 *              aborted.
 *   arg      - An argument that accompanies the callback
 *
 * Returned Value:
 *   Zero (OK) is returned on success; a negated errno value is returned on
 *   any failure.
 *
 ****************************************************************************/

int s32k3xx_dmach_start(DMACH_HANDLE handle, edma_callback_t callback,
                        void *arg)
{
  struct s32k3xx_dmach_s *dmach = (struct s32k3xx_dmach_s *)handle;
  irqstate_t flags;
  uint32_t regval;
  uint8_t chan;

  DEBUGASSERT(dmach != NULL && dmach->state == S32K3XX_DMA_CONFIGURED);
  chan            = dmach->chan;
  dmainfo("dmach%u: %p callback: %p arg: %p\n", chan, dmach, callback, arg);

  /* Save the callback info.  This will be invoked when the DMA completes */

  flags           = spin_lock_irqsave(NULL);
  dmach->callback = callback;
  dmach->arg      = arg;

#if CONFIG_S32K3XX_EDMA_NTCD > 0
  /* Although it is not recommended, it might be possible to call this
   * function multiple times while adding TCDs on the fly.
   */

  if (dmach->state != S32K3XX_DMA_ACTIVE)
#endif
    {
      dmach->state    = S32K3XX_DMA_ACTIVE;

      regval         = getreg32(S32K3XX_EDMA_TCD[chan]
                       + S32K3XX_EDMA_CH_CSR_OFFSET);
      regval        |= EDMA_CH_CSR_ERQ | EDMA_CH_CSR_EEI;
      putreg32(regval, S32K3XX_EDMA_TCD[chan] + S32K3XX_EDMA_CH_CSR_OFFSET);
    }

  spin_unlock_irqrestore(NULL, flags);
  return OK;
}

/****************************************************************************
 * Name: s32k3xx_dmach_stop
 *
 * Description:
 *   Cancel the DMA.  After s32k3xx_dmach_stop() is called, the DMA channel
 *   is reset, all TCDs are freed, and s32k3xx_dmarx/txsetup() must be called
 *   before s32k3xx_dmach_start() can be called again.
 *
 * Input Parameters:
 *   handle   - DMA channel handle created by s32k3xx_dmach_alloc()
 *
 * Returned Value:
 *   None.
 *
 ****************************************************************************/

void s32k3xx_dmach_stop(DMACH_HANDLE handle)
{
  struct s32k3xx_dmach_s *dmach = (struct s32k3xx_dmach_s *)handle;
  irqstate_t flags;

  dmainfo("dmach: %p\n", dmach);
  DEBUGASSERT(dmach != NULL);

  flags = spin_lock_irqsave(NULL);
  s32k3xx_dmaterminate(dmach, -EINTR);
  spin_unlock_irqrestore(NULL, flags);
}

/****************************************************************************
 * Name: s32k3xx_dmach_getcount
 *
 * Description:
 *   This function checks the TCD (Task Control Descriptor) status for a
 *   specified eDMA channel and returns the the number of major loop counts
 *   that have not finished.
 *
 *   NOTES:
 *   1. This function can only be used to get unfinished major loop count of
 *      transfer without the next TCD, or it might be inaccuracy.
 *   2. The unfinished/remaining transfer bytes cannot be obtained directly
 *      from registers while the channel is running.
 *
 *   Because to calculate the remaining bytes, the initial NBYTES configured
 *   in DMA_TCDn_NBYTES_MLNO register is needed while the eDMA IP does not
 *   support getting it while a channel is active.  In another words, the
 *   NBYTES value reading is always the actual (decrementing) NBYTES value
 *   the dma_engine is working with while a channel is running.
 *   Consequently, to get the remaining transfer bytes, a software-saved
 *   initial value of NBYTES (for example copied before enabling the channel)
 *   is needed. The formula to calculate it is shown below:
 *
 *     RemainingBytes = RemainingMajorLoopCount *
 *                      NBYTES(initially configured)
 *
 * Input Parameters:
 *   handle  - DMA channel handle created by s32k3xx_dmach_alloc()
 *
 * Returned Value:
 *   Major loop count which has not been transferred yet for the current TCD.
 *
 ****************************************************************************/

unsigned int s32k3xx_dmach_getcount(DMACH_HANDLE *handle)
{
  struct s32k3xx_dmach_s *dmach = (struct s32k3xx_dmach_s *)handle;
  unsigned int remaining = 0;
  uintptr_t regval32;
  uint16_t regval16;

  DEBUGASSERT(dmach != NULL);

  /* If the DMA is done, then the remaining count is zero */

  regval32  = getreg32(S32K3XX_EDMA_TCD[dmach->chan]
             + S32K3XX_EDMA_CH_CSR_OFFSET);

  if ((regval32 & EDMA_CH_CSR_DONE) == 0)
    {
      /* Calculate the unfinished bytes */

      regval16 = getreg16(S32K3XX_EDMA_TCD[dmach->chan]
                   + S32K3XX_EDMA_TCD_CITER_OFFSET);

      if ((regval16 & EDMA_TCD_CITER_ELINK) != 0)
        {
          remaining = (regval16 & EDMA_TCD_CITER_MASK) >>
                      EDMA_TCD_CITER_SHIFT;
        }
      else
        {
          remaining = 1;
        }
    }

  return remaining;
}

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

#ifdef CONFIG_DEBUG_DMA
void s32k3xx_dmasample(DMACH_HANDLE handle, struct s32k3xx_dmaregs_s *regs)
{
  struct s32k3xx_dmach_s *dmach = (struct s32k3xx_dmach_s *)handle;
  uintptr_t regaddr;
  unsigned int chan;
  irqstate_t flags;
  uintptr_t base = S32K3XX_EDMA_TCD[dmach->chan];

  DEBUGASSERT(dmach != NULL && regs != NULL);
  chan           = dmach->chan;
  regs->chan     = chan;

  /* eDMA Global Registers */

  flags          = spin_lock_irqsave(NULL);

  regs->cr       = getreg32(S32K3XX_EDMA_CSR);  /* Control */
  regs->es       = getreg32(S32K3XX_EDMA_ES);   /* Error Status */
  regs->req      = getreg32(S32K3XX_EDMA_INT);  /* Interrupt Request */
  regs->hrs      = getreg32(S32K3XX_EDMA_HRS);  /* Hardware Request Status */

  /* eDMA TCD */

  regs->saddr    = getreg32(base + S32K3XX_EDMA_TCD_SADDR_OFFSET);
  regs->soff     = getreg16(base + S32K3XX_EDMA_TCD_SOFF_OFFSET);
  regs->attr     = getreg16(base + S32K3XX_EDMA_TCD_ATTR_OFFSET);
  regs->nbml     = getreg32(base + S32K3XX_EDMA_TCD_NBYTES_OFFSET);
  regs->slast    = getreg32(base + S32K3XX_EDMA_TCD_SLAST_SDA_OFFSET);
  regs->daddr    = getreg32(base + S32K3XX_EDMA_TCD_DADDR_OFFSET);
  regs->doff     = getreg16(base + S32K3XX_EDMA_TCD_DOFF_OFFSET);
  regs->citer    = getreg16(base + S32K3XX_EDMA_TCD_CITER_OFFSET);
  regs->dlastsga = getreg32(base + S32K3XX_EDMA_TCD_DLAST_SGA_OFFSET);
  regs->csr      = getreg16(base + S32K3XX_EDMA_TCD_CSR_OFFSET);
  regs->biter    = getreg16(base + S32K3XX_EDMA_TCD_BITER_OFFSET);

  /* DMAMUX registers */

  if (chan < 16)
    {
      regs->dmamux   = getreg32(S32K3XX_DMAMUX0_CHCFG(chan)); /* Channel configuration */
    }
  else
    {
      regs->dmamux   = getreg32(S32K3XX_DMAMUX1_CHCFG(chan - 16)); /* Channel configuration */
    }

  spin_unlock_irqrestore(NULL, flags);
}
#endif /* CONFIG_DEBUG_DMA */

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

#ifdef CONFIG_DEBUG_DMA
void s32k3xx_dmadump(const struct s32k3xx_dmaregs_s *regs, const char *msg)
{
  unsigned int chan;

  DEBUGASSERT(regs != NULL && msg != NULL);

  chan = regs->chan;
  DEBUGASSERT(chan < S32K3XX_EDMA_NCHANNELS);

  dmainfo("%s\n", msg);
  dmainfo("  eDMA Global Registers:\n");
  dmainfo("          CR: %08x\n", (unsigned int)regs->cr);
  dmainfo("          ES: %08x\n", (unsigned int)regs->es);
  dmainfo("         INT: %08x\n", (unsigned int)regs->req);
  dmainfo("        EARS: %08x\n", (unsigned int)regs->hrs);

  /* eDMA Channel registers */

  dmainfo("  eDMA Channel %u Registers:\n", chan);
  dmainfo("    DCHPRI: %02x\n", regs->dchpri);

  /* eDMA TCD */

  dmainfo("  eDMA Channel %u TCD Registers:\n", chan);
  dmainfo("       SADDR: %08x\n", (unsigned int)regs->saddr);
  dmainfo("        SOFF: %04x\n", (unsigned int)regs->soff);
  dmainfo("        ATTR: %04x\n", (unsigned int)regs->attr);
  dmainfo("        NBML: %05x\n", (unsigned int)regs->nbml);
  dmainfo("       SLAST: %05x\n", (unsigned int)regs->slast);
  dmainfo("       DADDR: %05x\n", (unsigned int)regs->daddr);
  dmainfo("        DOFF: %04x\n", (unsigned int)regs->doff);
  dmainfo("       CITER: %04x\n", (unsigned int)regs->citer);
  dmainfo("    DLASTSGA: %08x\n", (unsigned int)regs->dlastsga);
  dmainfo("         CSR: %04x\n", (unsigned int)regs->csr);
  dmainfo("       BITER: %04x\n", (unsigned int)regs->biter);

  /* DMAMUX registers */

  dmainfo("  DMAMUX Channel %u Registers:\n", chan);
  dmainfo("      DMAMUX: %08x\n", (unsigned int)regs->dmamux);
}
#endif /* CONFIG_DEBUG_DMA */
#endif /* CONFIG_S32K3XX_EDMA */
