/**
 * @file
 *
 * @ingroup RTEMSBSPsARMLPC24XX_dma
 *
 * @brief Direct memory access (DMA) support.
 */

/*
 * Copyright (c) 2008, 2009
 * embedded brains GmbH
 * Obere Lagerstr. 30
 * D-82178 Puchheim
 * Germany
 * <rtems@embedded-brains.de>
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 * http://www.rtems.org/license/LICENSE.
 */

#include <bsp/lpc24xx.h>
#include <bsp/dma.h>
#include <bsp/irq.h>

static rtems_id lpc24xx_dma_sema_table [GPDMA_CH_NUMBER];

static bool lpc24xx_dma_status_table [GPDMA_CH_NUMBER];

static void lpc24xx_dma_copy_handler(void *arg)
{
  /* Get interrupt status */
  uint32_t tc = GPDMA_INT_TCSTAT;
  uint32_t err = GPDMA_INT_ERR_STAT;

  /* Clear interrupt status */
  GPDMA_INT_TCCLR = tc;
  GPDMA_INT_ERR_CLR = err;

  /* Check channel 0 */
  if ((tc & GPDMA_STATUS_CH_0) != 0) {
    rtems_semaphore_release(lpc24xx_dma_sema_table [0]);
  }
  lpc24xx_dma_status_table [0] = (err & GPDMA_STATUS_CH_0) == 0;

  /* Check channel 1 */
  if ((tc & GPDMA_STATUS_CH_1) != 0) {
    rtems_semaphore_release(lpc24xx_dma_sema_table [1]);
  }
  lpc24xx_dma_status_table [1] = (err & GPDMA_STATUS_CH_1) == 0;
}

rtems_status_code lpc24xx_dma_copy_initialize(void)
{
  rtems_status_code sc = RTEMS_SUCCESSFUL;
  rtems_id id0 = RTEMS_ID_NONE;
  rtems_id id1 = RTEMS_ID_NONE;

  /* Create semaphore for channel 0 */
  sc = rtems_semaphore_create(
    rtems_build_name('D', 'M', 'A', '0'),
    0,
    RTEMS_LOCAL | RTEMS_FIFO | RTEMS_SIMPLE_BINARY_SEMAPHORE,
    0,
    &id0
  );
  if (sc != RTEMS_SUCCESSFUL) {
    return sc;
  }

  /* Create semaphore for channel 1 */
  sc = rtems_semaphore_create(
    rtems_build_name('D', 'M', 'A', '1'),
    0,
    RTEMS_LOCAL | RTEMS_FIFO | RTEMS_SIMPLE_BINARY_SEMAPHORE,
    0,
    &id1
  );
  if (sc != RTEMS_SUCCESSFUL) {
    rtems_semaphore_delete(id0);

    return sc;
  }

  /* Install DMA interrupt handler */
  sc = rtems_interrupt_handler_install(
    LPC24XX_IRQ_DMA,
    "DMA copy",
    RTEMS_INTERRUPT_UNIQUE,
    lpc24xx_dma_copy_handler,
    NULL
  );
  if (sc != RTEMS_SUCCESSFUL) {
    rtems_semaphore_delete(id0);
    rtems_semaphore_delete(id1);

    return sc;
  }

  /* Initialize global data */
  lpc24xx_dma_sema_table [0] = id0;
  lpc24xx_dma_sema_table [1] = id1;

  return RTEMS_SUCCESSFUL;
}

rtems_status_code lpc24xx_dma_copy_release(void)
{
  rtems_status_code sc = RTEMS_SUCCESSFUL;
  rtems_status_code rsc = RTEMS_SUCCESSFUL;

  sc = rtems_interrupt_handler_remove(
    LPC24XX_IRQ_DMA,
    lpc24xx_dma_copy_handler,
    NULL
  );
  if (sc != RTEMS_SUCCESSFUL) {
    rsc = sc;
  }

  sc = rtems_semaphore_delete(lpc24xx_dma_sema_table [0]);
  if (sc != RTEMS_SUCCESSFUL) {
    rsc = sc;
  }

  sc = rtems_semaphore_delete(lpc24xx_dma_sema_table [1]);
  if (sc != RTEMS_SUCCESSFUL) {
    rsc = sc;
  }

  return rsc;
}

rtems_status_code lpc24xx_dma_copy(
  unsigned channel,
  void *dest,
  const void *src,
  size_t n,
  size_t width
)
{
  volatile lpc24xx_dma_channel *e = GPDMA_CH_BASE_ADDR(channel);
  uint32_t w = GPDMA_CH_CTRL_W_8;

  switch (width) {
    case 4:
      w = GPDMA_CH_CTRL_W_32;
      break;
    case 2:
      w = GPDMA_CH_CTRL_W_16;
      break;
  }

  n = n >> w;

  if (n > 0U && n < 4096U) {
    e->desc.src = (uint32_t) src;
    e->desc.dest = (uint32_t) dest;
    e->desc.lli = 0;
    e->desc.ctrl = SET_GPDMA_CH_CTRL_TSZ(0, n)
      | SET_GPDMA_CH_CTRL_SBSZ(0, GPDMA_CH_CTRL_BSZ_1)
      | SET_GPDMA_CH_CTRL_DBSZ(0, GPDMA_CH_CTRL_BSZ_1)
      | SET_GPDMA_CH_CTRL_SW(0, w)
      | SET_GPDMA_CH_CTRL_DW(0, w)
      | GPDMA_CH_CTRL_ITC
      | GPDMA_CH_CTRL_SI
      | GPDMA_CH_CTRL_DI;
    e->cfg = SET_GPDMA_CH_CFG_FLOW(0, GPDMA_CH_CFG_FLOW_MEM_TO_MEM_DMA)
      | GPDMA_CH_CFG_IE
      | GPDMA_CH_CFG_ITC
      | GPDMA_CH_CFG_EN;
  } else {
    return RTEMS_INVALID_SIZE;
  }

  return RTEMS_SUCCESSFUL;
}

rtems_status_code lpc24xx_dma_copy_wait(unsigned channel)
{
  rtems_status_code sc = RTEMS_SUCCESSFUL;

  sc = rtems_semaphore_obtain(
    lpc24xx_dma_sema_table [channel],
    RTEMS_WAIT,
    RTEMS_NO_TIMEOUT
  );
  if (sc != RTEMS_SUCCESSFUL) {
    return sc;
  }

  return lpc24xx_dma_status_table [channel]
    ? RTEMS_SUCCESSFUL : RTEMS_IO_ERROR;
}
