/* ---------------------------------------------------------------------------- */
/*                  Atmel Microcontroller Software Support                      */
/*                       SAM Software Package License                           */
/* ---------------------------------------------------------------------------- */
/* Copyright (c) 2015, Atmel Corporation                                        */
/*                                                                              */
/* All rights reserved.                                                         */
/*                                                                              */
/* Redistribution and use in source and binary forms, with or without           */
/* modification, are permitted provided that the following condition is met:    */
/*                                                                              */
/* - Redistributions of source code must retain the above copyright notice,     */
/* this list of conditions and the disclaimer below.                            */
/*                                                                              */
/* Atmel's name may not be used to endorse or promote products derived from     */
/* this software without specific prior written permission.                     */
/*                                                                              */
/* DISCLAIMER:  THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR   */
/* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE   */
/* DISCLAIMED. IN NO EVENT SHALL ATMEL 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.                           */
/* ---------------------------------------------------------------------------- */

/** \addtogroup dacc_module Working with DACC
 *  \ingroup peripherals_module
 * The DACC driver provides the interface to configure and use the DACC
 * peripheral.\n
 *
 * The DACC(Digital-to-Analog Converter Controller) converts digital code to
 * analog output.
 * The data to be converted are sent in a common register for all channels.
 * It offers up to 2 analog outputs.The output voltage ranges from (1/6)ADVREF
 * to (5/6)ADVREF.
 *
 * To Enable a DACC conversion,the user has to follow these few steps:
 * <ul>
 * <li> Select an appropriate reference voltage on ADVREF   </li>
 * <li> Configure the DACC according to its requirements and special needs,
 * which could be broken down into several parts:
 * -#   Enable DACC in free running mode by clearing TRGEN in DACC_MR;
 * -#   Configure Refresh Period through setting REFRESH fields
 *      in DACC_MR; The refresh mechanism is used to protect the output analog
 * value from
 *      decreasing.
 * -#   Enable channels and write digital code to DACC_CDR,in free running mode,
 * the conversion is started right after at least one channel is enabled and
 * data is written .
 </li>
 * </ul>
 *
 * For more accurate information, please look at the DACC section of the
 * Datasheet.
 *
 * Related files :\n
 * \ref dac_dma.c\n
 * \ref dac_dma.h\n
 */
/*@{*/
/*@}*/
/**
 * \file
 *
 * Implementation of Digital-to-Analog Converter Controller (DACC).
 *
 */

/*----------------------------------------------------------------------------
 *        Headers
 *----------------------------------------------------------------------------*/

#include "chip.h"

#include <stdint.h>
#include <assert.h>

/*  DMA driver instance */
static uint32_t dacDmaTxChannel;
static LinkedListDescriporView1 dmaWriteLinkList[256];
/*----------------------------------------------------------------------------
 *        Local functions
 *----------------------------------------------------------------------------*/

/**
 * \brief Configure the DMA Channels: 0 RX.
 * Channels are disabled after configure.
 * \returns 0 if the dma channel configuration successfully; otherwise returns
 * DAC_ERROR_XXX.
 */
static uint8_t _DacConfigureDmaChannels(DacDma *pDacd)
{

	/* Driver initialize */
	XDMAD_FreeChannel(pDacd->pXdmad, dacDmaTxChannel);

	/* Allocate a DMA channel for DAC0/1 TX. */
	dacDmaTxChannel =
		XDMAD_AllocateChannel(pDacd->pXdmad, XDMAD_TRANSFER_MEMORY, ID_DACC);

	if (dacDmaTxChannel == XDMAD_ALLOC_FAILED)
		return DAC_ERROR;

	if (XDMAD_PrepareChannel(pDacd->pXdmad, dacDmaTxChannel))
		return DAC_ERROR;

	return DAC_OK;
}


/**
 * \brief Configure the DMA source and destination with Linker List mode.
 *
 * \param pBuffer Pointer to dac buffer
 * \param size length of buffer
 */

static uint8_t _Dac_configureLinkList(Dacc *pDacHw, void *pXdmad,
									  DacCmd *pCommand)
{
	uint32_t xdmaCndc;
	sXdmadCfg xdmadCfg;
	uint32_t *pBuffer;
	/* Setup TX Link List */
	uint8_t i;
	pBuffer = (uint32_t *)pCommand->pTxBuff;

	for (i = 0; i < pCommand->TxSize; i++) {
		dmaWriteLinkList[i].mbr_ubc = XDMA_UBC_NVIEW_NDV1
									  | XDMA_UBC_NDE_FETCH_EN
									  | XDMA_UBC_NSEN_UPDATED
									  | XDMAC_CUBC_UBLEN(4);
		dmaWriteLinkList[i].mbr_sa = (uint32_t)pBuffer;
		dmaWriteLinkList[i].mbr_da =
			(uint32_t) & (pDacHw->DACC_CDR[pCommand->dacChannel]);

		if (i == (pCommand->TxSize - 1)) {
			if (pCommand->loopback)
				dmaWriteLinkList[i].mbr_nda = (uint32_t)&dmaWriteLinkList[0];
			else
				dmaWriteLinkList[i].mbr_nda = 0;
		} else
			dmaWriteLinkList[i].mbr_nda = (uint32_t)&dmaWriteLinkList[i + 1];

		pBuffer++;
	}

	xdmadCfg.mbr_cfg = XDMAC_CC_TYPE_PER_TRAN
					   | XDMAC_CC_MBSIZE_SINGLE
					   | XDMAC_CC_DSYNC_MEM2PER
					   | XDMAC_CC_CSIZE_CHK_1
					   | XDMAC_CC_DWIDTH_WORD
					   | XDMAC_CC_SIF_AHB_IF1
					   | XDMAC_CC_DIF_AHB_IF1
					   | XDMAC_CC_SAM_INCREMENTED_AM
					   | XDMAC_CC_DAM_FIXED_AM
					   | XDMAC_CC_PERID(
							XDMAIF_Get_ChannelNumber(ID_DACC, XDMAD_TRANSFER_TX));
	xdmaCndc = XDMAC_CNDC_NDVIEW_NDV1
			   | XDMAC_CNDC_NDE_DSCR_FETCH_EN
			   | XDMAC_CNDC_NDSUP_SRC_PARAMS_UPDATED
			   | XDMAC_CNDC_NDDUP_DST_PARAMS_UPDATED;
	XDMAD_ConfigureTransfer(pXdmad, dacDmaTxChannel, &xdmadCfg, xdmaCndc,
							 (uint32_t)&dmaWriteLinkList[0], XDMAC_CIE_LIE);
	return DAC_OK;
}

/*----------------------------------------------------------------------------
 *        Exported functions
 *----------------------------------------------------------------------------*/
/**
 * \brief Initializes the DacDma structure and the corresponding DAC & DMA .
 * hardware select value.
 * The driver will uses DMA channel 0 for RX .
 * The DMA channels are freed automatically when no DMA command processing.
 *
 * \param pDacd  Pointer to a DacDma instance.
 * \param pDacHw Associated Dac peripheral.
 * \param DacId  Dac peripheral identifier.
 * \param pDmad  Pointer to a Dmad instance.
 */
uint32_t Dac_ConfigureDma(DacDma *pDacd ,
						   Dacc *pDacHw ,
						   uint8_t DacId,
						   sXdmad *pXdmad)
{
	/* Initialize the Dac structure */
	pDacd->pDacHw = pDacHw;
	pDacd->dacId  = DacId;
	pDacd->semaphore = 1;
	pDacd->pCurrentCommand = 0;
	assert(pXdmad == &XDMAD_Instance);
	pDacd->pXdmad = pXdmad;
	return 0;
}

/**
 * \brief Starts a DAC transfer. This is a non blocking function. It will
 *  return as soon as the transfer is started.
 *
 * \param pDacd  Pointer to a DacDma instance.
 * \param pCommand Pointer to the Dac command to execute.
 * \returns 0 if the transfer has been started successfully; otherwise returns
 * DAC_ERROR_LOCK is the driver is in use, or DAC_ERROR if the command is not
 * valid.
 */
uint32_t Dac_SendData(DacDma *pDacd, DacCmd *pCommand)
{
	Dacc *pDacHw = pDacd->pDacHw;

	/* Try to get the dataflash semaphore */
	if (pDacd->semaphore == 0)
		return DAC_ERROR_LOCK;

	pDacd->semaphore--;

	// Initialize the callback
	pDacd->pCurrentCommand = pCommand;

	/* Initialize DMA controller using channel 0 for RX. */
	if (_DacConfigureDmaChannels(pDacd))
		return DAC_ERROR_LOCK;

	if (_Dac_configureLinkList(pDacHw, pDacd->pXdmad, pCommand))
		return DAC_ERROR_LOCK;

	SCB_CleanDCache();

	/* Start DMA TX */
	if (XDMAD_StartTransfer(pDacd->pXdmad, dacDmaTxChannel))
		return DAC_ERROR_LOCK;

	return DAC_OK;;
}
