/*
    ChibiOS - Copyright (C) 2006..2018 Giovanni Di Sirio

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

/**
 * @file    MDMAv1/stm32_mdma.c
 * @brief   MDMA helper driver code.
 *
 * @addtogroup STM32_MDMA
 * @details MDMA sharing helper driver. In the STM32 the MDMA channels are a
 *          shared resource, this driver allows to allocate and free MDMA
 *          STM32 at runtime in order to allow all the other device
 *          drivers to coordinate the access to the resource.
 * @note    The MDMA ISR handlers are all declared into this module because
 *          sharing, the various device drivers can associate a callback to
 *          ISRs when allocating channels.
 * @{
 */

#include "hal.h"

/* The following macro is only defined if some driver requiring MDMA services
   has been enabled.*/
#if defined(STM32_MDMA_REQUIRED) || defined(__DOXYGEN__)

/*===========================================================================*/
/* Driver local definitions.                                                 */
/*===========================================================================*/

/*===========================================================================*/
/* Driver exported variables.                                                */
/*===========================================================================*/

/*===========================================================================*/
/* Driver local variables and types.                                         */
/*===========================================================================*/

/**
 * @brief   Global MDMA-related data structures.
 */
static struct {
  /**
   * @brief   Mask of the allocated channels.
   */
  uint32_t              allocated_mask;
  /**
   * @brief   MDMA IRQ redirectors.
   */
  stm32_mdma_channel_t  channels[STM32_MDMA_CHANNELS];
} mdma;

/*===========================================================================*/
/* Driver local functions.                                                   */
/*===========================================================================*/

static void mdma_serve_interrupt(const stm32_mdma_channel_t *mdmachp) {
  uint32_t flags;

  flags = mdmachp->channel->CISR;
  mdmachp->channel->CIFCR = flags;
  if (mdmachp->func != NULL) {
    mdmachp->func(mdmachp->param, flags);
  }
}

/*===========================================================================*/
/* Driver interrupt handlers.                                                */
/*===========================================================================*/

/**
 * @brief   MDMA shared interrupt handler.
 *
 * @isr
 */
OSAL_IRQ_HANDLER(STM32_MDMA_HANDLER) {
  uint32_t gisr = MDMA->GISR0;
  OSAL_IRQ_PROLOGUE();

  if ((gisr & (1U << 0)) != 0U) {
    mdma_serve_interrupt(&mdma.channels[0]);
  }

  if ((gisr & (1U << 1)) != 0U) {
    mdma_serve_interrupt(&mdma.channels[1]);
  }

  if ((gisr & (1U << 2)) != 0U) {
    mdma_serve_interrupt(&mdma.channels[2]);
  }

  if ((gisr & (1U << 3)) != 0U) {
    mdma_serve_interrupt(&mdma.channels[3]);
  }

  if ((gisr & (1U << 4)) != 0U) {
    mdma_serve_interrupt(&mdma.channels[4]);
  }

  if ((gisr & (1U << 5)) != 0U) {
    mdma_serve_interrupt(&mdma.channels[5]);
  }

  if ((gisr & (1U << 6)) != 0U) {
    mdma_serve_interrupt(&mdma.channels[6]);
  }

  if ((gisr & (1U << 7)) != 0U) {
    mdma_serve_interrupt(&mdma.channels[7]);
  }

  if ((gisr & (1U << 8)) != 0U) {
    mdma_serve_interrupt(&mdma.channels[8]);
  }

  if ((gisr & (1U << 9)) != 0U) {
    mdma_serve_interrupt(&mdma.channels[9]);
  }

  if ((gisr & (1U << 10)) != 0U) {
    mdma_serve_interrupt(&mdma.channels[10]);
  }

  if ((gisr & (1U << 11)) != 0U) {
    mdma_serve_interrupt(&mdma.channels[11]);
  }

  if ((gisr & (1U << 12)) != 0U) {
    mdma_serve_interrupt(&mdma.channels[12]);
  }

  if ((gisr & (1U << 13)) != 0U) {
    mdma_serve_interrupt(&mdma.channels[13]);
  }

  if ((gisr & (1U << 14)) != 0U) {
    mdma_serve_interrupt(&mdma.channels[14]);
  }

  if ((gisr & (1U << 15)) != 0U) {
    mdma_serve_interrupt(&mdma.channels[15]);
  }

  OSAL_IRQ_EPILOGUE();
}

/*===========================================================================*/
/* Driver exported functions.                                                */
/*===========================================================================*/

/**
 * @brief   STM32 MDMA helper initialization.
 *
 * @init
 */
void mdmaInit(void) {
  static MDMA_Channel_TypeDef * const ch[STM32_MDMA_CHANNELS] = {
    MDMA_Channel0,  MDMA_Channel1,  MDMA_Channel2,  MDMA_Channel3,
    MDMA_Channel4,  MDMA_Channel5,  MDMA_Channel6,  MDMA_Channel7,
    MDMA_Channel8,  MDMA_Channel9,  MDMA_Channel10, MDMA_Channel11,
    MDMA_Channel12, MDMA_Channel13, MDMA_Channel14, MDMA_Channel15
  };
  unsigned i;

  mdma.allocated_mask = 0U;
  for (i = 0U; i < STM32_MDMA_CHANNELS; i++) {
    MDMA_Channel_TypeDef *cp = ch[i];
    mdma.channels[i].channel        = cp;
    mdma.channels[i].func           = NULL;
    mdma.channels[i].channel->CCR   = STM32_MDMA_CCR_RESET_VALUE;
    mdma.channels[i].channel->CTCR  = STM32_MDMA_CTCR_RESET_VALUE;
    mdma.channels[i].channel->CIFCR = STM32_MDMA_CIFCR_CTEIF  |
                                      STM32_MDMA_CIFCR_CBRTIF |
                                      STM32_MDMA_CIFCR_CBRTIF |
                                      STM32_MDMA_CIFCR_CCTCIF |
                                      STM32_MDMA_CIFCR_CTEIF;
  }
}

/**
 * @brief   Allocates an MDMA channel.
 * @details The channel is allocated and, if required, the MDMA clock enabled.
 *          The function also enables the IRQ vector associated to the channel
 *          and initializes its priority.
 *
 * @param[in] id        numeric identifiers of a specific channel or:
 *                      - @p STM32_MDMA_CHANNEL_ID_ANY for any channel.
 *                      .
 * @param[in] func      handling function pointer, can be @p NULL
 * @param[in] param     a parameter to be passed to the handling function
 * @return              Pointer to the allocated @p stm32_mdma_channel_t
 *                      structure.
 * @retval NULL         if a/the channel is not available.
 *
 * @iclass
 */
const stm32_mdma_channel_t *dmaChannelAllocI(uint32_t id,
                                             stm32_mdmaisr_t func,
                                             void *param) {
  uint32_t i, startid, endid;

  osalDbgCheckClassI();

  if (id < STM32_MDMA_CHANNELS) {
    startid = id;
    endid   = id;
  }
  else if (id == STM32_MDMA_CHANNEL_ID_ANY) {
    startid = 0U;
    endid   = STM32_MDMA_CHANNELS - 1U;
  }
  else {
    osalDbgCheck(false);
    return NULL;
  }

  for (i = startid; i <= endid; i++) {
    uint32_t mask = (1U << i);
    if ((mdma.allocated_mask & mask) == 0U) {
      stm32_mdma_channel_t *mdmachp = &mdma.channels[i];

      /* Installs the MDMA handler.*/
      mdma.allocated_mask |= mask;
      mdmachp->func  = func;
      mdmachp->param = param;

      /* Enabling MDMA clocks required by the current channels set.*/
      if (mdma.allocated_mask != 0U) {
        rccEnableMDMA(true);
      }

      return mdmachp;
    }
  }

  return NULL;
}

/**
 * @brief   Allocates a MDMA channel.
 * @details The channel is allocated and, if required, the MDMA clock enabled.
 *          The function also enables the IRQ vector associated to the channel
 *          and initializes its priority.
 *
 * @param[in] id        numeric identifiers of a specific channel or:
 *                      - @p STM32_MDMA_CHANNEL_ID_ANY for any channel.
 *                      .
 * @param[in] func      handling function pointer, can be @p NULL
 * @param[in] param     a parameter to be passed to the handling function
 * @return              Pointer to the allocated @p stm32_mdma_channel_t
 *                      structure.
 * @retval NULL         if a/the channel is not available.
 *
 * @api
 */
const stm32_mdma_channel_t *dmaChannelAlloc(uint32_t id,
                                            stm32_mdmaisr_t func,
                                            void *param) {
  const stm32_mdma_channel_t *mdmachp;

  osalSysLock();
  mdmachp = mdmaChannelAllocI(id, func, param);
  osalSysUnlock();

  return mdmachp;
}

/**
 * @brief   Releases a MDMA channel.
 * @details The channel is freed and, if required, the MDMA clock disabled.
 *          Trying to release a unallocated channel is an illegal operation
 *          and is trapped if assertions are enabled.
 *
 * @param[in] mdmachp   pointer to a stm32_mdma_channel_t structure
 *
 * @iclass
 */
void mdmaChannelFreeI(const stm32_mdma_channel_t *mdmachp) {
  uint32_t channel = mdmachp - mdma.channels;
  osalDbgCheck(mdmachp != NULL);

  /* Check if the channels is not taken.*/
  osalDbgAssert((mdma.allocated_mask & (1U << channel)) != 0U,
                "not allocated");

  /* Marks the channel as not allocated.*/
  mdma.allocated_mask &= ~(1U << channel);

  /* Shutting down clocks that are no more required, if any.*/
  if (mdma.allocated_mask == 0U) {
    rccDisableMDMA();
  }
}

/**
 * @brief   Releases a MDMA channel.
 * @details The channel is freed and, if required, the MDMA clock disabled.
 *          Trying to release a unallocated channel is an illegal operation
 *          and is trapped if assertions are enabled.
 *
 * @param[in] mdmachp   pointer to a stm32_mdma_channel_t structure
 *
 * @api
 */
void mdmaChannelFree(const stm32_mdma_channel_t *mdmachp) {

  osalSysLock();
  mdmaChannelFreeI(mdmachp);
  osalSysUnlock();
}

/**
 * @brief   MDMA stream disable.
 * @details The function disables the specified stream, waits for the disable
 *          operation to complete and then clears any pending interrupt.
 * @pre     The stream must have been allocated using @p mdmaChannelAlloc().
 * @post    After use the stream can be released using @p mdmaChannelFree().
 *
 * @param[in] mdmachp   pointer to a stm32_mdma_channel_t structure
 *
 * @xclass
 */
void mdmaChannelDisableX(const stm32_mdma_channel_t *mdmachp) {
  uint32_t ccr = mdmachp->channel->CCR;

  /* Clearing CCR regardless of previous state.*/
  mdmachp->channel->CCR &= ~(STM32_MDMA_CCR_TCIE  | STM32_MDMA_CCR_BTIE  |
                             STM32_MDMA_CCR_BRTIE | STM32_MDMA_CCR_CTCIE |
                             STM32_MDMA_CCR_TEIE  | STM32_MDMA_CCR_EN);

  /* If the channel was enabled then waiting for ongoing operations
     to finish.*/
  if ((ccr & STM32_MDMA_CCR_EN) != 0U) {
    while (((mdmachp)->channel->CISR & STM32_MDMA_CISR_CTCIF) == 0U)
      ;
  }

  /* Clearing IRQ sources.*/
  mdmaChannelClearInterruptX(mdmachp);
}

#endif /* defined(STM32_MDMA_REQUIRED) */

/** @} */