Newer
Older
mbed-os / targets / TARGET_Maxim / TARGET_MAX32670 / Libraries / PeriphDrivers / Source / I2S / i2s_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.
 *
 ******************************************************************************/

#include <stdbool.h>
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#include "mxc_device.h"
#include "mxc_assert.h"
#include "mxc_lock.h"
#include "mxc_sys.h"
#include "mxc_delay.h"
#include "dma.h"
#include "i2s_reva.h"
#include "i2s.h"

/* ***** Definitions ***** */
#define DATALENGTH_EIGHT (8 - 1)
#define DATALENGTH_SIXTEEN (16 - 1)
#define DATALENGTH_TWENTY (20 - 1)
#define DATALENGTH_TWENTYFOUR (24 - 1)
#define DATALENGTH_THIRTYTWO (32 - 1)

// #define USE_LEGACY_I2S_DMA_CFG

typedef struct {
    int rxCnt;
    int txCnt;
    bool async;
} mxc_i2s_reva_txn_t;

/* ****** Globals ****** */
static mxc_i2s_req_t *request;
static void (*dma_cb)(int, int) = NULL;
static void (*async_cb)(int) = NULL;

static mxc_i2s_req_t txn_req;
static mxc_i2s_reva_txn_t txn_state;
static uint32_t txn_lock = 0;

/* ****** Functions ****** */
int MXC_I2S_RevA_Init(mxc_i2s_reva_regs_t *i2s, mxc_i2s_req_t *req)
{
    if (((req->txData == NULL) || (req->rawData == NULL)) && (req->rxData == NULL)) {
        return E_NULL_PTR;
    }

    if (req->length == 0) {
        return E_BAD_PARAM;
    }

    request = req;

    if (req->stereoMode) {
        i2s->ctrl0ch0 |= (req->stereoMode << MXC_F_I2S_REVA_CTRL0CH0_STEREO_POS);
    }

    //Set RX Threshold 2 (default)
    i2s->ctrl0ch0 |= (2 << MXC_F_I2S_REVA_CTRL0CH0_RX_THD_VAL_POS);

    //Set justify
    MXC_SETFIELD(i2s->ctrl0ch0, MXC_F_I2S_REVA_CTRL0CH0_ALIGN,
                 (req->justify) << MXC_F_I2S_REVA_CTRL0CH0_ALIGN_POS);

    if (MXC_I2S_ConfigData((mxc_i2s_req_t *)req) != E_NO_ERROR) {
        return E_BAD_PARAM;
    }

    MXC_I2S_SetFrequency(req->channelMode, req->clkdiv);

    return E_NO_ERROR;
}

int MXC_I2S_RevA_Shutdown(mxc_i2s_reva_regs_t *i2s)
{
    MXC_I2S_DisableInt(0xFF);

    //Disable I2S TX and RX channel
    MXC_I2S_TXDisable();
    MXC_I2S_RXDisable();

    MXC_I2S_Flush();

    //Clear all the registers. Not cleared on reset
    i2s->ctrl0ch0 = 0x00;
    i2s->dmach0 = 0x00;
    i2s->ctrl1ch0 = 0x00;

    i2s->ctrl0ch0 |= MXC_F_I2S_REVA_CTRL0CH0_RST; //Reset channel

    return E_NO_ERROR;
}

int MXC_I2S_RevA_ConfigData(mxc_i2s_reva_regs_t *i2s, mxc_i2s_req_t *req)
{
    uint32_t dataMask;

    //Data pointers
    uint8_t *txdata_8 = (uint8_t *)req->txData;
    uint16_t *txdata_16 = (uint16_t *)req->txData;
    uint32_t *txdata_32 = (uint32_t *)req->txData;
    uint8_t *rawdata_8 = (uint8_t *)req->rawData;
    uint16_t *rawdata_16 = (uint16_t *)req->rawData;
    uint32_t *rawdata_32 = (uint32_t *)req->rawData;

    if ((req->txData == NULL) && (req->rxData == NULL)) {
        return E_NULL_PTR;
    }

    if (req->length == 0) {
        return E_BAD_PARAM;
    }

    // Clear configuration bits
    i2s->ctrl0ch0 &= ~MXC_F_I2S_REVA_CTRL0CH0_WSIZE;
    i2s->ctrl1ch0 &= ~MXC_F_I2S_REVA_CTRL1CH0_BITS_WORD;
    i2s->ctrl1ch0 &= ~MXC_F_I2S_REVA_CTRL1CH0_SMP_SIZE;

    switch (req->sampleSize) {
    case MXC_I2S_SAMPLESIZE_EIGHT:
        if (req->wordSize == MXC_I2S_DATASIZE_WORD) {
            //Set word length
            i2s->ctrl1ch0 |= (DATALENGTH_THIRTYTWO << MXC_F_I2S_REVA_CTRL1CH0_BITS_WORD_POS);
        } else if (req->wordSize == MXC_I2S_DATASIZE_HALFWORD) {
            //Set word length
            i2s->ctrl1ch0 |= (DATALENGTH_SIXTEEN << MXC_F_I2S_REVA_CTRL1CH0_BITS_WORD_POS);
        } else {
            //Set word length
            i2s->ctrl1ch0 |= (DATALENGTH_EIGHT << MXC_F_I2S_REVA_CTRL1CH0_BITS_WORD_POS);
        }

        //Set sample length
        i2s->ctrl1ch0 |= (DATALENGTH_EIGHT << MXC_F_I2S_REVA_CTRL1CH0_SMP_SIZE_POS);

        //Set datasize to load in FIFO
        MXC_SETFIELD(i2s->ctrl0ch0, MXC_F_I2S_REVA_CTRL0CH0_WSIZE,
                     (MXC_I2S_DATASIZE_BYTE) << MXC_F_I2S_REVA_CTRL0CH0_WSIZE_POS);

        dataMask = 0x000000ff;

        if ((req->rawData != NULL) && (req->txData != NULL)) {
            for (uint32_t i = 0; i < req->length; i++) {
                *txdata_8++ = *rawdata_8++ & dataMask;
            }
        }

        break;

    case MXC_I2S_SAMPLESIZE_SIXTEEN:
        if (req->wordSize == MXC_I2S_DATASIZE_WORD) {
            //Set word length
            i2s->ctrl1ch0 |= (DATALENGTH_THIRTYTWO << MXC_F_I2S_REVA_CTRL1CH0_BITS_WORD_POS);
        } else {
            //Set word length
            i2s->ctrl1ch0 |= (DATALENGTH_SIXTEEN << MXC_F_I2S_REVA_CTRL1CH0_BITS_WORD_POS);
        }

        //Set sample length
        i2s->ctrl1ch0 |= (DATALENGTH_SIXTEEN << MXC_F_I2S_REVA_CTRL1CH0_SMP_SIZE_POS);

        //Set datasize
        MXC_SETFIELD(i2s->ctrl0ch0, MXC_F_I2S_REVA_CTRL0CH0_WSIZE,
                     (MXC_I2S_DATASIZE_HALFWORD) << MXC_F_I2S_REVA_CTRL0CH0_WSIZE_POS);

        dataMask = 0x0000ffff;

        if ((req->rawData != NULL) && (req->txData != NULL)) {
            for (uint32_t i = 0; i < req->length; i++) {
                *txdata_16++ = *rawdata_16++ & dataMask;
            }
        }

        break;

    case MXC_I2S_SAMPLESIZE_TWENTY:
        //Set word length
        i2s->ctrl1ch0 |= (DATALENGTH_THIRTYTWO << MXC_F_I2S_REVA_CTRL1CH0_BITS_WORD_POS);

        //Set sample length
        i2s->ctrl1ch0 |= (DATALENGTH_TWENTY << MXC_F_I2S_REVA_CTRL1CH0_SMP_SIZE_POS);

        //Set datasize
        MXC_SETFIELD(i2s->ctrl0ch0, MXC_F_I2S_REVA_CTRL0CH0_WSIZE,
                     (MXC_I2S_DATASIZE_WORD) << MXC_F_I2S_REVA_CTRL0CH0_WSIZE_POS);

        dataMask = 0x00fffff;

        if ((req->rawData != NULL) && (req->txData != NULL)) {
            for (uint32_t i = 0; i < req->length; i++) {
                *txdata_32++ = (*rawdata_32++ & dataMask) << 12;
            }
        }

        break;

    case MXC_I2S_SAMPLESIZE_TWENTYFOUR:
        //Set word length
        i2s->ctrl1ch0 |= (DATALENGTH_THIRTYTWO << MXC_F_I2S_REVA_CTRL1CH0_BITS_WORD_POS);

        //Set sample length
        i2s->ctrl1ch0 |= (DATALENGTH_TWENTYFOUR << MXC_F_I2S_REVA_CTRL1CH0_SMP_SIZE_POS);

        //Set datasize
        MXC_SETFIELD(i2s->ctrl0ch0, MXC_F_I2S_REVA_CTRL0CH0_WSIZE,
                     (MXC_I2S_DATASIZE_WORD) << MXC_F_I2S_REVA_CTRL0CH0_WSIZE_POS);

        dataMask = 0x00ffffff;

        if ((req->rawData != NULL) && (req->txData != NULL)) {
            for (uint32_t i = 0; i < req->length; i++) {
                *txdata_32++ = (*rawdata_32++ & dataMask) << 8;
            }
        }

        break;

    case MXC_I2S_SAMPLESIZE_THIRTYTWO:
        //Set word length
        i2s->ctrl1ch0 |= (DATALENGTH_THIRTYTWO << MXC_F_I2S_REVA_CTRL1CH0_BITS_WORD_POS);

        //Set sample length
        i2s->ctrl1ch0 |= (DATALENGTH_THIRTYTWO << MXC_F_I2S_REVA_CTRL1CH0_SMP_SIZE_POS);

        //Set datasize
        MXC_SETFIELD(i2s->ctrl0ch0, MXC_F_I2S_REVA_CTRL0CH0_WSIZE,
                     (MXC_I2S_DATASIZE_WORD) << MXC_F_I2S_REVA_CTRL0CH0_WSIZE_POS);

        dataMask = 0xffffffff;

        if ((req->rawData != NULL) && (req->txData != NULL)) {
            for (uint32_t i = 0; i < req->length; i++) {
                *txdata_32++ = *rawdata_32++ & dataMask;
            }
        }

        break;

    default:
        return E_BAD_PARAM;
        break;
    }

    return E_NO_ERROR;
}

void MXC_I2S_RevA_TXEnable(mxc_i2s_reva_regs_t *i2s)
{
    MXC_SETFIELD(i2s->ctrl0ch0, MXC_F_I2S_REVA_CTRL0CH0_TX_EN,
                 1 << MXC_F_I2S_REVA_CTRL0CH0_TX_EN_POS);
}

void MXC_I2S_RevA_TXDisable(mxc_i2s_reva_regs_t *i2s)
{
    MXC_SETFIELD(i2s->ctrl0ch0, MXC_F_I2S_REVA_CTRL0CH0_TX_EN,
                 0 << MXC_F_I2S_REVA_CTRL0CH0_TX_EN_POS);
}

void MXC_I2S_RevA_RXEnable(mxc_i2s_reva_regs_t *i2s)
{
    MXC_SETFIELD(i2s->ctrl0ch0, MXC_F_I2S_REVA_CTRL0CH0_RX_EN,
                 1 << MXC_F_I2S_REVA_CTRL0CH0_RX_EN_POS);
}

void MXC_I2S_RevA_RXDisable(mxc_i2s_reva_regs_t *i2s)
{
    MXC_SETFIELD(i2s->ctrl0ch0, MXC_F_I2S_REVA_CTRL0CH0_RX_EN,
                 0 << MXC_F_I2S_REVA_CTRL0CH0_RX_EN_POS);
}

int MXC_I2S_RevA_SetRXThreshold(mxc_i2s_reva_regs_t *i2s, uint8_t threshold)
{
    if ((threshold == 0) || (threshold > 8)) {
        return E_NOT_SUPPORTED;
    }

    i2s->ctrl0ch0 |= (threshold << MXC_F_I2S_REVA_CTRL0CH0_RX_THD_VAL_POS);

    return E_NO_ERROR;
}

int MXC_I2S_RevA_SetFrequency(mxc_i2s_reva_regs_t *i2s, mxc_i2s_ch_mode_t mode, uint16_t clkdiv)
{
    i2s->ctrl1ch0 &= ~MXC_F_I2S_REVA_CTRL1CH0_EN;

    MXC_SETFIELD(i2s->ctrl0ch0, MXC_F_I2S_REVA_CTRL0CH0_CH_MODE,
                 (mode) << MXC_F_I2S_REVA_CTRL0CH0_CH_MODE_POS);

    i2s->ctrl1ch0 |= ((uint32_t)clkdiv) << MXC_F_I2S_REVA_CTRL1CH0_CLKDIV_POS;

    i2s->ctrl1ch0 |= MXC_F_I2S_REVA_CTRL1CH0_EN;

    return E_NO_ERROR;
}

int MXC_I2S_RevA_SetSampleRate(mxc_i2s_reva_regs_t *i2s, uint32_t smpl_rate,
                               mxc_i2s_wsize_t smpl_sz, uint32_t src_clk)
{
    int clk_div;

    clk_div = MXC_I2S_RevA_CalculateClockDiv(i2s, smpl_rate, smpl_sz, src_clk);
    if (clk_div < 0) {
        return clk_div;
    }

    i2s->ctrl1ch0 &= ~MXC_F_I2S_REVA_CTRL1CH0_EN;
    i2s->ctrl1ch0 |= ((uint32_t)clk_div) << MXC_F_I2S_REVA_CTRL1CH0_CLKDIV_POS;
    i2s->ctrl1ch0 |= MXC_F_I2S_REVA_CTRL1CH0_EN;

    return MXC_I2S_RevA_GetSampleRate(i2s, src_clk);
}

int MXC_I2S_RevA_GetSampleRate(mxc_i2s_reva_regs_t *i2s, uint32_t src_clk)
{
    uint16_t word_sz, clk_div;
    uint32_t bclk;

    word_sz = (i2s->ctrl0ch0 & MXC_F_I2S_REVA_CTRL0CH0_WSIZE) >> MXC_F_I2S_REVA_CTRL0CH0_WSIZE_POS;
    clk_div = (i2s->ctrl1ch0 & MXC_F_I2S_REVA_CTRL1CH0_CLKDIV) >>
              MXC_F_I2S_REVA_CTRL1CH0_CLKDIV_POS; // Get clock divider value

    switch (word_sz) { // Get word size
    case MXC_I2S_DATASIZE_BYTE:
        word_sz = 8;
        break;
    case MXC_I2S_DATASIZE_HALFWORD:
        word_sz = 16;
        break;
    case MXC_I2S_DATASIZE_WORD:
    default:
        word_sz = 32;
        break;
    }

    bclk = (src_clk / (clk_div + 1)) >>
           1; // bclk_frequency = src_clk_frequency / (clk_divider + 1) / 2
    return (bclk / word_sz) >>
           1; // return sample rate (sample_rate = bclk_frequency / word_size / 2)
}

int MXC_I2S_RevA_CalculateClockDiv(mxc_i2s_reva_regs_t *i2s, uint32_t smpl_rate,
                                   mxc_i2s_wsize_t smpl_sz, uint32_t src_clk)
{
    uint32_t bclk;

    switch (smpl_sz) { // Get word size
    case MXC_I2S_DATASIZE_BYTE:
        bclk = 8;
        break;
    case MXC_I2S_DATASIZE_HALFWORD:
        bclk = 16;
        break;
    case MXC_I2S_DATASIZE_WORD:
        bclk = 32;
        break;
    default:
        return E_BAD_PARAM;
    }

    bclk *= smpl_rate * 4; // bclk_frequency = sample_rate * word_size * 2

    if (bclk > src_clk) {
        return E_INVALID;
    }

    return (src_clk / bclk) - 1; // clk_divider = src_clk_frequency / (bclk_frequency * 2) - 1
}

void MXC_I2S_RevA_Flush(mxc_i2s_reva_regs_t *i2s)
{
    i2s->ctrl0ch0 |= MXC_F_I2S_REVA_CTRL0CH0_FLUSH;

    while (i2s->ctrl0ch0 & MXC_F_I2S_REVA_CTRL0CH0_FLUSH) {}
}

static uint32_t write_tx_fifo(void *tx, mxc_i2s_wsize_t wordSize, int smpl_cnt)
{
    uint32_t write_val = 0;

    if (wordSize == MXC_I2S_DATASIZE_BYTE) {
        uint8_t *tx8 = (uint8_t *)tx;
        for (int i = 0; i < 4; i++) {
            write_val |= (tx8[smpl_cnt++] << (i * 8));
        }
    } else if (wordSize == MXC_I2S_DATASIZE_HALFWORD) {
        uint16_t *tx16 = (uint16_t *)tx;
        for (int i = 0; i < 2; i++) {
            write_val |= (tx16[smpl_cnt++] << (i * 16));
        }
    } else if (wordSize == MXC_I2S_DATASIZE_WORD) {
        uint32_t *tx32 = (uint32_t *)tx;
        write_val = tx32[smpl_cnt];
    }

    return write_val;
}

int MXC_I2S_RevA_FillTXFIFO(mxc_i2s_reva_regs_t *i2s, void *txData, mxc_i2s_wsize_t wordSize,
                            int len, int smpl_cnt)
{
    int num_smpl = 0x4 >> wordSize; // Number of samples per FIFO write
    int sent = 0; // Total number of samples transmitted
    uint32_t fifo_write, fifo_avail; // Value to write to I2S TX FIFO

    if (txData == NULL) { // Check for bad parameters
        return E_NULL_PTR;
    } else if (wordSize < MXC_I2S_DATASIZE_BYTE || wordSize > MXC_I2S_DATASIZE_WORD) {
        return E_BAD_PARAM;
    } else if (len == 0) {
        return E_NO_ERROR;
    }

    len -= len % num_smpl; // TEST
    fifo_avail =
        8 - ((i2s->dmach0 & MXC_F_I2S_REVA_DMACH0_TX_LVL) >> MXC_F_I2S_REVA_DMACH0_TX_LVL_POS);
    fifo_avail *= num_smpl;
    while (sent < len && sent < fifo_avail) {
        fifo_write = write_tx_fifo(txData, wordSize, sent + smpl_cnt);
        sent += num_smpl;

        i2s->fifoch0 = fifo_write;
    }

    return sent;
}

static void read_rx_fifo(mxc_i2s_reva_regs_t *i2s, void *rxData, mxc_i2s_wsize_t wordSize, int cnt)
{
    uint32_t fifo_val = i2s->fifoch0;

    if (wordSize == MXC_I2S_DATASIZE_BYTE) {
        uint8_t *rx8 = (uint8_t *)rxData;
        for (int i = 0; i < 4; i++) {
            rx8[cnt++] = fifo_val & 0xFF;
            fifo_val = fifo_val >> 8;
        }
    } else if (wordSize == MXC_I2S_DATASIZE_HALFWORD) {
        uint16_t *rx16 = (uint16_t *)rxData;
        for (int i = 0; i < 2; i++) {
            rx16[cnt++] = fifo_val & 0xFFFF;
            fifo_val = fifo_val >> 16;
        }
    } else if (wordSize == MXC_I2S_DATASIZE_WORD) {
        uint32_t *rx32 = (uint32_t *)rxData;
        rx32[cnt] = fifo_val;
    }
}

int MXC_I2S_RevA_ReadRXFIFO(mxc_i2s_reva_regs_t *i2s, void *rxData, mxc_i2s_wsize_t wordSize,
                            int len, int smpl_cnt)
{
    int received = 0;
    int num_smpl = 0x4 >> wordSize;
    uint32_t fifo_avail;

    if (rxData == NULL) { // Check for bad parameters
        return E_NULL_PTR;
    } else if (wordSize < MXC_I2S_DATASIZE_BYTE || wordSize > MXC_I2S_DATASIZE_WORD) {
        return E_BAD_PARAM;
    } else if (len == 0) {
        return E_NO_ERROR;
    }

    len -= len % num_smpl;
    fifo_avail = (i2s->dmach0 & MXC_F_I2S_REVA_DMACH0_RX_LVL) >> MXC_F_I2S_REVA_DMACH0_RX_LVL_POS;
    while (received < len && fifo_avail) {
        read_rx_fifo(i2s, rxData, wordSize, received + smpl_cnt);
        received += num_smpl;
        fifo_avail = (i2s->dmach0 & MXC_F_I2S_REVA_DMACH0_RX_LVL) >>
                     MXC_F_I2S_REVA_DMACH0_RX_LVL_POS;
    }

    return received;
}

void MXC_I2S_RevA_EnableInt(mxc_i2s_reva_regs_t *i2s, uint32_t flags)
{
    i2s->inten |= flags;
}

void MXC_I2S_RevA_DisableInt(mxc_i2s_reva_regs_t *i2s, uint32_t flags)
{
    i2s->inten &= ~flags;
}

int MXC_I2S_RevA_GetFlags(mxc_i2s_reva_regs_t *i2s)
{
    return (i2s->intfl & 0xF);
}

void MXC_I2S_RevA_ClearFlags(mxc_i2s_reva_regs_t *i2s, uint32_t flags)
{
    i2s->intfl |= flags;
}

int MXC_I2S_RevA_Transaction(mxc_i2s_reva_regs_t *i2s, mxc_i2s_req_t *i2s_req)
{
    int err;

    if (i2s_req->rawData != NULL && i2s_req->txData == NULL) {
        return E_INVALID;
    } else if (i2s_req->length == 0) {
        return E_INVALID;
    } else if (MXC_GetLock(&txn_lock, 1) != E_NO_ERROR) {
        return E_BUSY;
    }

    i2s->ctrl1ch0 &= ~MXC_F_I2S_REVA_CTRL1CH0_EN; // Disable I2S while it's being configured

    txn_req = *i2s_req; // Initialize transaction request state variables
    txn_state.rxCnt = txn_req.length;
    txn_state.txCnt = txn_req.length;
    txn_state.async = false;

    MXC_I2S_Flush();

    if (txn_req.rawData != NULL &&
        txn_req.txData != NULL) { // Set up transmit if transmit parameters valid
        txn_state.txCnt = 0;

        err = MXC_I2S_ConfigData(&txn_req);
        if (err) {
            MXC_FreeLock(&txn_lock);
            return err;
        }

        err = MXC_I2S_FillTXFIFO(txn_req.txData, txn_req.wordSize, txn_req.length,
                                 txn_state.txCnt); // Initialize TX FIFO
        if (err < E_NO_ERROR) {
            MXC_FreeLock(&txn_lock);
            return err;
        }
        txn_state.txCnt = err;

        MXC_I2S_TXEnable(); // Enable I2S transmit (Do this before Fill FIFO?)
    }

    if (txn_req.rxData != NULL) { // Setup I2S receive if receive parameters valid
        txn_state.rxCnt = 0;

        MXC_SETFIELD(i2s->ctrl0ch0, MXC_F_I2S_REVA_CTRL0CH0_RX_THD_VAL,
                     (6 << MXC_F_I2S_REVA_CTRL0CH0_RX_THD_VAL_POS)); // Set RX threshold

        MXC_I2S_RXEnable(); // Enable I2S Receive
    }

    i2s->ctrl1ch0 |= MXC_F_I2S_REVA_CTRL1CH0_EN; // Enable I2S RX/TX

    while (MXC_GetLock(&txn_lock, 1) != E_NO_ERROR) {
        MXC_I2S_RevA_Handler(i2s);
    }

    MXC_FreeLock(&txn_lock);

    return E_NO_ERROR;
}

int MXC_I2S_RevA_TransactionAsync(mxc_i2s_reva_regs_t *i2s, mxc_i2s_req_t *i2s_req)
{
    int err;
    uint32_t int_en = 0;

    if (i2s_req->rawData != NULL && i2s_req->txData == NULL) {
        return E_INVALID;
    } else if (i2s_req->length == 0) {
        return E_INVALID;
    } else if (MXC_GetLock(&txn_lock, 1) != E_NO_ERROR) {
        return E_BUSY;
    }

    i2s->ctrl1ch0 &= ~MXC_F_I2S_REVA_CTRL1CH0_EN; // Disable I2S while it's being configured

    txn_req = *i2s_req; // Initialize transacion request state variables
    txn_state.rxCnt = txn_req.length;
    txn_state.txCnt = txn_req.length;
    txn_state.async = true;

    MXC_I2S_Flush();

    if (txn_req.rawData != NULL &&
        txn_req.txData != NULL) { // Set up transmit if transmit parameters valid
        txn_state.txCnt = 0;

        err = MXC_I2S_ConfigData(&txn_req);
        if (err) {
            MXC_FreeLock(&txn_lock);
            return err;
        }

        int_en |= MXC_F_I2S_REVA_INTFL_TX_OB_CH0; // Enable TX one entry remaining interrupt

        err = MXC_I2S_FillTXFIFO(txn_req.txData, txn_req.wordSize, txn_req.length,
                                 txn_state.txCnt); // Initialize TX FIFO
        if (err < E_NO_ERROR) {
            MXC_FreeLock(&txn_lock);
            return err;
        }
        txn_state.txCnt = err;

        MXC_I2S_TXEnable(); // Enable I2S transmit (Do this before Fill FIFO?)
    }

    if (txn_req.rxData != NULL) { // Setup I2S receive if receive parameters valid
        txn_state.rxCnt = 0;

        int_en |= MXC_F_I2S_REVA_INTEN_RX_THD_CH0; // Enable RX threshold interrupt
        MXC_SETFIELD(i2s->ctrl0ch0, MXC_F_I2S_REVA_CTRL0CH0_RX_THD_VAL,
                     (6 << MXC_F_I2S_REVA_CTRL0CH0_RX_THD_VAL_POS)); // Set RX threshold

        MXC_I2S_RXEnable(); // Enable I2S Receive
    }

    MXC_I2S_DisableInt(0xF); // Configure interrupts
    MXC_I2S_ClearFlags(0xF);
    MXC_I2S_EnableInt(int_en);

    i2s->ctrl1ch0 |= MXC_F_I2S_REVA_CTRL1CH0_EN; // Enable I2S RX/TX

    return E_NO_ERROR;
}

int MXC_I2S_RevA_TXDMAConfig(mxc_i2s_reva_regs_t *i2s, void *src_addr, int len)
{
    int channel;
    mxc_dma_config_t config;
    mxc_dma_adv_config_t advConfig;
    mxc_dma_srcdst_t srcdst;

    MXC_DMA_Init();

    i2s->dmach0 |= (2 << MXC_F_I2S_REVA_DMACH0_DMA_TX_THD_VAL_POS); //TX DMA Threshold

    channel = MXC_DMA_AcquireChannel();
    if (channel < E_NO_ERROR) {
        return channel;
    }

    config.reqsel = MXC_DMA_REQUEST_I2STX;

    config.ch = channel;

    switch (request->wordSize) {
    case MXC_I2S_DATASIZE_WORD:
        config.srcwd = MXC_DMA_WIDTH_WORD;
        config.dstwd = MXC_DMA_WIDTH_WORD;
        advConfig.burst_size = 4;
        break;

    case MXC_I2S_DATASIZE_HALFWORD:
        config.srcwd = MXC_DMA_WIDTH_HALFWORD;
        config.dstwd = MXC_DMA_WIDTH_WORD;
        advConfig.burst_size = 2;
        break;

    case MXC_I2S_DATASIZE_BYTE:
        config.srcwd = MXC_DMA_WIDTH_BYTE;
        config.dstwd = MXC_DMA_WIDTH_WORD;
        advConfig.burst_size = 1;
        break;

    default:
        config.srcwd = MXC_DMA_WIDTH_BYTE;
        config.dstwd = MXC_DMA_WIDTH_WORD;
        advConfig.burst_size = 1;
        break;
    }

#ifndef USE_LEGACY_I2S_DMA_CFG
    advConfig.burst_size = 4;
#endif

    config.srcinc_en = 1;
    config.dstinc_en = 0;

    advConfig.ch = channel;
    advConfig.prio = 0;
    advConfig.reqwait_en = 0;
    advConfig.tosel = 0;
    advConfig.pssel = 0;

    srcdst.ch = channel;
    srcdst.source = src_addr;
    srcdst.len = len;

    MXC_DMA_ConfigChannel(config, srcdst);
    MXC_DMA_AdvConfigChannel(advConfig);
    MXC_DMA_SetCallback(channel, dma_cb);

    MXC_I2S_TXEnable(); //Enable I2S TX
    i2s->dmach0 |= MXC_F_I2S_REVA_DMACH0_DMA_TX_EN; //Enable I2S DMA

    MXC_DMA_EnableInt(channel);
    MXC_DMA_Start(channel);
    MXC_DMA->ch[channel].ctrl |= MXC_F_DMA_CTRL_CTZ_IE;

    return channel;
}

int MXC_I2S_RevA_RXDMAConfig(mxc_i2s_reva_regs_t *i2s, void *dest_addr, int len)
{
    int channel;
    mxc_dma_config_t config;
    mxc_dma_adv_config_t advConfig;
    mxc_dma_srcdst_t srcdst;

    MXC_DMA_Init();

    i2s->dmach0 |= (6 << MXC_F_I2S_REVA_DMACH0_DMA_RX_THD_VAL_POS); //RX DMA Threshold

    channel = MXC_DMA_AcquireChannel();
    if (channel < E_NO_ERROR) {
        return channel;
    }

    config.reqsel = MXC_DMA_REQUEST_I2SRX;

    config.ch = channel;

    switch (request->wordSize) {
    case MXC_I2S_DATASIZE_WORD:
        config.srcwd = MXC_DMA_WIDTH_WORD;
        config.dstwd = MXC_DMA_WIDTH_WORD;
        advConfig.burst_size = 4;
        break;

    case MXC_I2S_DATASIZE_HALFWORD:
        config.srcwd = MXC_DMA_WIDTH_WORD;
        config.dstwd = MXC_DMA_WIDTH_HALFWORD;
        advConfig.burst_size = 2;
        break;

    case MXC_I2S_DATASIZE_BYTE:
        config.srcwd = MXC_DMA_WIDTH_WORD;
        config.dstwd = MXC_DMA_WIDTH_BYTE;
        advConfig.burst_size = 1;
        break;

    default:
        config.srcwd = MXC_DMA_WIDTH_WORD;
        config.dstwd = MXC_DMA_WIDTH_BYTE;
        advConfig.burst_size = 1;
        break;
    }

#ifndef USE_LEGACY_I2S_DMA_CFG
    advConfig.burst_size = 4;
#endif

    config.srcinc_en = 0;
    config.dstinc_en = 1;

    advConfig.ch = channel;
    advConfig.prio = 0;
    advConfig.reqwait_en = 0;
    advConfig.tosel = 0;
    advConfig.pssel = 0;

    srcdst.ch = channel;
    srcdst.dest = dest_addr;
    srcdst.len = len;

    MXC_DMA_ConfigChannel(config, srcdst);
    MXC_DMA_AdvConfigChannel(advConfig);
    MXC_DMA_SetCallback(channel, dma_cb);

    MXC_I2S_RXEnable(); //Enable I2S RX
    i2s->dmach0 |= MXC_F_I2S_REVA_DMACH0_DMA_RX_EN; //Enable I2S DMA

    MXC_DMA_EnableInt(channel);
    MXC_DMA_Start(channel);
    MXC_DMA->ch[channel].ctrl |= MXC_F_DMA_CTRL_CTZ_IE;

    return channel;
}

void MXC_I2S_RevA_Handler(mxc_i2s_reva_regs_t *i2s)
{
    uint32_t flags = MXC_I2S_GetFlags();

    if (txn_state.txCnt == txn_req.length && txn_state.rxCnt == txn_req.length) {
        MXC_I2S_DisableInt(MXC_F_I2S_REVA_INTEN_TX_OB_CH0 | MXC_F_I2S_REVA_INTEN_RX_THD_CH0);
        MXC_I2S_ClearFlags(MXC_F_I2S_REVA_INTFL_TX_OB_CH0 | MXC_F_I2S_REVA_INTFL_RX_THD_CH0);

        while (i2s->dmach0 & MXC_F_I2S_REVA_DMACH0_TX_LVL) {}

        MXC_I2S_TXDisable();
        MXC_I2S_RXDisable();

        if (async_cb != NULL && txn_state.async) {
            async_cb(E_NO_ERROR);
        }

        MXC_FreeLock(&txn_lock);
    } else if (txn_req.txData != NULL && (flags & MXC_F_I2S_REVA_INTFL_TX_OB_CH0)) {
        MXC_I2S_ClearFlags(MXC_F_I2S_REVA_INTFL_TX_OB_CH0);

        if (txn_state.txCnt < txn_req.length) {
            txn_state.txCnt += MXC_I2S_FillTXFIFO(txn_req.txData, txn_req.wordSize,
                                                  (txn_req.length - txn_state.txCnt),
                                                  txn_state.txCnt);
        }
    } else if (txn_req.rxData != NULL && (flags & MXC_F_I2S_REVA_INTFL_RX_THD_CH0)) {
        MXC_I2S_ClearFlags(MXC_F_I2S_REVA_INTFL_RX_THD_CH0);

        if (txn_state.rxCnt < txn_req.length) {
            txn_state.rxCnt += MXC_I2S_ReadRXFIFO(txn_req.rxData, txn_req.wordSize,
                                                  (txn_req.length - txn_state.rxCnt),
                                                  txn_state.rxCnt);
        }
    }
}

void MXC_I2S_RevA_RegisterDMACallback(void (*callback)(int, int))
{
    dma_cb = callback;
}

void MXC_I2S_RevA_RegisterAsyncCallback(void (*callback)(int))
{
    async_cb = callback;
}