text: /*
      Copyright (C) 2013-2015 Andrea Zoppi
  
      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    hal_stm32_dma2d.c
   * @brief   DMA2D/Chrom-ART driver.
   */
  
  #include "hal.h"
  
  #include "hal_stm32_dma2d.h"
  
  #if STM32_DMA2D_USE_DMA2D || defined(__DOXYGEN__)
  
  /* Ignore annoying warning messages for actually safe code.*/
  #if defined(__GNUC__) && !defined(__DOXYGEN__)
  #pragma GCC diagnostic ignored "-Wtype-limits"
  #endif
  
  /**
   * @addtogroup dma2d
   * @{
   */
  
  /*===========================================================================*/
  /* Driver local definitions.                                                 */
  /*===========================================================================*/
  
  /*===========================================================================*/
  /* Driver exported variables.                                                */
  /*===========================================================================*/
  
  /** @brief DMA2DD1 driver identifier.*/
  DMA2DDriver DMA2DD1;
  
  /*===========================================================================*/
  /* Driver local variables and types.                                         */
  /*===========================================================================*/
  
  /**
   * @brief   Bits per pixel lookup table.
   */
  static const uint8_t dma2d_bpp[DMA2D_MAX_PIXFMT_ID + 1] = {
    32,  /* DMA2D_FMT_ARGB8888 */
    24,  /* DMA2D_FMT_RGB888 */
    16,  /* DMA2D_FMT_RGB565 */
    16,  /* DMA2D_FMT_ARGB1555 */
    16,  /* DMA2D_FMT_ARGB4444 */
     8,  /* DMA2D_FMT_L8 */
     8,  /* DMA2D_FMT_AL44 */
    16,  /* DMA2D_FMT_AL88 */
     4,  /* DMA2D_FMT_L4 */
     8,  /* DMA2D_FMT_A8 */
     4   /* DMA2D_FMT_A4 */
  };
  
  /*===========================================================================*/
  /* Driver local functions.                                                   */
  /*===========================================================================*/
  
  /*===========================================================================*/
  /* Driver exported functions.                                                */
  /*===========================================================================*/
  
  /**
   * @name    DMA2D interrupt handlers
   * @{
   */
  
  /**
   * @brief   DMA2D global interrupt handler.
   *
   * @isr
   */
  OSAL_IRQ_HANDLER(STM32_DMA2D_HANDLER) {
  
    DMA2DDriver *const dma2dp = &DMA2DD1;
    bool job_done = false;
    thread_t *tp = NULL;
  
    OSAL_IRQ_PROLOGUE();
  
    /* Handle Configuration Error ISR.*/
    if ((DMA2D->ISR & DMA2D_ISR_CEIF) && (DMA2D->CR & DMA2D_CR_CEIE)) {
      if (dma2dp->config->cfgerr_isr != NULL)
        dma2dp->config->cfgerr_isr(dma2dp);
      job_done = true;
      DMA2D->IFCR |= DMA2D_IFSR_CCEIF;
    }
  
    /* Handle CLUT (Palette) Transfer Complete ISR.*/
    if ((DMA2D->ISR & DMA2D_ISR_CTCIF) && (DMA2D->CR & DMA2D_CR_CTCIE)) {
      if (dma2dp->config->paltrfdone_isr != NULL)
        dma2dp->config->paltrfdone_isr(dma2dp);
      job_done = true;
      DMA2D->IFCR |= DMA2D_IFSR_CCTCIF;
    }
  
    /* Handle CLUT (Palette) Access Error ISR.*/
    if ((DMA2D->ISR & DMA2D_ISR_CAEIF) && (DMA2D->CR & DMA2D_CR_CAEIE)) {
      if (dma2dp->config->palacserr_isr != NULL)
        dma2dp->config->palacserr_isr(dma2dp);
      job_done = true;
      DMA2D->IFCR |= DMA2D_IFSR_CCAEIF;
    }
  
    /* Handle Transfer Watermark ISR.*/
    if ((DMA2D->ISR & DMA2D_ISR_TWIF) && (DMA2D->CR & DMA2D_CR_TWIE)) {
      if (dma2dp->config->trfwmark_isr != NULL)
        dma2dp->config->trfwmark_isr(dma2dp);
      DMA2D->IFCR |= DMA2D_IFSR_CTWIF;
    }
  
    /* Handle Transfer Complete ISR.*/
    if ((DMA2D->ISR & DMA2D_ISR_TCIF) && (DMA2D->CR & DMA2D_CR_TCIE)) {
      if (dma2dp->config->trfdone_isr != NULL)
        dma2dp->config->trfdone_isr(dma2dp);
      job_done = true;
      DMA2D->IFCR |= DMA2D_IFSR_CTCIF;
    }
  
    /* Handle Transfer Error ISR.*/
    if ((DMA2D->ISR & DMA2D_ISR_TEIF) && (DMA2D->CR & DMA2D_CR_TEIE)) {
      if (dma2dp->config->trferr_isr != NULL)
        dma2dp->config->trferr_isr(dma2dp);
      job_done = true;
      DMA2D->IFCR |= DMA2D_IFSR_CTEIF;
    }
  
    if (job_done) {
      osalSysLockFromISR();
      osalDbgAssert(dma2dp->state == DMA2D_ACTIVE, "invalid state");
  
    #if DMA2D_USE_WAIT
      /* Wake the waiting thread up.*/
      if (dma2dp->thread != NULL) {
        tp = dma2dp->thread;
        dma2dp->thread = NULL;
        tp->u.rdymsg = MSG_OK;
        chSchReadyI(tp);
      }
    #endif  /* DMA2D_USE_WAIT */
  
      dma2dp->state = DMA2D_READY;
      osalSysUnlockFromISR();
    }
  
    OSAL_IRQ_EPILOGUE();
  }
  
  /** @} */
  
  /**
   * @name    DMA2D driver-specific methods
   * @{
   */
  
  /**
   * @brief   DMA2D Driver initialization.
   * @details Initializes the DMA2D subsystem and chosen drivers. Should be
   *          called at board initialization.
   *
   * @init
   */
  void dma2dInit(void) {
  
    /* Reset the DMA2D hardware module.*/
    rccResetDMA2D();
  
    /* Enable the DMA2D clock.*/
    rccEnableDMA2D(false);
  
    /* Driver struct initialization.*/
    dma2dObjectInit(&DMA2DD1);
    DMA2DD1.state = DMA2D_STOP;
  }
  
  /**
   * @brief   Initializes the standard part of a @p DMA2DDriver structure.
   *
   * @param[out] dma2dp   pointer to the @p DMA2DDriver object
   *
   * @init
   */
  void dma2dObjectInit(DMA2DDriver *dma2dp) {
  
    osalDbgCheck(dma2dp == &DMA2DD1);
  
    dma2dp->state = DMA2D_UNINIT;
    dma2dp->config = NULL;
  #if DMA2D_USE_WAIT
    dma2dp->thread = NULL;
  #endif  /* DMA2D_USE_WAIT */
  #if (TRUE == DMA2D_USE_MUTUAL_EXCLUSION)
  #if (TRUE == CH_CFG_USE_MUTEXES)
    chMtxObjectInit(&dma2dp->lock);
  #else
    chSemObjectInit(&dma2dp->lock, 1);
  #endif
  #endif  /* (TRUE == DMA2D_USE_MUTUAL_EXCLUSION) */
  }
  
  /**
   * @brief   Get the driver state.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @retun               driver state
   *
   * @iclass
   */
  dma2d_state_t dma2dGetStateI(DMA2DDriver *dma2dp) {
  
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgCheckClassI();
  
    return dma2dp->state;
  }
  
  /**
   * @brief   Get the driver state.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @retun               driver state
   *
   * @api
   */
  dma2d_state_t dma2dGetState(DMA2DDriver *dma2dp) {
  
    dma2d_state_t state;
    chSysLock();
    state = dma2dGetStateI(dma2dp);
    chSysUnlock();
    return state;
  }
  
  /**
   * @brief   Configures and activates the DMA2D peripheral.
   * @pre     DMA2D is stopped.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] configp   pointer to the @p DMA2DConfig object
   *
   * @api
   */
  void dma2dStart(DMA2DDriver *dma2dp, const DMA2DConfig *configp) {
  
    chSysLock();
  
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgCheck(configp != NULL);
    osalDbgAssert(dma2dp->state == DMA2D_STOP, "invalid state");
  
    dma2dp->config = configp;
  
    /* Turn off the controller and its interrupts.*/
    DMA2D->CR = 0;
  
    /* Enable interrupts, except Line Watermark.*/
    nvicEnableVector(STM32_DMA2D_NUMBER, STM32_DMA2D_IRQ_PRIORITY);
  
    DMA2D->CR = (DMA2D_CR_CEIE | DMA2D_CR_CTCIE | DMA2D_CR_CAEIE |
                 DMA2D_CR_TCIE | DMA2D_CR_TEIE);
  
    dma2dp->state = DMA2D_READY;
    chSysUnlock();
  }
  
  /**
   * @brief   Deactivates the DMA2D peripheral.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @api
   */
  void dma2dStop(DMA2DDriver *dma2dp) {
  
    chSysLock();
  
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgAssert(dma2dp->state == DMA2D_READY, "invalid state");
  #if DMA2D_USE_WAIT
    osalDbgAssert(dma2dp->thread == NULL, "still waiting");
  #endif  /* DMA2D_USE_WAIT */
  
    dma2dp->state = DMA2D_STOP;
    chSysUnlock();
  }
  
  #if DMA2D_USE_MUTUAL_EXCLUSION
  
  /**
   * @brief   Gains exclusive access to the DMA2D module.
   * @details This function tries to gain ownership to the DMA2D module, if the
   *          module is already being used then the invoking thread is queued.
   * @pre     In order to use this function the option
   *          @p DMA2D_USE_MUTUAL_EXCLUSION must be enabled.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @sclass
   */
  void dma2dAcquireBusS(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassS();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgAssert(dma2dp->state == DMA2D_READY, "not ready");
  
  #if (TRUE == CH_CFG_USE_MUTEXES)
    chMtxLockS(&dma2dp->lock);
  #else
    chSemWaitS(&dma2dp->lock);
  #endif
  }
  
  /**
   * @brief   Gains exclusive access to the DMA2D module.
   * @details This function tries to gain ownership to the DMA2D module, if the
   *          module is already being used then the invoking thread is queued.
   * @pre     In order to use this function the option
   *          @p DMA2D_USE_MUTUAL_EXCLUSION must be enabled.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @api
   */
  void dma2dAcquireBus(DMA2DDriver *dma2dp) {
  
    chSysLock();
    dma2dAcquireBusS(dma2dp);
    chSysUnlock();
  }
  
  /**
   * @brief   Releases exclusive access to the DMA2D module.
   * @pre     In order to use this function the option
   *          @p DMA2D_USE_MUTUAL_EXCLUSION must be enabled.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @sclass
   */
  void dma2dReleaseBusS(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassS();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgAssert(dma2dp->state == DMA2D_READY, "not ready");
  
  #if (TRUE == CH_CFG_USE_MUTEXES)
    chMtxUnlockS(&dma2dp->lock);
  #else
    chSemSignalI(&dma2dp->lock);
  #endif
  }
  
  /**
   * @brief   Releases exclusive access to the DMA2D module.
   * @pre     In order to use this function the option
   *          @p DMA2D_USE_MUTUAL_EXCLUSION must be enabled.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @api
   */
  void dma2dReleaseBus(DMA2DDriver *dma2dp) {
  
    chSysLock();
    dma2dReleaseBusS(dma2dp);
    chSysUnlock();
  }
  
  #endif  /* DMA2D_USE_MUTUAL_EXCLUSION */
  
  /** @} */
  
  /**
   * @name    DMA2D global methods
   * @{
   */
  
  /**
   * @brief   Get watermark position.
   * @details Gets the watermark line position.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              watermark line position
   *
   * @iclass
   */
  uint16_t dma2dGetWatermarkPosI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    (void)dma2dp;
  
    return (uint16_t)(DMA2D->LWR & DMA2D_LWR_LW);
  }
  
  /**
   * @brief   Get watermark position.
   * @details Gets the watermark line position.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              watermark line position
   *
   * @api
   */
  uint16_t dma2dGetWatermarkPos(DMA2DDriver *dma2dp) {
  
    uint16_t line;
    chSysLock();
    line = dma2dGetWatermarkPosI(dma2dp);
    chSysUnlock();
    return line;
  }
  
  /**
   * @brief   Set watermark position.
   * @details Sets the watermark line position.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] line      watermark line position
   *
   * @iclass
   */
  void dma2dSetWatermarkPosI(DMA2DDriver *dma2dp, uint16_t line) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgAssert(dma2dp->state == DMA2D_READY, "not ready");
    (void)dma2dp;
  
    DMA2D->LWR = ((DMA2D->LWR & ~DMA2D_LWR_LW) |
                  ((uint32_t)line & DMA2D_LWR_LW));
  }
  
  /**
   * @brief   Set watermark position.
   * @details Sets the watermark line position.
   * @note    The interrupt is invoked after the last pixel of the watermark line
   *          is written.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] line      watermark line position
   *
   * @iclass
   */
  void dma2dSetWatermarkPos(DMA2DDriver *dma2dp, uint16_t line) {
  
    chSysLock();
    dma2dSetWatermarkPosI(dma2dp, line);
    chSysUnlock();
  }
  
  /**
   * @brief   Watermark interrupt enabled.
   * @details Tells whether the watermark interrupt is enabled.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              enabled
   *
   * @iclass
   */
  bool dma2dIsWatermarkEnabledI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    (void)dma2dp;
  
    return (DMA2D->CR & DMA2D_CR_TWIE) != 0;
  }
  
  /**
   * @brief   Watermark interrupt enabled.
   * @details Tells whether the watermark interrupt is enabled.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              enabled
   *
   * @api
   */
  bool dma2dIsWatermarkEnabled(DMA2DDriver *dma2dp) {
  
    bool enabled;
    chSysLock();
    enabled = dma2dIsWatermarkEnabledI(dma2dp);
    chSysUnlock();
    return enabled;
  }
  
  /**
   * @brief   Enable watermark interrupt.
   * @details Enables the watermark interrupt. The interrupt is invoked after the
   *          last pixel of the watermark line is written to the output layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @iclass
   */
  void dma2dEnableWatermarkI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    (void)dma2dp;
  
    DMA2D->CR |= DMA2D_CR_TWIE;
  }
  
  /**
   * @brief   Enable watermark interrupt.
   * @details Enables the watermark interrupt. The interrupt is invoked after the
   *          last pixel of the watermark line is written to the output layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @api
   */
  void dma2dEnableWatermark(DMA2DDriver *dma2dp) {
  
    chSysLock();
    dma2dEnableWatermarkI(dma2dp);
    chSysUnlock();
  }
  
  /**
   * @brief   Disable watermark interrupt.
   * @details Disables the watermark interrupt.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @iclass
   */
  void dma2dDisableWatermarkI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    (void)dma2dp;
  
    DMA2D->CR &= ~DMA2D_CR_TWIE;
  }
  
  /**
   * @brief   Disable watermark interrupt.
   * @details Disables the watermark interrupt.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @api
   */
  void dma2dDisableWatermark(DMA2DDriver *dma2dp) {
  
    chSysLock();
    dma2dDisableWatermarkI(dma2dp);
    chSysUnlock();
  }
  
  /**
   * @brief   Get dead time cycles.
   * @details Gets the minimum dead time DMA2D clock cycles between DMA2D
   *          transactions.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              dead time, in DMA2D clock cycles
   *
   * @iclass
   */
  uint32_t dma2dGetDeadTimeI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    (void)dma2dp;
  
    return (DMA2D->AMTCR & DMA2D_AMTCR_DT) >> 8;
  }
  
  /**
   * @brief   Get dead time cycles.
   * @details Gets the minimum dead time DMA2D clock cycles between DMA2D
   *          transactions.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              dead time, in DMA2D clock cycles
   *
   * @api
   */
  uint32_t dma2dGetDeadTime(DMA2DDriver *dma2dp) {
  
    uint32_t cycles;
    chSysLock();
    cycles = dma2dGetDeadTimeI(dma2dp);
    chSysUnlock();
    return cycles;
  }
  
  /**
   * @brief   Set dead time cycles.
   * @details Sets the minimum dead time DMA2D clock cycles between DMA2D
   *          transactions.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] cycles    dead time, in DMA2D clock cycles
   *
   * @iclass
   */
  void dma2dSetDeadTimeI(DMA2DDriver *dma2dp, uint32_t cycles) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgAssert(cycles <= DMA2D_MAX_DEADTIME_CYCLES, "bounds");
    (void)dma2dp;
  
    DMA2D->AMTCR = ((DMA2D->AMTCR & ~DMA2D_AMTCR_DT) |
                    ((cycles << 8) & DMA2D_AMTCR_DT));
  }
  
  /**
   * @brief   Set dead time cycles.
   * @details Sets the minimum dead time DMA2D clock cycles between DMA2D
   *          transactions.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] cycles    dead time, in DMA2D clock cycles
   *
   * @api
   */
  void dma2dSetDeadTime(DMA2DDriver *dma2dp, uint32_t cycles) {
  
    chSysLock();
    dma2dSetDeadTimeI(dma2dp, cycles);
    chSysUnlock();
  }
  
  /**
   * @brief   Dead time enabled.
   * @details Tells whether the dead time between DMA2D transactions is enabled.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              enabled
   *
   * @iclass
   */
  bool dma2dIsDeadTimeEnabledI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    (void)dma2dp;
  
    return (DMA2D->AMTCR & DMA2D_AMTCR_EN) != 0;
  }
  
  /**
   * @brief   Dead time enabled.
   * @details Tells whether the dead time between DMA2D transactions is enabled.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              enabled
   *
   * @api
   */
  bool dma2dIsDeadTimeEnabled(DMA2DDriver *dma2dp) {
  
    bool enabled;
    chSysLock();
    enabled = dma2dIsDeadTimeEnabledI(dma2dp);
    chSysUnlock();
    return enabled;
  }
  
  /**
   * @brief   Enable dead time.
   * @details Enables the dead time between DMA2D transactions.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @iclass
   */
  void dma2dEnableDeadTimeI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    (void)dma2dp;
  
    DMA2D->AMTCR |= DMA2D_AMTCR_EN;
  }
  
  /**
   * @brief   Enable dead time.
   * @details Enables the dead time between DMA2D transactions.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @api
   */
  void dma2dEnableDeadTime(DMA2DDriver *dma2dp) {
  
    chSysLock();
    dma2dEnableDeadTimeI(dma2dp);
    chSysUnlock();
  }
  
  /**
   * @brief   Disable dead time.
   * @details Disables the dead time between DMA2D transactions.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @iclass
   */
  void dma2dDisableDeadTimeI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    (void)dma2dp;
  
    DMA2D->AMTCR &= ~DMA2D_AMTCR_EN;
  }
  
  /**
   * @brief   Disable dead time.
   * @details Disables the dead time between DMA2D transactions.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @api
   */
  void dma2dDisableDeadTime(DMA2DDriver *dma2dp) {
  
    chSysLock();
    dma2dDisableDeadTimeI(dma2dp);
    chSysUnlock();
  }
  
  /** @} */
  
  /**
   * @name    DMA2D job (transaction) methods
   * @{
   */
  
  /**
   * @brief   Get job mode.
   * @details Gets the job mode.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              job mode
   *
   * @iclass
   */
  dma2d_jobmode_t dma2dJobGetModeI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    (void)dma2dp;
  
    return (dma2d_jobmode_t)(DMA2D->CR & DMA2D_CR_MODE);
  }
  
  /**
   * @brief   Get job mode.
   * @details Gets the job mode.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              job mode
   *
   * @api
   */
  dma2d_jobmode_t dma2dJobGetMode(DMA2DDriver *dma2dp) {
  
    dma2d_jobmode_t mode;
    chSysLock();
    mode = dma2dJobGetModeI(dma2dp);
    chSysUnlock();
    return mode;
  }
  
  /**
   * @brief   Set job mode.
   * @details Sets the job mode.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] mode      job mode
   *
   * @iclass
   */
  void dma2dJobSetModeI(DMA2DDriver *dma2dp, dma2d_jobmode_t mode) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgAssert(dma2dp->state == DMA2D_READY, "not ready");
    osalDbgAssert((mode & ~DMA2D_CR_MODE) == 0, "bounds");
    (void)dma2dp;
  
    DMA2D->CR = ((DMA2D->CR & ~DMA2D_CR_MODE) |
                 ((uint32_t)mode & DMA2D_CR_MODE));
  }
  
  /**
   * @brief   Set job mode.
   * @details Sets the job mode.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] mode      job mode
   *
   * @api
   */
  void dma2dJobSetMode(DMA2DDriver *dma2dp, dma2d_jobmode_t mode) {
  
    chSysLock();
    dma2dJobSetModeI(dma2dp, mode);
    chSysUnlock();
  }
  
  /**
   * @brief   Get job size.
   * @details Gets the job size.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[out] widthp   pointer to the job width, in pixels
   * @param[out] heightp  pointer to the job height, in pixels
   *
   * @iclass
   */
  void dma2dJobGetSizeI(DMA2DDriver *dma2dp,
                        uint16_t *widthp, uint16_t *heightp) {
  
    uint32_t r;
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgCheck(widthp != NULL);
    osalDbgCheck(heightp != NULL);
    (void)dma2dp;
  
    r = DMA2D->NLR;
    *widthp  = (uint16_t)((r & DMA2D_NLR_PL) >> 16);
    *heightp = (uint16_t)((r & DMA2D_NLR_NL) >>  0);
  }
  
  /**
   * @brief   Get job size.
   * @details Gets the job size.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[out] widthp   pointer to the job width, in pixels
   * @param[out] heightp  pointer to the job height, in pixels
   *
   * @api
   */
  void dma2dJobGetSize(DMA2DDriver *dma2dp,
                       uint16_t *widthp, uint16_t *heightp) {
  
    chSysLock();
    dma2dJobGetSizeI(dma2dp, widthp, heightp);
    chSysUnlock();
  }
  
  /**
   * @brief   Set job size.
   * @details Sets the job size.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] widthp    job width, in pixels
   * @param[in] heightp   job height, in pixels
   *
   * @iclass
   */
  void dma2dJobSetSizeI(DMA2DDriver *dma2dp, uint16_t width, uint16_t height) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgAssert(dma2dp->state == DMA2D_READY, "not ready");
    osalDbgAssert(width <= DMA2D_MAX_WIDTH, "bounds");
    osalDbgAssert(height <= DMA2D_MAX_HEIGHT, "bounds");
    (void)dma2dp;
  
    DMA2D->NLR = ((((uint32_t)width  << 16) & DMA2D_NLR_PL) |
                  (((uint32_t)height <<  0) & DMA2D_NLR_NL));
  }
  
  /**
   * @brief   Set job size.
   * @details Sets the job size.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] widthp    job width, in pixels
   * @param[in] heightp   job height, in pixels
   *
   * @api
   */
  void dma2dJobSetSize(DMA2DDriver *dma2dp, uint16_t width, uint16_t height) {
  
    chSysLock();
    dma2dJobSetSizeI(dma2dp, width, height);
    chSysUnlock();
  }
  
  /**
   * @brief   Job executing.
   * @details Tells whether a job (transaction) is active or paused.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              executing
   *
   * @iclass
   */
  bool dma2dJobIsExecutingI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
  
    return dma2dp->state > DMA2D_READY;
  }
  
  /**
   * @brief   Job executing.
   * @details Tells whether a job (transaction) is active or paused.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              executing
   *
   * @api
   */
  bool dma2dJobIsExecuting(DMA2DDriver *dma2dp) {
  
    bool executing;
    chSysLock();
    executing = dma2dJobIsExecutingI(dma2dp);
    chSysUnlock();
    return executing;
  }
  
  /**
   * @brief   Start job.
   * @details The job is started, and the DMA2D is set to active.
   * @note    Should there be invalid parameters, the appropriate interrupt
   *          handler will be invoked, and the DMA2D set back to ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @iclass
   */
  void dma2dJobStartI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgAssert(dma2dp->state == DMA2D_READY, "not ready");
  
    dma2dp->state = DMA2D_ACTIVE;
    DMA2D->CR |= DMA2D_CR_START;
  }
  
  /**
   * @brief   Start job.
   * @details The job is started, and the DMA2D is set to active.
   * @note    Should there be invalid parameters, the appropriate interrupt
   *          handler will be invoked, and the DMA2D set back to ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @api
   */
  void dma2dJobStart(DMA2DDriver *dma2dp) {
  
    chSysLock();
    dma2dJobStartI(dma2dp);
    chSysUnlock();
  }
  
  /**
   * @brief   Execute job.
   * @details Starts the job and waits for its completion, synchronously.
   * @note    Should there be invalid parameters, the appropriate interrupt
   *          handler will be invoked, and the DMA2D set back to ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @sclass
   */
  void dma2dJobExecuteS(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassS();
    osalDbgCheck(dma2dp == &DMA2DD1);
  
    dma2dJobStartI(dma2dp);
  #if DMA2D_USE_WAIT
    dma2dp->thread = chThdGetSelfX();
    chSchGoSleepS(CH_STATE_SUSPENDED);
  #else
    while (DMA2D->CR & DMA2D_CR_START)
      chSchDoYieldS();
  #endif
  }
  
  /**
   * @brief   Execute job.
   * @details Starts the job and waits for its completion, synchronously.
   * @note    Should there be invalid parameters, the appropriate interrupt
   *          handler will be invoked, and the DMA2D set back to ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @api
   */
  void dma2dJobExecute(DMA2DDriver *dma2dp) {
  
    chSysLock();
    dma2dJobExecuteS(dma2dp);
    chSysUnlock();
  }
  
  /**
   * @brief   Suspend current job.
   * @details Suspends the current job. The driver is set to a paused state.
   * @pre     There is an active job.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @iclass
   */
  void dma2dJobSuspendI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgCheck((DMA2D->CR & DMA2D_CR_SUSP) == 0);
    osalDbgAssert(dma2dp->state == DMA2D_ACTIVE, "invalid state");
  
    dma2dp->state = DMA2D_PAUSED;
    DMA2D->CR |= DMA2D_CR_SUSP;
  }
  
  /**
   * @brief   Suspend current job.
   * @details Suspends the current job. The driver is set to a paused state.
   * @pre     There is an active job.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @api
   */
  void dma2dJobSuspend(DMA2DDriver *dma2dp) {
  
    chSysLock();
    dma2dJobSuspendI(dma2dp);
    chSysUnlock();
  }
  
  /**
   * @brief   Resume current job.
   * @details Resumes the current job.
   * @pre     There is a paused job.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @iclass
   */
  void dma2dJobResumeI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgCheck((DMA2D->CR & DMA2D_CR_SUSP) != 0);
    osalDbgAssert(dma2dp->state == DMA2D_PAUSED, "invalid state");
  
    dma2dp->state = DMA2D_ACTIVE;
    DMA2D->CR &= ~DMA2D_CR_SUSP;
  }
  
  /**
   * @brief   Resume current job.
   * @details Resumes the current job.
   * @pre     There is a paused job.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @api
   */
  void dma2dJobResume(DMA2DDriver *dma2dp) {
  
    chSysLock();
    dma2dJobResumeI(dma2dp);
    chSysUnlock();
  }
  
  /**
   * @brief   Abort current job.
   * @details Abots the current job (if any), and the driver becomes ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @iclass
   */
  void dma2dJobAbortI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgCheck((DMA2D->CR & DMA2D_CR_SUSP) == 0);
    osalDbgAssert(dma2dp->state >= DMA2D_READY, "invalid state");
  
    dma2dp->state = DMA2D_READY;
    DMA2D->CR |= DMA2D_CR_ABORT;
  }
  
  /**
   * @brief   Abort current job.
   * @details Abots the current job (if any), and the driver becomes ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @api
   */
  void dma2dJobAbort(DMA2DDriver *dma2dp) {
  
    chSysLock();
    dma2dJobAbortI(dma2dp);
    chSysUnlock();
  }
  
  /** @} */
  
  /**
   * @name    DMA2D background layer methods
   * @{
   */
  
  /**
   * @brief   Get background layer buffer address.
   * @details Gets the buffer address of the background layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              buffer address
   *
   * @iclass
   */
  void *dma2dBgGetAddressI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    (void)dma2dp;
  
    return (void *)DMA2D->BGMAR;
  }
  
  /**
   * @brief   Get background layer buffer address.
   * @details Gets the buffer address of the background layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              buffer address
   *
   * @api
   */
  void *dma2dBgGetAddress(DMA2DDriver *dma2dp) {
  
    void *bufferp;
    chSysLock();
    bufferp = dma2dBgGetAddressI(dma2dp);
    chSysUnlock();
    return bufferp;
  }
  
  /**
   * @brief   Set background layer buffer address.
   * @details Sets the buffer address of the background layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] bufferp   buffer address
   *
   * @iclass
   */
  void dma2dBgSetAddressI(DMA2DDriver *dma2dp, void *bufferp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgAssert(dma2dp->state == DMA2D_READY, "not ready");
    osalDbgCheck(dma2dIsAligned(bufferp, dma2dBgGetPixelFormatI(dma2dp)));
    (void)dma2dp;
  
    DMA2D->BGMAR = (uint32_t)bufferp;
  }
  
  /**
   * @brief   Set background layer buffer address.
   * @details Sets the buffer address of the background layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] bufferp   buffer address
   *
   * @api
   */
  void dma2dBgSetAddress(DMA2DDriver *dma2dp, void *bufferp) {
  
    chSysLock();
    dma2dBgSetAddressI(dma2dp, bufferp);
    chSysUnlock();
  }
  
  /**
   * @brief   Get background layer wrap offset.
   * @details Gets the buffer line wrap offset of the background layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              wrap offset, in pixels
   *
   * @iclass
   */
  size_t dma2dBgGetWrapOffsetI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    (void)dma2dp;
  
    return (size_t)(DMA2D->BGOR & DMA2D_BGOR_LO);
  }
  
  /**
   * @brief   Get background layer wrap offset.
   * @details Gets the buffer line wrap offset of the background layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              wrap offset, in pixels
   *
   * @api
   */
  size_t dma2dBgGetWrapOffset(DMA2DDriver *dma2dp) {
  
    size_t offset;
    chSysLock();
    offset = dma2dBgGetWrapOffsetI(dma2dp);
    chSysUnlock();
    return offset;
  }
  
  /**
   * @brief   Set background layer wrap offset.
   * @details Sets the buffer line wrap offset of the background layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] offset    wrap offset, in pixels
   *
   * @iclass
   */
  void dma2dBgSetWrapOffsetI(DMA2DDriver *dma2dp, size_t offset) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgAssert(dma2dp->state == DMA2D_READY, "not ready");
    osalDbgAssert(offset <= DMA2D_MAX_OFFSET, "bounds");
    (void)dma2dp;
  
    DMA2D->BGOR = ((DMA2D->BGOR & ~DMA2D_BGOR_LO) |
                   ((uint32_t)offset & DMA2D_BGOR_LO));
  }
  
  /**
   * @brief   Set background layer wrap offset.
   * @details Sets the buffer line wrap offset of the background layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] offset    wrap offset, in pixels
   *
   * @api
   */
  void dma2dBgSetWrapOffset(DMA2DDriver *dma2dp, size_t offset) {
  
    chSysLock();
    dma2dBgSetWrapOffsetI(dma2dp, offset);
    chSysUnlock();
  }
  
  /**
   * @brief   Get background layer constant alpha.
   * @details Gets the constant alpha component of the background layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              constant alpha component, A-8
   *
   * @iclass
   */
  uint8_t dma2dBgGetConstantAlphaI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    (void)dma2dp;
  
    return (uint8_t)((DMA2D->BGPFCCR & DMA2D_BGPFCCR_ALPHA) >> 24);
  }
  
  /**
   * @brief   Get background layer constant alpha.
   * @details Gets the constant alpha component of the background layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              constant alpha component, A-8
   *
   * @api
   */
  uint8_t dma2dBgGetConstantAlpha(DMA2DDriver *dma2dp) {
  
    uint8_t a;
    chSysLock();
    a = dma2dBgGetConstantAlphaI(dma2dp);
    chSysUnlock();
    return a;
  }
  
  /**
   * @brief   Set background layer constant alpha.
   * @details Sets the constant alpha component of the background layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] a         constant alpha component, A-8
   *
   * @iclass
   */
  void dma2dBgSetConstantAlphaI(DMA2DDriver *dma2dp, uint8_t a) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgAssert(dma2dp->state == DMA2D_READY, "not ready");
    (void)dma2dp;
  
    DMA2D->BGPFCCR = ((DMA2D->BGPFCCR & ~DMA2D_BGPFCCR_ALPHA) |
                      (((uint32_t)a << 24) & DMA2D_BGPFCCR_ALPHA));
  }
  
  /**
   * @brief   Set background layer constant alpha.
   * @details Sets the constant alpha component of the background layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] a         constant alpha component, A-8
   *
   * @api
   */
  void dma2dBgSetConstantAlpha(DMA2DDriver *dma2dp, uint8_t a) {
  
    chSysLock();
    dma2dBgSetConstantAlphaI(dma2dp, a);
    chSysUnlock();
  }
  
  /**
   * @brief   Get background layer alpha mode.
   * @details Gets the alpha mode of the background layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              alpha mode
   *
   * @iclass
   */
  dma2d_amode_t dma2dBgGetAlphaModeI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    (void)dma2dp;
  
    return (dma2d_amode_t)(DMA2D->BGPFCCR & DMA2D_BGPFCCR_AM);
  }
  
  /**
   * @brief   Get background layer alpha mode.
   * @details Gets the alpha mode of the background layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              alpha mode
   *
   * @api
   */
  dma2d_amode_t dma2dBgGetAlphaMode(DMA2DDriver *dma2dp) {
  
    dma2d_amode_t mode;
    chSysLock();
    mode = dma2dBgGetAlphaModeI(dma2dp);
    chSysUnlock();
    return mode;
  }
  
  /**
   * @brief   Set background layer alpha mode.
   * @details Sets the alpha mode of the background layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] mode      alpha mode
   *
   * @iclass
   */
  void dma2dBgSetAlphaModeI(DMA2DDriver *dma2dp, dma2d_amode_t mode) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgAssert(dma2dp->state == DMA2D_READY, "not ready");
    osalDbgAssert((mode & ~DMA2D_BGPFCCR_AM) == 0, "bounds");
    osalDbgAssert((mode & DMA2D_BGPFCCR_AM) != DMA2D_BGPFCCR_AM, "bounds");
    (void)dma2dp;
  
    DMA2D->BGPFCCR = ((DMA2D->BGPFCCR & ~DMA2D_BGPFCCR_AM) |
                      ((uint32_t)mode & DMA2D_BGPFCCR_AM));
  }
  
  /**
   * @brief   Set background layer alpha mode.
   * @details Sets the alpha mode of the background layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] mode      alpha mode
   *
   * @api
   */
  void dma2dBgSetAlphaMode(DMA2DDriver *dma2dp, dma2d_amode_t mode) {
  
    chSysLock();
    dma2dBgSetAlphaModeI(dma2dp, mode);
    chSysUnlock();
  }
  
  /**
   * @brief   Get background layer pixel format.
   * @details Gets the pixel format of the background layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              pixel format
   *
   * @iclass
   */
  dma2d_pixfmt_t dma2dBgGetPixelFormatI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    (void)dma2dp;
  
    return (dma2d_pixfmt_t)(DMA2D->BGPFCCR & DMA2D_BGPFCCR_CM);
  }
  
  /**
   * @brief   Get background layer pixel format.
   * @details Gets the pixel format of the background layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              pixel format
   *
   * @api
   */
  dma2d_pixfmt_t dma2dBgGetPixelFormat(DMA2DDriver *dma2dp) {
  
    dma2d_pixfmt_t fmt;
    chSysLock();
    fmt = dma2dBgGetPixelFormatI(dma2dp);
    chSysUnlock();
    return fmt;
  }
  
  /**
   * @brief   Set background layer pixel format.
   * @details Sets the pixel format of the background layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] fmt       pixel format
   *
   * @iclass
   */
  void dma2dBgSetPixelFormatI(DMA2DDriver *dma2dp, dma2d_pixfmt_t fmt) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgAssert(dma2dp->state == DMA2D_READY, "not ready");
    osalDbgAssert(fmt <= DMA2D_MAX_PIXFMT_ID, "bounds");
    (void)dma2dp;
  
    DMA2D->BGPFCCR = ((DMA2D->BGPFCCR & ~DMA2D_BGPFCCR_CM) |
                      ((uint32_t)fmt & DMA2D_BGPFCCR_CM));
  }
  
  /**
   * @brief   Set background layer pixel format.
   * @details Sets the pixel format of the background layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] fmt       pixel format
   *
   * @api
   */
  void dma2dBgSetPixelFormat(DMA2DDriver *dma2dp, dma2d_pixfmt_t fmt) {
  
    chSysLock();
    dma2dBgSetPixelFormatI(dma2dp, fmt);
    chSysUnlock();
  }
  
  /**
   * @brief   Get background layer default color.
   * @details Gets the default color of the background layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              default color, RGB-888
   *
   * @iclass
   */
  dma2d_color_t dma2dBgGetDefaultColorI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    (void)dma2dp;
  
    return (dma2d_color_t)(DMA2D->BGCOLR & 0x00FFFFFF);
  }
  
  /**
   * @brief   Get background layer default color.
   * @details Gets the default color of the background layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              default color, RGB-888
   *
   * @api
   */
  dma2d_color_t dma2dBgGetDefaultColor(DMA2DDriver *dma2dp) {
  
    dma2d_color_t c;
    chSysLock();
    c = dma2dBgGetDefaultColorI(dma2dp);
    chSysUnlock();
    return c;
  }
  
  /**
   * @brief   Set background layer default color.
   * @details Sets the default color of the background layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] c         default color, RGB-888
   *
   * @iclass
   */
  void dma2dBgSetDefaultColorI(DMA2DDriver *dma2dp, dma2d_color_t c) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgAssert(dma2dp->state == DMA2D_READY, "not ready");
    (void)dma2dp;
  
    DMA2D->BGCOLR = (uint32_t)c & 0x00FFFFFF;
  }
  
  /**
   * @brief   Set background layer default color.
   * @details Sets the default color of the background layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] c         default color, RGB-888
   *
   * @api
   */
  void dma2dBgSetDefaultColor(DMA2DDriver *dma2dp, dma2d_color_t c) {
  
    chSysLock();
    dma2dBgSetDefaultColorI(dma2dp, c);
    chSysUnlock();
  }
  
  /**
   * @brief   Get background layer palette specifications.
   * @details Gets the palette specifications of the background layer.
   * @note    The palette colors pointer is actually addressed to a @p volatile
   *          memory zone.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[out] palettep pointer to the palette specifications
   *
   * @iclass
   */
  void dma2dBgGetPaletteI(DMA2DDriver *dma2dp, dma2d_palcfg_t *palettep) {
  
    uint32_t r;
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgCheck(palettep != NULL);
    (void)dma2dp;
  
    r = DMA2D->BGPFCCR;
    palettep->colorsp = (const void *)DMA2D->BGCLUT;
    palettep->length = (uint16_t)((r & DMA2D_BGPFCCR_CS) >> 8) + 1;
    palettep->fmt = (dma2d_pixfmt_t)((r & DMA2D_BGPFCCR_CCM) >> 4);
  }
  
  /**
   * @brief   Get background layer palette specifications.
   * @details Gets the palette specifications of the background layer.
   * @note    The palette colors pointer is actually addressed to a @p volatile
   *          memory zone.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[out] palettep pointer to the palette specifications
   *
   * @api
   */
  void dma2dBgGetPalette(DMA2DDriver *dma2dp, dma2d_palcfg_t *palettep) {
  
    chSysLock();
    dma2dBgGetPaletteI(dma2dp, palettep);
    chSysUnlock();
  }
  
  /**
   * @brief   Set background layer palette specifications.
   * @details Sets the palette specifications of the background layer.
   * @note    This function should not be called while the DMA2D is already
   *          executing a job, otherwise the appropriate error interrupt might be
   *          invoked.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] palettep  pointer to the palette specifications
   *
   * @sclass
   */
  void dma2dBgSetPaletteS(DMA2DDriver *dma2dp, const dma2d_palcfg_t *palettep) {
  
    osalDbgCheckClassS();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgAssert(dma2dp->state == DMA2D_READY, "not ready");
    osalDbgCheck(palettep != NULL);
    osalDbgCheck(palettep->colorsp != NULL);
    osalDbgAssert(palettep->length > 0, "bounds");
    osalDbgAssert(palettep->length <= DMA2D_MAX_PALETTE_LENGTH, "bounds");
    osalDbgAssert(((palettep->fmt == DMA2D_FMT_ARGB8888) ||
                   (palettep->fmt == DMA2D_FMT_RGB888)), "invalid format");
  
    DMA2D->BGCMAR = (uint32_t)palettep->colorsp;
    DMA2D->BGPFCCR = (
      (DMA2D->BGPFCCR & ~(DMA2D_BGPFCCR_CS | DMA2D_BGPFCCR_CCM)) |
      ((((uint32_t)palettep->length - 1) << 8) & DMA2D_BGPFCCR_CS) |
      ((uint32_t)palettep->fmt << 4)
    );
  
    dma2dp->state = DMA2D_ACTIVE;
    DMA2D->BGPFCCR |= DMA2D_BGPFCCR_START;
  
  #if DMA2D_USE_WAIT
    dma2dp->thread = chThdGetSelfX();
    chSchGoSleepS(CH_STATE_SUSPENDED);
  #else
    while (DMA2D->BGPFCCR & DMA2D_BGPFCCR_START)
      chSchDoYieldS();
  #endif  /* DMA2D_USE_WAIT */
  }
  
  /**
   * @brief   Set background layer palette specifications.
   * @details Sets the palette specifications of the background layer.
   * @note    This function should not be called while the DMA2D is already
   *          executing a job, otherwise the appropriate error interrupt might be
   *          invoked.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] palettep  pointer to the palette specifications
   *
   * @api
   */
  void dma2dBgSetPalette(DMA2DDriver *dma2dp, const dma2d_palcfg_t *palettep) {
  
    chSysLock();
    dma2dBgSetPaletteS(dma2dp, palettep);
    chSysUnlock();
  }
  
  /**
   * @brief   Get background layer specifications.
   * @details Gets the background layer specifications at once.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[out] cfgp     pointer to the layer specifications
   *
   * @iclass
   */
  void dma2dBgGetLayerI(DMA2DDriver *dma2dp, dma2d_laycfg_t *cfgp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgCheck(cfgp != NULL);
  
    cfgp->bufferp = dma2dBgGetAddressI(dma2dp);
    cfgp->wrap_offset = dma2dBgGetWrapOffsetI(dma2dp);
    cfgp->fmt = dma2dBgGetPixelFormatI(dma2dp);
    cfgp->def_color = dma2dBgGetDefaultColorI(dma2dp);
    cfgp->const_alpha = dma2dBgGetConstantAlphaI(dma2dp);
    if (cfgp->palettep != NULL)
      dma2dBgGetPaletteI(dma2dp, (dma2d_palcfg_t *)cfgp->palettep);
  }
  
  /**
   * @brief   Get background layer specifications.
   * @details Gets the background layer specifications at once.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[out] cfgp     pointer to the layer specifications
   *
   * @api
   */
  void dma2dBgGetLayer(DMA2DDriver *dma2dp, dma2d_laycfg_t *cfgp) {
  
    chSysLock();
    dma2dBgGetLayerI(dma2dp, cfgp);
    chSysUnlock();
  }
  
  /**
   * @brief   Set background layer specifications.
   * @details Sets the background layer specifications at once.
   * @note    If the palette is unspecified, the layer palette is unmodified.
   * @note    This function should not be called while the DMA2D is already
   *          executing a job, otherwise the appropriate error interrupt might be
   *          invoked.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] cfgp      pointer to the layer specifications
   *
   * @sclass
   */
  void dma2dBgSetConfigS(DMA2DDriver *dma2dp, const dma2d_laycfg_t *cfgp) {
  
    osalDbgCheckClassS();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgAssert(dma2dp->state == DMA2D_READY, "not ready");
    osalDbgCheck(cfgp != NULL);
  
    dma2dBgSetAddressI(dma2dp, cfgp->bufferp);
    dma2dBgSetWrapOffsetI(dma2dp, cfgp->wrap_offset);
    dma2dBgSetPixelFormatI(dma2dp, cfgp->fmt);
    dma2dBgSetDefaultColorI(dma2dp, cfgp->def_color);
    dma2dBgSetConstantAlphaI(dma2dp, cfgp->const_alpha);
    if (cfgp->palettep != NULL)
      dma2dBgSetPaletteS(dma2dp, cfgp->palettep);
  }
  
  /**
   * @brief   Set background layer specifications.
   * @details Sets the background layer specifications at once.
   * @note    If the palette is unspecified, the layer palette is unmodified.
   * @note    This function should not be called while the DMA2D is already
   *          executing a job, otherwise the appropriate error interrupt might be
   *          invoked.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] cfgp      pointer to the layer specifications
   *
   * @api
   */
  void dma2dBgSetConfig(DMA2DDriver *dma2dp, const dma2d_laycfg_t *cfgp) {
  
    chSysLock();
    dma2dBgSetConfigS(dma2dp, cfgp);
    chSysUnlock();
  }
  
  /** @} */
  
  /**
   * @name    DMA2D foreground layer methods
   * @{
   */
  
  /**
   * @brief   Get foreground layer buffer address.
   * @details Gets the buffer address of the foreground layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              buffer address
   *
   * @iclass
   */
  void *dma2dFgGetAddressI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    (void)dma2dp;
  
    return (void *)DMA2D->FGMAR;
  }
  
  /**
   * @brief   Get foreground layer buffer address.
   * @details Gets the buffer address of the foreground layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              buffer address
   *
   * @api
   */
  void *dma2dFgGetAddress(DMA2DDriver *dma2dp) {
  
    void *bufferp;
    chSysLock();
    bufferp = dma2dFgGetAddressI(dma2dp);
    chSysUnlock();
    return bufferp;
  }
  
  /**
   * @brief   Set foreground layer buffer address.
   * @details Sets the buffer address of the foreground layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] bufferp   buffer address
   *
   * @iclass
   */
  void dma2dFgSetAddressI(DMA2DDriver *dma2dp, void *bufferp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgAssert(dma2dp->state == DMA2D_READY, "not ready");
    osalDbgCheck(dma2dIsAligned(bufferp, dma2dFgGetPixelFormatI(dma2dp)));
    (void)dma2dp;
  
    DMA2D->FGMAR = (uint32_t)bufferp;
  }
  
  /**
   * @brief   Set foreground layer buffer address.
   * @details Sets the buffer address of the foreground layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] bufferp   buffer address
   *
   * @api
   */
  void dma2dFgSetAddress(DMA2DDriver *dma2dp, void *bufferp) {
  
    chSysLock();
    dma2dFgSetAddressI(dma2dp, bufferp);
    chSysUnlock();
  }
  
  /**
   * @brief   Get foreground layer wrap offset.
   * @details Gets the buffer line wrap offset of the foreground layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              wrap offset, in pixels
   *
   * @iclass
   */
  size_t dma2dFgGetWrapOffsetI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    (void)dma2dp;
  
    return (size_t)(DMA2D->FGOR & DMA2D_FGOR_LO);
  }
  
  /**
   * @brief   Get foreground layer wrap offset.
   * @details Gets the buffer line wrap offset of the foreground layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              wrap offset, in pixels
   *
   * @api
   */
  size_t dma2dFgGetWrapOffset(DMA2DDriver *dma2dp) {
  
    size_t offset;
    chSysLock();
    offset = dma2dFgGetWrapOffsetI(dma2dp);
    chSysUnlock();
    return offset;
  }
  
  /**
   * @brief   Set foreground layer wrap offset.
   * @details Sets the buffer line wrap offset of the foreground layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] offset    wrap offset, in pixels
   *
   * @iclass
   */
  void dma2dFgSetWrapOffsetI(DMA2DDriver *dma2dp, size_t offset) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgAssert(dma2dp->state == DMA2D_READY, "not ready");
    osalDbgAssert(offset <= DMA2D_MAX_OFFSET, "bounds");
    (void)dma2dp;
  
    DMA2D->FGOR = ((DMA2D->FGOR & ~DMA2D_FGOR_LO) |
                   ((uint32_t)offset & DMA2D_FGOR_LO));
  }
  
  /**
   * @brief   Set foreground layer wrap offset.
   * @details Sets the buffer line wrap offset of the foreground layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] offset    wrap offset, in pixels
   *
   * @api
   */
  void dma2dFgSetWrapOffset(DMA2DDriver *dma2dp, size_t offset) {
  
    chSysLock();
    dma2dFgSetWrapOffsetI(dma2dp, offset);
    chSysUnlock();
  }
  
  /**
   * @brief   Get foreground layer constant alpha.
   * @details Gets the constant alpha component of the foreground layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              constant alpha component, A-8
   *
   * @iclass
   */
  uint8_t dma2dFgGetConstantAlphaI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    (void)dma2dp;
  
    return (uint8_t)((DMA2D->FGPFCCR & DMA2D_FGPFCCR_ALPHA) >> 24);
  }
  
  /**
   * @brief   Get foreground layer constant alpha.
   * @details Gets the constant alpha component of the foreground layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              constant alpha component, A-8
   *
   * @api
   */
  uint8_t dma2dFgGetConstantAlpha(DMA2DDriver *dma2dp) {
  
    uint8_t a;
    chSysLock();
    a = dma2dFgGetConstantAlphaI(dma2dp);
    chSysUnlock();
    return a;
  }
  
  /**
   * @brief   Set foreground layer constant alpha.
   * @details Sets the constant alpha component of the foreground layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] a         constant alpha component, A-8
   *
   * @iclass
   */
  void dma2dFgSetConstantAlphaI(DMA2DDriver *dma2dp, uint8_t a) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgAssert(dma2dp->state == DMA2D_READY, "not ready");
    (void)dma2dp;
  
    DMA2D->FGPFCCR = ((DMA2D->FGPFCCR & ~DMA2D_FGPFCCR_ALPHA) |
                      (((uint32_t)a << 24) & DMA2D_FGPFCCR_ALPHA));
  }
  
  /**
   * @brief   Set foreground layer constant alpha.
   * @details Sets the constant alpha component of the foreground layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] a         constant alpha component, A-8
   *
   * @api
   */
  void dma2dFgSetConstantAlpha(DMA2DDriver *dma2dp, uint8_t a) {
  
    chSysLock();
    dma2dFgSetConstantAlphaI(dma2dp, a);
    chSysUnlock();
  }
  
  /**
   * @brief   Get foreground layer alpha mode.
   * @details Gets the alpha mode of the foreground layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              alpha mode
   *
   * @iclass
   */
  dma2d_amode_t dma2dFgGetAlphaModeI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    (void)dma2dp;
  
    return (dma2d_amode_t)(DMA2D->FGPFCCR & DMA2D_FGPFCCR_AM);
  }
  
  /**
   * @brief   Get foreground layer alpha mode.
   * @details Gets the alpha mode of the foreground layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              alpha mode
   *
   * @api
   */
  dma2d_amode_t dma2dFgGetAlphaMode(DMA2DDriver *dma2dp) {
  
    dma2d_amode_t mode;
    chSysLock();
    mode = dma2dFgGetAlphaModeI(dma2dp);
    chSysUnlock();
    return mode;
  }
  
  /**
   * @brief   Set foreground layer alpha mode.
   * @details Sets the alpha mode of the foreground layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] mode      alpha mode
   *
   * @iclass
   */
  void dma2dFgSetAlphaModeI(DMA2DDriver *dma2dp, dma2d_amode_t mode) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgAssert(dma2dp->state == DMA2D_READY, "not ready");
    osalDbgAssert((mode & ~DMA2D_FGPFCCR_AM) == 0, "bounds");
    osalDbgAssert((mode & DMA2D_FGPFCCR_AM) != DMA2D_FGPFCCR_AM, "bounds");
    (void)dma2dp;
  
    DMA2D->FGPFCCR = ((DMA2D->FGPFCCR & ~DMA2D_FGPFCCR_AM) |
                      ((uint32_t)mode & DMA2D_FGPFCCR_AM));
  }
  
  /**
   * @brief   Set foreground layer alpha mode.
   * @details Sets the alpha mode of the foreground layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] mode      alpha mode
   *
   * @api
   */
  void dma2dFgSetAlphaMode(DMA2DDriver *dma2dp, dma2d_amode_t mode) {
  
    chSysLock();
    dma2dFgSetAlphaModeI(dma2dp, mode);
    chSysUnlock();
  }
  
  /**
   * @brief   Get foreground layer pixel format.
   * @details Gets the pixel format of the foreground layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              pixel format
   *
   * @iclass
   */
  dma2d_pixfmt_t dma2dFgGetPixelFormatI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    (void)dma2dp;
  
    return (dma2d_pixfmt_t)(DMA2D->FGPFCCR & DMA2D_FGPFCCR_CM);
  }
  
  /**
   * @brief   Get foreground layer pixel format.
   * @details Gets the pixel format of the foreground layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              pixel format
   *
   * @api
   */
  dma2d_pixfmt_t dma2dFgGetPixelFormat(DMA2DDriver *dma2dp) {
  
    dma2d_pixfmt_t fmt;
    chSysLock();
    fmt = dma2dFgGetPixelFormatI(dma2dp);
    chSysUnlock();
    return fmt;
  }
  
  /**
   * @brief   Set foreground layer pixel format.
   * @details Sets the pixel format of the foreground layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] fmt       pixel format
   *
   * @iclass
   */
  void dma2dFgSetPixelFormatI(DMA2DDriver *dma2dp, dma2d_pixfmt_t fmt) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgAssert(dma2dp->state == DMA2D_READY, "not ready");
    osalDbgAssert(fmt <= DMA2D_MAX_PIXFMT_ID, "bounds");
    (void)dma2dp;
  
    DMA2D->FGPFCCR = ((DMA2D->FGPFCCR & ~DMA2D_FGPFCCR_CM) |
                      ((uint32_t)fmt & DMA2D_FGPFCCR_CM));
  }
  
  /**
   * @brief   Set foreground layer pixel format.
   * @details Sets the pixel format of the foreground layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] fmt       pixel format
   *
   * @api
   */
  void dma2dFgSetPixelFormat(DMA2DDriver *dma2dp, dma2d_pixfmt_t fmt) {
  
    chSysLock();
    dma2dFgSetPixelFormatI(dma2dp, fmt);
    chSysUnlock();
  }
  
  /**
   * @brief   Get foreground layer default color.
   * @details Gets the default color of the foreground layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              default color, RGB-888
   *
   * @iclass
   */
  dma2d_color_t dma2dFgGetDefaultColorI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    (void)dma2dp;
  
    return (dma2d_color_t)(DMA2D->FGCOLR & 0x00FFFFFF);
  }
  
  /**
   * @brief   Get foreground layer default color.
   * @details Gets the default color of the foreground layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              default color, RGB-888
   *
   * @api
   */
  dma2d_color_t dma2dFgGetDefaultColor(DMA2DDriver *dma2dp) {
  
    dma2d_color_t c;
    chSysLock();
    c = dma2dFgGetDefaultColorI(dma2dp);
    chSysUnlock();
    return c;
  }
  
  /**
   * @brief   Set foreground layer default color.
   * @details Sets the default color of the foreground layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] c         default color, RGB-888
   *
   * @iclass
   */
  void dma2dFgSetDefaultColorI(DMA2DDriver *dma2dp, dma2d_color_t c) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgAssert(dma2dp->state == DMA2D_READY, "not ready");
    (void)dma2dp;
  
    DMA2D->FGCOLR = (uint32_t)c & 0x00FFFFFF;
  }
  
  /**
   * @brief   Set foreground layer default color.
   * @details Sets the default color of the foreground layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] c         default color, RGB-888
   *
   * @api
   */
  void dma2dFgSetDefaultColor(DMA2DDriver *dma2dp, dma2d_color_t c) {
  
    chSysLock();
    dma2dFgSetDefaultColorI(dma2dp, c);
    chSysUnlock();
  }
  
  /**
   * @brief   Get foreground layer palette specifications.
   * @details Gets the palette specifications of the foreground layer.
   * @note    The palette colors pointer is actually addressed to a @p volatile
   *          memory zone.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[out] palettep pointer to the palette specifications
   *
   * @iclass
   */
  void dma2dFgGetPaletteI(DMA2DDriver *dma2dp, dma2d_palcfg_t *palettep) {
  
    uint32_t r;
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgCheck(palettep != NULL);
    (void)dma2dp;
  
    r = DMA2D->FGPFCCR;
    palettep->colorsp = (const void *)DMA2D->FGCLUT;
    palettep->length = (uint16_t)((r & DMA2D_FGPFCCR_CS) >> 8) + 1;
    palettep->fmt = (dma2d_pixfmt_t)((r & DMA2D_FGPFCCR_CCM) >> 4);
  }
  
  /**
   * @brief   Get foreground layer palette specifications.
   * @details Gets the palette specifications of the foreground layer.
   * @note    The palette colors pointer is actually addressed to a @p volatile
   *          memory zone.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[out] palettep pointer to the palette specifications
   *
   * @api
   */
  void dma2dFgGetPalette(DMA2DDriver *dma2dp, dma2d_palcfg_t *palettep) {
  
    chSysLock();
    dma2dFgGetPaletteI(dma2dp, palettep);
    chSysUnlock();
  }
  
  /**
   * @brief   Set foreground layer palette specifications.
   * @details Sets the palette specifications of the foreground layer.
   * @note    This function should not be called while the DMA2D is already
   *          executing a job, otherwise the appropriate error interrupt might be
   *          invoked.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] palettep  pointer to the palette specifications
   *
   * @sclass
   */
  void dma2dFgSetPaletteS(DMA2DDriver *dma2dp, const dma2d_palcfg_t *palettep) {
  
    osalDbgCheckClassS();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgAssert(dma2dp->state == DMA2D_READY, "not ready");
    osalDbgCheck(palettep != NULL);
    osalDbgCheck(palettep->colorsp != NULL);
    osalDbgAssert(palettep->length > 0, "bounds");
    osalDbgAssert(palettep->length <= DMA2D_MAX_PALETTE_LENGTH, "bounds");
    osalDbgAssert(((palettep->fmt == DMA2D_FMT_ARGB8888) ||
                   (palettep->fmt == DMA2D_FMT_RGB888)), "invalid format");
  
    DMA2D->FGCMAR = (uint32_t)palettep->colorsp;
    DMA2D->FGPFCCR = (
      (DMA2D->FGPFCCR & ~(DMA2D_FGPFCCR_CS | DMA2D_FGPFCCR_CCM)) |
      ((((uint32_t)palettep->length - 1) << 8) & DMA2D_FGPFCCR_CS) |
      ((uint32_t)palettep->fmt << 4)
    );
  
    dma2dp->state = DMA2D_ACTIVE;
    DMA2D->FGPFCCR |= DMA2D_FGPFCCR_START;
  
  #if DMA2D_USE_WAIT
    dma2dp->thread = chThdGetSelfX();
    chSchGoSleepS(CH_STATE_SUSPENDED);
  #else
    while (DMA2D->FGPFCCR & DMA2D_FGPFCCR_START)
      chSchDoYieldS();
  #endif  /* DMA2D_USE_WAIT */
  }
  
  /**
   * @brief   Set foreground layer palette specifications.
   * @details Sets the palette specifications of the foreground layer.
   * @note    This function should not be called while the DMA2D is already
   *          executing a job, otherwise the appropriate error interrupt might be
   *          invoked.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] palettep  pointer to the palette specifications
   *
   * @api
   */
  void dma2dFgSetPalette(DMA2DDriver *dma2dp, const dma2d_palcfg_t *palettep) {
  
    chSysLock();
    dma2dFgSetPaletteS(dma2dp, palettep);
    chSysUnlock();
  }
  
  /**
   * @brief   Get foreground layer specifications.
   * @details Gets the foreground layer specifications at once.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[out] cfgp     pointer to the layer specifications
   *
   * @iclass
   */
  void dma2dFgGetLayerI(DMA2DDriver *dma2dp, dma2d_laycfg_t *cfgp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgCheck(cfgp != NULL);
  
    cfgp->bufferp = dma2dFgGetAddressI(dma2dp);
    cfgp->wrap_offset = dma2dFgGetWrapOffsetI(dma2dp);
    cfgp->fmt = dma2dFgGetPixelFormatI(dma2dp);
    cfgp->def_color = dma2dFgGetDefaultColorI(dma2dp);
    cfgp->const_alpha = dma2dFgGetConstantAlphaI(dma2dp);
    if (cfgp->palettep != NULL)
      dma2dFgGetPaletteI(dma2dp, (dma2d_palcfg_t *)cfgp->palettep);
  }
  
  /**
   * @brief   Get foreground layer specifications.
   * @details Gets the foreground layer specifications at once.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[out] cfgp     pointer to the layer specifications
   *
   * @api
   */
  void dma2dFgGetLayer(DMA2DDriver *dma2dp, dma2d_laycfg_t *cfgp) {
  
    chSysLock();
    dma2dFgGetLayerI(dma2dp, cfgp);
    chSysUnlock();
  }
  
  /**
   * @brief   Set foreground layer specifications.
   * @details Sets the foreground layer specifications at once.
   * @note    If the palette is unspecified, the layer palette is unmodified.
   * @note    This function should not be called while the DMA2D is already
   *          executing a job, otherwise the appropriate error interrupt might be
   *          invoked.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] cfgp      pointer to the layer specifications
   *
   * @sclass
   */
  void dma2dFgSetConfigS(DMA2DDriver *dma2dp, const dma2d_laycfg_t *cfgp) {
  
    osalDbgCheckClassS();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgAssert(dma2dp->state == DMA2D_READY, "not ready");
    osalDbgCheck(cfgp != NULL);
  
    dma2dFgSetAddressI(dma2dp, cfgp->bufferp);
    dma2dFgSetWrapOffsetI(dma2dp, cfgp->wrap_offset);
    dma2dFgSetPixelFormatI(dma2dp, cfgp->fmt);
    dma2dFgSetDefaultColorI(dma2dp, cfgp->def_color);
    dma2dFgSetConstantAlphaI(dma2dp, cfgp->const_alpha);
    if (cfgp->palettep != NULL)
      dma2dFgSetPaletteS(dma2dp, cfgp->palettep);
  }
  
  /**
   * @brief   Set foreground layer specifications.
   * @details Sets the foreground layer specifications at once.
   * @note    If the palette is unspecified, the layer palette is unmodified.
   * @note    This function should not be called while the DMA2D is already
   *          executing a job, otherwise the appropriate error interrupt might be
   *          invoked.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] cfgp      pointer to the layer specifications
   *
   * @api
   */
  void dma2dFgSetConfig(DMA2DDriver *dma2dp, const dma2d_laycfg_t *cfgp) {
  
    chSysLock();
    dma2dFgSetConfigS(dma2dp, cfgp);
    chSysUnlock();
  }
  
  /** @} */
  
  /**
   * @name    DMA2D output layer methods
   * @{
   */
  
  /**
   * @brief   Get output layer buffer address.
   * @details Gets the buffer address of the output layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              buffer address
   *
   * @iclass
   */
  void *dma2dOutGetAddressI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    (void)dma2dp;
  
    return (void *)DMA2D->OMAR;
  }
  
  /**
   * @brief   Get output layer buffer address.
   * @details Gets the buffer address of the output layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              buffer address
   *
   * @api
   */
  void *dma2dOutGetAddress(DMA2DDriver *dma2dp) {
  
    void *bufferp;
    chSysLock();
    bufferp = dma2dOutGetAddressI(dma2dp);
    chSysUnlock();
    return bufferp;
  }
  
  /**
   * @brief   Set output layer buffer address.
   * @details Sets the buffer address of the output layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] bufferp   buffer address
   *
   * @iclass
   */
  void dma2dOutSetAddressI(DMA2DDriver *dma2dp, void *bufferp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgAssert(dma2dp->state == DMA2D_READY, "not ready");
    osalDbgCheck(dma2dIsAligned(bufferp, dma2dOutGetPixelFormatI(dma2dp)));
    (void)dma2dp;
  
    DMA2D->OMAR = (uint32_t)bufferp;
  }
  
  /**
   * @brief   Set output layer buffer address.
   * @details Sets the buffer address of the output layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] bufferp   buffer address
   *
   * @api
   */
  void dma2dOutSetAddress(DMA2DDriver *dma2dp, void *bufferp) {
  
    chSysLock();
    dma2dOutSetAddressI(dma2dp, bufferp);
    chSysUnlock();
  }
  
  /**
   * @brief   Get output layer wrap offset.
   * @details Gets the buffer line wrap offset of the output layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              wrap offset, in pixels
   *
   * @iclass
   */
  size_t dma2dOutGetWrapOffsetI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    (void)dma2dp;
  
    return (size_t)(DMA2D->OOR & DMA2D_OOR_LO);
  }
  
  /**
   * @brief   Get output layer wrap offset.
   * @details Gets the buffer line wrap offset of the output layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              wrap offset, in pixels
   *
   * @api
   */
  size_t dma2dOutGetWrapOffset(DMA2DDriver *dma2dp) {
  
    size_t offset;
    chSysLock();
    offset = dma2dOutGetWrapOffsetI(dma2dp);
    chSysUnlock();
    return offset;
  }
  
  /**
   * @brief   Set output layer wrap offset.
   * @details Sets the buffer line wrap offset of the output layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] offset    wrap offset, in pixels
   *
   * @iclass
   */
  void dma2dOutSetWrapOffsetI(DMA2DDriver *dma2dp, size_t offset) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgAssert(dma2dp->state == DMA2D_READY, "not ready");
    osalDbgAssert(offset <= DMA2D_MAX_OFFSET, "bounds");
    (void)dma2dp;
  
    DMA2D->OOR = ((DMA2D->OOR & ~DMA2D_OOR_LO) |
                  ((uint32_t)offset & DMA2D_OOR_LO));
  }
  
  /**
   * @brief   Set output layer wrap offset.
   * @details Sets the buffer line wrap offset of the output layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] offset    wrap offset, in pixels
   *
   * @api
   */
  void dma2dOutSetWrapOffset(DMA2DDriver *dma2dp, size_t offset) {
  
    chSysLock();
    dma2dOutSetWrapOffsetI(dma2dp, offset);
    chSysUnlock();
  }
  
  /**
   * @brief   Get output layer pixel format.
   * @details Gets the pixel format of the output layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              pixel format
   *
   * @iclass
   */
  dma2d_pixfmt_t dma2dOutGetPixelFormatI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    (void)dma2dp;
  
    return (dma2d_pixfmt_t)(DMA2D->OPFCCR & DMA2D_OPFCCR_CM);
  }
  
  /**
   * @brief   Get output layer pixel format.
   * @details Gets the pixel format of the output layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              pixel format
   *
   * @api
   */
  dma2d_pixfmt_t dma2dOutGetPixelFormat(DMA2DDriver *dma2dp) {
  
    dma2d_pixfmt_t fmt;
    chSysLock();
    fmt = dma2dOutGetPixelFormatI(dma2dp);
    chSysUnlock();
    return fmt;
  }
  
  /**
   * @brief   Set output layer pixel format.
   * @details Sets the pixel format of the output layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] fmt       pixel format
   *
   * @iclass
   */
  void dma2dOutSetPixelFormatI(DMA2DDriver *dma2dp, dma2d_pixfmt_t fmt) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgAssert(dma2dp->state == DMA2D_READY, "not ready");
    osalDbgAssert(fmt <= DMA2D_MAX_OUTPIXFMT_ID, "bounds");
    (void)dma2dp;
  
    DMA2D->OPFCCR = ((DMA2D->OPFCCR & ~DMA2D_OPFCCR_CM) |
                     ((uint32_t)fmt & DMA2D_OPFCCR_CM));
  }
  
  /**
   * @brief   Set output layer pixel format.
   * @details Sets the pixel format of the output layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] fmt       pixel format
   *
   * @api
   */
  void dma2dOutSetPixelFormat(DMA2DDriver *dma2dp, dma2d_pixfmt_t fmt) {
  
    chSysLock();
    dma2dOutSetPixelFormatI(dma2dp, fmt);
    chSysUnlock();
  }
  
  /**
   * @brief   Get output layer default color.
   * @details Gets the default color of the output layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              default color, chosen output format
   *
   * @iclass
   */
  dma2d_color_t dma2dOutGetDefaultColorI(DMA2DDriver *dma2dp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    (void)dma2dp;
  
    return (dma2d_color_t)(DMA2D->OCOLR & 0x00FFFFFF);
  }
  
  /**
   * @brief   Get output layer default color.
   * @details Gets the default color of the output layer.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   *
   * @return              default color, chosen output format
   *
   * @api
   */
  dma2d_color_t dma2dOutGetDefaultColor(DMA2DDriver *dma2dp) {
  
    dma2d_color_t c;
    chSysLock();
    c = dma2dOutGetDefaultColorI(dma2dp);
    chSysUnlock();
    return c;
  }
  
  /**
   * @brief   Set output layer default color.
   * @details Sets the default color of the output layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] c         default color, chosen output format
   *
   * @iclass
   */
  void dma2dOutSetDefaultColorI(DMA2DDriver *dma2dp, dma2d_color_t c) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgAssert(dma2dp->state == DMA2D_READY, "not ready");
    (void)dma2dp;
  
    DMA2D->OCOLR = (uint32_t)c & 0x00FFFFFF;
  }
  
  /**
   * @brief   Set output layer default color.
   * @details Sets the default color of the output layer.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] c         default color, chosen output format
   *
   * @api
   */
  void dma2dOutSetDefaultColor(DMA2DDriver *dma2dp, dma2d_color_t c) {
  
    chSysLock();
    dma2dOutSetDefaultColorI(dma2dp, c);
    chSysUnlock();
  }
  
  /**
   * @brief   Get output layer specifications.
   * @details Gets the output layer specifications at once.
   * @note    Constant alpha and palette specifications are ignored.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[out] cfgp     pointer to the layer specifications
   *
   * @iclass
   */
  void dma2dOutGetLayerI(DMA2DDriver *dma2dp, dma2d_laycfg_t *cfgp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgCheck(cfgp != NULL);
  
    cfgp->bufferp = dma2dOutGetAddressI(dma2dp);
    cfgp->wrap_offset = dma2dOutGetWrapOffsetI(dma2dp);
    cfgp->fmt = dma2dOutGetPixelFormatI(dma2dp);
    cfgp->def_color = dma2dOutGetDefaultColorI(dma2dp);
  }
  
  /**
   * @brief   Get output layer specifications.
   * @details Gets the output layer specifications at once.
   * @note    Constant alpha and palette specifications are ignored.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[out] cfgp     pointer to the layer specifications
   *
   * @api
   */
  void dma2dOutGetLayer(DMA2DDriver *dma2dp, dma2d_laycfg_t *cfgp) {
  
    chSysLock();
    dma2dOutGetLayerI(dma2dp, cfgp);
    chSysUnlock();
  }
  
  /**
   * @brief   Set output layer specifications.
   * @details Sets the output layer specifications at once.
   * @note    Constant alpha and palette specifications are ignored.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] cfgp      pointer to the layer specifications
   *
   * @iclass
   */
  void dma2dOutSetConfigI(DMA2DDriver *dma2dp, const dma2d_laycfg_t *cfgp) {
  
    osalDbgCheckClassI();
    osalDbgCheck(dma2dp == &DMA2DD1);
    osalDbgAssert(dma2dp->state == DMA2D_READY, "not ready");
    osalDbgCheck(cfgp != NULL);
  
    dma2dOutSetAddressI(dma2dp, cfgp->bufferp);
    dma2dOutSetWrapOffsetI(dma2dp, cfgp->wrap_offset);
    dma2dOutSetPixelFormatI(dma2dp, cfgp->fmt);
    dma2dOutSetDefaultColorI(dma2dp, cfgp->def_color);
  }
  
  /**
   * @brief   Set output layer specifications.
   * @details Sets the output layer specifications at once.
   * @note    Constant alpha and palette specifications are ignored.
   * @pre     DMA2D is ready.
   *
   * @param[in] dma2dp    pointer to the @p DMA2DDriver object
   * @param[in] cfgp      pointer to the layer specifications
   *
   * @api
   */
  void dma2dOutSetConfig(DMA2DDriver *dma2dp, const dma2d_laycfg_t *cfgp) {
  
    chSysLock();
    dma2dOutSetConfigI(dma2dp, cfgp);
    chSysUnlock();
  }
  
  /** @} */
  
  /**
   * @name    DMA2D helper functions
   * @{
   */
  
  /**
   * @brief   Compute pixel address.
   * @details Computes the buffer address of a pixel, given the buffer
   *          specifications.
   *
   * @param[in] originp   buffer origin address
   * @param[in] pitch     buffer pitch, in bytes
   * @param[in] fmt       buffer pixel format
   * @param[in] x         horizontal pixel coordinate
   * @param[in] y         vertical pixel coordinate
   *
   * @return              pixel address, constant data
   *
   * @api
   */
  const void *dma2dComputeAddressConst(const void *originp, size_t pitch,
                                       dma2d_pixfmt_t fmt,
                                       uint16_t x, uint16_t y) {
  
    osalDbgCheck(pitch > 0);
  
    switch (fmt) {
    case DMA2D_FMT_ARGB8888:
      return (const void *)((uintptr_t)originp +
                            (uintptr_t)y * pitch + (uintptr_t)x * 4);
    case DMA2D_FMT_RGB888:
      return (const void *)((uintptr_t)originp +
                            (uintptr_t)y * pitch + (uintptr_t)x * 3);
    case DMA2D_FMT_RGB565:
    case DMA2D_FMT_ARGB1555:
    case DMA2D_FMT_ARGB4444:
    case DMA2D_FMT_AL88:
      return (const void *)((uintptr_t)originp +
                            (uintptr_t)y * pitch + (uintptr_t)x * 2);
    case DMA2D_FMT_L8:
    case DMA2D_FMT_AL44:
    case DMA2D_FMT_A8:
      return (const void *)((uintptr_t)originp +
                            (uintptr_t)y * pitch + (uintptr_t)x);
    case DMA2D_FMT_L4:
    case DMA2D_FMT_A4:
      osalDbgAssert((x & 1) == 0, "not aligned");
      return (const void *)((uintptr_t)originp +
                            (uintptr_t)y * pitch + (uintptr_t)x / 2);
    default:
      osalDbgAssert(false, "invalid format");
      return NULL;
    }
  }
  
  /**
   * @brief   Address is aligned.
   * @details Tells whether the address is aligned with the provided pixel format.
   *
   * @param[in] bufferp   address
   * @param[in] fmt       pixel format
   *
   * @return              address is aligned
   *
   * @api
   */
  bool dma2dIsAligned(const void *bufferp, dma2d_pixfmt_t fmt) {
  
    switch (fmt) {
    case DMA2D_FMT_ARGB8888:
    case DMA2D_FMT_RGB888:
      return ((uintptr_t)bufferp & 3) == 0;   /* 32-bit alignment.*/
    case DMA2D_FMT_RGB565:
    case DMA2D_FMT_ARGB1555:
    case DMA2D_FMT_ARGB4444:
    case DMA2D_FMT_AL88:
      return ((uintptr_t)bufferp & 1) == 0;   /* 16-bit alignment.*/
    case DMA2D_FMT_L8:
    case DMA2D_FMT_AL44:
    case DMA2D_FMT_L4:
    case DMA2D_FMT_A8:
    case DMA2D_FMT_A4:
      return true;                            /* 8-bit alignment.*/
    default:
      osalDbgAssert(false, "invalid format");
      return false;
    }
  }
  
  /**
   * @brief   Compute bits per pixel.
   * @details Computes the bits per pixel for the specified pixel format.
   *
   * @param[in] fmt       pixel format
   *
   * @retuen              bits per pixel
   *
   * @api
   */
  size_t dma2dBitsPerPixel(dma2d_pixfmt_t fmt) {
  
    osalDbgAssert(fmt < DMA2D_MAX_PIXFMT_ID, "invalid format");
  
    return (size_t)dma2d_bpp[(unsigned)fmt];
  }
  
  #if DMA2D_USE_SOFTWARE_CONVERSIONS || defined(__DOXYGEN__)
  
  /**
   * @brief   Convert from ARGB-8888.
   * @details Converts an ARGB-8888 color to the specified pixel format.
   *
   * @param[in] c         color, ARGB-8888
   * @param[in] fmt       target pixel format
   *
   * @return              raw color value for the target pixel format, left
   *                      padded with zeros.
   *
   * @api
   */
  dma2d_color_t dma2dFromARGB8888(dma2d_color_t c, dma2d_pixfmt_t fmt) {
  
    switch (fmt) {
    case DMA2D_FMT_ARGB8888: {
      return c;
    }
    case DMA2D_FMT_RGB888: {
      return (c & 0x00FFFFFF);
    }
    case DMA2D_FMT_RGB565: {
      return (((c & 0x000000F8) >> ( 8 -  5)) |
              ((c & 0x0000FC00) >> (16 - 11)) |
              ((c & 0x00F80000) >> (24 - 16)));
    }
    case DMA2D_FMT_ARGB1555: {
      return (((c & 0x000000F8) >> ( 8 -  5)) |
              ((c & 0x0000F800) >> (16 - 10)) |
              ((c & 0x00F80000) >> (24 - 15)) |
              ((c & 0x80000000) >> (32 - 16)));
    }
    case DMA2D_FMT_ARGB4444: {
      return (((c & 0x000000F0) >> ( 8 -  4)) |
              ((c & 0x0000F000) >> (16 -  8)) |
              ((c & 0x00F00000) >> (24 - 12)) |
              ((c & 0xF0000000) >> (32 - 16)));
    }
    case DMA2D_FMT_L8: {
      return (c & 0x000000FF);
    }
    case DMA2D_FMT_AL44: {
      return (((c & 0x000000F0) >> ( 8 - 4)) |
              ((c & 0xF0000000) >> (32 - 8)));
    }
    case DMA2D_FMT_AL88: {
      return (((c & 0x000000FF) >> ( 8 -  8)) |
              ((c & 0xFF000000) >> (32 - 16)));
    }
    case DMA2D_FMT_L4: {
      return (c & 0x0000000F);
    }
    case DMA2D_FMT_A8: {
      return ((c & 0xFF000000) >> (32 - 8));
    }
    case DMA2D_FMT_A4: {
      return ((c & 0xF0000000) >> (32 - 4));
    }
    default:
      osalDbgAssert(false, "invalid format");
      return 0;
    }
  }
  
  /**
   * @brief   Convert to ARGB-8888.
   * @details Converts color of the specified pixel format to an ARGB-8888 color.
   *
   * @param[in] c         color for the source pixel format, left padded with
   *                      zeros.
   * @param[in] fmt       source pixel format
   *
   * @return              color in ARGB-8888 format
   *
   * @api
   */
  dma2d_color_t dma2dToARGB8888(dma2d_color_t c, dma2d_pixfmt_t fmt) {
  
    switch (fmt) {
    case DMA2D_FMT_ARGB8888: {
      return c;
    }
    case DMA2D_FMT_RGB888: {
      return ((c & 0x00FFFFFF) | 0xFF000000);
    }
    case DMA2D_FMT_RGB565: {
      register dma2d_color_t output = 0xFF000000;
      if (c & 0x001F) output |= (((c & 0x001F) << ( 8 -  5)) | 0x00000007);
      if (c & 0x07E0) output |= (((c & 0x07E0) << (16 - 11)) | 0x00000300);
      if (c & 0xF800) output |= (((c & 0xF800) << (24 - 16)) | 0x00070000);
      return output;
    }
    case DMA2D_FMT_ARGB1555: {
      register dma2d_color_t output = 0x00000000;
      if (c & 0x001F) output |= (((c & 0x001F) << ( 8 -  5)) | 0x00000007);
      if (c & 0x03E0) output |= (((c & 0x03E0) << (16 - 10)) | 0x00000700);
      if (c & 0x7C00) output |= (((c & 0x7C00) << (24 - 15)) | 0x00070000);
      if (c & 0x8000) output |= 0xFF000000;
      return output;
    }
    case DMA2D_FMT_ARGB4444: {
      register dma2d_color_t output = 0x00000000;
      if (c & 0x000F) output |= (((c & 0x000F) << ( 8 -  4)) | 0x0000000F);
      if (c & 0x00F0) output |= (((c & 0x00F0) << (16 -  8)) | 0x00000F00);
      if (c & 0x0F00) output |= (((c & 0x0F00) << (24 - 12)) | 0x000F0000);
      if (c & 0xF000) output |= (((c & 0xF000) << (32 - 16)) | 0x0F000000);
      return output;
    }
    case DMA2D_FMT_L8: {
      return (c & 0xFF) | 0xFF000000;
    }
    case DMA2D_FMT_AL44: {
      register dma2d_color_t output = 0x00000000;
      if (c & 0x0F) output |= (((c & 0x0F) << ( 8 - 4)) | 0x0000000F);
      if (c & 0xF0) output |= (((c & 0xF0) << (32 - 8)) | 0x0F000000);
      return output;
    }
    case DMA2D_FMT_AL88: {
      return (((c & 0x00FF) << ( 8 -  8)) |
              ((c & 0xFF00) << (32 - 16)));
    }
    case DMA2D_FMT_L4: {
      return ((c & 0x0F) | 0xFF000000);
    }
    case DMA2D_FMT_A8: {
      return ((c & 0xFF) << (32 - 8));
    }
    case DMA2D_FMT_A4: {
      return ((c & 0x0F) << (32 - 4));
    }
    default:
      osalDbgAssert(false, "invalid format");
      return 0;
    }
  }
  
  #endif  /* DMA2D_NEED_CONVERSIONS */
  
  /** @} */
  
  /** @} */
  
  #endif  /* STM32_DMA2D_USE_DMA2D */
  