518 lines
13 KiB
C
518 lines
13 KiB
C
/*
|
|
* Copyright (C) 2017 C-SKY Microsystems Co., Ltd. All rights reserved.
|
|
*
|
|
* 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 ck_dmac.c
|
|
* @brief CSI Source File for DMAC Driver
|
|
* @version V1.0
|
|
* @date 02. June 2017
|
|
******************************************************************************/
|
|
#include <stdbool.h>
|
|
#include "ck_dmac.h"
|
|
#include "csi_core.h"
|
|
#include "drv_dmac.h"
|
|
#include "soc.h"
|
|
|
|
#define ERR_DMA(errno) (CSI_DRV_ERRNO_DMA_BASE | errno)
|
|
|
|
typedef struct {
|
|
uint32_t base;
|
|
uint32_t irq;
|
|
dma_event_cb_t cb_event;
|
|
uint8_t ch_num;
|
|
} ck_dma_priv_t;
|
|
|
|
static ck_dma_priv_t dma_instance[CONFIG_DMAC_NUM];
|
|
|
|
static const dma_capabilities_t dma_capabilities = {
|
|
.unalign_addr = 1, ///< support for unalign address transfer when memory is source
|
|
};
|
|
|
|
static volatile dma_status_e status[CK_DMA_MAXCHANNEL] = {DMA_STATE_FREE, DMA_STATE_FREE};
|
|
static volatile uint8_t ch_opened[CK_DMA_MAXCHANNEL] = {0, 0};
|
|
|
|
static int32_t ck_dma_set_channel(ck_dma_reg_t *addr, uint32_t source, uint32_t dest, uint32_t size)
|
|
{
|
|
uint32_t temp = addr->CHCTRLA;
|
|
temp &= 0xff000fff;
|
|
temp |= (size << 12);
|
|
addr->SAR = source;
|
|
addr->DAR = dest ;
|
|
addr->CHCTRLA = temp;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ck_dma_set_transfertype(ck_dma_reg_t *addr, dma_trans_type_e transtype)
|
|
{
|
|
uint32_t temp = addr->CHCTRLB;
|
|
temp &= 0xffffff7f;
|
|
|
|
if (transtype >= DMA_PERH2PERH) {
|
|
return ERR_DMA(EDRV_PARAMETER);
|
|
}
|
|
|
|
if (transtype == DMA_MEM2MEM) {
|
|
temp |= (transtype << 7);
|
|
} else {
|
|
temp |= (1 << 7);
|
|
}
|
|
|
|
addr->CHCTRLB = temp;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ck_dma_set_addrinc(ck_dma_reg_t *addr, enum_addr_state_e src_addrinc, enum_addr_state_e dst_addrinc)
|
|
{
|
|
if ((src_addrinc != DMA_ADDR_INCREMENT && src_addrinc != DMA_ADDR_DECREMENT && src_addrinc != DMA_ADDR_NOCHANGE) ||
|
|
(dst_addrinc != DMA_ADDR_INCREMENT && dst_addrinc != DMA_ADDR_DECREMENT && dst_addrinc != DMA_ADDR_NOCHANGE)) {
|
|
return ERR_DMA(EDRV_PARAMETER);
|
|
}
|
|
|
|
uint32_t temp = addr->CHCTRLA;
|
|
temp &= 0xffffff0f;
|
|
temp |= (src_addrinc << 6);
|
|
temp |= (dst_addrinc << 4);
|
|
addr->CHCTRLA = temp;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ck_dma_set_transferwidth(ck_dma_reg_t *addr, dma_datawidth_e src_width, dma_datawidth_e dst_width)
|
|
{
|
|
if ((src_width != DMA_DATAWIDTH_SIZE8 && src_width != DMA_DATAWIDTH_SIZE16 && src_width != DMA_DATAWIDTH_SIZE32) ||
|
|
(dst_width != DMA_DATAWIDTH_SIZE8 && dst_width != DMA_DATAWIDTH_SIZE16 && dst_width != DMA_DATAWIDTH_SIZE32)) {
|
|
return ERR_DMA(EDRV_PARAMETER);
|
|
}
|
|
|
|
uint32_t temp = addr->CHCTRLA;
|
|
temp &= 0xfffffff0;
|
|
temp |= (src_width - 1) << 2;
|
|
temp |= dst_width - 1;
|
|
addr->CHCTRLA = temp;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int32_t ck_dma_set_burstlength(ck_dma_reg_t *addr, uint8_t burstlength)
|
|
{
|
|
uint32_t temp = addr->CHCTRLA;
|
|
temp &= 0xfffff0ff;
|
|
temp |= (burstlength << 8);
|
|
addr->CHCTRLA = temp;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
/**
|
|
\brief Set software or hardware handshaking.
|
|
\param[in] addr pointer to dma register.
|
|
\return error code
|
|
*/
|
|
static int32_t ck_dma_set_handshaking(ck_dma_reg_t *addr, dma_handshaking_select_e handshaking)
|
|
{
|
|
uint32_t temp = addr->CHCTRLB;
|
|
temp &= 0xfffffeff;
|
|
temp |= (handshaking << 8);
|
|
addr->CHCTRLB = temp;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ck_dma_assign_hdhs_interface(ck_dma_reg_t *addr, ckenum_dma_device_e device)
|
|
{
|
|
if (device < 0 || device >= CKENUM_DMA_MEMORY) {
|
|
return ERR_DMA(EDRV_PARAMETER);
|
|
}
|
|
|
|
addr->CHCTRLB &= 0xffffe1ff;
|
|
addr->CHCTRLB |= (device << 9);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void ck_dma_irqhandler(int32_t idx)
|
|
{
|
|
ck_dma_priv_t *dma_priv = &dma_instance[idx];
|
|
ck_dma_reg_t *addr = (ck_dma_reg_t *)(dma_priv->base);
|
|
|
|
/*
|
|
* StatusInt_temp contain the information that which types of interrupr are
|
|
* requested.
|
|
*/
|
|
int32_t count = 0;
|
|
uint32_t temp = 0;
|
|
|
|
for (count = 0; count < dma_priv->ch_num; count++) {
|
|
addr = (ck_dma_reg_t *)(dma_priv->base + count * 0x30);
|
|
|
|
temp = addr->CHINTS;
|
|
|
|
if (temp != 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If Tfr interrupt is requested */
|
|
if (temp == CK_DMA_TFR) {
|
|
status[count] = DMA_STATE_DONE;
|
|
addr->CHINTC = temp;
|
|
|
|
if (dma_priv->cb_event) {
|
|
dma_priv->cb_event(DMA_EVENT_TRANSFER_DONE, count);
|
|
}
|
|
}
|
|
|
|
/* If Err interrput is requested */
|
|
if (temp == CK_DMA_ERR) {
|
|
status[count] = DMA_STATE_ERROR;
|
|
addr->CHINTC = temp;
|
|
|
|
if (dma_priv->cb_event) {
|
|
dma_priv->cb_event(DMA_EVENT_TRANSFER_ERROR, count);
|
|
}
|
|
}
|
|
}
|
|
|
|
int32_t __attribute__((weak)) target_get_dmac_count(void)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int32_t __attribute__((weak)) target_get_dmac(uint32_t idx, uint32_t *base, uint32_t *irq)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
\brief get dma instance count.
|
|
\return dma instance count
|
|
*/
|
|
int32_t csi_dma_get_instance_count(void)
|
|
{
|
|
return target_get_dmac_count();
|
|
}
|
|
|
|
/**
|
|
\brief Initialize DMA Interface. 1. Initializes the resources needed for the DMA interface 2.registers event callback function
|
|
\param[in] idx must not exceed return value of csi_dma_get_instance_count()
|
|
\return pointer to dma instances
|
|
*/
|
|
dmac_handle_t csi_dma_initialize(int32_t idx)
|
|
{
|
|
|
|
if (idx < 0 || idx >= CONFIG_DMAC_NUM) {
|
|
return NULL;
|
|
}
|
|
|
|
uint32_t base = 0u;
|
|
uint32_t irq = 0u;
|
|
|
|
int32_t real_idx = target_get_dmac(idx, &base, &irq);
|
|
|
|
if (real_idx != idx) {
|
|
return NULL;
|
|
}
|
|
|
|
ck_dma_priv_t *dma_priv = &dma_instance[idx];
|
|
|
|
dma_priv->base = base;
|
|
dma_priv->irq = irq;
|
|
dma_priv->ch_num = CK_DMA_MAXCHANNEL;
|
|
drv_nvic_enable_irq(dma_priv->irq);
|
|
uint8_t count = 0u;
|
|
|
|
for (count = 0; count < dma_priv->ch_num; count++) {
|
|
ck_dma_reg_t *addr = (ck_dma_reg_t *)(dma_priv->base + count * 0x30);
|
|
addr->CHINTM = CK_DMA_MASK;
|
|
addr->CHINTC = CK_DMA_INTC;
|
|
}
|
|
|
|
return (dmac_handle_t)dma_priv;
|
|
}
|
|
|
|
/**
|
|
\brief De-initialize DMA Interface. stops operation and releases the software resources used by the interface
|
|
\param[in] handle damc handle to operate.
|
|
\return error code
|
|
*/
|
|
int32_t csi_dma_uninitialize(dmac_handle_t handle)
|
|
{
|
|
if (handle == NULL) {
|
|
return ERR_DMA(EDRV_PARAMETER);
|
|
}
|
|
|
|
ck_dma_priv_t *dma_priv = handle;
|
|
ck_dma_reg_t *addr = (ck_dma_reg_t *)(dma_priv->base);
|
|
|
|
uint8_t count;
|
|
|
|
for (count = 0; count < dma_priv->ch_num; count++) {
|
|
addr = (ck_dma_reg_t *)(dma_priv->base + count * 0x30);
|
|
addr->CHINTM = CK_DMA_MASK;
|
|
addr->CHINTC = CK_DMA_INTC;
|
|
}
|
|
|
|
drv_nvic_disable_irq(dma_priv->irq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
\brief Get driver capabilities.
|
|
\param[in] handle damc handle to operate.
|
|
\return \ref dma_capabilities_t
|
|
*/
|
|
dma_capabilities_t csi_dma_get_capabilities(dmac_handle_t handle)
|
|
{
|
|
return dma_capabilities;
|
|
}
|
|
|
|
/**
|
|
\brief get one free dma channel
|
|
\param[in] handle damc handle to operate.
|
|
\param[in] ch channel num. if -1 then allocate a free channal in this dma
|
|
\return -1 - no channel can be used, other - channel index
|
|
*/
|
|
int32_t csi_dma_alloc_channel(dmac_handle_t handle, int32_t ch)
|
|
{
|
|
ck_dma_priv_t *dma_priv = handle;
|
|
|
|
if (handle == NULL || ch > dma_priv->ch_num) {
|
|
return ERR_DMA(EDRV_PARAMETER);
|
|
}
|
|
|
|
uint8_t ch_num = 0;
|
|
ck_dma_reg_t *addr = NULL;
|
|
|
|
if (ch == -1) { // alloc a free channal
|
|
for (ch_num = 0; ch_num < dma_priv->ch_num; ch_num++) {
|
|
addr = (ck_dma_reg_t *)(dma_priv->base + ch_num * 0x30);
|
|
|
|
if (ch_opened[ch_num] != 0x1) {
|
|
ch_opened[ch_num] = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ch_num >= dma_priv->ch_num) {
|
|
return -1;
|
|
}
|
|
} else { //alloc a fixed channel
|
|
addr = (ck_dma_reg_t *)(dma_priv->base + ch * 0x30);
|
|
|
|
if (ch_opened[ch] == 0x1) {
|
|
return ERR_DMA(EDRV_BUSY);
|
|
}
|
|
|
|
ch_opened[ch] = 1;
|
|
ch_num = ch;
|
|
}
|
|
|
|
addr->CHINTC = CK_DMA_INTC;
|
|
addr->CHINTM &= ~CK_DMA_MASK;
|
|
status[ch_num] = DMA_STATE_READY;
|
|
|
|
return ch_num;
|
|
}
|
|
|
|
/**
|
|
\brief release dma channel and related resources
|
|
\param[in] handle damc handle to operate.
|
|
\param[in] ch channel num.
|
|
\return error code
|
|
*/
|
|
int32_t csi_dma_release_channel(dmac_handle_t handle, int32_t ch)
|
|
{
|
|
ck_dma_priv_t *dma_priv = handle;
|
|
|
|
if (handle == NULL || ch >= dma_priv->ch_num || ch < 0) {
|
|
return ERR_DMA(EDRV_PARAMETER);
|
|
}
|
|
|
|
ck_dma_reg_t *addr = (ck_dma_reg_t *)(dma_priv->base + ch * 0x30);
|
|
status[ch] = DMA_STATE_FREE;
|
|
ch_opened[ch] = 0;
|
|
|
|
addr->CHINTC = CK_DMA_INTC;
|
|
addr->CHINTM = CK_DMA_MASK;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
\brief
|
|
\param[in] handle damc handle to operate.
|
|
\param[in] ch channel num. if -1 then allocate a free channal in this dma
|
|
\param[in] psrcaddr dma transfer source address
|
|
\param[in] pstdaddr dma transfer source address
|
|
\param[in] length dma transfer length
|
|
\param[in] config dma transfer configure
|
|
\param[in] cb_event Pointer to \ref dma_event_cb_t
|
|
\return error code
|
|
*/
|
|
int32_t csi_dma_config(dmac_handle_t handle, int32_t ch,
|
|
void *psrcaddr, void *pstdaddr,
|
|
uint32_t length, dma_config_t *config, dma_event_cb_t cb_event)
|
|
{
|
|
ck_dma_priv_t *dma_priv = handle;
|
|
|
|
if (handle == NULL || ch >= dma_priv->ch_num || config == NULL) {
|
|
return ERR_DMA(EDRV_PARAMETER);
|
|
}
|
|
|
|
if (ch == -1) { //alloc a free channel
|
|
ch = csi_dma_alloc_channel(handle, -1);
|
|
|
|
if (ch < 0) {
|
|
return ERR_DMA(EDRV_BUSY);
|
|
}
|
|
}
|
|
|
|
dma_priv->cb_event = cb_event;
|
|
|
|
ck_dma_reg_t *addr = (ck_dma_reg_t *)(dma_priv->base + ch * 0x30);
|
|
|
|
/* Initializes corresponding channel registers */
|
|
|
|
if ((length * config->src_tw) % config->dst_tw != 0) {
|
|
return ERR_DMA(EDRV_PARAMETER);
|
|
}
|
|
|
|
int32_t ret = ck_dma_set_transferwidth(addr, config->src_tw, config->dst_tw);
|
|
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
int32_t grouplen = ((length * config->src_tw / config->dst_tw) - 1) % 16;
|
|
ck_dma_set_burstlength(addr, grouplen);
|
|
|
|
ret = ck_dma_set_transfertype(addr, config->type);
|
|
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
if (config->type == DMA_MEM2MEM) {
|
|
ck_dma_set_handshaking(addr, DMA_HANDSHAKING_SOFTWARE);
|
|
ret = ck_dma_set_addrinc(addr , config->src_inc, config->dst_inc);
|
|
} else if (config->type == DMA_MEM2PERH) {
|
|
ck_dma_set_handshaking(addr, DMA_HANDSHAKING_HARDWARE);
|
|
ret = ck_dma_set_addrinc(addr , config->src_inc, config->dst_inc);
|
|
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
ret = ck_dma_assign_hdhs_interface(addr, config->hs_if);
|
|
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
} else if (config->type == DMA_PERH2MEM) {
|
|
ck_dma_set_handshaking(addr, DMA_HANDSHAKING_HARDWARE);
|
|
ret = ck_dma_set_addrinc(addr , config->src_inc, config->dst_inc);
|
|
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
ret = ck_dma_assign_hdhs_interface(addr, config->hs_if);
|
|
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
ck_dma_set_channel(addr, (uint32_t)psrcaddr, (uint32_t)pstdaddr, length);
|
|
status[ch] = DMA_STATE_READY;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
\brief start generate dma signal.
|
|
\param[in] handle damc handle to operate.
|
|
\param[in] ch channel num.
|
|
\return error code
|
|
*/
|
|
int32_t csi_dma_start(dmac_handle_t handle, int32_t ch)
|
|
{
|
|
ck_dma_priv_t *dma_priv = handle;
|
|
|
|
if (handle == NULL || ch >= dma_priv->ch_num || ch < 0) {
|
|
return ERR_DMA(EDRV_PARAMETER);
|
|
}
|
|
|
|
status[ch] = DMA_STATE_BUSY;
|
|
ck_dma_reg_t *addr = (ck_dma_reg_t *)(dma_priv->base + ch * 0x30);
|
|
addr->CHCTRLB |= CK_DMA_INT_EN; // interrupt enable
|
|
addr->CHEN |= CK_DMA_CH_EN;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
\brief Stop generate dma signal.
|
|
\param[in] handle damc handle to operate.
|
|
\param[in] ch channel num.
|
|
\return error code
|
|
*/
|
|
int32_t csi_dma_stop(dmac_handle_t handle, int32_t ch)
|
|
{
|
|
if (handle == NULL) {
|
|
return ERR_DMA(EDRV_PARAMETER);
|
|
}
|
|
|
|
ck_dma_priv_t *dma_priv = handle;
|
|
|
|
if (ch >= dma_priv->ch_num || ch < 0) {
|
|
return ERR_DMA(EDRV_PARAMETER);
|
|
}
|
|
|
|
status[ch] = DMA_STATE_DONE;
|
|
|
|
ck_dma_reg_t *addr = (ck_dma_reg_t *)(dma_priv->base + ch * 0x30);
|
|
addr->CHCTRLB &= ~CK_DMA_INT_EN; // interrupt disable
|
|
addr->CHEN &= ~CK_DMA_CH_EN;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
\brief Get DMA status.
|
|
\param[in] handle damc handle to operate.
|
|
\param[in] ch channel num.
|
|
\return DMA status \ref dma_status_t
|
|
*/
|
|
dma_status_e csi_dma_get_status(dmac_handle_t handle, int32_t ch)
|
|
{
|
|
if (handle == NULL) {
|
|
return ERR_DMA(EDRV_PARAMETER);
|
|
}
|
|
|
|
ck_dma_priv_t *dma_priv = handle;
|
|
|
|
if (ch >= dma_priv->ch_num || ch < 0) {
|
|
return ERR_DMA(EDRV_PARAMETER);
|
|
}
|
|
|
|
return status[ch];
|
|
}
|
|
|