STMicroelectronics' implementation of an I2S driver, also including DMA support.

Dependents:   temp X_NUCLEO_CCA01M1 X_NUCLEO_CCA01M1 X_NUCLEO_CCA02M1

Platform compatibility

This driver has been designed to support a wide range of the Nucleo F4 Family of platforms and MCUs, but not all members of this family support I2S and/or some of the members might require slight modifications to the sources of this driver in order to make it work on those.

This driver has for now been tested only with the following platforms:

targets/TARGET_STM/stm_i2s_api.c

Committer:
Wolfgang Betz
Date:
2016-12-22
Revision:
8:561d7ee70ef6
Parent:
4:21603d68bcf7
Parent:
7:e9105ae127ad
Child:
9:c4c2240e06d6

File content as of revision 8:561d7ee70ef6:

#include "mbed_assert.h"
#include "mbed_error.h"
#include "stm_i2s_api.h"

#include "stm_dma_caps.h"

#if DEVICE_I2S

#include <math.h>
#include <string.h>
#include "cmsis.h"
#include "pinmap.h"
#include "PeripheralPins.h"
#include "StmI2sPeripheralPins.h"

// #define DEBUG_STDIO 1 // betzw - TODO: temporarily enable debug printfs

#ifndef DEBUG_STDIO
#   define DEBUG_STDIO 0
#endif

#if DEBUG_STDIO
#   include <stdio.h>
#   define DEBUG_PRINTF(...) do { printf(__VA_ARGS__); } while(0)
#else
#   define DEBUG_PRINTF(...) {}
#endif

typedef enum {
    I2S_TRANSFER_TYPE_TX = 1,
    I2S_TRANSFER_TYPE_RX = 2,
    I2S_TRANSFER_TYPE_TXRX = 3,
} transfer_type_t;

typedef struct {
    DMA_HandleTypeDef tx_dma_handle;
    DMA_HandleTypeDef rx_dma_handle;
} dma_handles_t;

#define I2S_NUM (5) // betzw: this approach wastes quite a bit of memory - TO BE IMPROVED!?!?

static I2S_HandleTypeDef I2sHandle[I2S_NUM];
static DMA_HandleTypeDef DMaHandles[I2S_NUM][NUM_OF_DIRECTIONS];

static void init_i2s(i2s_t *obj) {
    I2S_HandleTypeDef *handle = &I2sHandle[obj->i2s.module];

    __HAL_I2S_DISABLE(handle);
    HAL_I2S_Init(handle);
    __HAL_I2S_ENABLE(handle);
}

static void init_dmas(i2s_t *obj) {
    DMA_HandleTypeDef *primary_handle = NULL;
    DMA_HandleTypeDef *secondary_handle = NULL;
    DMA_HandleTypeDef *hdmatx = NULL;

    switch(obj->dma.dma_direction) {
    case DMA_TX:
	if(obj->dma.dma[DMA_TX] != NULL) {
	    hdmatx = primary_handle = &DMaHandles[obj->i2s.module][DMA_TX];
	}
	if(obj->dma.dma[DMA_RX] != NULL) {
	    secondary_handle = &DMaHandles[obj->i2s.module][DMA_RX];
	}
	break;
    case DMA_RX:
    default:
	if(obj->dma.dma[DMA_RX] != NULL) {
	    primary_handle = &DMaHandles[obj->i2s.module][DMA_RX];
	}
	if(obj->dma.dma[DMA_TX] != NULL) {
	    hdmatx = secondary_handle = &DMaHandles[obj->i2s.module][DMA_TX];
	}
	break;
    }

    if(primary_handle != NULL) {
	__HAL_DMA_DISABLE(primary_handle);
	HAL_DMA_Init(primary_handle);

	if(hdmatx == primary_handle) {
	    __HAL_LINKDMA(&I2sHandle[obj->i2s.module], hdmatx, *primary_handle);
	} else {
	    __HAL_LINKDMA(&I2sHandle[obj->i2s.module], hdmarx, *primary_handle);
	}
    }

    if(secondary_handle != NULL) {
	__HAL_DMA_DISABLE(secondary_handle);
	HAL_DMA_Init(secondary_handle);

	if(hdmatx == secondary_handle) {
	    __HAL_LINKDMA(&I2sHandle[obj->i2s.module], hdmatx, *secondary_handle);
	} else {
	    __HAL_LINKDMA(&I2sHandle[obj->i2s.module], hdmarx, *secondary_handle);
	}
    }
}

static inline uint32_t i2s_get_mode(i2s_mode_t mode, uint8_t *direction) {
    switch(mode) {
    case SLAVE_TX:
	*direction = DMA_TX;
	return I2S_MODE_SLAVE_TX;
    case SLAVE_RX:
	*direction = DMA_RX;
	return I2S_MODE_SLAVE_RX;
    case MASTER_TX:
	*direction = DMA_TX;
	return I2S_MODE_MASTER_TX;
    case MASTER_RX:
    default:
	*direction = DMA_RX;
	return I2S_MODE_MASTER_RX;
    }
}

static inline uint32_t i2s_get_priority(i2s_dma_prio_t priority) {
    switch(priority) {
    case LOW:
	return DMA_PRIORITY_LOW;
    case URGENT:
	return DMA_PRIORITY_VERY_HIGH;
    case HIGH:
	return DMA_PRIORITY_HIGH;
    default:
	return DMA_PRIORITY_MEDIUM;
    }
}

static void dma_i2s_init(i2s_t *obj, bool *use_tx, bool *use_rx, bool circular, i2s_dma_prio_t prio) {
    // DMA declarations
    DMA_HandleTypeDef *primary_handle = &DMaHandles[obj->i2s.module][obj->dma.dma_direction];
    DMA_HandleTypeDef *secondary_handle = NULL;

    // DMA initialization & configuration
    stm_dma_init();
    obj->dma.dma[DMA_TX] = obj->dma.dma[DMA_RX] = NULL;

    switch(obj->dma.dma_direction) {
    case DMA_TX:
	if(*use_tx) {
	    obj->dma.dma[DMA_TX] = stm_dma_channel_allocate(MAKE_CAP(obj->dma.dma_device, DMA_TX));
	    MBED_ASSERT(obj->dma.dma[DMA_TX] != STM_DMA_ERROR_OUT_OF_CHANNELS);
	}
	break;
    case DMA_RX:
    default:
	if(*use_rx) {
	    obj->dma.dma[DMA_RX] = stm_dma_channel_allocate(MAKE_CAP(obj->dma.dma_device, DMA_RX));
	    MBED_ASSERT(obj->dma.dma[DMA_RX] != STM_DMA_ERROR_OUT_OF_CHANNELS);
	}
	break;
    }

    // Primary DMA configuration
    if(obj->dma.dma[obj->dma.dma_direction] != NULL) {
	primary_handle->Instance = obj->dma.dma[obj->dma.dma_direction]->dma_stream;
	primary_handle->Init.Channel = obj->dma.dma[obj->dma.dma_direction]->channel_nr;
	primary_handle->Init.Direction = (obj->dma.dma_direction == DMA_TX) ? 
	    DMA_MEMORY_TO_PERIPH : DMA_PERIPH_TO_MEMORY;
	primary_handle->Init.FIFOMode = DMA_FIFOMODE_DISABLE;
	primary_handle->Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
	primary_handle->Init.MemBurst = DMA_MBURST_SINGLE;
	primary_handle->Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
	primary_handle->Init.MemInc = DMA_MINC_ENABLE;
	primary_handle->Init.Mode = (circular ? DMA_CIRCULAR : DMA_NORMAL);
	primary_handle->Init.PeriphBurst = DMA_PBURST_SINGLE;
	primary_handle->Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
	primary_handle->Init.PeriphInc = DMA_PINC_DISABLE;
	primary_handle->Init.Priority = i2s_get_priority(prio);
    }

    // Allocate secondary DMA channel (if full-duplex)
    if(obj->i2s.pin_fdpx != NC) {
	switch(obj->dma.dma_direction) {
	case DMA_TX:
	    if(*use_rx) {
		obj->dma.dma[DMA_RX] = stm_dma_channel_allocate(MAKE_CAP(obj->dma.dma_device, DMA_RX));
		secondary_handle = &DMaHandles[obj->i2s.module][DMA_RX];
		MBED_ASSERT(obj->dma.dma[DMA_RX] != STM_DMA_ERROR_OUT_OF_CHANNELS);
	    }
	    break;
	case DMA_RX:
	default:
	    if(*use_tx) {
		obj->dma.dma[DMA_TX] = stm_dma_channel_allocate(MAKE_CAP(obj->dma.dma_device, DMA_TX));
		secondary_handle = &DMaHandles[obj->i2s.module][DMA_TX];
		MBED_ASSERT(obj->dma.dma[DMA_TX] != STM_DMA_ERROR_OUT_OF_CHANNELS);
	    }
	    break;
	}
    }

    // Secondary DMA configuration
    if(secondary_handle != NULL) {
	uint8_t secondary_dma_direction = (obj->dma.dma_direction == DMA_TX) ? DMA_RX : DMA_TX;

	secondary_handle->Instance = obj->dma.dma[secondary_dma_direction]->dma_stream;
	secondary_handle->Init.Channel = obj->dma.dma[secondary_dma_direction]->channel_nr_fd;
	secondary_handle->Init.Direction = (secondary_dma_direction == DMA_TX) ? 
	    DMA_MEMORY_TO_PERIPH : DMA_PERIPH_TO_MEMORY;
	secondary_handle->Init.FIFOMode = DMA_FIFOMODE_DISABLE;
	secondary_handle->Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
	secondary_handle->Init.MemBurst = DMA_MBURST_SINGLE;
	secondary_handle->Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
	secondary_handle->Init.MemInc = DMA_MINC_ENABLE;
	secondary_handle->Init.Mode = (circular ? DMA_CIRCULAR : DMA_NORMAL);
	secondary_handle->Init.PeriphBurst = DMA_PBURST_SINGLE;
	secondary_handle->Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
	secondary_handle->Init.PeriphInc = DMA_PINC_DISABLE;
	secondary_handle->Init.Priority = i2s_get_priority(prio);
    }

    if(obj->dma.dma[DMA_TX] == NULL) *use_tx = false;
    if(obj->dma.dma[DMA_RX] == NULL) *use_rx = false;

    // don't do anything, if the buffers aren't valid
    if (!use_tx && !use_rx) {
	DEBUG_PRINTF("I2S%u: No DMAs to init\n", obj->i2s.module+1);
	return;
    }

    DEBUG_PRINTF("I2S%u: DMA(s) Init\n", obj->i2s.module+1);
    init_dmas(obj);
}

static void dma_i2s_free(i2s_t *obj, uint8_t direction) {
    const struct dma_stream_s *stream = obj->dma.dma[direction];

    MBED_ASSERT(stream != NULL);

    // disable irq
    NVIC_DisableIRQ(stream->dma_stream_irq);

    // free channel
    stm_dma_channel_free((void*)stream);
    obj->dma.dma[direction] = NULL;
}

void i2s_init(i2s_t *obj, PinName data, PinName sclk, PinName wsel, PinName fdpx, PinName mclk, i2s_mode_t mode) {
    uint8_t dma_dev = 0, dma_direction = 0;

    // Determine the I2S/SPI to use
    SPIName i2s_data = (SPIName)pinmap_peripheral(data, PinMap_I2S_DATA);
    SPIName i2s_sclk = (SPIName)pinmap_peripheral(sclk, PinMap_I2S_SCLK);

    SPIName i2s_wsel = (SPIName)pinmap_peripheral(wsel, PinMap_I2S_WSEL);
    SPIName i2s_fdpx = (SPIName)pinmap_peripheral(fdpx, PinMap_I2S_FDPX);

    SPIName i2s_mclk = (SPIName)pinmap_peripheral(mclk, PinMap_I2S_MCLK);

    SPIName i2s_merge1 = (SPIName)pinmap_merge(i2s_data, i2s_sclk);
    SPIName i2s_merge2 = (SPIName)pinmap_merge(i2s_wsel, i2s_fdpx);

    SPIName i2s_merge3 = (SPIName)pinmap_merge(i2s_merge1, i2s_merge2);
    SPIName instance   = (SPIName)pinmap_merge(i2s_merge3, i2s_mclk);
    MBED_ASSERT(instance != (SPIName)NC);

    // Enable I2S/SPI clock and set the right module number
    switch(instance) {
#if defined(I2S1ext_BASE)
    case SPI_1:
	__SPI1_CLK_ENABLE();
	obj->i2s.module = 0;
	dma_dev = DMA_SPI1;
	break;
#endif
#if defined(I2S2ext_BASE)
    case SPI_2:
	__SPI2_CLK_ENABLE();
	obj->i2s.module = 1;
	dma_dev = DMA_SPI2;
	break;
#endif
#if defined(I2S3ext_BASE)
    case SPI_3:
	__SPI3_CLK_ENABLE();
	obj->i2s.module = 2;
	dma_dev = DMA_SPI3;
	break;
#endif
#if defined(I2S4ext_BASE)
    case SPI_4:
	__SPI4_CLK_ENABLE();
	obj->i2s.module = 3;
	dma_dev = DMA_SPI4;
	break;
#endif
#if defined(I2S5ext_BASE)
    case SPI_5:
	__SPI5_CLK_ENABLE();
	obj->i2s.module = 4;
	dma_dev = DMA_SPI5;
	break;
#endif
    default:
	MBED_ASSERT(0);
	break;
    }

    // Save DMA device
    obj->dma.dma_device = dma_dev;

    // Configure the I2S pins
    pinmap_pinout(data, PinMap_I2S_DATA);
    pinmap_pinout(wsel, PinMap_I2S_WSEL);
    pinmap_pinout(sclk, PinMap_I2S_SCLK);
    pinmap_pinout(fdpx, PinMap_I2S_FDPX);
    pinmap_pinout(mclk, PinMap_I2S_MCLK);

    obj->i2s.pin_wsel = wsel;
    obj->i2s.pin_data = data;
    obj->i2s.pin_sclk = sclk;
    obj->i2s.pin_fdpx = fdpx;
    obj->i2s.pin_mclk = mclk;

    /* Configure PLLI2S */
    static bool first_time = true;
    if(first_time) {
	RCC_PeriphCLKInitTypeDef PeriphClkInitStruct;

	/* Get RTCClockSelection */
	HAL_RCCEx_GetPeriphCLKConfig(&PeriphClkInitStruct);

	/* Set default configuration. Default frequency is 44100Hz. */
	PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;
        PeriphClkInitStruct.PLLI2S.PLLI2SN = 271; // betzw: use values which are suggested in Table 91 of the
        PeriphClkInitStruct.PLLI2S.PLLI2SR = 2;   //        reference manual for master clock enabled & 44100Hz.

#ifdef NDEBUG
	HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);
#else
	HAL_StatusTypeDef ret = HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);
#endif
	MBED_ASSERT(ret == HAL_OK);
	first_time = false;
    }

    // initialize the handle for this master!
    I2S_HandleTypeDef *handle = &I2sHandle[obj->i2s.module];

    handle->Instance               = (SPI_TypeDef *)(instance);
    handle->Init.Mode              = i2s_get_mode(mode, &dma_direction);
    handle->Init.Standard          = I2S_STANDARD_PCM_SHORT;
    handle->Init.DataFormat        = I2S_DATAFORMAT_16B;
    handle->Init.MCLKOutput        = (mclk == NC ? I2S_MCLKOUTPUT_DISABLE : I2S_MCLKOUTPUT_ENABLE);
    handle->Init.AudioFreq         = I2S_AUDIOFREQ_44K;
    handle->Init.CPOL              = I2S_CPOL_LOW;
    handle->Init.ClockSource       = I2S_CLOCK_PLL;
    handle->Init.FullDuplexMode    = (fdpx == NC) ? I2S_FULLDUPLEXMODE_DISABLE : I2S_FULLDUPLEXMODE_ENABLE;

    // Save primary DMA direction
    obj->dma.dma_direction = dma_direction;

    DEBUG_PRINTF("I2S%u: Init\n", obj->i2s.module+1);

    init_i2s(obj);
}

void i2s_free(i2s_t *obj) {
    // Reset I2S and disable clock
    switch(obj->i2s.module) {
#if defined(I2S1ext_BASE)
    case 0:
	__SPI1_FORCE_RESET();
	__SPI1_RELEASE_RESET();
	__SPI1_CLK_DISABLE();
	break;
#endif
#if defined(I2S2ext_BASE)
    case 1:
	__SPI2_FORCE_RESET();
	__SPI2_RELEASE_RESET();
	__SPI2_CLK_DISABLE();
	break;
#endif
#if defined(I2S3ext_BASE)
    case 2:
	__SPI3_FORCE_RESET();
	__SPI3_RELEASE_RESET();
	__SPI3_CLK_DISABLE();
	break;
#endif
#if defined(I2S4ext_BASE)
    case 3:
	__SPI4_FORCE_RESET();
	__SPI4_RELEASE_RESET();
	__SPI4_CLK_DISABLE();
	break;
#endif
#if defined(I2S5ext_BASE)
    case 4:
	__SPI5_FORCE_RESET();
	__SPI5_RELEASE_RESET();
	__SPI5_CLK_DISABLE();
	break;
#endif
    default:
	MBED_ASSERT(0);
	break;
    }

    // betzw - TODO: what about 'PLLI2S'?!?
    //               for the moment we leave it enabled!

    // Configure GPIOs
    pin_function(obj->i2s.pin_wsel, STM_PIN_DATA(STM_MODE_INPUT, GPIO_NOPULL, 0));
    pin_function(obj->i2s.pin_data, STM_PIN_DATA(STM_MODE_INPUT, GPIO_NOPULL, 0));
    pin_function(obj->i2s.pin_sclk, STM_PIN_DATA(STM_MODE_INPUT, GPIO_NOPULL, 0));
    pin_function(obj->i2s.pin_fdpx, STM_PIN_DATA(STM_MODE_INPUT, GPIO_NOPULL, 0));
    pin_function(obj->i2s.pin_mclk, STM_PIN_DATA(STM_MODE_INPUT, GPIO_NOPULL, 0));

    DEBUG_PRINTF("I2S%u: Free\n", obj->i2s.module+1);
}

void i2s_format(i2s_t *obj, int dbits, int fbits, int polarity) {
    I2S_HandleTypeDef *handle = &I2sHandle[obj->i2s.module];

    // Save new values
    if (fbits == 16) { // format MUST be 16B
	handle->Init.DataFormat = I2S_DATAFORMAT_16B;
    } else { // format may NOT be 16B
	switch (dbits) {
	case 16:
	    handle->Init.DataFormat = I2S_DATAFORMAT_16B_EXTENDED;
	    break;
	case 24:
	    handle->Init.DataFormat = I2S_DATAFORMAT_24B;
	    break;
	case 32:
	default:
	    handle->Init.DataFormat = I2S_DATAFORMAT_32B;
	    break;
	}
    }

    handle->Init.CPOL = (polarity == 0) ? I2S_CPOL_LOW : I2S_CPOL_HIGH;

    DEBUG_PRINTF("I2S%u: Format: %u (%u, %u), %u (%u)\n", obj->i2s.module+1,
		 (unsigned int)handle->Init.DataFormat, (unsigned int)dbits, (unsigned int)fbits,
		 (unsigned int)handle->Init.CPOL, (unsigned int)polarity);

    init_i2s(obj);
}

void i2s_set_mode(i2s_t *obj, i2s_mode_t mode) {
    uint8_t dma_direction;
    I2S_HandleTypeDef *handle = &I2sHandle[obj->i2s.module];

    handle->Init.Mode = i2s_get_mode(mode, &dma_direction);

    // Save primary DMA direction
    obj->dma.dma_direction = dma_direction;

    DEBUG_PRINTF("I2S%u: Mode: %u (%u)\n", obj->i2s.module+1,
		 (unsigned int)handle->Init.Mode, (unsigned int)mode);

    init_i2s(obj);
}

void i2s_set_protocol(i2s_t *obj, i2s_bitorder_t protocol) {
    I2S_HandleTypeDef *handle = &I2sHandle[obj->i2s.module];

    switch (protocol) {
    case PHILIPS:
	handle->Init.Standard = I2S_STANDARD_PHILIPS;
	break;
    case MSB:
	handle->Init.Standard = I2S_STANDARD_MSB;
	break;
    case LSB:
	handle->Init.Standard = I2S_STANDARD_LSB;
	break;
    case PCM_SHORT:
	handle->Init.Standard = I2S_STANDARD_PCM_SHORT;
	break;
    case PCM_LONG:
    default:
	handle->Init.Standard = I2S_STANDARD_PCM_LONG;
	break;
    }

    DEBUG_PRINTF("I2S%u: Protocol: %u (%u)\n", obj->i2s.module+1,
		 (unsigned int)handle->Init.Standard, (unsigned int)protocol);

    init_i2s(obj);
}

void i2s_audio_frequency(i2s_t *obj, uint32_t hz) {
    I2S_HandleTypeDef *handle = &I2sHandle[obj->i2s.module];

    if (IS_I2S_AUDIO_FREQ(hz) && (hz != I2S_AUDIOFREQ_DEFAULT)) {
	handle->Init.AudioFreq = hz;
    } else if (hz < I2S_AUDIOFREQ_8K) {
	handle->Init.AudioFreq = I2S_AUDIOFREQ_8K;
    } else {
	handle->Init.AudioFreq = I2S_AUDIOFREQ_192K;
    }

    DEBUG_PRINTF("I2S%u: Audio frequency: %u (%u)\n", obj->i2s.module+1,
		 (unsigned int)handle->Init.AudioFreq, (unsigned int)hz);

    init_i2s(obj);
}

uint8_t i2s_get_module(i2s_t *obj) {
    return obj->i2s.module;
}

static void i2s_start_asynch_transfer(i2s_t *obj, transfer_type_t transfer_type,
				      void *tx, void *rx, int length)
{
    I2S_HandleTypeDef *handle = &I2sHandle[obj->i2s.module];
    obj->i2s.transfer_type = transfer_type;

    // the HAL expects number of transfers instead of number of bytes
    int words;
    switch(handle->Init.DataFormat) {
    case I2S_DATAFORMAT_16B:
    case I2S_DATAFORMAT_16B_EXTENDED:
	words = length / 2;
	if(words > 0xFFFC) words = 0xFFFC; // truncate in order to respect max DMA length
	break;
    case I2S_DATAFORMAT_24B:
    case I2S_DATAFORMAT_32B:
    default:
	words = length / 4;
	if(words > 0x7FFC) words = 0x7FFC; // truncate in order to respect max DMA length
	break;
    }

    // enable the right hal transfer
    int rc = 0;
    switch(transfer_type) {
    case I2S_TRANSFER_TYPE_TXRX:
	// enable the interrupts
	NVIC_EnableIRQ(obj->dma.dma[DMA_TX]->dma_stream_irq);
	NVIC_EnableIRQ(obj->dma.dma[DMA_RX]->dma_stream_irq);
	// trigger DMA transfers
	rc = HAL_I2SEx_TransmitReceive_DMA(handle, (uint16_t*)tx, (uint16_t*)rx, (uint16_t)words);
	break;
    case I2S_TRANSFER_TYPE_TX:
	// enable the interrupt
	NVIC_EnableIRQ(obj->dma.dma[DMA_TX]->dma_stream_irq);
	// trigger DMA transfer
	rc = HAL_I2S_Transmit_DMA(handle, (uint16_t*)tx, (uint16_t)words);
	break;
    case I2S_TRANSFER_TYPE_RX:
	// enable the interrupt
	NVIC_EnableIRQ(obj->dma.dma[DMA_RX]->dma_stream_irq);
	// trigger DMA transfer
	rc = HAL_I2S_Receive_DMA(handle, (uint16_t*)rx, (uint16_t)words);
	break;
    }

    if (rc) {
	DEBUG_PRINTF("I2S%u: RC=%d\n", obj->i2s.module+1, rc);
    }

    return;
}

// asynchronous API
void i2s_transfer(i2s_t *obj,
		  void *tx, int tx_length,
		  void *rx, int rx_length,
		  bool circular, i2s_dma_prio_t prio,
		  uint32_t handler_tx, uint32_t handler_rx, uint32_t event)
{
    // check which use-case we have
    bool use_tx = (tx != NULL && tx_length > 0);
    bool use_rx = (rx != NULL && rx_length > 0);

    // Init DMAs
    dma_i2s_init(obj, &use_tx, &use_rx, circular, prio);

    // don't do anything, if the buffers aren't valid
    if (!use_tx && !use_rx)
	return;

    // copy the buffers to the I2S object
    obj->tx_buff.buffer = tx;
    obj->tx_buff.length = tx_length;
    obj->tx_buff.pos = 0;
    obj->tx_buff.width = 16;

    obj->rx_buff.buffer = rx;
    obj->rx_buff.length = rx_length;
    obj->rx_buff.pos = 0;
    obj->rx_buff.width = obj->tx_buff.width;

    obj->i2s.event = event;

    DEBUG_PRINTF("I2S%u: Transfer: %u, %u\n", obj->i2s.module+1, tx_length, rx_length);

    // register the thunking handler
    if(use_tx) {
	NVIC_SetVector(obj->dma.dma[DMA_TX]->dma_stream_irq, handler_tx);
    }
    if(use_rx) {
	NVIC_SetVector(obj->dma.dma[DMA_RX]->dma_stream_irq, handler_rx);
    }

    // enable the right hal transfer
    if (use_tx && use_rx) {
	int size = (tx_length < rx_length)? tx_length : rx_length;
	i2s_start_asynch_transfer(obj, I2S_TRANSFER_TYPE_TXRX, tx, rx, size);
    } else if (use_tx) {
	i2s_start_asynch_transfer(obj, I2S_TRANSFER_TYPE_TX, tx, NULL, tx_length);
    } else if (use_rx) {
	i2s_start_asynch_transfer(obj, I2S_TRANSFER_TYPE_RX, NULL, rx, rx_length);
    }
}

uint32_t i2s_irq_handler_asynch(i2s_t *obj, uint8_t direction) {
    direction = (direction == I2S_TX_EVENT) ? DMA_TX : DMA_RX;

    // use the right instance
    I2S_HandleTypeDef *i2s_handle = &I2sHandle[obj->i2s.module];
    DMA_HandleTypeDef *dma_handle = (direction == DMA_TX) ? i2s_handle->hdmatx : i2s_handle->hdmarx;

    MBED_ASSERT(dma_handle != NULL);

    int event = 0;

    // call the Cube handler, this will update the handle
    HAL_DMA_IRQHandler(dma_handle);

    switch(HAL_I2S_GetState(i2s_handle)) {
    case HAL_I2S_STATE_READY: {
	// adjust buffer positions (betzw - TODO: to be checked for DMA transfers!!!)
	int tx_size = (i2s_handle->TxXferSize - i2s_handle->TxXferCount);
	int rx_size = (i2s_handle->RxXferSize - i2s_handle->RxXferCount);

	// take data format into consideration
	switch(i2s_handle->Init.DataFormat) {
	case I2S_DATAFORMAT_16B:
	case I2S_DATAFORMAT_16B_EXTENDED:
	    tx_size *= 2;
	    rx_size *= 2;
	    break;
	case I2S_DATAFORMAT_24B:
	case I2S_DATAFORMAT_32B:
	default:
	    tx_size *= 4;
	    rx_size *= 4;
	    break;
	}

	// adjust buffer positions
	if (obj->i2s.transfer_type != I2S_TRANSFER_TYPE_RX) {
	    obj->tx_buff.pos += tx_size;
	}
	if (obj->i2s.transfer_type != I2S_TRANSFER_TYPE_TX) {
	    obj->rx_buff.pos += rx_size;
	}

	if (i2s_handle->TxXferCount > 0) {
	    DEBUG_PRINTF("I2S%u: TxXferCount: %u\n", obj->i2s.module+1, i2s_handle->TxXferCount);
	}
	if (i2s_handle->RxXferCount > 0) {
	    DEBUG_PRINTF("I2S%u: RxXferCount: %u\n", obj->i2s.module+1, i2s_handle->RxXferCount);
	}
    }
	/* no break */

    case HAL_I2S_STATE_BUSY_TX:
    case HAL_I2S_STATE_BUSY_RX:
    case HAL_I2S_STATE_BUSY_TX_RX: {
	int error = HAL_I2S_GetError(i2s_handle);

	if(error != HAL_I2S_ERROR_NONE) {
	    // something went wrong and the transfer has definitely completed
	    event = ((direction == DMA_TX) ? I2S_EVENT_TX_ERROR : I2S_EVENT_RX_ERROR) | I2S_EVENT_INTERNAL_TRANSFER_COMPLETE;

	    if (error & HAL_I2S_ERROR_OVR) {
		// buffer overrun
		event |= I2S_EVENT_RX_OVERFLOW;
	    }

	    if (error & HAL_I2S_ERROR_UDR) {
		// buffer underrun
		event |= I2S_EVENT_TX_UNDERRUN;
	    }

	    // cleanup DMA (after error)
	    dma_i2s_free(obj, direction);
	} else { // no error detected
	    HAL_DMA_StateTypeDef dma_state = HAL_DMA_GetState(dma_handle);

	    switch(dma_state) {
	    case HAL_DMA_STATE_READY_HALF_MEM0:
	    case HAL_DMA_STATE_READY_HALF_MEM1:
		event = ((direction == DMA_TX) ? I2S_EVENT_TX_HALF_COMPLETE : I2S_EVENT_RX_HALF_COMPLETE);
		break;
	    case HAL_DMA_STATE_READY_MEM0:
	    case HAL_DMA_STATE_READY_MEM1:
		event = ((direction == DMA_TX) ? I2S_EVENT_TX_COMPLETE : I2S_EVENT_RX_COMPLETE);

		if(dma_handle->Init.Mode != DMA_CIRCULAR) {
		    if (!i2s_active(obj)) { // Check for full-duplex transfer complete!
			event |= I2S_EVENT_INTERNAL_TRANSFER_COMPLETE;
		    }

		    // cleanup DMA (because we are done)
		    dma_i2s_free(obj, direction);
		}
		break;
	    default:
		printf("betzw(%s, %d): dma_state=0x%x\r\n", __func__, __LINE__, (int)dma_state);
		MBED_ASSERT(0);
		break;
	    }
	}
    }
	break;

    default:
	// nothing to do?!?
	break;
    }

    if (event) DEBUG_PRINTF("I2S%u: Event: 0x%x\n", obj->i2s.module+1, event);

    return (event & (obj->i2s.event | I2S_EVENT_INTERNAL_TRANSFER_COMPLETE));
}

uint8_t i2s_active(i2s_t *obj) {
    I2S_HandleTypeDef *handle = &I2sHandle[obj->i2s.module];
    HAL_I2S_StateTypeDef state = HAL_I2S_GetState(handle);

    switch(state) {
    case HAL_I2S_STATE_RESET:
    case HAL_I2S_STATE_READY:
    case HAL_I2S_STATE_ERROR:
	return 0;
    default:
	return -1;
    }
}

void i2s_abort_asynch(i2s_t *obj) {
    I2S_HandleTypeDef *i2s_handle = &I2sHandle[obj->i2s.module];

    // Stop transfer
    HAL_I2S_DMAStop(i2s_handle);

    if(obj->dma.dma[DMA_TX] != NULL) {
	DMA_HandleTypeDef *dma_handle_tx = &DMaHandles[obj->i2s.module][DMA_TX];

	// disable interrupt & free resource
	dma_i2s_free(obj, DMA_TX);
 
	//clean up
	__HAL_DMA_DISABLE(dma_handle_tx);
	HAL_DMA_DeInit(dma_handle_tx);
	HAL_DMA_Init(dma_handle_tx);
	__HAL_DMA_ENABLE(dma_handle_tx);
    }
    if(obj->dma.dma[DMA_RX] != NULL) {
	DMA_HandleTypeDef *dma_handle_rx = &DMaHandles[obj->i2s.module][DMA_RX];

	// disable interrupt & free resource
	dma_i2s_free(obj, DMA_RX);

	//clean up
	__HAL_DMA_DISABLE(dma_handle_rx);
	HAL_DMA_DeInit(dma_handle_rx);
	HAL_DMA_Init(dma_handle_rx);
	__HAL_DMA_ENABLE(dma_handle_rx);
    }

    // clean-up I2S
    __HAL_I2S_DISABLE(i2s_handle);
    HAL_I2S_DeInit(i2s_handle);
    HAL_I2S_Init(i2s_handle);
    __HAL_I2S_ENABLE(i2s_handle);
}

I2S_HandleTypeDef *i2s_get_handle(i2s_t *obj)
{
	return (I2S_HandleTypeDef *) &I2sHandle[obj->i2s.module];
}

#endif