text: /*
   * Copyright (c) 2017, Intel Corporation
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   *
   * 1. Redistributions of source code must retain the above copyright notice,
   *    this list of conditions and the following disclaimer.
   * 2. Redistributions in binary form must reproduce the above copyright notice,
   *    this list of conditions and the following disclaimer in the documentation
   *    and/or other materials provided with the distribution.
   * 3. Neither the name of the Intel Corporation nor the names of its
   *    contributors may be used to endorse or promote products derived from this
   *    software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   * ARE DISCLAIMED. IN NO EVENT SHALL THE INTEL CORPORATION OR CONTRIBUTORS BE
   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   * POSSIBILITY OF SUCH DAMAGE.
   */
  
  #include "clk.h"
  #include "dma.h"
  
  #ifndef UNIT_TEST
  qm_dma_reg_t *qm_dma[QM_DMA_NUM] = {(qm_dma_reg_t *)QM_DMA_BASE};
  #endif
  
  /* DMA driver private data structures */
  dma_cfg_prv_t dma_channel_config[QM_DMA_NUM][QM_DMA_CHANNEL_NUM] = {{{0}}};
  
  /*
   * Transfer interrupt handler.
   * - Single block: TFR triggers an user callback invocation
   * - Multiblock (contiguous): TFR triggers an user callback invocation, block
   *   interrupts are silent
   * - Multiblock (linked list): Last block interrupt on each buffer triggers an
   *   user callback invocation, TFR is silent
   */
  static void qm_dma_isr_handler(const qm_dma_t dma,
  			       const qm_dma_channel_id_t channel_id)
  {
  	dma_cfg_prv_t *prv_cfg = &dma_channel_config[dma][channel_id];
  	volatile qm_dma_int_reg_t *int_reg = &QM_DMA[dma]->int_reg;
  	volatile qm_dma_chan_reg_t *chan_reg =
  	    &QM_DMA[dma]->chan_reg[channel_id];
  	uint32_t transfer_length =
  	    get_transfer_length(dma, channel_id, prv_cfg);
  
  	/* The status can't be asserted here as there is a possible race
  	 * condition when terminating channels. It's possible that an interrupt
  	 * can be generated before the terminate function masks the
  	 * interrupts. */
  
  	if (int_reg->status_int_low & QM_DMA_INT_STATUS_TFR) {
  
  		QM_ASSERT(int_reg->status_tfr_low & BIT(channel_id));
  
  		/* Transfer completed, clear interrupt */
  		int_reg->clear_tfr_low = BIT(channel_id);
  
  		/* If multiblock, the final block is also completed. */
  		int_reg->clear_block_low = BIT(channel_id);
  
  		/* Mask interrupts for this channel */
  		int_reg->mask_block_low = BIT(channel_id) << 8;
  		int_reg->mask_tfr_low = BIT(channel_id) << 8;
  		int_reg->mask_err_low = BIT(channel_id) << 8;
  
  		/* Clear llp register */
  		chan_reg->llp_low = 0;
  
  		/*
  		 * Call the callback if registered and pass the transfer length.
  		 */
  		if (prv_cfg->client_callback) {
  			/* Single block or contiguous multiblock. */
  			prv_cfg->client_callback(prv_cfg->callback_context,
  						 transfer_length, 0);
  		}
  	} else if (int_reg->status_int_low & QM_DMA_INT_STATUS_BLOCK) {
  		/* Block interrupts are only unmasked in multiblock mode. */
  		QM_ASSERT(int_reg->status_block_low & BIT(channel_id));
  
  		/* Block completed, clear interrupt. */
  		int_reg->clear_block_low = BIT(channel_id);
  
  		prv_cfg->num_blocks_int_pending--;
  
  		if (NULL != prv_cfg->lli_tail &&
  		    0 == prv_cfg->num_blocks_int_pending) {
  			/*
  			 * Linked list mode, invoke callback if this is last
  			 * block of buffer.
  			 */
  			if (prv_cfg->client_callback) {
  				prv_cfg->client_callback(
  				    prv_cfg->callback_context, transfer_length,
  				    0);
  			}
  
  			/* Buffer done, set for next buffer. */
  			prv_cfg->num_blocks_int_pending =
  			    prv_cfg->num_blocks_per_buffer;
  
  		} else if (NULL == prv_cfg->lli_tail) {
  			QM_ASSERT(prv_cfg->num_blocks_int_pending <
  				  prv_cfg->num_blocks_per_buffer);
  			if (1 == prv_cfg->num_blocks_int_pending) {
  				/*
  				 * Contiguous mode. We have just processed the
  				 * next to last block, clear CFG.RELOAD so
  				 * that the next block is the last one to be
  				 * transfered.
  				 */
  				chan_reg->cfg_low &=
  				    ~QM_DMA_CFG_L_RELOAD_SRC_MASK;
  				chan_reg->cfg_low &=
  				    ~QM_DMA_CFG_L_RELOAD_DST_MASK;
  			}
  		}
  	}
  }
  
  /*
   * Error interrupt handler.
   */
  static void qm_dma_isr_err_handler(const qm_dma_t dma)
  {
  	uint32_t interrupt_channel_mask;
  	dma_cfg_prv_t *chan_cfg;
  	qm_dma_channel_id_t channel_id = 0;
  	volatile qm_dma_int_reg_t *int_reg = &QM_DMA[dma]->int_reg;
  
  	QM_ASSERT(int_reg->status_int_low & QM_DMA_INT_STATUS_ERR);
  	QM_ASSERT(int_reg->status_err_low);
  
  	interrupt_channel_mask = int_reg->status_err_low;
  	while (interrupt_channel_mask) {
  
  		/* Find the channel that the interrupt is for */
  		if (!(interrupt_channel_mask & 0x1)) {
  			interrupt_channel_mask >>= 1;
  			channel_id++;
  			continue;
  		}
  
  		/* Clear the error interrupt for this channel */
  		int_reg->clear_err_low = BIT(channel_id);
  
  		/* Mask interrupts for this channel */
  		int_reg->mask_block_low = BIT(channel_id) << 8;
  		int_reg->mask_tfr_low = BIT(channel_id) << 8;
  		int_reg->mask_err_low = BIT(channel_id) << 8;
  
  		/* Call the callback if registered and pass the
  		 * transfer error code */
  		chan_cfg = &dma_channel_config[dma][channel_id];
  		if (chan_cfg->client_callback) {
  			chan_cfg->client_callback(chan_cfg->callback_context, 0,
  						  -EIO);
  		}
  
  		interrupt_channel_mask >>= 1;
  		channel_id++;
  	}
  }
  
  QM_ISR_DECLARE(qm_dma_0_error_isr)
  {
  	qm_dma_isr_err_handler(QM_DMA_0);
  	QM_ISR_EOI(QM_IRQ_DMA_0_ERROR_INT_VECTOR);
  }
  
  QM_ISR_DECLARE(qm_dma_0_isr_0)
  {
  	qm_dma_isr_handler(QM_DMA_0, QM_DMA_CHANNEL_0);
  	QM_ISR_EOI(QM_IRQ_DMA_0_INT_0_VECTOR);
  }
  
  QM_ISR_DECLARE(qm_dma_0_isr_1)
  {
  	qm_dma_isr_handler(QM_DMA_0, QM_DMA_CHANNEL_1);
  	QM_ISR_EOI(QM_IRQ_DMA_0_INT_1_VECTOR);
  }
  
  #if (QUARK_SE)
  QM_ISR_DECLARE(qm_dma_0_isr_2)
  {
  	qm_dma_isr_handler(QM_DMA_0, QM_DMA_CHANNEL_2);
  	QM_ISR_EOI(QM_IRQ_DMA_0_INT_2_VECTOR);
  }
  
  QM_ISR_DECLARE(qm_dma_0_isr_3)
  {
  	qm_dma_isr_handler(QM_DMA_0, QM_DMA_CHANNEL_3);
  	QM_ISR_EOI(QM_IRQ_DMA_0_INT_3_VECTOR);
  }
  
  QM_ISR_DECLARE(qm_dma_0_isr_4)
  {
  	qm_dma_isr_handler(QM_DMA_0, QM_DMA_CHANNEL_4);
  	QM_ISR_EOI(QM_IRQ_DMA_0_INT_4_VECTOR);
  }
  
  QM_ISR_DECLARE(qm_dma_0_isr_5)
  {
  	qm_dma_isr_handler(QM_DMA_0, QM_DMA_CHANNEL_5);
  	QM_ISR_EOI(QM_IRQ_DMA_0_INT_5_VECTOR);
  }
  
  QM_ISR_DECLARE(qm_dma_0_isr_6)
  {
  	qm_dma_isr_handler(QM_DMA_0, QM_DMA_CHANNEL_6);
  	QM_ISR_EOI(QM_IRQ_DMA_0_INT_6_VECTOR);
  }
  
  QM_ISR_DECLARE(qm_dma_0_isr_7)
  {
  	qm_dma_isr_handler(QM_DMA_0, QM_DMA_CHANNEL_7);
  	QM_ISR_EOI(QM_IRQ_DMA_0_INT_7_VECTOR);
  }
  #endif /* QUARK_SE */
  
  int qm_dma_init(const qm_dma_t dma)
  {
  	QM_CHECK(dma < QM_DMA_NUM, -EINVAL);
  
  	qm_dma_channel_id_t channel_id;
  	volatile qm_dma_int_reg_t *int_reg = &QM_DMA[dma]->int_reg;
  	int return_code;
  
  	/* Enable the DMA Clock */
  	clk_dma_enable();
  
  	/* Disable the controller */
  	return_code = dma_controller_disable(dma);
  	if (return_code) {
  		return return_code;
  	}
  
  	/* Disable the channels and interrupts */
  	for (channel_id = 0; channel_id < QM_DMA_CHANNEL_NUM; channel_id++) {
  		return_code = dma_channel_disable(dma, channel_id);
  		if (return_code) {
  			return return_code;
  		}
  		dma_interrupt_disable(dma, channel_id);
  	}
  
  	/* Mask all interrupts */
  	int_reg->mask_tfr_low = CHANNEL_MASK_ALL << 8;
  	int_reg->mask_block_low = CHANNEL_MASK_ALL << 8;
  	int_reg->mask_src_trans_low = CHANNEL_MASK_ALL << 8;
  	int_reg->mask_dst_trans_low = CHANNEL_MASK_ALL << 8;
  	int_reg->mask_err_low = CHANNEL_MASK_ALL << 8;
  
  	/* Clear all interrupts */
  	int_reg->clear_tfr_low = CHANNEL_MASK_ALL;
  	int_reg->clear_block_low = CHANNEL_MASK_ALL;
  	int_reg->clear_src_trans_low = CHANNEL_MASK_ALL;
  	int_reg->clear_dst_trans_low = CHANNEL_MASK_ALL;
  	int_reg->clear_err_low = CHANNEL_MASK_ALL;
  
  	/* Enable the controller */
  	dma_controller_enable(dma);
  
  	return 0;
  }
  
  int qm_dma_channel_set_config(const qm_dma_t dma,
  			      const qm_dma_channel_id_t channel_id,
  			      qm_dma_channel_config_t *const channel_config)
  {
  	QM_CHECK(dma < QM_DMA_NUM, -EINVAL);
  	QM_CHECK(channel_id < QM_DMA_CHANNEL_NUM, -EINVAL);
  	QM_CHECK(channel_config != NULL, -EINVAL);
  
  	dma_cfg_prv_t *chan_cfg = &dma_channel_config[dma][channel_id];
  	int return_code;
  
  	/* Set the transfer type. */
  	return_code = dma_set_transfer_type(dma, channel_id,
  					    channel_config->transfer_type,
  					    channel_config->channel_direction);
  	if (return_code) {
  		return return_code;
  	}
  
  	/* Set the source and destination transfer width. */
  	dma_set_source_transfer_width(dma, channel_id,
  				      channel_config->source_transfer_width);
  	dma_set_destination_transfer_width(
  	    dma, channel_id, channel_config->destination_transfer_width);
  
  	/* Set the source and destination burst transfer length. */
  	dma_set_source_burst_length(dma, channel_id,
  				    channel_config->source_burst_length);
  	dma_set_destination_burst_length(
  	    dma, channel_id, channel_config->destination_burst_length);
  
  	/* Set channel direction */
  	dma_set_transfer_direction(dma, channel_id,
  				   channel_config->channel_direction);
  
  	/* Set the increment type depending on direction */
  	switch (channel_config->channel_direction) {
  	case QM_DMA_PERIPHERAL_TO_MEMORY:
  		dma_set_source_increment(dma, channel_id,
  					 QM_DMA_ADDRESS_NO_CHANGE);
  		dma_set_destination_increment(dma, channel_id,
  					      QM_DMA_ADDRESS_INCREMENT);
  		break;
  	case QM_DMA_MEMORY_TO_PERIPHERAL:
  		dma_set_source_increment(dma, channel_id,
  					 QM_DMA_ADDRESS_INCREMENT);
  		dma_set_destination_increment(dma, channel_id,
  					      QM_DMA_ADDRESS_NO_CHANGE);
  		break;
  	case QM_DMA_MEMORY_TO_MEMORY:
  		dma_set_source_increment(dma, channel_id,
  					 QM_DMA_ADDRESS_INCREMENT);
  		dma_set_destination_increment(dma, channel_id,
  					      QM_DMA_ADDRESS_INCREMENT);
  		break;
  	}
  
  	if (channel_config->channel_direction != QM_DMA_MEMORY_TO_MEMORY) {
  		/* Set the handshake interface. */
  		dma_set_handshake_interface(
  		    dma, channel_id, channel_config->handshake_interface);
  
  		/* Set the handshake type. This is hardcoded to hardware */
  		dma_set_handshake_type(dma, channel_id, 0);
  
  		/* Set the handshake polarity. */
  		dma_set_handshake_polarity(dma, channel_id,
  					   channel_config->handshake_polarity);
  	}
  
  	/* Save the client ID */
  	chan_cfg->callback_context = channel_config->callback_context;
  
  	/* Save the callback provided by DMA client */
  	chan_cfg->client_callback = channel_config->client_callback;
  
  	/* Multiblock linked list not configured. */
  	chan_cfg->lli_tail = NULL;
  
  	/* Number of blocks per buffer (>1 when multiblock). */
  	chan_cfg->num_blocks_per_buffer = 1;
  
  	/* Multiblock circular linked list flag. */
  	chan_cfg->transfer_type_ll_circular =
  	    (channel_config->transfer_type == QM_DMA_TYPE_MULTI_LL_CIRCULAR)
  		? true
  		: false;
  
  	return 0;
  }
  
  int qm_dma_transfer_set_config(const qm_dma_t dma,
  			       const qm_dma_channel_id_t channel_id,
  			       qm_dma_transfer_t *const transfer_config)
  {
  	QM_CHECK(dma < QM_DMA_NUM, -EINVAL);
  	QM_CHECK(channel_id < QM_DMA_CHANNEL_NUM, -EINVAL);
  	QM_CHECK(transfer_config != NULL, -EINVAL);
  	QM_CHECK(transfer_config->source_address != NULL, -EINVAL);
  	QM_CHECK(transfer_config->destination_address != NULL, -EINVAL);
  	QM_CHECK(transfer_config->block_size >= QM_DMA_CTL_H_BLOCK_TS_MIN,
  		 -EINVAL);
  	QM_CHECK(transfer_config->block_size <= QM_DMA_CTL_H_BLOCK_TS_MAX,
  		 -EINVAL);
  
  	/* Set the source and destination addresses. */
  	dma_set_source_address(dma, channel_id,
  			       (uint32_t)transfer_config->source_address);
  	dma_set_destination_address(
  	    dma, channel_id, (uint32_t)transfer_config->destination_address);
  
  	/* Set the block size for the transfer. */
  	dma_set_block_size(dma, channel_id, transfer_config->block_size);
  
  	return 0;
  }
  
  /* Populate a linked list. */
  static qm_dma_linked_list_item_t *
  dma_linked_list_init(const qm_dma_multi_transfer_t *multi_transfer,
  		     uint32_t ctrl_low, uint32_t tail_pointing_lli)
  {
  	uint32_t source_address = (uint32_t)multi_transfer->source_address;
  	uint32_t destination_address =
  	    (uint32_t)multi_transfer->destination_address;
  	/*
  	 * Extracted source/destination increment type, used to calculate
  	 * address increment between consecutive blocks.
  	 */
  	qm_dma_address_increment_t source_address_inc_type =
  	    (ctrl_low & QM_DMA_CTL_L_SINC_MASK) >> QM_DMA_CTL_L_SINC_OFFSET;
  	qm_dma_address_increment_t destination_address_inc_type =
  	    (ctrl_low & QM_DMA_CTL_L_DINC_MASK) >> QM_DMA_CTL_L_DINC_OFFSET;
  	/* Linked list node iteration variable. */
  	qm_dma_linked_list_item_t *lli = multi_transfer->linked_list_first;
  	uint32_t source_inc = 0;
  	uint32_t destination_inc = 0;
  	uint32_t i;
  
  	QM_ASSERT(source_address_inc_type == QM_DMA_ADDRESS_INCREMENT ||
  		  source_address_inc_type == QM_DMA_ADDRESS_NO_CHANGE);
  	QM_ASSERT(destination_address_inc_type == QM_DMA_ADDRESS_INCREMENT ||
  		  destination_address_inc_type == QM_DMA_ADDRESS_NO_CHANGE);
  
  	/*
  	 * Memory endpoints increment the source/destination address between
  	 * consecutive LLIs by the block size times the transfer width in
  	 * bytes.
  	 */
  	if (source_address_inc_type == QM_DMA_ADDRESS_INCREMENT) {
  		source_inc = multi_transfer->block_size *
  			     BIT((ctrl_low & QM_DMA_CTL_L_SRC_TR_WIDTH_MASK) >>
  				 QM_DMA_CTL_L_SRC_TR_WIDTH_OFFSET);
  	}
  
  	if (destination_address_inc_type == QM_DMA_ADDRESS_INCREMENT) {
  		destination_inc =
  		    multi_transfer->block_size *
  		    BIT((ctrl_low & QM_DMA_CTL_L_DST_TR_WIDTH_MASK) >>
  			QM_DMA_CTL_L_DST_TR_WIDTH_OFFSET);
  	}
  
  	for (i = 0; i < multi_transfer->num_blocks; i++) {
  		lli->source_address = source_address;
  		lli->destination_address = destination_address;
  		lli->ctrl_low = ctrl_low;
  		lli->ctrl_high = multi_transfer->block_size;
  		if (i < (uint32_t)(multi_transfer->num_blocks - 1)) {
  			lli->linked_list_address =
  			    (uint32_t)(qm_dma_linked_list_item_t *)(lli + 1);
  			lli++;
  			source_address += source_inc;
  			destination_address += destination_inc;
  		} else {
  			/* Last node. */
  			lli->linked_list_address = tail_pointing_lli;
  		}
  	}
  
  	/* Last node of the populated linked list. */
  	return lli;
  }
  
  int qm_dma_multi_transfer_set_config(
      const qm_dma_t dma, const qm_dma_channel_id_t channel_id,
      qm_dma_multi_transfer_t *const multi_transfer_config)
  {
  	QM_CHECK(dma < QM_DMA_NUM, -EINVAL);
  	QM_CHECK(channel_id < QM_DMA_CHANNEL_NUM, -EINVAL);
  	QM_CHECK(multi_transfer_config != NULL, -EINVAL);
  	QM_CHECK(multi_transfer_config->source_address != NULL, -EINVAL);
  	QM_CHECK(multi_transfer_config->destination_address != NULL, -EINVAL);
  	QM_CHECK(multi_transfer_config->block_size >= QM_DMA_CTL_H_BLOCK_TS_MIN,
  		 -EINVAL);
  	QM_CHECK(multi_transfer_config->block_size <= QM_DMA_CTL_H_BLOCK_TS_MAX,
  		 -EINVAL);
  	QM_CHECK(multi_transfer_config->num_blocks > 0, -EINVAL);
  
  	dma_cfg_prv_t *prv_cfg = &dma_channel_config[dma][channel_id];
  	qm_dma_transfer_type_t transfer_type =
  	    dma_get_transfer_type(dma, channel_id, prv_cfg);
  	volatile qm_dma_chan_reg_t *chan_reg =
  	    &QM_DMA[dma]->chan_reg[channel_id];
  	/*
  	 * Node to which last node points to, 0 on linear linked lists or first
  	 * node on circular linked lists.
  	 */
  	uint32_t tail_pointing_lli;
  
  	/*
  	 * Initialize block counting internal variables, needed in ISR to manage
  	 * client callback invocations.
  	 */
  	if (0 == chan_reg->llp_low) {
  		prv_cfg->num_blocks_per_buffer =
  		    multi_transfer_config->num_blocks;
  	}
  	prv_cfg->num_blocks_int_pending = multi_transfer_config->num_blocks;
  
  	switch (transfer_type) {
  	case QM_DMA_TYPE_MULTI_CONT:
  		/* Contiguous multiblock transfer. */
  		dma_set_source_address(
  		    dma, channel_id,
  		    (uint32_t)multi_transfer_config->source_address);
  		dma_set_destination_address(
  		    dma, channel_id,
  		    (uint32_t)multi_transfer_config->destination_address);
  		dma_set_block_size(dma, channel_id,
  				   multi_transfer_config->block_size);
  		break;
  
  	case QM_DMA_TYPE_MULTI_LL:
  		/*
  		 * Block interrupts are not enabled in linear linked list with
  		 * single buffer as only one client callback invocation is
  		 * needed, which takes place on transfer callback interrupt.
  		 */
  		if (0 == chan_reg->llp_low) {
  			prv_cfg->num_blocks_int_pending = 0;
  		}
  	/* FALLTHROUGH - continue to common circular/linear LL code */
  
  	case QM_DMA_TYPE_MULTI_LL_CIRCULAR:
  		if (multi_transfer_config->linked_list_first == NULL ||
  		    ((uint32_t)multi_transfer_config->linked_list_first &
  		     0x3) != 0) {
  			/*
  			 * User-allocated linked list memory needs to be 4-byte
  			 * alligned.
  			 */
  			return -EINVAL;
  		}
  
  		if (0 == chan_reg->llp_low) {
  			/*
  			 * Either first call to this function after DMA channel
  			 * config or transfer reconfiguration after a completed
  			 * multiblock transfer.
  			 */
  			tail_pointing_lli =
  			    (transfer_type == QM_DMA_TYPE_MULTI_LL_CIRCULAR)
  				? (uint32_t)
  				      multi_transfer_config->linked_list_first
  				: 0;
  
  			/*
  			 * Initialize LLIs using CTL drom DMA register (plus
  			 * INT_EN bit).
  			 */
  			prv_cfg->lli_tail = dma_linked_list_init(
  			    multi_transfer_config,
  			    chan_reg->ctrl_low | QM_DMA_CTL_L_INT_EN_MASK,
  			    tail_pointing_lli);
  
  			/* Point DMA LLP register to this LLI. */
  			chan_reg->llp_low =
  			    (uint32_t)multi_transfer_config->linked_list_first;
  		} else {
  			/*
  			 * Linked list multiblock transfer (additional appended
  			 * LLIs). The number of blocks needs to match the number
  			 * of blocks on previous calls to this function (we only
  			 * allow scatter/gather buffers of same size).
  			 */
  			if (prv_cfg->num_blocks_per_buffer !=
  			    multi_transfer_config->num_blocks) {
  				return -EINVAL;
  			}
  
  			/*
  			 * Reference to NULL (linear LL) or the first LLI node
  			 * (circular LL), extracted from previously configured
  			 * linked list.
  			 */
  			tail_pointing_lli =
  			    prv_cfg->lli_tail->linked_list_address;
  
  			/*
  			 * Point last previously configured linked list to this
  			 * node.
  			 */
  			prv_cfg->lli_tail->linked_list_address =
  			    (uint32_t)multi_transfer_config->linked_list_first;
  
  			/*
  			 * Initialize LLI using CTL from last previously
  			 * configured LLI, returning a pointer to the new tail
  			 * node.
  			 */
  			prv_cfg->lli_tail = dma_linked_list_init(
  			    multi_transfer_config, prv_cfg->lli_tail->ctrl_low,
  			    tail_pointing_lli);
  
  			QM_ASSERT(prv_cfg->lli_tail->linked_list_address ==
  				  tail_pointing_lli);
  		}
  		break;
  
  	default:
  		/* Single block not allowed */
  		return -EINVAL;
  		break;
  	}
  
  	return 0;
  }
  
  int qm_dma_transfer_start(const qm_dma_t dma,
  			  const qm_dma_channel_id_t channel_id)
  {
  	QM_CHECK(dma < QM_DMA_NUM, -EINVAL);
  	QM_CHECK(channel_id < QM_DMA_CHANNEL_NUM, -EINVAL);
  
  	volatile qm_dma_int_reg_t *int_reg = &QM_DMA[dma]->int_reg;
  	dma_cfg_prv_t *prv_cfg = &dma_channel_config[dma][channel_id];
  
  	/* Clear all interrupts as they may be asserted from a previous
  	 * transfer */
  	int_reg->clear_tfr_low = BIT(channel_id);
  	int_reg->clear_block_low = BIT(channel_id);
  	int_reg->clear_err_low = BIT(channel_id);
  
  	/* Unmask Interrupts */
  	int_reg->mask_tfr_low = ((BIT(channel_id) << 8) | BIT(channel_id));
  	int_reg->mask_err_low = ((BIT(channel_id) << 8) | BIT(channel_id));
  
  	if (prv_cfg->num_blocks_int_pending > 0) {
  		/*
  		 * Block interrupts are only unmasked in multiblock mode
  		 * (contiguous, circular linked list or multibuffer linear
  		 * linked list).
  		 */
  		int_reg->mask_block_low =
  		    ((BIT(channel_id) << 8) | BIT(channel_id));
  	}
  
  	/* Enable interrupts and the channel */
  	dma_interrupt_enable(dma, channel_id);
  	dma_channel_enable(dma, channel_id);
  
  	return 0;
  }
  
  int qm_dma_transfer_terminate(const qm_dma_t dma,
  			      const qm_dma_channel_id_t channel_id)
  {
  	QM_CHECK(dma < QM_DMA_NUM, -EINVAL);
  	QM_CHECK(channel_id < QM_DMA_CHANNEL_NUM, -EINVAL);
  
  	int return_code;
  	volatile qm_dma_int_reg_t *int_reg = &QM_DMA[dma]->int_reg;
  	volatile qm_dma_chan_reg_t *chan_reg =
  	    &QM_DMA[dma]->chan_reg[channel_id];
  
  	/* Disable interrupts for the channel */
  	dma_interrupt_disable(dma, channel_id);
  
  	/* Mask Interrupts */
  	int_reg->mask_tfr_low = (BIT(channel_id) << 8);
  	int_reg->mask_block_low = (BIT(channel_id) << 8);
  	int_reg->mask_err_low = (BIT(channel_id) << 8);
  
  	/* Clear llp register */
  	chan_reg->llp_low = 0;
  
  	/* The channel is disabled and the transfer complete callback is
  	 * triggered. This callback provides the client with the data length
  	 * transferred before the transfer was stopped. */
  	return_code = dma_channel_disable(dma, channel_id);
  	if (!return_code) {
  		dma_cfg_prv_t *prv_cfg = &dma_channel_config[dma][channel_id];
  		if (prv_cfg->client_callback) {
  			prv_cfg->client_callback(
  			    prv_cfg->callback_context,
  			    get_transfer_length(dma, channel_id, prv_cfg), 0);
  		}
  	}
  
  	return return_code;
  }
  
  int qm_dma_transfer_mem_to_mem(const qm_dma_t dma,
  			       const qm_dma_channel_id_t channel_id,
  			       qm_dma_transfer_t *const transfer_config)
  {
  	QM_CHECK(dma < QM_DMA_NUM, -EINVAL);
  	QM_CHECK(channel_id < QM_DMA_CHANNEL_NUM, -EINVAL);
  	QM_CHECK(transfer_config != NULL, -EINVAL);
  	QM_CHECK(transfer_config->source_address != NULL, -EINVAL);
  	QM_CHECK(transfer_config->destination_address != NULL, -EINVAL);
  	QM_CHECK(transfer_config->block_size <= QM_DMA_CTL_H_BLOCK_TS_MAX,
  		 -EINVAL);
  
  	int return_code;
  
  	/* Set the transfer configuration and start the transfer */
  	return_code =
  	    qm_dma_transfer_set_config(dma, channel_id, transfer_config);
  	if (!return_code) {
  		return_code = qm_dma_transfer_start(dma, channel_id);
  	}
  
  	return return_code;
  }
  
  #if (ENABLE_RESTORE_CONTEXT)
  int qm_dma_save_context(const qm_dma_t dma, qm_dma_context_t *const ctx)
  {
  	QM_CHECK(dma < QM_DMA_NUM, -EINVAL);
  	QM_CHECK(ctx != NULL, -EINVAL);
  	int i;
  
  	QM_RW qm_dma_misc_reg_t *misc_reg = &QM_DMA[dma]->misc_reg;
  
  	ctx->misc_cfg_low = misc_reg->cfg_low;
  
  	for (i = 0; i < QM_DMA_CHANNEL_NUM; i++) {
  		QM_RW qm_dma_chan_reg_t *chan_reg = &QM_DMA[dma]->chan_reg[i];
  
  		/* Masking the bit QM_DMA_CTL_L_INT_EN_MASK disables a possible
  		* trigger of a new transition. */
  		ctx->channel[i].ctrl_low =
  		    chan_reg->ctrl_low & ~QM_DMA_CTL_L_INT_EN_MASK;
  		ctx->channel[i].cfg_low = chan_reg->cfg_low;
  		ctx->channel[i].cfg_high = chan_reg->cfg_high;
  		ctx->channel[i].llp_low = chan_reg->llp_low;
  	}
  	return 0;
  }
  
  int qm_dma_restore_context(const qm_dma_t dma,
  			   const qm_dma_context_t *const ctx)
  {
  	QM_CHECK(dma < QM_DMA_NUM, -EINVAL);
  	QM_CHECK(ctx != NULL, -EINVAL);
  	int i;
  	QM_RW qm_dma_misc_reg_t *misc_reg = &QM_DMA[dma]->misc_reg;
  
  	misc_reg->cfg_low = ctx->misc_cfg_low;
  
  	for (i = 0; i < QM_DMA_CHANNEL_NUM; i++) {
  		QM_RW qm_dma_chan_reg_t *chan_reg = &QM_DMA[dma]->chan_reg[i];
  
  		chan_reg->ctrl_low = ctx->channel[i].ctrl_low;
  		chan_reg->cfg_low = ctx->channel[i].cfg_low;
  		chan_reg->cfg_high = ctx->channel[i].cfg_high;
  		chan_reg->llp_low = ctx->channel[i].llp_low;
  	}
  	return 0;
  }
  #else
  int qm_dma_save_context(const qm_dma_t dma, qm_dma_context_t *const ctx)
  {
  	(void)dma;
  	(void)ctx;
  
  	return 0;
  }
  
  int qm_dma_restore_context(const qm_dma_t dma,
  			   const qm_dma_context_t *const ctx)
  {
  	(void)dma;
  	(void)ctx;
  
  	return 0;
  }
  #endif /* ENABLE_RESTORE_CONTEXT */
  