atmel-asf-projects/sam/drivers/i2sc/i2sc.c

494 lines
12 KiB
C

/**
*
* \file
*
* \brief I2S driver for SAM.
*
* This file defines a useful set of functions for the I2S on SAM devices.
*
* Copyright (c) 2014-2017 Atmel Corporation. All rights reserved.
*
* \asf_license_start
*
* \page License
*
* 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. The name of Atmel may not be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* 4. This software may only be redistributed and used in connection with an
* Atmel microcontroller product.
*
* THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
* EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL 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.
*
* \asf_license_stop
*
*/
/*
* Support and FAQ: visit <a href="http://www.atmel.com/design-support/">Atmel Support</a>
*/
#include "i2sc.h"
#include "sysclk.h"
#include "sleepmgr.h"
#if defined(ID_I2SC1)
#define I2SC_NUM 2
#elif defined(ID_I2SC0)
#define I2SC_NUM 1
#else
#error There is no I2SC module on this device.
#endif
/**
* \internal
* \brief I2S callback function pointer array
*/
i2s_callback_t i2s_callback_pointer[I2SC_NUM][_I2S_INTERRUPT_SOURCE_NUM];
/**
* \brief Enable the I2S module.
*
* \param dev_inst Device structure pointer.
*
*/
void i2s_enable(struct i2s_dev_inst *const dev_inst)
{
UNUSED(dev_inst);
sleepmgr_lock_mode(SLEEPMGR_ACTIVE);
}
/**
* \brief Disable the I2S module.
*
* \param dev_inst Device structure pointer.
*
*/
void i2s_disable(struct i2s_dev_inst *const dev_inst)
{
UNUSED(dev_inst);
sleepmgr_unlock_mode(SLEEPMGR_ACTIVE);
}
/**
* \internal Set configurations to module.
*
* \param dev_inst Pointer to device instance structure.
*
*/
static enum status_code _i2s_set_config(struct i2s_dev_inst *const dev_inst)
{
/* Sanity check arguments */
Assert(dev_inst);
Assert(dev_inst->cfg);
uint32_t mr = 0;
mr = (dev_inst->cfg->master_mode? I2SC_MR_MODE : 0) |
(dev_inst->cfg->loopback ? I2SC_MR_RXLOOP : 0) |
((dev_inst->cfg->rx_dma == I2S_ONE_DMA_CHANNEL_FOR_ONE_CHANNEL) ?
I2SC_MR_RXDMA : 0) |
((dev_inst->cfg->tx_dma == I2S_ONE_DMA_CHANNEL_FOR_ONE_CHANNEL) ?
I2SC_MR_TXDMA : 0) |
((dev_inst->cfg->rx_channels == I2S_CHANNEL_MONO) ?
I2SC_MR_RXMONO : 0) |
((dev_inst->cfg->tx_channels == I2S_CHANNEL_MONO) ?
I2SC_MR_TXMONO : 0) |
(dev_inst->cfg->fs_ratio << I2SC_MR_IMCKFS_Pos) |
(dev_inst->cfg->data_format << I2SC_MR_DATALENGTH_Pos);
/* Set the master clock mode */
if (dev_inst->cfg->master_clock_enable) {
mr |= I2SC_MR_IMCKMODE | I2SC_MR_IMCKDIV(dev_inst->cfg->master_clock_divide);
}
if (dev_inst->cfg->slot_length_24) {
mr |= I2SC_MR_IWS;
}
if (dev_inst->cfg->transmit_mode_underrun) {
mr |= I2SC_MR_TXSAME;
}
dev_inst->hw_dev->I2SC_MR = mr;
return STATUS_OK;
}
/**
* \brief Initialize and configure the I2S module.
*
* \param dev_inst Device structure pointer.
* \param i2sc Base address of the I2SC instance.
* \param cfg Pointer to I2S configuration.
*
* \return status
*/
enum status_code i2s_init(struct i2s_dev_inst *const dev_inst, I2sc *i2sc,
struct i2s_config *const cfg)
{
/* Sanity check arguments */
Assert(dev_inst);
Assert(i2sc);
Assert(cfg);
dev_inst->hw_dev = i2sc;
dev_inst->cfg = cfg;
/* Enable PMC clock for I2SC */
#ifdef ID_I2SC1
if (dev_inst->hw_dev == I2SC1) {
sysclk_enable_peripheral_clock(ID_I2SC1);
} else
#endif
#ifdef ID_I2SC0
if (dev_inst->hw_dev == I2SC0) {
sysclk_enable_peripheral_clock(ID_I2SC0);
} else
#endif
{
Assert(false);
}
i2s_reset(dev_inst);
return _i2s_set_config(dev_inst);
}
/**
* \brief Set callback for I2S
*
* \param dev_inst Device structure pointer.
* \param source Interrupt source.
* \param callback Callback function pointer.
* \param irq_level Interrupt level.
*/
void i2s_set_callback(struct i2s_dev_inst *const dev_inst,
i2s_interrupt_source_t source, i2s_callback_t callback,
uint8_t irq_level)
{
uint32_t irq_line = 0;
uint32_t row_num = 0;
/* Sanity check arguments */
Assert(dev_inst);
#if defined(ID_I2SC1)
if (dev_inst->hw_dev == I2SC1) {
irq_line = I2SC1_IRQn;
row_num = 1;
}
#endif
#if defined(ID_I2SC0)
if (dev_inst->hw_dev == I2SC0) {
irq_line = I2SC0_IRQn;
row_num = 0;
}
#endif
i2s_callback_pointer[row_num][source] = callback;
NVIC_ClearPendingIRQ((IRQn_Type)irq_line);
NVIC_SetPriority((IRQn_Type)irq_line, irq_level);
NVIC_EnableIRQ((IRQn_Type)irq_line);
i2s_enable_interrupt(dev_inst, source);
}
/**
* \brief Enable the specified interrput sources.
*
* \param dev_inst Device structure pointer.
* \param source Interrupt source
*/
void i2s_enable_interrupt(struct i2s_dev_inst *const dev_inst,
i2s_interrupt_source_t source)
{
/* Sanity check arguments */
Assert(dev_inst);
switch (source) {
case I2S_INTERRUPT_RXRDY:
dev_inst->hw_dev->I2SC_IER = I2SC_IER_RXRDY;
break;
case I2S_INTERRUPT_RXOR:
dev_inst->hw_dev->I2SC_IER = I2SC_IER_RXOR;
break;
case I2S_INTERRUPT_TXRDY:
dev_inst->hw_dev->I2SC_IER = I2SC_IER_TXRDY;
break;
case I2S_INTERRUPT_TXUR:
dev_inst->hw_dev->I2SC_IER = I2SC_IER_TXUR;
break;
#if defined(PDC_I2SC) || defined(PDC_I2SC0) || defined(PDC_I2SC1)
case I2S_INTERRUPT_ENDRX:
dev_inst->hw_dev->I2SC_IER = I2SC_IER_ENDRX;
break;
case I2S_INTERRUPT_ENDTX:
dev_inst->hw_dev->I2SC_IER = I2SC_IER_ENDTX;
break;
case I2S_INTERRUPT_RXBUFF:
dev_inst->hw_dev->I2SC_IER = I2SC_IER_RXFULL;
break;
case I2S_INTERRUPT_TXBUFE:
dev_inst->hw_dev->I2SC_IER = I2SC_IER_TXEMPTY;
break;
#endif
default:
break;
}
}
/**
* \brief Disable the specified interrput sources.
*
* \param dev_inst Device structure pointer.
* \param source Interrupt source
*/
void i2s_disable_interrupt(struct i2s_dev_inst *const dev_inst,
i2s_interrupt_source_t source)
{
/* Sanity check arguments */
Assert(dev_inst);
switch (source) {
case I2S_INTERRUPT_RXRDY:
dev_inst->hw_dev->I2SC_IDR = I2SC_IDR_RXRDY;
break;
case I2S_INTERRUPT_RXOR:
dev_inst->hw_dev->I2SC_IDR = I2SC_IDR_RXOR;
break;
case I2S_INTERRUPT_TXRDY:
dev_inst->hw_dev->I2SC_IDR = I2SC_IDR_TXRDY;
break;
case I2S_INTERRUPT_TXUR:
dev_inst->hw_dev->I2SC_IDR = I2SC_IDR_TXUR;
break;
#if defined(PDC_I2SC) || defined(PDC_I2SC0) || defined(PDC_I2SC1)
case I2S_INTERRUPT_ENDRX:
dev_inst->hw_dev->I2SC_IDR = I2SC_IDR_ENDRX;
break;
case I2S_INTERRUPT_ENDTX:
dev_inst->hw_dev->I2SC_IDR = I2SC_IDR_ENDTX;
break;
case I2S_INTERRUPT_RXBUFF:
dev_inst->hw_dev->I2SC_IDR = I2SC_IDR_RXFULL;
break;
case I2S_INTERRUPT_TXBUFE:
dev_inst->hw_dev->I2SC_IDR = I2SC_IDR_TXEMPTY;
break;
#endif
default:
break;
}
}
/**
* \brief Clear the I2S status value.
*
* \param dev_inst Device structure pointer.
* \param source Interrupt source
*/
void i2s_clear_status(struct i2s_dev_inst *dev_inst,
i2s_interrupt_source_t source)
{
/* Sanity check arguments */
Assert(dev_inst);
switch (source) {
case I2S_INTERRUPT_RXOR:
dev_inst->hw_dev->I2SC_SCR = I2SC_SCR_RXOR;
break;
case I2S_INTERRUPT_TXUR:
dev_inst->hw_dev->I2SC_SCR = I2SC_SCR_TXUR;
break;
default:
break;
}
}
/**
* \brief Write a single message of data.
*
* \param dev_inst Device structure pointer.
* \param data The data to write
*
* \return status
*/
enum status_code i2s_write(struct i2s_dev_inst *const dev_inst, uint32_t data)
{
/* Sanity check arguments */
Assert(dev_inst);
Assert(dev_inst->hw_dev);
uint32_t timeout = I2S_RETRY_VALUE;
while (!(dev_inst->hw_dev->I2SC_SR & I2SC_SR_TXRDY) && timeout) {
--timeout;
}
if (timeout == 0) {
return STATUS_ERR_TIMEOUT;
}
dev_inst->hw_dev->I2SC_THR = data;
return STATUS_OK;
}
/**
* \brief Read a single message of data.
*
* \param dev_inst Device structure pointer.
* \param *data Pointer for receive data
*
* \return status
*/
enum status_code i2s_read(struct i2s_dev_inst *const dev_inst, uint32_t *data)
{
/* Sanity check arguments */
Assert(dev_inst);
Assert(dev_inst->hw_dev);
uint32_t timeout = I2S_RETRY_VALUE;
while (!(dev_inst->hw_dev->I2SC_SR & I2SC_SR_RXRDY) && timeout) {
--timeout;
}
if (timeout == 0) {
return STATUS_ERR_TIMEOUT;
}
*data = dev_inst->hw_dev->I2SC_RHR;
return STATUS_OK;
}
/**
* \brief Internal interrupt handler for I2S.
*/
static void i2s_interrupt_handler(I2sc *i2sc)
{
uint32_t row_num;
struct i2s_dev_inst i2s_instance;
i2s_instance.hw_dev = i2sc;
uint32_t status = i2s_get_status(&i2s_instance);
uint32_t mask = i2s_get_interrupt_mask(&i2s_instance);
#if defined(ID_I2SC1)
if (i2sc == I2SC1) {
row_num = 1;
}
#endif
#if defined(ID_I2SC0)
if (i2sc == I2SC0) {
row_num = 0;
}
#endif
if ((status & I2SC_SR_RXRDY) && (mask & I2SC_IMR_RXRDY)) {
i2s_callback_pointer[row_num][I2S_INTERRUPT_RXRDY]();
}
if ((status & I2SC_SR_RXOR) && (mask & I2SC_IMR_RXOR)) {
i2s_callback_pointer[row_num][I2S_INTERRUPT_RXOR]();
}
if ((status & I2SC_SR_TXRDY) && (mask & I2SC_IMR_TXRDY)) {
i2s_callback_pointer[row_num][I2S_INTERRUPT_TXRDY]();
}
if ((status & I2SC_SR_TXUR) && (mask & I2SC_IMR_TXUR)) {
i2s_callback_pointer[row_num][I2S_INTERRUPT_TXUR]();
}
#if defined(PDC_I2SC) || defined(PDC_I2SC0) || defined(PDC_I2SC1)
if ((status & I2SC_SR_RXRDY) && (mask & I2SC_IMR_ENDRX)) {
i2s_callback_pointer[row_num][I2S_INTERRUPT_ENDRX]();
}
if ((status & I2SC_SR_RXOR) && (mask & I2SC_IMR_ENDTX)) {
i2s_callback_pointer[row_num][I2S_INTERRUPT_ENDTX]();
}
if ((status & I2SC_SR_TXRDY) && (mask & I2SC_IMR_RXFULL)) {
i2s_callback_pointer[row_num][I2S_INTERRUPT_RXBUFF]();
}
if ((status & I2SC_SR_TXUR) && (mask & I2SC_IMR_TXEMPTY)) {
i2s_callback_pointer[row_num][I2S_INTERRUPT_TXBUFE]();
}
#endif
}
#if defined(ID_I2SC0)
/**
* \brief Interrupt handler for I2S.
*/
void I2SC0_Handler(void)
{
i2s_interrupt_handler(I2SC0);
}
#endif
#if defined(ID_I2SC1)
/**
* \brief Interrupt handler for I2S.
*/
void I2SC1_Handler(void)
{
i2s_interrupt_handler(I2SC1);
}
#endif
#if defined(PDC_I2SC) || defined(PDC_I2SC0) || defined(PDC_I2SC1)
/**
* \brief Get I2SC PDC base address.
*
* \note The second PDC base address for the same instance is the return value plus 0x100.
*
* \param dev_inst Device structure pointer.
*
* \return I2SC PDC registers base for PDC driver to access.
*/
Pdc *i2s_get_pdc_base(struct i2s_dev_inst *const dev_inst)
{
Pdc *p_pdc_base = NULL;
#ifdef PDC_I2SC1
if (dev_inst->hw_dev == I2SC1) {
p_pdc_base = PDC_I2SC1;
} else
#endif
#ifdef PDC_I2SC0
if (dev_inst->hw_dev == I2SC0) {
p_pdc_base = PDC_I2SC0;
} else
#endif
{
Assert(false);
}
return p_pdc_base;
}
#endif /* defined(PDC_I2SC) || defined(PDC_I2SC0) || defined(PDC_I2SC1) */