Newer
Older
mbed-os / targets / TARGET_Maxim / TARGET_MAX32670 / Libraries / PeriphDrivers / Source / DMA / dma_reva.c
/******************************************************************************
 * Copyright (C) 2023 Maxim Integrated Products, Inc., All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES
 * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the name of Maxim Integrated
 * Products, Inc. shall not be used except as stated in the Maxim Integrated
 * Products, Inc. Branding Policy.
 *
 * The mere transfer of this software does not imply any licenses
 * of trade secrets, proprietary technology, copyrights, patents,
 * trademarks, maskwork rights, or any other form of intellectual
 * property whatsoever. Maxim Integrated Products, Inc. retains all
 * ownership rights.
 *
 ******************************************************************************/

/****** Includes *******/
#include <stddef.h>
#include <stdint.h>
#include "mxc_device.h"
#include "mxc_assert.h"
#include "mxc_lock.h"
#include "mxc_sys.h"
#include "dma.h"
#include "dma_reva.h"
#include "dma_reva_regs.h"

/***** Definitions *****/
#define CHECK_HANDLE(x) ((x >= 0) && (x < MXC_DMA_CHANNELS) && (dma_resource[x].valid))

typedef struct {
    void *userCallback; // user given callback
    void *dest; // memcpy destination
} mxc_dma_highlevel_t;

typedef struct {
    unsigned int valid; // Flag to invalidate this resource
    unsigned int instance; // Hardware instance of this DMA controller
    unsigned int id; // Channel ID, which matches the index into the underlying hardware
    mxc_dma_reva_ch_regs_t *regs; // Pointer to the registers for this channel
    void (*cb)(int, int); // Pointer to a callback function type
} mxc_dma_channel_t;

/******* Globals *******/
static unsigned int dma_initialized[MXC_DMA_INSTANCES] = { 0 };
static mxc_dma_channel_t dma_resource[MXC_DMA_CHANNELS];
static mxc_dma_highlevel_t memcpy_resource[MXC_DMA_CHANNELS];
static uint32_t dma_lock;

/****** Functions ******/
static void memcpy_callback(int ch, int error);
static void transfer_callback(int ch, int error);

int MXC_DMA_RevA_Init(mxc_dma_reva_regs_t *dma)
{
    int i, numCh, offset;
#if TARGET_NUM == 32665
    numCh = MXC_DMA_CH_OFFSET;
    offset = numCh * MXC_DMA_GET_IDX((mxc_dma_regs_t *)dma);
#else
    numCh = MXC_DMA_CHANNELS;
    offset = 0;
#endif

    if (dma_initialized[MXC_DMA_GET_IDX((mxc_dma_regs_t *)dma)]) {
        return E_BAD_STATE;
    }

#ifndef __riscv
    /* Initialize mutex */
    MXC_FreeLock(&dma_lock);

    if (MXC_GetLock(&dma_lock, 1) != E_NO_ERROR) {
        return E_BUSY;
    }
#endif

    /* Ensure all channels are disabled at start, clear flags, init handles */
    dma->inten = 0;

    for (i = offset; i < (offset + numCh); i++) {
        dma_resource[i].valid = 0;
        dma_resource[i].instance = 0;
        dma_resource[i].id = i;
        dma_resource[i].regs = (mxc_dma_reva_ch_regs_t *)&(dma->ch[(i % numCh)]);
        dma_resource[i].regs->ctrl = 0;
        dma_resource[i].regs->status = dma_resource[i].regs->status;

        dma_resource[i].cb = NULL;
    }

    dma_initialized[MXC_DMA_GET_IDX((mxc_dma_regs_t *)dma)]++;
#ifndef __riscv
    MXC_FreeLock(&dma_lock);
#endif

    return E_NO_ERROR;
}

int MXC_DMA_RevA_AcquireChannel(mxc_dma_reva_regs_t *dma)
{
    int i, channel, numCh, offset;

    /* Check for initialization */
    if (!dma_initialized[MXC_DMA_GET_IDX((mxc_dma_regs_t *)dma)]) {
        return E_BAD_STATE;
    }

#if TARGET_NUM == 32665
    numCh = MXC_DMA_CH_OFFSET;
    offset = MXC_DMA_CH_OFFSET * MXC_DMA_GET_IDX((mxc_dma_regs_t *)dma);
#else
    numCh = MXC_DMA_CHANNELS;
    offset = 0;
#endif

#ifndef __riscv
    /* If DMA is locked return busy */
    if (MXC_GetLock(&dma_lock, 1) != E_NO_ERROR) {
        return E_BUSY;
    }
#endif
    /* Default is no channel available */
    channel = E_NONE_AVAIL;

    for (i = offset; i < (offset + numCh); i++) {
        if (!dma_resource[i].valid) {
            /* Found one */
            channel = i;
            dma_resource[i].valid = 1;
            dma_resource[i].regs->ctrl = 0;
            dma_resource[i].regs->cntrld = 0; /* Used by DMA_Start() to conditionally set RLDEN */
            break;
        }
    }
#ifndef __riscv
    MXC_FreeLock(&dma_lock);
#endif

    return channel;
}

int MXC_DMA_RevA_ReleaseChannel(int ch)
{
    if (CHECK_HANDLE(ch)) {
        if (MXC_GetLock(&dma_lock, 1) != E_NO_ERROR) {
            return E_BUSY;
        }

        dma_resource[ch].valid = 0;
        dma_resource[ch].regs->ctrl = 0;
        dma_resource[ch].regs->status = dma_resource[ch].regs->status;
        MXC_FreeLock(&dma_lock);
    } else {
        return E_BAD_PARAM;
    }

    return E_NO_ERROR;
}

int MXC_DMA_RevA_ConfigChannel(mxc_dma_config_t config, mxc_dma_srcdst_t srcdst)
{
    if (CHECK_HANDLE(config.ch)) {
        /* Designed to be safe, not speedy. Should not be called often */
        dma_resource[config.ch].regs->ctrl =
            ((config.srcinc_en ? MXC_F_DMA_REVA_CTRL_SRCINC : 0) |
             (config.dstinc_en ? MXC_F_DMA_REVA_CTRL_DSTINC : 0) | config.reqsel |
             (config.srcwd << MXC_F_DMA_REVA_CTRL_SRCWD_POS) |
             (config.dstwd << MXC_F_DMA_REVA_CTRL_DSTWD_POS));
    } else {
        return E_BAD_PARAM;
    }

    return MXC_DMA_RevA_SetSrcDst(srcdst);
}

int MXC_DMA_RevA_AdvConfigChannel(mxc_dma_adv_config_t advConfig)
{
    if (CHECK_HANDLE(advConfig.ch) && (advConfig.burst_size > 0)) {
        dma_resource[advConfig.ch].regs->ctrl &= ~(0x1F00FC0C); // Clear all fields we set here
        /* Designed to be safe, not speedy. Should not be called often */
        dma_resource[advConfig.ch].regs->ctrl |=
            ((advConfig.reqwait_en ? MXC_F_DMA_REVA_CTRL_TO_WAIT : 0) | advConfig.prio |
             advConfig.tosel | advConfig.pssel |
             (((advConfig.burst_size - 1) << MXC_F_DMA_REVA_CTRL_BURST_SIZE_POS) &
              MXC_F_DMA_REVA_CTRL_BURST_SIZE));
    } else {
        return E_BAD_PARAM;
    }

    return E_NO_ERROR;
}

int MXC_DMA_RevA_SetSrcDst(mxc_dma_srcdst_t srcdst)
{
    if (CHECK_HANDLE(srcdst.ch)) {
        dma_resource[srcdst.ch].regs->src = (unsigned int)srcdst.source;
        dma_resource[srcdst.ch].regs->dst = (unsigned int)srcdst.dest;
        dma_resource[srcdst.ch].regs->cnt = srcdst.len;
    } else {
        return E_BAD_PARAM;
    }

    return E_NO_ERROR;
}

int MXC_DMA_RevA_GetSrcDst(mxc_dma_srcdst_t *srcdst)
{
    if (CHECK_HANDLE(srcdst->ch)) {
        srcdst->source = (void *)dma_resource[srcdst->ch].regs->src;
        srcdst->dest = (void *)dma_resource[srcdst->ch].regs->dst;
        srcdst->len = (dma_resource[srcdst->ch].regs->cnt) & ~MXC_F_DMA_REVA_CNTRLD_EN;
    } else {
        return E_BAD_PARAM;
    }

    return E_NO_ERROR;
}

int MXC_DMA_RevA_SetSrcReload(mxc_dma_srcdst_t srcdst)
{
    if (CHECK_HANDLE(srcdst.ch)) {
        dma_resource[srcdst.ch].regs->srcrld = (unsigned int)srcdst.source;
        dma_resource[srcdst.ch].regs->dstrld = (unsigned int)srcdst.dest;

        if (dma_resource[srcdst.ch].regs->ctrl & MXC_F_DMA_REVA_CTRL_EN) {
            /* If channel is already running, set RLDEN to enable next reload */
            dma_resource[srcdst.ch].regs->cntrld = MXC_F_DMA_REVA_CNTRLD_EN | srcdst.len;
        } else {
            /* Otherwise, this is the initial setup, so DMA_Start() will handle setting that bit */
            dma_resource[srcdst.ch].regs->cntrld = srcdst.len;
        }
    } else {
        return E_BAD_PARAM;
    }

    return E_NO_ERROR;
}

int MXC_DMA_RevA_GetSrcReload(mxc_dma_srcdst_t *srcdst)
{
    if (CHECK_HANDLE(srcdst->ch)) {
        srcdst->source = (void *)dma_resource[srcdst->ch].regs->srcrld;
        srcdst->dest = (void *)dma_resource[srcdst->ch].regs->dstrld;
        srcdst->len = (dma_resource[srcdst->ch].regs->cntrld) & ~MXC_F_DMA_REVA_CNTRLD_EN;
    } else {
        return E_BAD_PARAM;
    }

    return E_NO_ERROR;
}

int MXC_DMA_RevA_SetCallback(int ch, void (*callback)(int, int))
{
    if (CHECK_HANDLE(ch)) {
        /* Callback for interrupt handler, no checking is done, as NULL is valid for(none)  */
        dma_resource[ch].cb = callback;
    } else {
        return E_BAD_PARAM;
    }

    return E_NO_ERROR;
}

int MXC_DMA_RevA_SetChannelInterruptEn(int ch, bool chdis, bool ctz)
{
    if (CHECK_HANDLE(ch)) {
        if (chdis) {
            dma_resource[ch].regs->ctrl |= (MXC_F_DMA_REVA_CTRL_DIS_IE);
        }
        if (ctz) {
            dma_resource[ch].regs->ctrl |= (MXC_F_DMA_REVA_CTRL_CTZ_IE);
        }
    } else {
        return E_BAD_PARAM;
    }

    return E_NO_ERROR;
}

int MXC_DMA_RevA_GetChannelInterruptEn(int ch)
{
    return E_NOT_SUPPORTED;
}

int MXC_DMA_RevA_ChannelEnableInt(int ch, int flags)
{
    if (CHECK_HANDLE(ch)) {
        dma_resource[ch].regs->ctrl |=
            (flags & (MXC_F_DMA_REVA_CTRL_DIS_IE | MXC_F_DMA_REVA_CTRL_CTZ_IE));
    } else {
        return E_BAD_PARAM;
    }

    return E_NO_ERROR;
}

int MXC_DMA_RevA_ChannelDisableInt(int ch, int flags)
{
    if (CHECK_HANDLE(ch)) {
        dma_resource[ch].regs->ctrl &=
            ~(flags & (MXC_F_DMA_REVA_CTRL_DIS_IE | MXC_F_DMA_REVA_CTRL_CTZ_IE));
    } else {
        return E_BAD_PARAM;
    }

    return E_NO_ERROR;
}

int MXC_DMA_RevA_EnableInt(mxc_dma_reva_regs_t *dma, int ch)
{
    if (CHECK_HANDLE(ch)) {
#if TARGET_NUM == 32665
        ch %= MXC_DMA_CH_OFFSET;
#endif
        dma->inten |= (1 << ch);
    } else {
        return E_BAD_PARAM;
    }

    return E_NO_ERROR;
}

int MXC_DMA_RevA_DisableInt(mxc_dma_reva_regs_t *dma, int ch)
{
    if (CHECK_HANDLE(ch)) {
#if TARGET_NUM == 32665
        ch %= MXC_DMA_CH_OFFSET;
#endif
        dma->inten &= ~(1 << ch);
    } else {
        return E_BAD_PARAM;
    }

    return E_NO_ERROR;
}

int MXC_DMA_RevA_ChannelGetFlags(int ch)
{
    if (CHECK_HANDLE(ch)) {
        return dma_resource[ch].regs->status;
    } else {
        return E_BAD_PARAM;
    }

    return E_NO_ERROR;
}

int MXC_DMA_RevA_ChannelClearFlags(int ch, int flags)
{
    if (CHECK_HANDLE(ch)) {
        dma_resource[ch].regs->status |= (flags & 0x5F); // Mask for Interrupt flags
    } else {
        return E_BAD_PARAM;
    }

    return E_NO_ERROR;
}

int MXC_DMA_RevA_Start(int ch)
{
    if (CHECK_HANDLE(ch)) {
        MXC_DMA_ChannelClearFlags(ch, MXC_DMA_RevA_ChannelGetFlags(ch));

        if (dma_resource[ch].regs->cntrld) {
            dma_resource[ch].regs->ctrl |= (MXC_F_DMA_REVA_CTRL_EN | MXC_F_DMA_REVA_CTRL_RLDEN);
        } else {
            dma_resource[ch].regs->ctrl |= MXC_F_DMA_REVA_CTRL_EN;
        }
    } else {
        return E_BAD_PARAM;
    }

    return E_NO_ERROR;
}

int MXC_DMA_RevA_Stop(int ch)
{
    if (CHECK_HANDLE(ch)) {
        dma_resource[ch].regs->ctrl &= ~MXC_F_DMA_REVA_CTRL_EN;
    } else {
        return E_BAD_PARAM;
    }

    return E_NO_ERROR;
}

mxc_dma_ch_regs_t *MXC_DMA_RevA_GetCHRegs(int ch)
{
    if (CHECK_HANDLE(ch)) {
        return (mxc_dma_ch_regs_t *)dma_resource[ch].regs;
    } else {
        return NULL;
    }
}

void MXC_DMA_RevA_Handler(mxc_dma_reva_regs_t *dma)
{
    int numCh = MXC_DMA_CHANNELS / MXC_DMA_INSTANCES;
    int offset = numCh * MXC_DMA_GET_IDX((mxc_dma_regs_t *)dma);
    /* Do callback, if enabled */
    for (int i = offset; i < (offset + numCh); i++) {
        if (CHECK_HANDLE(i)) {
            if (dma->intfl & (0x1 << (i % numCh))) {
                if (dma_resource[i].cb != NULL) {
                    dma_resource[i].cb(i, E_NO_ERROR);
                }

                MXC_DMA_ChannelClearFlags(i, MXC_DMA_RevA_ChannelGetFlags(i));

                // No need to check rest of the channels if no interrupt flags set.
                if (dma->intfl == 0) {
                    break;
                }
            }
        }
    }
}

void memcpy_callback(int ch, int error)
{
    mxc_dma_complete_cb_t callback;
    callback = (mxc_dma_complete_cb_t)memcpy_resource[ch].userCallback;

    if (error != E_NO_ERROR) {
        callback(NULL);
    }

    callback(memcpy_resource[ch].dest);

    // Release global objects and local resources
    callback = NULL;
    memcpy_resource[ch].userCallback = NULL;
    memcpy_resource[ch].dest = NULL;
    MXC_DMA_ReleaseChannel(ch);
}

int MXC_DMA_RevA_MemCpy(mxc_dma_reva_regs_t *dma, void *dest, void *src, int len,
                        mxc_dma_complete_cb_t callback)
{
    int retval;
    mxc_dma_config_t config;
    mxc_dma_srcdst_t transfer;
    int channel;

#if TARGET_NUM == 32665
    channel = MXC_DMA_AcquireChannel((mxc_dma_regs_t *)dma);
#else
    channel = MXC_DMA_AcquireChannel();
#endif

    if (memcpy_resource[channel].userCallback != NULL) {
        // We acquired a channel we haven't cleared yet
        MXC_DMA_ReleaseChannel(channel);
        return E_UNKNOWN;
    }

    transfer.ch = channel;
    transfer.source = src;
    transfer.dest = dest;
    transfer.len = len;

    config.ch = channel;
    config.reqsel = MXC_DMA_REQUEST_MEMTOMEM;
    config.srcwd = MXC_DMA_WIDTH_WORD;
    config.dstwd = MXC_DMA_WIDTH_WORD;
    config.srcinc_en = 1;
    config.dstinc_en = 1;

    retval = MXC_DMA_ConfigChannel(config, transfer);

    if (retval != E_NO_ERROR) {
        return retval;
    }

    retval = MXC_DMA_EnableInt(channel);

    if (retval != E_NO_ERROR) {
        return retval;
    }

    retval = MXC_DMA_ChannelEnableInt(channel, MXC_F_DMA_REVA_CTRL_CTZ_IE);

    if (retval != E_NO_ERROR) {
        return retval;
    }

    MXC_DMA_SetCallback(channel, memcpy_callback);

    memcpy_resource[channel].userCallback = (void *)callback;
    memcpy_resource[channel].dest = dest;

    return MXC_DMA_Start(channel);
}

void transfer_callback(int ch, int error)
{
    // Unimplemented
    // Check for reason
    // Call user callback for next transfer
    // determine whether to load into the transfer slot or reload slot
    // continue on or stop
    while (1) {}
}

int MXC_DMA_RevA_DoTransfer(mxc_dma_reva_regs_t *dma, mxc_dma_config_t config,
                            mxc_dma_srcdst_t firstSrcDst, mxc_dma_trans_chain_t callback)
{
    int retval, channel;

#if TARGET_NUM == 32665
    channel = MXC_DMA_AcquireChannel((mxc_dma_regs_t *)dma);
#else
    channel = MXC_DMA_AcquireChannel();
#endif

    if (memcpy_resource[channel].userCallback != NULL) {
        // We acquired a channel we haven't cleared yet
        MXC_DMA_ReleaseChannel(channel);
        return E_UNKNOWN;
    }

    retval = MXC_DMA_ConfigChannel(config, firstSrcDst);

    if (retval != E_NO_ERROR) {
        return retval;
    }

    retval = MXC_DMA_EnableInt(channel);

    if (retval != E_NO_ERROR) {
        return retval;
    }

    retval = MXC_DMA_ChannelEnableInt(channel, MXC_F_DMA_REVA_CTRL_CTZ_IE);

    if (retval != E_NO_ERROR) {
        return retval;
    }

    MXC_DMA_SetCallback(channel, transfer_callback);

    memcpy_resource[channel].userCallback = (void *)callback;

    return MXC_DMA_Start(channel);
}