nuttx/boards/arm/cxd56xx/drivers/audio/cxd56_audio_dma.c

690 lines
18 KiB
C
Raw Normal View History

/****************************************************************************
* boards/arm/cxd56xx/drivers/audio/cxd56_audio_dma.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you 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.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <stdint.h>
#include <nuttx/arch.h>
#include <nuttx/config.h>
#include "cxd56_audio_config.h"
#include "cxd56_audio_dma.h"
#include "cxd56_audio_mic.h"
#include "cxd56_audio_ac_reg.h"
#include "cxd56_audio_bca_reg.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Register type. */
enum audio_irq_reg_type_e
{
INT_EN1_REG = 0,
INT_POL_REG,
INT_IRQ1_REG
};
/* INT_EN1 */
#define CXD56_INTC_BASE 0xe0045000
#define INT_EN1_REG_ADDR (CXD56_INTC_BASE + 0x10 + 3 * 4)
#define INT_EN1_BIT_AU0 6
#define INT_EN1_BIT_AU1 7
#define INT_EN1_BIT_AU2 8
#define INT_EN1_BIT_AU3 9
/* INT_POL */
#define INT_POL_REG (CXD56_INTC_BASE + 0x20 + 3 * 4)
#define INT_POL_BIT_AU0 6
#define INT_POL_BIT_AU1 7
#define INT_POL_BIT_AU2 8
#define INT_POL_BIT_AU3 9
/* INT_IRQ1 */
#define INT_IRQ1_REG_ADDR (CXD56_INTC_BASE + 0x30 + 3 * 4)
#define INT_IRQ1_BIT_AU0 6
#define INT_IRQ1_BIT_AU1 7
#define INT_IRQ1_BIT_AU2 8
#define INT_IRQ1_BIT_AU3 9
#define DMA_HANDLE_MAX_NUM (CXD56_AUDIO_DMAC_I2S1_DOWN + 1)
#define DMA_TIMEOUT_CNT 10000
#define DMA_START_RETRY_CNT 10
#define DMA_SMP_WAIT_HIRES 10 /* usec per sample. */
#define DMA_SMP_WAIT_NORMALT 40 /* usec per sample. */
/* Private Macros */
#define SET_DMA_ACT(_path_) g_dma_act_status |= (1 << _path_)
#define CLR_DMA_ACT(_path_) g_dma_act_status &= ~(1 << _path_)
#define IS_DMA_ACT(_path_) ((g_dma_act_status & (1 << _path_)) != 0)
/****************************************************************************
* Private Data
****************************************************************************/
static uint8_t g_dma_act_status = 0;
static cxd56_audio_dma_cb_t g_dma_cb[DMA_HANDLE_MAX_NUM];
static bool s_work_arroud_dmac[DMA_HANDLE_MAX_NUM] =
{
true,
true,
true
};
/****************************************************************************
* Private Functions
****************************************************************************/
static uint32_t read_int_reg(uint32_t reg)
{
volatile uint32_t *addr;
uint32_t data = 0;
if (reg == INT_EN1_REG)
{
addr = (volatile uint32_t *)INT_EN1_REG_ADDR;
}
else if (reg == INT_IRQ1_REG)
{
addr = (volatile uint32_t *)INT_IRQ1_REG_ADDR;
}
else
{
addr = (volatile uint32_t *)INT_POL_REG;
}
data = *addr;
return data;
}
static uint32_t write_int_reg(uint32_t reg, uint32_t data)
{
volatile uint32_t *addr;
if (reg == INT_EN1_REG)
{
addr = (volatile uint32_t *)INT_EN1_REG_ADDR;
*addr = data;
}
return 0;
}
static CXD56_AUDIO_ECODE get_dma_handle(cxd56_audio_dma_path_t path,
FAR cxd56_audio_dma_t *handle)
{
switch (path)
{
case CXD56_AUDIO_DMA_PATH_MIC_TO_MEM:
*handle = CXD56_AUDIO_DMAC_MIC;
break;
case CXD56_AUDIO_DMA_PATH_MEM_TO_BUSIF1:
*handle = CXD56_AUDIO_DMAC_I2S0_DOWN;
break;
case CXD56_AUDIO_DMA_PATH_MEM_TO_BUSIF2:
*handle = CXD56_AUDIO_DMAC_I2S1_DOWN;
break;
default:
return CXD56_AUDIO_ECODE_DMA_PATH_INV;
}
return CXD56_AUDIO_ECODE_OK;
}
static CXD56_AUDIO_ECODE get_dma_path(cxd56_audio_dma_t handle,
FAR cxd56_audio_dma_path_t *path)
{
switch (handle)
{
case CXD56_AUDIO_DMAC_MIC:
*path = CXD56_AUDIO_DMA_PATH_MIC_TO_MEM;
break;
case CXD56_AUDIO_DMAC_I2S0_DOWN:
*path = CXD56_AUDIO_DMA_PATH_MEM_TO_BUSIF1;
break;
case CXD56_AUDIO_DMAC_I2S1_DOWN:
*path = CXD56_AUDIO_DMA_PATH_MEM_TO_BUSIF2;
break;
default:
return CXD56_AUDIO_ECODE_DMA_HANDLE_INV;
}
return CXD56_AUDIO_ECODE_OK;
}
static CXD56_AUDIO_ECODE start_dma(cxd56_audio_dma_t handle)
{
cxd56_audio_bca_reg_start_dma(handle, false);
return CXD56_AUDIO_ECODE_OK;
}
static CXD56_AUDIO_ECODE exec_dma_ch_sync_workaround(
cxd56_audio_dma_t handle)
{
int timeout_cnt = 0;
int retry_cnt;
cxd56_audio_clkmode_t clk_mode = cxd56_audio_config_get_clkmode();
/* Execute out-of-sync workaround.
* 1. Clear smp interrupt status
* 2. Read until smp interrupt state is true
* 3. Reset channel select setting
* 4. Start dma transfer
* It needs to be less than 9 us by the processing so far.
* If it does not fit below 9 us, err_int is generated, so retry.
*/
/* Mask dma done interrupt. */
cxd56_audio_bca_reg_mask_done_int(handle);
for (retry_cnt = 0; retry_cnt < DMA_START_RETRY_CNT; retry_cnt++)
{
/* Clear interrupt status */
cxd56_audio_bca_reg_clear_err_int(handle);
cxd56_audio_bca_reg_clear_smp_int(handle);
/* Lock interrupt */
up_irq_disable();
sched_lock();
/* Wait smp interrupt. */
for (timeout_cnt = 0; timeout_cnt < DMA_TIMEOUT_CNT; timeout_cnt++)
{
if (cxd56_audio_bca_reg_is_smp_int(handle))
{
break;
}
}
if (timeout_cnt == DMA_TIMEOUT_CNT)
{
sched_unlock();
up_irq_enable();
return CXD56_AUDIO_ECODE_DMA_SMP_TIMEOUT;
}
/* Reset Channel select. */
cxd56_audio_bca_reg_reset_chsel(handle);
/* Start dma. */
cxd56_audio_bca_reg_start_dma(handle, false);
/* Unlock interrupt */
sched_unlock();
up_irq_enable();
/* Wait for 1sample tramsfer. */
if (clk_mode == CXD56_AUDIO_CLKMODE_HIRES)
{
up_udelay(DMA_SMP_WAIT_HIRES);
}
else
{
up_udelay(DMA_SMP_WAIT_NORMALT);
}
/* Check whether an error interrupt has occurred. */
if (cxd56_audio_bca_reg_is_err_int(handle))
{
cxd56_audio_bca_reg_stop_dma(handle);
cxd56_audio_bca_reg_clear_err_int(handle);
for (timeout_cnt = 0; timeout_cnt < DMA_TIMEOUT_CNT; timeout_cnt++)
{
if (DMA_MSTATE_BUF_EMPTY ==
cxd56_audio_bca_reg_get_mon_state_buf(handle))
{
if (cxd56_audio_bca_reg_is_done_int(handle))
{
cxd56_audio_bca_reg_clear_done_int(handle);
break;
}
}
}
}
else
{
break;
}
}
/* Unmask dma done interrupt. */
cxd56_audio_bca_reg_unmask_done_int(handle);
return CXD56_AUDIO_ECODE_OK;
}
static CXD56_AUDIO_ECODE start_dma_workaround(cxd56_audio_dma_t handle)
{
/* There are two workarounds.
* One is a workaround in which the error interrupt of
* dma is incorrectly generated.
* The other is a workaround for the problem that the channel
* is out of sync.
* Because both require processing at the beginning of dma,
* call out workaround with out-of-sync from the workaround
* for interrupt error.
*/
/* Execute error interrupt workaround.
* 1. Mask dma error interrupt
* 2. Wait 77 cycle after dma transfer starts
* 3. Clear interrupt status
* 4. Unmask dma error interrupt
*/
cxd56_audio_bca_reg_mask_err_int(handle);
/* Transfer start and wait processing of dma is done
* in out-of-sync workaround.
*/
CXD56_AUDIO_ECODE ret = exec_dma_ch_sync_workaround(handle);
cxd56_audio_bca_reg_clear_err_int(handle);
cxd56_audio_bca_reg_unmask_err_int(handle);
return ret;
}
/****************************************************************************
* Public Functions
****************************************************************************/
CXD56_AUDIO_ECODE cxd56_audio_dma_get_handle(cxd56_audio_dma_path_t path,
FAR cxd56_audio_dma_t *handle)
{
CXD56_AUDIO_ECODE ret = CXD56_AUDIO_ECODE_OK;
/* Check error of argument */
if (handle == NULL)
{
return CXD56_AUDIO_ECODE_DMA_HANDLE_NULL;
}
/* Check duplicate order */
if (IS_DMA_ACT(path))
{
return CXD56_AUDIO_ECODE_DMA_PATH_DUP;
}
ret = get_dma_handle(path, handle);
if (CXD56_AUDIO_ECODE_OK != ret)
{
return ret;
}
SET_DMA_ACT(path);
return CXD56_AUDIO_ECODE_OK;
}
CXD56_AUDIO_ECODE cxd56_audio_dma_free_handle(FAR cxd56_audio_dma_t handle)
{
CXD56_AUDIO_ECODE ret = CXD56_AUDIO_ECODE_OK;
cxd56_audio_dma_path_t path;
ret = get_dma_path(handle, &path);
if (CXD56_AUDIO_ECODE_OK != ret)
{
return ret;
}
CLR_DMA_ACT(path);
return CXD56_AUDIO_ECODE_OK;
}
CXD56_AUDIO_ECODE cxd56_audio_dma_init(cxd56_audio_dma_t handle,
cxd56_audio_samp_fmt_t fmt,
FAR uint8_t *ch_num)
{
CXD56_AUDIO_ECODE ret = CXD56_AUDIO_ECODE_OK;
uint32_t ch_setting;
cxd56_audio_ac_reg_enable_dma();
if (handle == CXD56_AUDIO_DMAC_MIC)
{
ret = cxd56_audio_mic_set_seloutch(*ch_num, fmt);
if (CXD56_AUDIO_ECODE_OK != ret)
{
return ret;
}
}
else
{
/* Set Stereo channel number. */
*ch_num = 2;
}
if (fmt == CXD56_AUDIO_SAMP_FMT_24)
{
cxd56_audio_bca_reg_en_fmt24(handle, *ch_num);
}
else
{
cxd56_audio_bca_reg_en_fmt16(handle, *ch_num);
}
/* Clear interrupt state. */
cxd56_audio_bca_reg_clear_done_int(handle);
cxd56_audio_bca_reg_clear_err_int(handle);
cxd56_audio_bca_reg_clear_cmb_int(handle);
/* Enable interrupt. */
cxd56_audio_bca_reg_unmask_done_int(handle);
/* cxd56_audio_bca_reg_mask_done_int(handle); TODO: polling */
/* Enable interrupt. */
cxd56_audio_bca_reg_unmask_err_int(handle);
cxd56_audio_bca_reg_unmask_cmb_int(handle);
cxd56_audio_bca_reg_unmask_bus_int(handle);
/* Check channel setting. */
ch_setting = cxd56_audio_bca_reg_get_mon_state_err(handle);
switch (ch_setting)
{
case DMA_MSTATE_ERR_NO_ENABLE_CH:
return CXD56_AUDIO_ECODE_DMA_CH_NO_ENABLE;
case DMA_MSTATE_ERR_CH1_4_INVALID:
return CXD56_AUDIO_ECODE_DMA_CH1_4_INV;
case DMA_MSTATE_ERR_CH5_8_INVALID:
return CXD56_AUDIO_ECODE_DMA_CH5_8_INV;
default:
break;
}
return ret;
}
CXD56_AUDIO_ECODE cxd56_audio_dma_set_cb(cxd56_audio_dma_t handle,
FAR cxd56_audio_dma_cb_t cb)
{
g_dma_cb[handle] = cb;
return CXD56_AUDIO_ECODE_OK;
}
CXD56_AUDIO_ECODE cxd56_audio_dma_get_mstate(cxd56_audio_dma_t handle,
FAR cxd56_audio_dma_mstate_t *state)
{
cxd56_audio_bca_reg_get_dma_mstate(handle, state);
return CXD56_AUDIO_ECODE_OK;
}
CXD56_AUDIO_ECODE cxd56_audio_dma_en_dmaint(void)
{
volatile uint32_t int_en;
int_en = read_int_reg(INT_EN1_REG);
int_en |= (1 << INT_EN1_BIT_AU0);
int_en |= (1 << INT_EN1_BIT_AU1);
int_en |= (1 << INT_EN1_BIT_AU2);
int_en |= (1 << INT_EN1_BIT_AU3);
write_int_reg(INT_EN1_REG, int_en);
/* Enable bus error interrupt. */
cxd56_audio_bca_reg_en_bus_err_int();
return CXD56_AUDIO_ECODE_OK;
}
CXD56_AUDIO_ECODE cxd56_audio_dma_dis_dmaint(void)
{
volatile uint32_t int_en;
int_en = read_int_reg(INT_EN1_REG);
int_en &= ~(1 << INT_EN1_BIT_AU0);
int_en &= ~(1 << INT_EN1_BIT_AU1);
int_en &= ~(1 << INT_EN1_BIT_AU2);
int_en &= ~(1 << INT_EN1_BIT_AU3);
write_int_reg(INT_EN1_REG, int_en);
/* Disable bus error interrupt. */
cxd56_audio_bca_reg_dis_bus_err_int();
return CXD56_AUDIO_ECODE_OK;
}
CXD56_AUDIO_ECODE cxd56_audio_dma_start(cxd56_audio_dma_t handle,
uint32_t addr,
uint32_t sample)
{
CXD56_AUDIO_ECODE ret = CXD56_AUDIO_ECODE_OK;
if (DMA_CMD_FIFO_NOT_FULL != cxd56_audio_bca_reg_get_dma_state(handle))
{
return CXD56_AUDIO_ECODE_DMA_BUSY;
}
cxd56_audio_bca_reg_set_start_addr(handle, CXD56_PHYSADDR(addr));
cxd56_audio_bca_reg_set_sample_no(handle, sample);
if (s_work_arroud_dmac[handle])
{
s_work_arroud_dmac[handle] = false;
ret = start_dma_workaround(handle);
}
else
{
ret = start_dma(handle);
}
return ret;
}
CXD56_AUDIO_ECODE cxd56_audio_dma_stop(cxd56_audio_dma_t handle)
{
cxd56_audio_bca_reg_stop_dma(handle);
s_work_arroud_dmac[handle] = true;
return CXD56_AUDIO_ECODE_OK;
}
void cxd56_audio_dma_int_handler(void)
{
uint32_t int_irq = read_int_reg(INT_IRQ1_REG);
uint32_t int_ac = cxd56_audio_bca_reg_get_dma_done_state_mic();
uint32_t int_i2s = cxd56_audio_bca_reg_get_dma_done_state_i2s1();
uint32_t int_i2s2 = cxd56_audio_bca_reg_get_dma_done_state_i2s2();
/* AUDIO_INT_AC : check interruption from mic */
if ((int_irq & (1 << INT_IRQ1_BIT_AU0)) && (int_ac != 0))
{
/* Clear interrupt. */
cxd56_audio_bca_reg_clear_dma_done_state_mic(int_ac);
/* Check done complete state. */
if (int_ac & DMA_STATE_BIT_AC_DONE)
{
(*g_dma_cb[CXD56_AUDIO_DMAC_MIC])(CXD56_AUDIO_DMAC_MIC,
CXD56_AUDIO_ECODE_DMA_CMPLT);
}
/* Check transfer err state. */
if (int_ac & DMA_STATE_BIT_AC_ERR)
{
cxd56_audio_bca_reg_mask_err_int(CXD56_AUDIO_DMAC_MIC);
cxd56_audio_bca_reg_clear_err_int(CXD56_AUDIO_DMAC_MIC);
(*g_dma_cb[CXD56_AUDIO_DMAC_MIC])(CXD56_AUDIO_DMAC_MIC,
CXD56_AUDIO_ECODE_DMA_TRANS);
}
/* Check bus err state. */
if (int_ac & DMA_STATE_BIT_AC_CMB)
{
cxd56_audio_bca_reg_mask_cmb_int(CXD56_AUDIO_DMAC_MIC);
cxd56_audio_bca_reg_clear_cmb_int(CXD56_AUDIO_DMAC_MIC);
(*g_dma_cb[CXD56_AUDIO_DMAC_MIC])(CXD56_AUDIO_DMAC_MIC,
CXD56_AUDIO_ECODE_DMA_CMB);
}
}
/* AUDIO_INT_I2S1 : check interruption from I2S-1 */
if ((int_irq & (1 << INT_IRQ1_BIT_AU1)) && (int_i2s != 0))
{
/* Clear interrupt. */
cxd56_audio_bca_reg_clear_dma_done_state_i2s1(int_i2s);
/* Check done complete state. */
if (int_i2s & DMA_STATE_BIT_I2S_OUT_DONE)
{
(*g_dma_cb[CXD56_AUDIO_DMAC_I2S0_DOWN])(CXD56_AUDIO_DMAC_I2S0_DOWN,
CXD56_AUDIO_ECODE_DMA_CMPLT);
}
/* Check transfer err state. */
if (int_i2s & DMA_STATE_BIT_I2S_OUT_ERR)
{
cxd56_audio_bca_reg_mask_err_int(CXD56_AUDIO_DMAC_I2S0_DOWN);
cxd56_audio_bca_reg_clear_err_int(CXD56_AUDIO_DMAC_I2S0_DOWN);
(*g_dma_cb[CXD56_AUDIO_DMAC_I2S0_DOWN])(CXD56_AUDIO_DMAC_I2S0_DOWN,
CXD56_AUDIO_ECODE_DMA_TRANS);
}
/* Check bus err state. */
if (int_i2s & DMA_STATE_BIT_I2S_CMB)
{
cxd56_audio_bca_reg_mask_cmb_int(CXD56_AUDIO_DMAC_I2S0_DOWN);
cxd56_audio_bca_reg_clear_cmb_int(CXD56_AUDIO_DMAC_I2S0_DOWN);
(*g_dma_cb[CXD56_AUDIO_DMAC_I2S0_DOWN])(CXD56_AUDIO_DMAC_I2S0_DOWN,
CXD56_AUDIO_ECODE_DMA_CMB);
}
}
/* AUDIO_INT_I2S2 : check interruption from I2S-2 */
if ((int_irq & (1 << INT_IRQ1_BIT_AU2)) && (int_i2s2 != 0))
{
/* Clear interrupt. */
cxd56_audio_bca_reg_clear_dma_done_state_i2s2(int_i2s2);
/* Check done complete state. */
if (int_i2s2 & DMA_STATE_BIT_I2S_OUT_DONE)
{
(*g_dma_cb[CXD56_AUDIO_DMAC_I2S1_DOWN])
(CXD56_AUDIO_DMAC_I2S1_DOWN,
CXD56_AUDIO_ECODE_DMA_CMPLT);
}
/* Check transfer err state. */
if (int_i2s2 & DMA_STATE_BIT_I2S_OUT_ERR)
{
cxd56_audio_bca_reg_mask_err_int(CXD56_AUDIO_DMAC_I2S1_DOWN);
cxd56_audio_bca_reg_clear_err_int(CXD56_AUDIO_DMAC_I2S1_DOWN);
(*g_dma_cb[CXD56_AUDIO_DMAC_I2S1_DOWN])
(CXD56_AUDIO_DMAC_I2S1_DOWN,
CXD56_AUDIO_ECODE_DMA_TRANS);
}
/* Check bus err state. */
if (int_i2s2 & DMA_STATE_BIT_I2S_CMB)
{
cxd56_audio_bca_reg_mask_cmb_int(CXD56_AUDIO_DMAC_I2S1_DOWN);
cxd56_audio_bca_reg_clear_cmb_int(CXD56_AUDIO_DMAC_I2S1_DOWN);
(*g_dma_cb[CXD56_AUDIO_DMAC_I2S1_DOWN])(CXD56_AUDIO_DMAC_I2S1_DOWN,
CXD56_AUDIO_ECODE_DMA_CMB);
}
}
if (int_irq & (1 << INT_IRQ1_BIT_AU3))
{
uint32_t int_au = cxd56_audio_bca_reg_get_int_status();
if (int_au != 0)
{
cxd56_audio_bca_reg_clear_int_status(int_au);
}
}
}