blob: 35726b9b3739ac529c81e1aa331b75cc1b3e9a95 [file] [log] [blame]
/**
* \addtogroup BSP
* \{
* \addtogroup SYSTEM
* \{
* \addtogroup DMA
* \{
*/
/**
****************************************************************************************
*
* @file hw_dma.c
*
* @brief Implementation of the DMA Low Level Driver.
*
* Copyright (c) 2016, Dialog Semiconductor
* 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 copyright holder 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 COPYRIGHT HOLDER 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.
*
*
****************************************************************************************
*/
#if dg_configUSE_HW_DMA
#include <hw_gpio.h>
#include <hw_dma.h>
#if (dg_configSYSTEMVIEW)
# include "SEGGER_SYSVIEW_FreeRTOS.h"
#else
# define SEGGER_SYSTEMVIEW_ISR_ENTER()
# define SEGGER_SYSTEMVIEW_ISR_EXIT()
#endif
static struct hw_dma_callback_data
{
hw_dma_transfer_cb callback;
void *user_data;
} dma_callbacks_user_data[8];
#define DMA_CHN_REG(reg, chan) ((volatile uint16 *)(&(reg)) + ((chan) * 8))
/**
* \brief Initialize DMA Channel
*
* \param [in] channel_setup pointer to struct of type DMA_Setup
*
*/
void hw_dma_channel_initialization(DMA_setup *channel_setup)
{
volatile uint16 *dma_x_ctrl_reg;
volatile uint16 *dma_x_a_start_low_reg;
volatile uint16 *dma_x_a_start_high_reg;
volatile uint16 *dma_x_b_start_low_reg;
volatile uint16 *dma_x_b_start_high_reg;
volatile uint16 *dma_x_len_reg;
volatile uint16 *dma_x_int_reg;
uint32 src_address;
uint32 dest_address;
/* Make sure the DMA channel length is not zero */
ASSERT_WARNING(channel_setup->length > 0);
// Look up DMAx_CTRL_REG address
dma_x_ctrl_reg = DMA_CHN_REG(DMA->DMA0_CTRL_REG, channel_setup->channel_number);
// Look up DMAx_A_STARTL_REG address
dma_x_a_start_low_reg = DMA_CHN_REG(DMA->DMA0_A_STARTL_REG, channel_setup->channel_number);
// Look up DMAx_A_STARTH_REG address
dma_x_a_start_high_reg = DMA_CHN_REG(DMA->DMA0_A_STARTH_REG, channel_setup->channel_number);
// Look up DMAx_B_STARTL_REG address
dma_x_b_start_low_reg = DMA_CHN_REG(DMA->DMA0_B_STARTL_REG, channel_setup->channel_number);
// Look up DMAx_B_STARTH_REG address
dma_x_b_start_high_reg = DMA_CHN_REG(DMA->DMA0_B_STARTH_REG, channel_setup->channel_number);
// Look up DMAX_LEN_REG address
dma_x_len_reg = DMA_CHN_REG(DMA->DMA0_LEN_REG, channel_setup->channel_number);
// Look up DMAX_INT
dma_x_int_reg = DMA_CHN_REG(DMA->DMA0_INT_REG, channel_setup->channel_number);
// Make sure DMA channel is disabled first
REG_SET_FIELD(DMA, DMA0_CTRL_REG, DMA_ON, *dma_x_ctrl_reg, HW_DMA_STATE_DISABLED);
// Set DMAx_CTRL_REG width provided settings, but do not start the channel.
// Start the channel with the "dma_channel_enable" function separately.
*dma_x_ctrl_reg =
channel_setup->bus_width |
channel_setup->irq_enable |
channel_setup->dreq_mode |
channel_setup->b_inc |
channel_setup->a_inc |
channel_setup->circular |
channel_setup->dma_prio |
channel_setup->dma_idle |
channel_setup->dma_init;
// Set DMA_REQ_MUX_REG for the requested channel / trigger combination
if (channel_setup->dma_req_mux != HW_DMA_TRIG_NONE)
{
switch (channel_setup->channel_number)
{
case HW_DMA_CHANNEL_0:
case HW_DMA_CHANNEL_1:
GLOBAL_INT_DISABLE();
REG_SETF(DMA, DMA_REQ_MUX_REG, DMA01_SEL, channel_setup->dma_req_mux);
GLOBAL_INT_RESTORE();
break;
case HW_DMA_CHANNEL_2:
case HW_DMA_CHANNEL_3:
GLOBAL_INT_DISABLE();
REG_SETF(DMA, DMA_REQ_MUX_REG, DMA23_SEL, channel_setup->dma_req_mux);
GLOBAL_INT_RESTORE();
break;
case HW_DMA_CHANNEL_4:
case HW_DMA_CHANNEL_5:
GLOBAL_INT_DISABLE();
REG_SETF(DMA, DMA_REQ_MUX_REG, DMA45_SEL, channel_setup->dma_req_mux);
GLOBAL_INT_RESTORE();
break;
case HW_DMA_CHANNEL_6:
case HW_DMA_CHANNEL_7:
GLOBAL_INT_DISABLE();
REG_SETF(DMA, DMA_REQ_MUX_REG, DMA67_SEL, channel_setup->dma_req_mux);
GLOBAL_INT_RESTORE();
break;
default:
break;
}
#if dg_configDMA_DYNAMIC_MUX || (dg_configBLACK_ORCA_IC_REV == BLACK_ORCA_IC_REV_A)
/*
* When different DMA channels are used for same device it is important
* that only one trigger is set for specific device at a time.
* Having same trigger for different channels can cause unpredictable results.
* Following code also should help when SPI1 is assigned to non 0 channel.
*/
GLOBAL_INT_DISABLE();
switch (channel_setup->channel_number)
{
case HW_DMA_CHANNEL_6:
case HW_DMA_CHANNEL_7:
if (REG_GETF(DMA, DMA_REQ_MUX_REG, DMA45_SEL) == channel_setup->dma_req_mux)
{
REG_SETF(DMA, DMA_REQ_MUX_REG, DMA45_SEL, HW_DMA_TRIG_NONE);
}
/* no break */
case HW_DMA_CHANNEL_4:
case HW_DMA_CHANNEL_5:
if (REG_GETF(DMA, DMA_REQ_MUX_REG, DMA23_SEL) == channel_setup->dma_req_mux)
{
REG_SETF(DMA, DMA_REQ_MUX_REG, DMA23_SEL, HW_DMA_TRIG_NONE);
}
/* no break */
case HW_DMA_CHANNEL_2:
case HW_DMA_CHANNEL_3:
if (REG_GETF(DMA, DMA_REQ_MUX_REG, DMA01_SEL) == channel_setup->dma_req_mux)
{
REG_SETF(DMA, DMA_REQ_MUX_REG, DMA01_SEL, HW_DMA_TRIG_NONE);
}
break;
case HW_DMA_CHANNEL_0:
case HW_DMA_CHANNEL_1:
default:
break;
}
GLOBAL_INT_RESTORE();
#endif
}
#if (dg_configBLACK_ORCA_IC_REV == BLACK_ORCA_IC_REV_B)
//Set REQ_SENSE bit of i2c and Uart peripherals TX path
if (((channel_setup->dma_req_mux == HW_DMA_TRIG_UART_RXTX) ||
(channel_setup->dma_req_mux == HW_DMA_TRIG_UART2_RXTX) ||
(channel_setup->dma_req_mux == HW_DMA_TRIG_I2C_RXTX) ||
(channel_setup->dma_req_mux == HW_DMA_TRIG_I2C2_RXTX)) &&
(channel_setup->channel_number & 1)) //odd channels used for TX
{
REG_SET_FIELD(DMA, DMA0_CTRL_REG, REQ_SENSE, *dma_x_ctrl_reg, 1);
}
#endif
src_address = black_orca_phy_addr(channel_setup->src_address);
dest_address = black_orca_phy_addr(channel_setup->dest_address);
// Set source address registers
*dma_x_a_start_low_reg = (src_address & 0xffff);
*dma_x_a_start_high_reg = (src_address >> 16);
// Set destination address registers
*dma_x_b_start_low_reg = (dest_address & 0xffff);
*dma_x_b_start_high_reg = (dest_address >> 16);
// Set IRQ number of transfers
if (channel_setup->irq_nr_of_trans > 0)
{
// If user explicitly set this number use it
*dma_x_int_reg = channel_setup->irq_nr_of_trans - 1;
}
else
{
// If user passed 0, use transfer length to fire interrupt after transfer ends
*dma_x_int_reg = channel_setup->length - 1;
}
// Set the transfer length
*dma_x_len_reg = (channel_setup->length) - 1;
if (channel_setup->irq_enable)
{
dma_callbacks_user_data[channel_setup->channel_number].callback = channel_setup->callback;
}
else
{
dma_callbacks_user_data[channel_setup->channel_number].callback = NULL;
}
dma_callbacks_user_data[channel_setup->channel_number].user_data = channel_setup->user_data;
}
void hw_dma_channel_update_source(HW_DMA_CHANNEL channel, void *addr, uint16_t length,
hw_dma_transfer_cb cb)
{
uint32_t phy_addr = black_orca_phy_addr((uint32_t) addr);
dma_callbacks_user_data[channel].callback = cb;
// Look up DMAx_A_STARTL_REG address
volatile uint16 *dma_x_a_start_low_reg = DMA_CHN_REG(DMA->DMA0_A_STARTL_REG, channel);
// Look up DMAx_A_STARTH_REG address
volatile uint16 *dma_x_a_start_high_reg = DMA_CHN_REG(DMA->DMA0_A_STARTH_REG, channel);
// Look up DMAX_LEN_REG address
volatile uint16 *dma_x_len_reg = DMA_CHN_REG(DMA->DMA0_LEN_REG, channel);
volatile uint16 *dma_x_int_reg = DMA_CHN_REG(DMA->DMA0_INT_REG, channel);
// Set source address registers
*dma_x_a_start_low_reg = (phy_addr & 0xffff);
*dma_x_a_start_high_reg = (phy_addr >> 16);
*dma_x_int_reg = length - 1;
// Set the transfer length
*dma_x_len_reg = length - 1;
}
void hw_dma_channel_update_destination(HW_DMA_CHANNEL channel, void *addr, uint16_t length,
hw_dma_transfer_cb cb)
{
uint32_t phy_addr = black_orca_phy_addr((uint32_t) addr);
dma_callbacks_user_data[channel].callback = cb;
// Look up DMAx_B_STARTL_REG address
volatile uint16 *dma_x_b_start_low_reg = DMA_CHN_REG(DMA->DMA0_B_STARTL_REG, channel);
// Look up DMAx_B_STARTH_REG address
volatile uint16 *dma_x_b_start_high_reg = DMA_CHN_REG(DMA->DMA0_B_STARTH_REG, channel);
// Look up DMAX_LEN_REG address
volatile uint16 *dma_x_len_reg = DMA_CHN_REG(DMA->DMA0_LEN_REG, channel);
volatile uint16 *dma_x_int_reg = DMA_CHN_REG(DMA->DMA0_INT_REG, channel);
// Set destination address registers
*dma_x_b_start_low_reg = (phy_addr & 0xffff);
*dma_x_b_start_high_reg = (phy_addr >> 16);
*dma_x_int_reg = length - 1;
// Set the transfer length
*dma_x_len_reg = length - 1;
}
void hw_dma_channel_update_int_ix(HW_DMA_CHANNEL channel, uint16_t int_ix)
{
volatile uint16 *dma_x_int_reg = DMA_CHN_REG(DMA->DMA0_INT_REG, channel);
*dma_x_int_reg = int_ix;
}
/**
* \brief Enable or disable a DMA channel
*
* \param [in] channel_number DMA channel number to start/stop
* \param [in] dma_on enable/disable DMA channel
*
*/
void hw_dma_channel_enable(HW_DMA_CHANNEL channel_number, HW_DMA_STATE dma_on)
{
volatile uint16 *dma_x_ctrl_reg;
// Look up DMAx_CTRL_REG address
dma_x_ctrl_reg = DMA_CHN_REG(DMA->DMA0_CTRL_REG, channel_number);
if (dma_on == HW_DMA_STATE_ENABLED)
{
uint16_t dma_ctrl = *dma_x_ctrl_reg;
REG_SET_FIELD(DMA, DMA0_CTRL_REG, DMA_ON, dma_ctrl, 1);
if (dma_callbacks_user_data[channel_number].callback)
{
REG_SET_FIELD(DMA, DMA0_CTRL_REG, IRQ_ENABLE, dma_ctrl, 1);
}
// Start the chosen DMA channel
*dma_x_ctrl_reg = dma_ctrl;
NVIC_EnableIRQ(DMA_IRQn);
}
else
{
// Stop the chosen DMA channel
REG_SET_FIELD(DMA, DMA0_CTRL_REG, DMA_ON, *dma_x_ctrl_reg, 0);
REG_SET_FIELD(DMA, DMA0_CTRL_REG, IRQ_ENABLE, *dma_x_ctrl_reg, 0);
}
}
static inline void dma_helper(HW_DMA_CHANNEL channel_number, uint16_t len, bool stop_dma)
{
hw_dma_transfer_cb cb;
NVIC_DisableIRQ(DMA_IRQn);
cb = dma_callbacks_user_data[channel_number].callback;
if (stop_dma)
{
dma_callbacks_user_data[channel_number].callback = NULL;
hw_dma_channel_enable(channel_number, HW_DMA_STATE_DISABLED);
}
if (cb)
{
cb(dma_callbacks_user_data[channel_number].user_data, len);
}
NVIC_EnableIRQ(DMA_IRQn);
}
bool hw_dma_channel_active(void)
{
int dma_on;
dma_on = REG_GETF(DMA, DMA0_CTRL_REG, DMA_ON);
dma_on |= REG_GETF(DMA, DMA1_CTRL_REG, DMA_ON);
dma_on |= REG_GETF(DMA, DMA2_CTRL_REG, DMA_ON);
dma_on |= REG_GETF(DMA, DMA3_CTRL_REG, DMA_ON);
dma_on |= REG_GETF(DMA, DMA4_CTRL_REG, DMA_ON);
dma_on |= REG_GETF(DMA, DMA5_CTRL_REG, DMA_ON);
dma_on |= REG_GETF(DMA, DMA6_CTRL_REG, DMA_ON);
dma_on |= REG_GETF(DMA, DMA7_CTRL_REG, DMA_ON);
return (dma_on == 1);
}
/**
* \brief Capture DMA Interrupt Handler
*
* Calls user interrupt handler
*
*/
void DMA_Handler(void)
{
SEGGER_SYSTEMVIEW_ISR_ENTER();
uint16_t risen;
uint16_t i;
volatile uint16 *dma_x_len_reg;
volatile uint16 *dma_x_int_reg;
volatile uint16 *dma_x_ctrl_reg;
risen = DMA->DMA_INT_STATUS_REG;
for (i = 0; risen != 0 && i < 8; ++i, risen >>= 1)
{
if (risen & 1)
{
bool stop;
/*
* DMAx_INT_REG shows after how many transfers the interrupt
* is generated
*/
dma_x_int_reg = DMA_CHN_REG(DMA->DMA0_INT_REG, i);
/*
* DMAx_LEN_REG shows the length of the DMA transfer
*/
dma_x_len_reg = DMA_CHN_REG(DMA->DMA0_LEN_REG, i);
dma_x_ctrl_reg = DMA_CHN_REG(DMA->DMA0_CTRL_REG, i);
/*
* Stop DMA if:
* - transfer is completed
* - mode is not circular
*/
stop = (*dma_x_int_reg == *dma_x_len_reg)
&& (!REG_GET_FIELD(DMA, DMA0_CTRL_REG, CIRCULAR, *dma_x_ctrl_reg));
DMA->DMA_CLEAR_INT_REG = 1 << i;
dma_helper(i, *dma_x_int_reg + 1, stop);
}
}
SEGGER_SYSTEMVIEW_ISR_EXIT();
}
void hw_dma_channel_stop(HW_DMA_CHANNEL channel_number)
{
// Stopping DMA will clear DMAx_IDX_REG so read it before
volatile uint16 *dma_x_idx_reg = DMA_CHN_REG(DMA->DMA0_IDX_REG, channel_number);
dma_helper(channel_number, *dma_x_idx_reg, true);
}
uint16_t hw_dma_transfered_bytes(HW_DMA_CHANNEL channel_number)
{
volatile uint16 *dma_x_int_reg = dma_x_int_reg = DMA_CHN_REG(DMA->DMA0_IDX_REG, channel_number);
return *dma_x_int_reg;
}
#endif /* dg_configUSE_HW_DMA */
/**
* \}
* \}
* \}
*/