/****************************************************************************
 * arch/arm/src/stm32/stm32f40xxx_dma.c
 *
 *   Copyright (C) 2011 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 <stdint.h>
#include <stdbool.h>
#include <semaphore.h>
#include <debug.h>
#include <errno.h>

#include <nuttx/irq.h>
#include <nuttx/arch.h>
#include <arch/irq.h>

#include "up_arch.h"
#include "up_internal.h"
#include "os_internal.h"
#include "chip.h"
#include "stm32_dma.h"
#include "stm32_internal.h"

/* This file supports only the STM32 F4 family (an probably the F2 family
 * as well?)
 */

#if defined(CONFIG_STM32_STM32F40XX)

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

#define DMA1_NSTREAMS    8
#if STM32_NDMA > 1
#  define DMA2_NSTREAMS  8
#  define DMA_NSTREAMS   (DMA1_NSTREAMS+DMA2_NSTREAMS)
#else
#  define DMA_NSTREAMS   DMA1_NSTREAMS
#endif

#ifndef CONFIG_DMA_PRI
#  define CONFIG_DMA_PRI NVIC_SYSH_PRIORITY_DEFAULT
#endif

/* Convert the DMA stream base address to the DMA register block address */
 
#define DMA_BASE(ch)     (ch & 0xfffffc00)

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

/* This structure descibes one DMA channel */

struct stm32_dma_s
{
  uint8_t        stream;   /* DMA stream number (0-7) */
  uint8_t        irq;      /* DMA stream IRQ number */
  uint8_t        shift;    /* ISR/IFCR bit shift value */
  uint8_t        pad;      /* Unused */
  sem_t          sem;      /* Used to wait for DMA channel to become available */
  uint32_t       base;     /* DMA register channel base address */
  dma_callback_t callback; /* Callback invoked when the DMA completes */
  void          *arg;      /* Argument passed to callback function */
};

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

/* This array describes the state of each DMA */

static struct stm32_dma_s g_dma[DMA_NSTREAMS] =
{
  {
    .stream   = 0,
    .irq      = STM32_IRQ_DMA1S0,
    .shift    = DMA_INT_STREAM0_SHIFT,
    .base     = STM32_DMA1_BASE + STM32_DMA_OFFSET(0),
  },
  {
    .stream   = 1,
    .irq      = STM32_IRQ_DMA1S1,
    .shift    = DMA_INT_STREAM1_SHIFT,
    .base     = STM32_DMA1_BASE + STM32_DMA_OFFSET(1),
  },
  {
    .stream   = 2,
    .irq      = STM32_IRQ_DMA1S2,
    .shift    = DMA_INT_STREAM2_SHIFT,
    .base     = STM32_DMA1_BASE + STM32_DMA_OFFSET(2),
  },
  {
    .stream   = 3,
    .irq      = STM32_IRQ_DMA1S3,
    .shift    = DMA_INT_STREAM3_SHIFT,
    .base     = STM32_DMA1_BASE + STM32_DMA_OFFSET(4),
  },
  {
    .stream   = 4,
    .irq      = STM32_IRQ_DMA1S4,
    .shift    = DMA_INT_STREAM4_SHIFT,
    .base     = STM32_DMA1_BASE + STM32_DMA_OFFSET(4),
  },
  {
    .stream   = 5,
    .irq      = STM32_IRQ_DMA1S5,
    .shift    = DMA_INT_STREAM5_SHIFT,
    .base     = STM32_DMA1_BASE + STM32_DMA_OFFSET(5),
  },
  {
    .stream   = 6,
    .irq      = STM32_IRQ_DMA1S6,
    .shift    = DMA_INT_STREAM6_SHIFT,
    .base     = STM32_DMA1_BASE + STM32_DMA_OFFSET(6),
  },
  {
    .stream   = 7,
    .irq      = STM32_IRQ_DMA1S7,
    .shift    = DMA_INT_STREAM7_SHIFT,
    .base     = STM32_DMA1_BASE + STM32_DMA_OFFSET(7),
  },
#if STM32_NDMA > 1
  {
    .stream   = 0,
    .irq      = STM32_IRQ_DMA2S0,
    .shift    = DMA_INT_STREAM0_SHIFT,
    .base     = STM32_DMA2_BASE + STM32_DMA_OFFSET(0),
  },
  {
    .stream   = 1,
    .irq      = STM32_IRQ_DMA2S1,
    .shift    = DMA_INT_STREAM1_SHIFT,
    .base     = STM32_DMA2_BASE + STM32_DMA_OFFSET(1),
  },
  {
    .stream   = 2,
    .irq      = STM32_IRQ_DMA2S2,
    .shift    = DMA_INT_STREAM2_SHIFT,
    .base     = STM32_DMA2_BASE + STM32_DMA_OFFSET(2),
  },
  {
    .stream   = 3,
    .irq      = STM32_IRQ_DMA2S3,
    .shift    = DMA_INT_STREAM3_SHIFT,
    .base     = STM32_DMA2_BASE + STM32_DMA_OFFSET(3),
  },
  {
    .stream   = 4,
    .irq      = STM32_IRQ_DMA2S4,
    .base     = STM32_DMA2_BASE + STM32_DMA_OFFSET(4),
  },
  {
    .stream   = 5,
    .irq      = STM32_IRQ_DMA2S5,
    .shift    = DMA_INT_STREAM5_SHIFT,
    .base     = STM32_DMA2_BASE + STM32_DMA_OFFSET(5),
  },
  {
    .stream   = 6,
    .irq      = STM32_IRQ_DMA2S6,
    .shift    = DMA_INT_STREAM6_SHIFT,
    .base     = STM32_DMA2_BASE + STM32_DMA_OFFSET(6),
  },
  {
    .stream   = 7,
    .irq      = STM32_IRQ_DMA2S7,
    .shift    = DMA_INT_STREAM7_SHIFT,
    .base     = STM32_DMA2_BASE + STM32_DMA_OFFSET(7),
  },
#endif
};

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

/****************************************************************************
 * DMA register access functions
 ****************************************************************************/

/* Get non-channel register from DMA1 or DMA2 */

static inline uint32_t dmabase_getreg(struct stm32_dma_s *dmast, uint32_t offset)
{
  return getreg32(DMA_BASE(dmast->base) + offset);
}

/* Write to non-channel register in DMA1 or DMA2 */

static inline void dmabase_putreg(struct stm32_dma_s *dmast, uint32_t offset, uint32_t value)
{
  putreg32(value, DMA_BASE(dmast->base) + offset);
}

/* Get channel register from DMA1 or DMA2 */

static inline uint32_t dmast_getreg(struct stm32_dma_s *dmast, uint32_t offset)
{
  return getreg32(dmast->base + offset);
}

/* Write to channel register in DMA1 or DMA2 */

static inline void dmast_putreg(struct stm32_dma_s *dmast, uint32_t offset, uint32_t value)
{
  putreg32(value, dmast->base + offset);
}

/************************************************************************************
 * Name: stm32_dmatake() and stm32_dmagive()
 *
 * Description:
 *   Used to get exclusive access to a DMA channel.
 *
 ************************************************************************************/

static void stm32_dmatake(FAR struct stm32_dma_s *dmast)
{
  /* Take the semaphore (perhaps waiting) */

  while (sem_wait(&dmast->sem) != 0)
    {
      /* The only case that an error should occur here is if the wait was awakened
       * by a signal.
       */

      ASSERT(errno == EINTR);
    }
}

static inline void stm32_dmagive(FAR struct stm32_dma_s *dmast)
{
  (void)sem_post(&dmast->sem);
}

/************************************************************************************
 * Name: stm32_dmastreamdisable
 *
 * Description:
 *  Disable the DMA stream
 *
 ************************************************************************************/

static void stm32_dmastreamdisable(struct stm32_dma_s *dmast)
{
  uint32_t regoffset;
  uint32_t regval;

  /* Disable all interrupts at the DMA controller */

  regval = dmast_getreg(dmast, STM32_DMA_SCR_OFFSET);
  regval &= ~DMA_SCR_ALLINTS;

  /* Disable the DMA stream */

  regval &= ~DMA_SCR_EN;
  dmast_putreg(dmast, STM32_DMA_SCR_OFFSET, regval);

  /* Clear pending stream interrupts by setting bits in the upper or lower IFCR
   * register
   */

  if (dmast->stream < 4)
    {
      regoffset = STM32_DMA_LIFCR_OFFSET;
    }
  else
    {
      regoffset = STM32_DMA_HIFCR_OFFSET;
    }
  
  dmabase_putreg(dmast, regoffset, (DMA_STREAM_MASK << dmast->shift));
}

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

static int stm32_dmainterrupt(int irq, void *context)
{
  struct stm32_dma_s *dmast;
  uint32_t isr;
  uint32_t regoffset = 0;
  int stndx = 0;

  /* Get the stream structure from the interrupt number */

  if (irq >= STM32_IRQ_DMA1S0 && irq <= STM32_IRQ_DMA1S7)
    {
      stndx     = irq - STM32_IRQ_DMA1S0;
      regoffset = STM32_DMA_LISR_OFFSET;
    }
  else
#if STM32_NDMA > 1
  if (irq >= STM32_IRQ_DMA2S0 && irq <= STM32_IRQ_DMA2S7)
    {
      stndx     = irq - STM32_IRQ_DMA2S0 + DMA1_NSTREAMS;
      regoffset = STM32_DMA_HISR_OFFSET;
    }
  else
#endif
    {
      PANIC(OSERR_INTERNAL);
    }
  dmast = &g_dma[stndx];

  /* Get the interrupt status for this stream */

  isr = (dmabase_getreg(dmast, regoffset) >> dmast->shift) & DMA_STREAM_MASK;

  /* Disable the DMA stream */

  stm32_dmastreamdisable(dmast);

  /* Invoke the callback */

  if (dmast->callback)
    {
      dmast->callback(dmast, isr, dmast->arg);
    }
  return OK;
}

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

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

void weak_function up_dmainitialize(void)
{
  struct stm32_dma_s *dmast;
  int stndx;

  /* Initialize each DMA stream */

  for (stndx = 0; stndx < DMA_NSTREAMS; stndx++)
    {
      dmast = &g_dma[stndx];
      sem_init(&dmast->sem, 0, 1);

      /* Attach DMA interrupt vectors */

      (void)irq_attach(dmast->irq, stm32_dmainterrupt);

      /* Disable the DMA stream */

      stm32_dmastreamdisable(dmast);

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

      up_enable_irq(dmast->irq);
 
      /* Set the interrrupt priority */

      up_prioritize_irq(dmast->irq, CONFIG_DMA_PRI);
    }
}

/****************************************************************************
 * Name: stm32_dmachannel
 *
 * Description:
 *   Allocate a DMA channel.  This function gives the caller mutually
 *   exclusive access to the DMA channel specified by the 'stndx' argument.
 *   DMA channels are shared on the STM32:  Devices sharing the same DMA
 *   channel cannot do DMA concurrently!  See the DMACHAN_* definitions in
 *   stm32_dma.h.
 *
 *   If the DMA channel is not available, then stm32_dmachannel() will wait
 *   until the holder of the channel relinquishes the channel by calling
 *   stm32_dmafree().  WARNING: If you have two devices sharing a DMA
 *   channel and the code never releases the channel, the stm32_dmachannel
 *   call for the other will hang forever in this function!  Don't let your
 *   design do that!
 *
 *   Hmm.. I suppose this interface could be extended to make a non-blocking
 *   version.  Feel free to do that if that is what you need.
 *
 * Returned Value:
 *   Provided that 'stndx' is valid, this function ALWAYS returns a non-NULL,
 *   void* DMA channel handle.  (If 'stndx' is invalid, the function will
 *   assert if debug is enabled or do something ignorant otherwise).
 *
 * Assumptions:
 *   - The caller does not hold he DMA channel.
 *   - The caller can wait for the DMA channel to be freed if it is no
 *     available.
 *
 ****************************************************************************/

DMA_HANDLE stm32_dmachannel(int stndx)
{
  struct stm32_dma_s *dmast = &g_dma[stndx];

  DEBUGASSERT(stndx < DMA_NSTREAMS);

  /* Get exclusive access to the DMA channel -- OR wait until the channel
   * is available if it is currently being used by another driver
   */

  stm32_dmatake(dmast);

  /* The caller now has exclusive use of the DMA channel */

  return (DMA_HANDLE)dmast;
}

/****************************************************************************
 * Name: stm32_dmafree
 *
 * Description:
 *   Release a DMA channel.  If another thread is waiting for this DMA channel
 *   in a call to stm32_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 stm32_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 stm32_dmafree(DMA_HANDLE handle)
{
  struct stm32_dma_s *dmast = (struct stm32_dma_s *)handle;

  DEBUGASSERT(handle != NULL);

  /* Release the channel */

  stm32_dmagive(dmast);
}

/****************************************************************************
 * Name: stm32_dmasetup
 *
 * Description:
 *   Configure DMA before using
 *
 ****************************************************************************/

void stm32_dmasetup(DMA_HANDLE handle, uint32_t paddr, uint32_t maddr, size_t ntransfers, uint32_t scr)
{
  struct stm32_dma_s *dmast = (struct stm32_dma_s *)handle;
  uint32_t regval;

  /* Set the peripheral register address in the DMA_SPARx register. The data
   * will be moved from/to this address to/from the memory after the
   * peripheral event.
   */

  dmast_putreg(dmast, STM32_DMA_SPAR_OFFSET, paddr);

  /* Set the memory address in the DMA_SM0ARx register. The data will be
   * written to or read from this memory after the peripheral event.
   * Note that only single-buffer mode is currently supported so SM1ARx
   * is not used.
   */

  dmast_putreg(dmast, STM32_DMA_SM0AR_OFFSET, maddr);

  /* Configure the total number of data items to be transferred in the DMA_SNDTRx
   * register.  After each peripheral event, this value will be decremented.
   */

  dmast_putreg(dmast, STM32_DMA_SNDTR_OFFSET, ntransfers);

  /* Configure the stream priority using the PL[1:0] bits in the DMA_SCRx
   * register.  Configure data transfer direction, circular mode, peripheral & memory
   * incremented mode, peripheral & memory data size, and interrupt after
   * half and/or full transfer in the DMA_CCRx register.
   */

  regval  = dmast_getreg(dmast, STM32_DMA_SCR_OFFSET);
  regval &= ~(DMA_SCR_DIR_MASK|DMA_SCR_PINC|DMA_SCR_MINC|DMA_SCR_PSIZE_MASK|
              DMA_SCR_MSIZE_MASK|DMA_SCR_PINCOS|DMA_SCR_PL_MASK|DMA_SCR_PBURST_MASK|
              DMA_SCR_MBURST_MASK);
  scr    &=  (DMA_SCR_DIR_MASK|DMA_SCR_PINC|DMA_SCR_MINC|DMA_SCR_PSIZE_MASK|
              DMA_SCR_MSIZE_MASK|DMA_SCR_PINCOS|DMA_SCR_PL_MASK|DMA_SCR_PBURST_MASK|
              DMA_SCR_MBURST_MASK);
  regval |= scr;
  dmast_putreg(dmast, STM32_DMA_SCR_OFFSET, regval);
}

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

void stm32_dmastart(DMA_HANDLE handle, dma_callback_t callback, void *arg, bool half)
{
  struct stm32_dma_s *dmast = (struct stm32_dma_s *)handle;
  uint32_t scr;

  DEBUGASSERT(handle != NULL);

  /* Save the callback info.  This will be invoked whent the DMA commpletes */

  dmast->callback = callback;
  dmast->arg      = arg;

  /* Activate the stream by setting the ENABLE bit in the DMA_SCRx register.
   * As soon as the stream is enabled, it can serve any DMA request from the
   * peripheral connected on the stream.
   */

  scr  = dmast_getreg(dmast, STM32_DMA_SCR_OFFSET);
  scr |= DMA_SCR_EN;

  /* Once half of the bytes are transferred, the half-transfer flag (HTIF) is
   * set and an interrupt is generated if the Half-Transfer Interrupt Enable
   * bit (HTIE) is set. At the end of the transfer, the Transfer Complete Flag
   * (TCIF) is set and an interrupt is generated if the Transfer Complete
   * Interrupt Enable bit (TCIE) is set.
   */

  scr |= (half ? (DMA_SCR_HTIE|DMA_SCR_TEIE) : (DMA_SCR_TCIE|DMA_SCR_TEIE));
  dmast_putreg(dmast, STM32_DMA_SCR_OFFSET, scr);
}

/****************************************************************************
 * Name: stm32_dmastop
 *
 * Description:
 *   Cancel the DMA.  After stm32_dmastop() is called, the DMA channel is
 *   reset and stm32_dmasetup() must be called before stm32_dmastart() can be
 *   called again
 *
 * Assumptions:
 *   - DMA handle allocated by stm32_dmachannel()
 *
 ****************************************************************************/

void stm32_dmastop(DMA_HANDLE handle)
{
  struct stm32_dma_s *dmast = (struct stm32_dma_s *)handle;
  stm32_dmastreamdisable(dmast);
}

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

#ifdef CONFIG_DEBUG_DMA
void stm32_dmasample(DMA_HANDLE handle, struct stm32_dmaregs_s *regs)
{
  struct stm32_dma_s *dmast = (struct stm32_dma_s *)handle;
  irqstate_t flags;

  flags       = irqsave();
  regs->lisr  = dmabase_getreg(dmast, STM32_DMA_LISR_OFFSET);
  regs->hisr  = dmabase_getreg(dmast, STM32_DMA_LISR_OFFSET);
  regs->scr   = dmast_getreg(dmast, STM32_DMA_HISR_OFFSET);
  regs->sndtr = dmast_getreg(dmast, STM32_DMA_SNDTR_OFFSET);
  regs->spar  = dmast_getreg(dmast, STM32_DMA_SPAR_OFFSET);
  regs->sm0ar = dmast_getreg(dmast, STM32_DMA_SM0AR_OFFSET);
  regs->sm1ar = dmast_getreg(dmast, STM32_DMA_SM1AR_OFFSET);
  regs->sfcr  = dmast_getreg(dmast, STM32_DMA_SFCR_OFFSET);
  irqrestore(flags);
}
#endif

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

#ifdef CONFIG_DEBUG_DMA
void stm32_dmadump(DMA_HANDLE handle, const struct stm32_dmaregs_s *regs,
                   const char *msg)
{
  struct stm32_dma_s *dmast = (struct stm32_dma_s *)handle;
  uint32_t dmabase = DMA_BASE(dmast->base);

  dmadbg("DMA Registers: %s\n", msg);
  dmadbg("   LISR[%08x]: %08x\n", dmabase + STM32_DMA_LISR_OFFSET, regs->lisr);
  dmadbg("   HISR[%08x]: %08x\n", dmabase + STM32_DMA_HISR_OFFSET, regs->hisr);
  dmadbg("    SCR[%08x]: %08x\n", dmast->base + STM32_DMA_SCR_OFFSET, regs->scr);
  dmadbg("  SNDTR[%08x]: %08x\n", dmast->base + STM32_DMA_SNDTR_OFFSET, regs->sndtr);
  dmadbg("   SPAR[%08x]: %08x\n", dmast->base + STM32_DMA_SPAR_OFFSET, regs->spar);
  dmadbg("  SM0AR[%08x]: %08x\n", dmast->base + STM32_DMA_SM0AR_OFFSET, regs->sm0ar);
  dmadbg("  SM1AR[%08x]: %08x\n", dmast->base + STM32_DMA_SM1AR_OFFSET, regs->sm1ar);
  dmadbg("  SFCRF[%08x]: %08x\n", dmast->base + STM32_DMA_SFCR_OFFSET, regs->sfcr);
}
#endif

#endif /* CONFIG_STM32_STM32F10XX */
