513 lines
15 KiB
C
513 lines
15 KiB
C
/* Copyright 2018 Canaan Inc.
|
|
*
|
|
* Licensed 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.
|
|
*/
|
|
#include <FreeRTOS.h>
|
|
#include <driver.h>
|
|
#include <fpioa.h>
|
|
#include <hal.h>
|
|
#include <i2s.h>
|
|
#include <semphr.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <sysctl.h>
|
|
#include "fpioa_cfg.h"
|
|
|
|
#define BUFFER_COUNT 2
|
|
#define COMMON_ENTRY \
|
|
i2s_data* data = (i2s_data*)userdata; \
|
|
volatile struct i2s_t* i2s = (volatile struct i2s_t*)data->base_addr; \
|
|
(void)i2s;
|
|
|
|
typedef enum
|
|
{
|
|
I2S_RECEIVE,
|
|
I2S_SEND
|
|
} i2s_transmit;
|
|
|
|
typedef struct
|
|
{
|
|
enum sysctl_clock_e clock;
|
|
uintptr_t base_addr;
|
|
enum sysctl_dma_select_e dma_req_base;
|
|
enum sysctl_threshold_e clock_threshold;
|
|
struct
|
|
{
|
|
i2s_transmit transmit;
|
|
char* buffer;
|
|
size_t buffer_frames;
|
|
size_t buffer_size;
|
|
size_t block_align;
|
|
size_t channels;
|
|
size_t buffer_ptr;
|
|
int next_free_buffer;
|
|
int dma_in_use_buffer;
|
|
int stop_signal;
|
|
uintptr_t transmit_dma;
|
|
SemaphoreHandle_t stage_completion_event;
|
|
SemaphoreHandle_t completion_event;
|
|
};
|
|
} i2s_data;
|
|
|
|
static void i2s_transmit_set_enable(i2s_transmit transmit, int enable, volatile struct i2s_t* i2s);
|
|
static void i2sc_transmit_set_enable(i2s_transmit transmit, int enable, volatile struct i2s_channel_t* i2sc);
|
|
|
|
static void i2s_install(void* userdata)
|
|
{
|
|
COMMON_ENTRY;
|
|
|
|
/* GPIO clock under APB0 clock, so enable APB0 clock firstly */
|
|
sysctl_clock_enable(data->clock);
|
|
|
|
union ier_u u_ier;
|
|
|
|
u_ier.reg_data = readl(&i2s->ier);
|
|
u_ier.ier.ien = 1;
|
|
writel(u_ier.reg_data, &i2s->ier);
|
|
|
|
data->buffer = NULL;
|
|
}
|
|
|
|
static int i2s_open(void* userdata)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
static void i2s_close(void* userdata)
|
|
{
|
|
}
|
|
|
|
static void i2s_set_threshold(volatile struct i2s_channel_t* i2sc, i2s_transmit transmit, enum fifo_threshold_t threshold)
|
|
{
|
|
if (transmit == I2S_RECEIVE)
|
|
{
|
|
union rfcr_u u_rfcr;
|
|
|
|
u_rfcr.reg_data = readl(&i2sc->rfcr);
|
|
u_rfcr.rfcr.rxchdt = threshold;
|
|
writel(u_rfcr.reg_data, &i2sc->rfcr);
|
|
}
|
|
else
|
|
{
|
|
union tfcr_u u_tfcr;
|
|
|
|
u_tfcr.reg_data = readl(&i2sc->tfcr);
|
|
u_tfcr.tfcr.txchet = threshold;
|
|
writel(u_tfcr.reg_data, &i2sc->tfcr);
|
|
}
|
|
}
|
|
|
|
static void i2sc_set_mask_interrupt(volatile struct i2s_channel_t* i2sc,
|
|
uint32_t rx_available_int, uint32_t rx_overrun_int,
|
|
uint32_t tx_empty_int, uint32_t tx_overrun_int)
|
|
{
|
|
union imr_u u_imr;
|
|
|
|
u_imr.reg_data = readl(&i2sc->imr);
|
|
|
|
if (rx_available_int == 1)
|
|
u_imr.imr.rxdam = 1;
|
|
else
|
|
u_imr.imr.rxdam = 0;
|
|
if (rx_overrun_int == 1)
|
|
u_imr.imr.rxfom = 1;
|
|
else
|
|
u_imr.imr.rxfom = 0;
|
|
|
|
if (tx_empty_int == 1)
|
|
u_imr.imr.txfem = 1;
|
|
else
|
|
u_imr.imr.txfem = 0;
|
|
if (tx_overrun_int == 1)
|
|
u_imr.imr.txfom = 1;
|
|
else
|
|
u_imr.imr.txfom = 0;
|
|
writel(u_imr.reg_data, &i2sc->imr);
|
|
}
|
|
|
|
static void extract_params(const audio_format_t* format, enum word_select_cycles_t* wsc, enum word_length_t* wlen, size_t* block_align, uint32_t* dma_divide16)
|
|
{
|
|
configASSERT(format->sample_rate == 44100);
|
|
|
|
switch (format->bits_per_sample)
|
|
{
|
|
case 16:
|
|
*wsc = SCLK_CYCLES_32;
|
|
*wlen = RESOLUTION_16_BIT;
|
|
*block_align = format->channels * 2;
|
|
*dma_divide16 = 1;
|
|
break;
|
|
case 24:
|
|
*wsc = SCLK_CYCLES_32;
|
|
*wlen = RESOLUTION_24_BIT;
|
|
*block_align = format->channels * 4;
|
|
*dma_divide16 = 0;
|
|
break;
|
|
case 32:
|
|
*wsc = SCLK_CYCLES_32;
|
|
*wlen = RESOLUTION_32_BIT;
|
|
*block_align = format->channels * 4;
|
|
*dma_divide16 = 0;
|
|
break;
|
|
default:
|
|
configASSERT(!"Invlid bits per sample");
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void i2s_config_as_render(const audio_format_t* format, size_t delay_ms, i2s_align_mode align_mode, size_t channels_mask, void* userdata)
|
|
{
|
|
COMMON_ENTRY;
|
|
|
|
data->transmit = I2S_SEND;
|
|
|
|
uint32_t am = 0;
|
|
|
|
switch (align_mode)
|
|
{
|
|
case I2S_AM_STANDARD:
|
|
am = 0x1;
|
|
break;
|
|
case I2S_AM_RIGHT:
|
|
am = 0x2;
|
|
break;
|
|
case I2S_AM_LEFT:
|
|
am = 0x4;
|
|
break;
|
|
default:
|
|
configASSERT(!"I2S align mode not supported.");
|
|
break;
|
|
}
|
|
|
|
enum word_select_cycles_t wsc;
|
|
enum word_length_t wlen;
|
|
size_t block_align;
|
|
uint32_t dma_divide16;
|
|
|
|
extract_params(format, &wsc, &wlen, &block_align, &dma_divide16);
|
|
|
|
sysctl_clock_set_threshold(data->clock_threshold, 7);
|
|
|
|
i2s_transmit_set_enable(I2S_RECEIVE, 0, userdata);
|
|
i2s_transmit_set_enable(I2S_SEND, 0, userdata);
|
|
|
|
union ccr_u u_ccr;
|
|
union cer_u u_cer;
|
|
|
|
u_cer.reg_data = readl(&i2s->cer);
|
|
u_cer.cer.clken = 0;
|
|
writel(u_cer.reg_data, &i2s->cer);
|
|
|
|
u_ccr.reg_data = readl(&i2s->ccr);
|
|
u_ccr.ccr.clk_word_size = wsc;
|
|
u_ccr.ccr.clk_gate = NO_CLOCK_GATING;
|
|
u_ccr.ccr.align_mode = am;
|
|
u_ccr.ccr.dma_tx_en = 1;
|
|
u_ccr.ccr.sign_expand_en = 1;
|
|
u_ccr.ccr.dma_divide_16 = dma_divide16;
|
|
u_ccr.ccr.dma_rx_en = 0;
|
|
writel(u_ccr.reg_data, &i2s->ccr);
|
|
|
|
u_cer.reg_data = readl(&i2s->cer);
|
|
u_cer.cer.clken = 1;
|
|
writel(u_cer.reg_data, &i2s->cer);
|
|
|
|
writel(1, &i2s->txffr);
|
|
writel(1, &i2s->rxffr);
|
|
|
|
size_t channel = 0;
|
|
size_t enabled_channel = 0;
|
|
for (channel = 0; channel < 4; channel++)
|
|
{
|
|
volatile struct i2s_channel_t* i2sc = i2s->channel + channel;
|
|
|
|
if ((channels_mask & 3) == 3)
|
|
{
|
|
i2sc_transmit_set_enable(I2S_SEND, 1, i2sc);
|
|
i2sc_transmit_set_enable(I2S_RECEIVE, 0, i2sc);
|
|
i2sc_set_mask_interrupt(i2sc, 0, 0, 1, 1);
|
|
|
|
union rcr_tcr_u u_tcr;
|
|
u_tcr.reg_data = readl(&i2sc->tcr);
|
|
u_tcr.rcr_tcr.wlen = wlen;
|
|
writel(u_tcr.reg_data, &i2sc->tcr);
|
|
|
|
i2s_set_threshold(i2sc, I2S_SEND, TRIGGER_LEVEL_4);
|
|
enabled_channel++;
|
|
}
|
|
else
|
|
{
|
|
i2sc_transmit_set_enable(I2S_SEND, 0, i2sc);
|
|
i2sc_transmit_set_enable(I2S_RECEIVE, 0, i2sc);
|
|
}
|
|
|
|
channels_mask >>= 2;
|
|
}
|
|
|
|
configASSERT(enabled_channel * 2 == format->channels);
|
|
|
|
data->channels = format->channels;
|
|
data->block_align = block_align;
|
|
data->buffer_frames = 44100 * delay_ms / 1000;
|
|
configASSERT(data->buffer_frames >= 100);
|
|
free(data->buffer);
|
|
data->buffer_size = data->block_align * data->buffer_frames;
|
|
data->buffer = (char*)malloc(data->buffer_size * BUFFER_COUNT);
|
|
data->buffer_ptr = 0;
|
|
data->next_free_buffer = 0;
|
|
data->stop_signal = 0;
|
|
data->transmit_dma = 0;
|
|
data->dma_in_use_buffer = -1;
|
|
}
|
|
|
|
static void i2s_config_as_capture(const audio_format_t* format, size_t delay_ms, i2s_align_mode align_mode, size_t channels_mask, void* userdata)
|
|
{
|
|
COMMON_ENTRY;
|
|
|
|
data->transmit = I2S_RECEIVE;
|
|
|
|
uint32_t am = 0;
|
|
|
|
switch (align_mode)
|
|
{
|
|
case I2S_AM_STANDARD:
|
|
am = 0x1;
|
|
break;
|
|
case I2S_AM_RIGHT:
|
|
am = 0x2;
|
|
break;
|
|
case I2S_AM_LEFT:
|
|
am = 0x4;
|
|
break;
|
|
default:
|
|
configASSERT(!"I2S align mode not supported.");
|
|
break;
|
|
}
|
|
|
|
enum word_select_cycles_t wsc;
|
|
enum word_length_t wlen;
|
|
size_t block_align;
|
|
uint32_t dma_divide16;
|
|
|
|
configASSERT(format->bits_per_sample != 16);
|
|
extract_params(format, &wsc, &wlen, &block_align, &dma_divide16);
|
|
|
|
sysctl_clock_set_threshold(data->clock_threshold, 7);
|
|
|
|
i2s_transmit_set_enable(I2S_RECEIVE, 0, userdata);
|
|
i2s_transmit_set_enable(I2S_SEND, 0, userdata);
|
|
|
|
union ccr_u u_ccr;
|
|
union cer_u u_cer;
|
|
|
|
u_cer.reg_data = readl(&i2s->cer);
|
|
u_cer.cer.clken = 0;
|
|
writel(u_cer.reg_data, &i2s->cer);
|
|
|
|
u_ccr.reg_data = readl(&i2s->ccr);
|
|
u_ccr.ccr.clk_word_size = wsc;
|
|
u_ccr.ccr.clk_gate = NO_CLOCK_GATING;
|
|
u_ccr.ccr.align_mode = am;
|
|
u_ccr.ccr.dma_tx_en = 0;
|
|
u_ccr.ccr.sign_expand_en = 1;
|
|
u_ccr.ccr.dma_divide_16 = dma_divide16;
|
|
u_ccr.ccr.dma_rx_en = 1;
|
|
writel(u_ccr.reg_data, &i2s->ccr);
|
|
|
|
u_cer.reg_data = readl(&i2s->cer);
|
|
u_cer.cer.clken = 1;
|
|
writel(u_cer.reg_data, &i2s->cer);
|
|
|
|
writel(1, &i2s->txffr);
|
|
writel(1, &i2s->rxffr);
|
|
|
|
size_t channel = 0;
|
|
size_t enabled_channel = 0;
|
|
for (channel = 0; channel < 4; channel++)
|
|
{
|
|
volatile struct i2s_channel_t* i2sc = i2s->channel + channel;
|
|
|
|
if ((channels_mask & 3) == 3)
|
|
{
|
|
i2sc_transmit_set_enable(I2S_SEND, 0, i2sc);
|
|
i2sc_transmit_set_enable(I2S_RECEIVE, 1, i2sc);
|
|
i2sc_set_mask_interrupt(i2sc, 1, 1, 0, 0);
|
|
|
|
/* set word length */
|
|
union rcr_tcr_u u_tcr;
|
|
u_tcr.reg_data = readl(&i2sc->rcr);
|
|
u_tcr.rcr_tcr.wlen = wlen;
|
|
writel(u_tcr.reg_data, &i2sc->rcr);
|
|
|
|
i2s_set_threshold(i2sc, I2S_RECEIVE, TRIGGER_LEVEL_4);
|
|
enabled_channel++;
|
|
}
|
|
else
|
|
{
|
|
i2sc_transmit_set_enable(I2S_SEND, 0, i2sc);
|
|
i2sc_transmit_set_enable(I2S_RECEIVE, 0, i2sc);
|
|
}
|
|
|
|
channels_mask >>= 2;
|
|
}
|
|
|
|
configASSERT(enabled_channel * 2 == format->channels);
|
|
|
|
data->channels = format->channels;
|
|
data->block_align = block_align;
|
|
data->buffer_frames = 44100 * delay_ms / 1000;
|
|
configASSERT(data->buffer_frames >= 100);
|
|
free(data->buffer);
|
|
data->buffer_size = data->block_align * data->buffer_frames;
|
|
data->buffer = (char*)malloc(data->buffer_size * BUFFER_COUNT);
|
|
data->buffer_ptr = 0;
|
|
data->next_free_buffer = 0;
|
|
data->stop_signal = 0;
|
|
data->transmit_dma = 0;
|
|
data->dma_in_use_buffer = 0;
|
|
}
|
|
|
|
static void i2s_get_buffer(char** buffer, size_t* frames, void* userdata)
|
|
{
|
|
COMMON_ENTRY;
|
|
|
|
while (data->next_free_buffer == data->dma_in_use_buffer)
|
|
{
|
|
xSemaphoreTake(data->stage_completion_event, portMAX_DELAY);
|
|
}
|
|
|
|
*buffer = data->buffer + data->buffer_size * data->next_free_buffer + data->buffer_ptr;
|
|
*frames = (data->buffer_size - data->buffer_ptr) / data->block_align;
|
|
}
|
|
|
|
static void i2s_release_buffer(size_t frames, void* userdata)
|
|
{
|
|
COMMON_ENTRY;
|
|
|
|
data->buffer_ptr += frames * data->block_align;
|
|
if (data->buffer_ptr >= data->buffer_size)
|
|
{
|
|
data->buffer_ptr = 0;
|
|
if (++data->next_free_buffer >= BUFFER_COUNT)
|
|
data->next_free_buffer = 0;
|
|
}
|
|
}
|
|
|
|
static void i2s_transmit_set_enable(i2s_transmit transmit, int enable, volatile struct i2s_t* i2s)
|
|
{
|
|
union irer_u u_irer;
|
|
union iter_u u_iter;
|
|
|
|
if (transmit == I2S_RECEIVE)
|
|
{
|
|
u_irer.reg_data = readl(&i2s->irer);
|
|
u_irer.irer.rxen = enable;
|
|
writel(u_irer.reg_data, &i2s->irer);
|
|
}
|
|
else
|
|
{
|
|
u_iter.reg_data = readl(&i2s->iter);
|
|
u_iter.iter.txen = enable;
|
|
writel(u_iter.reg_data, &i2s->iter);
|
|
}
|
|
}
|
|
|
|
static void i2sc_transmit_set_enable(i2s_transmit transmit, int enable, volatile struct i2s_channel_t* i2sc)
|
|
{
|
|
union rer_u u_rer;
|
|
union ter_u u_ter;
|
|
|
|
if (transmit == I2S_SEND)
|
|
{
|
|
u_ter.reg_data = readl(&i2sc->ter);
|
|
u_ter.ter.txchenx = enable;
|
|
writel(u_ter.reg_data, &i2sc->ter);
|
|
}
|
|
else
|
|
{
|
|
u_rer.reg_data = readl(&i2sc->rer);
|
|
u_rer.rer.rxchenx = enable;
|
|
writel(u_rer.reg_data, &i2sc->rer);
|
|
}
|
|
}
|
|
|
|
static void i2s_stage_completion_isr(void* userdata)
|
|
{
|
|
COMMON_ENTRY;
|
|
|
|
if (++data->dma_in_use_buffer >= BUFFER_COUNT)
|
|
data->dma_in_use_buffer = 0;
|
|
|
|
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
|
xSemaphoreGiveFromISR(data->stage_completion_event, &xHigherPriorityTaskWoken);
|
|
}
|
|
|
|
static void i2s_start(void* userdata)
|
|
{
|
|
COMMON_ENTRY;
|
|
|
|
if (data->transmit == I2S_SEND)
|
|
{
|
|
configASSERT(!data->transmit_dma);
|
|
|
|
data->stop_signal = 0;
|
|
data->transmit_dma = dma_open_free();
|
|
dma_set_select_request(data->transmit_dma, data->dma_req_base - 1);
|
|
data->dma_in_use_buffer = 0;
|
|
data->stage_completion_event = xSemaphoreCreateCounting(100, 0);
|
|
data->completion_event = xSemaphoreCreateBinary();
|
|
|
|
const volatile void* srcs[BUFFER_COUNT] = {
|
|
data->buffer,
|
|
data->buffer + data->buffer_size};
|
|
volatile void* dests[1] = {
|
|
&i2s->txdma};
|
|
|
|
dma_loop_async(data->transmit_dma, srcs, BUFFER_COUNT, dests, 1, 1, 0, sizeof(uint32_t), data->buffer_size >> 2, 4, i2s_stage_completion_isr, userdata, data->completion_event, &data->stop_signal);
|
|
}
|
|
else
|
|
{
|
|
configASSERT(!data->transmit_dma);
|
|
|
|
data->stop_signal = 0;
|
|
data->transmit_dma = dma_open_free();
|
|
dma_set_select_request(data->transmit_dma, data->dma_req_base);
|
|
data->dma_in_use_buffer = 0;
|
|
data->stage_completion_event = xSemaphoreCreateCounting(100, 0);
|
|
data->completion_event = xSemaphoreCreateBinary();
|
|
|
|
const volatile void* srcs[1] = {
|
|
&i2s->rxdma};
|
|
volatile void* dests[BUFFER_COUNT] = {
|
|
data->buffer,
|
|
data->buffer + data->buffer_size};
|
|
|
|
dma_loop_async(data->transmit_dma, srcs, 1, dests, BUFFER_COUNT, 0, 1, sizeof(uint32_t), data->buffer_size >> 2, 4, i2s_stage_completion_isr, userdata, data->completion_event, &data->stop_signal);
|
|
}
|
|
i2s_transmit_set_enable(data->transmit, 1, i2s);
|
|
}
|
|
|
|
static void i2s_stop(void* userdata)
|
|
{
|
|
COMMON_ENTRY;
|
|
i2s_transmit_set_enable(data->transmit, 0, i2s);
|
|
}
|
|
|
|
static i2s_data dev0_data = {SYSCTL_CLOCK_I2S0, I2S0_BASE_ADDR, SYSCTL_DMA_SELECT_I2S0_RX_REQ, SYSCTL_THRESHOLD_I2S0, {0}};
|
|
static i2s_data dev1_data = {SYSCTL_CLOCK_I2S1, I2S1_BASE_ADDR, SYSCTL_DMA_SELECT_I2S1_RX_REQ, SYSCTL_THRESHOLD_I2S1, {0}};
|
|
static i2s_data dev2_data = {SYSCTL_CLOCK_I2S2, I2S2_BASE_ADDR, SYSCTL_DMA_SELECT_I2S2_RX_REQ, SYSCTL_THRESHOLD_I2S2, {0}};
|
|
|
|
const i2s_driver_t g_i2s_driver_i2s0 = {{&dev0_data, i2s_install, i2s_open, i2s_close}, i2s_config_as_render, i2s_config_as_capture, i2s_get_buffer, i2s_release_buffer, i2s_start, i2s_stop};
|
|
const i2s_driver_t g_i2s_driver_i2s1 = {{&dev1_data, i2s_install, i2s_open, i2s_close}, i2s_config_as_render, i2s_config_as_capture, i2s_get_buffer, i2s_release_buffer, i2s_start, i2s_stop};
|
|
const i2s_driver_t g_i2s_driver_i2s2 = {{&dev2_data, i2s_install, i2s_open, i2s_close}, i2s_config_as_render, i2s_config_as_capture, i2s_get_buffer, i2s_release_buffer, i2s_start, i2s_stop};
|