blob: 751d93c0e0d30a7a8912b25eaaf9064b57fbcb3e [file] [log] [blame]
/*
* 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.
*/
#include <os/mynewt.h>
#include <bsp/bsp.h>
#include <i2s/i2s.h>
#include <i2s/i2s_driver.h>
#include <i2s_nrfx/i2s_nrfx.h>
#include <nrfx/drivers/include/nrfx_i2s.h>
struct i2s_nrfx {
nrfx_i2s_config_t nrfx_i2s_cfg;
bool running;
int8_t nrfx_queued_count;
struct i2s *i2s;
struct i2s_sample_buffer *nrfx_buffers[2];
};
static struct i2s_nrfx i2s_nrfx;
static void
nrfx_add_buffer(struct i2s *i2s, struct i2s_sample_buffer *buffer)
{
nrfx_i2s_buffers_t nrfx_buffers = {0};
nrfx_err_t err;
assert(i2s != NULL);
if (buffer == NULL) {
return;
}
if (i2s->direction == I2S_OUT || i2s->direction == I2S_OUT_IN) {
nrfx_buffers.p_tx_buffer = buffer->sample_data;
}
if (i2s->direction == I2S_IN || i2s->direction == I2S_OUT_IN) {
nrfx_buffers.p_rx_buffer = buffer->sample_data;
}
assert(i2s_nrfx.nrfx_queued_count < 2);
assert(i2s_nrfx.nrfx_buffers[i2s_nrfx.nrfx_queued_count] == NULL);
i2s_nrfx.nrfx_buffers[i2s_nrfx.nrfx_queued_count] = buffer;
i2s_nrfx.nrfx_queued_count++;
if (i2s_nrfx.nrfx_queued_count == 1) {
i2s_driver_state_changed (i2s, I2S_STATE_RUNNING);
err = nrfx_i2s_start(&nrfx_buffers, buffer->sample_count * i2s->sample_size_in_bytes / 4, 0);
} else {
err = nrfx_i2s_next_buffers_set(&nrfx_buffers);
}
assert(err == NRFX_SUCCESS);
}
static void
feed_nrfx(void)
{
struct i2s_sample_buffer *buffer;
buffer = i2s_driver_buffer_get(i2s_nrfx.i2s);
nrfx_add_buffer(i2s_nrfx.i2s, buffer);
}
static void
i2s_nrfx_data_handler(const nrfx_i2s_buffers_t *p_released, uint32_t status)
{
struct i2s_sample_buffer *buffer;
if (p_released != NULL &&
(p_released->p_rx_buffer != NULL || p_released->p_tx_buffer != NULL)) {
i2s_nrfx.nrfx_queued_count--;
assert(i2s_nrfx.nrfx_queued_count >= 0);
buffer = i2s_nrfx.nrfx_buffers[0];
assert(buffer->sample_data == p_released->p_tx_buffer || buffer->sample_data == p_released->p_rx_buffer);
i2s_nrfx.nrfx_buffers[0] = i2s_nrfx.nrfx_buffers[1];
i2s_nrfx.nrfx_buffers[1] = NULL;
i2s_driver_buffer_put(i2s_nrfx.i2s, buffer);
}
if (i2s_nrfx.running && i2s_nrfx.nrfx_queued_count < 2) {
assert(i2s_nrfx.nrfx_buffers[1] == NULL);
feed_nrfx();
}
if (status == NRFX_I2S_STATUS_TRANSFER_STOPPED) {
i2s_driver_state_changed(i2s_nrfx.i2s, I2S_STATE_STOPPED);
}
}
static int
i2s_nrfx_init(struct i2s *i2s, const struct i2s_cfg *cfg)
{
int rc;
i2s_nrfx.i2s = i2s;
NVIC_SetVector(nrfx_get_irq_number(NRF_I2S), (uint32_t)nrfx_i2s_irq_handler);
i2s_nrfx.nrfx_i2s_cfg = cfg->nrfx_i2s_cfg;
switch (cfg->nrfx_i2s_cfg.sample_width) {
case NRF_I2S_SWIDTH_8BIT:
#if defined(I2S_CONFIG_SWIDTH_SWIDTH_8BitIn16)
case NRF_I2S_SWIDTH_8BIT_IN16BIT:
#endif
#if defined(I2S_CONFIG_SWIDTH_SWIDTH_8BitIn32)
case NRF_I2S_SWIDTH_8BIT_IN32BIT:
#endif
i2s->sample_size_in_bytes = 1;
break;
case NRF_I2S_SWIDTH_16BIT:
#if defined(I2S_CONFIG_SWIDTH_SWIDTH_16BitIn32)
case NRF_I2S_SWIDTH_16BIT_IN32BIT:
#endif
i2s->sample_size_in_bytes = 2;
break;
case NRF_I2S_SWIDTH_24BIT:
#if defined(I2S_CONFIG_SWIDTH_SWIDTH_24BitIn32)
case NRF_I2S_SWIDTH_24BIT_IN32BIT:
#endif
#if defined(I2S_CONFIG_SWIDTH_SWIDTH_32Bit)
case NRF_I2S_SWIDTH_32BIT:
#endif
i2s->sample_size_in_bytes = 4;
break;
}
i2s->direction = I2S_INVALID;
if (cfg->nrfx_i2s_cfg.sdin_pin != NRFX_I2S_PIN_NOT_USED) {
i2s->direction = I2S_IN;
}
if (cfg->nrfx_i2s_cfg.sdout_pin != NRFX_I2S_PIN_NOT_USED) {
i2s->direction |= I2S_OUT;
}
rc = i2s_init(i2s, cfg->pool);
if (rc != OS_OK) {
nrfx_i2s_uninit();
goto end;
}
i2s->sample_rate = cfg->sample_rate;
i2s->driver_data = &i2s_nrfx;
end:
return rc;
}
int
i2s_create(struct i2s *i2s, const char *name, const struct i2s_cfg *cfg)
{
return os_dev_create(&i2s->dev, name, OS_DEV_INIT_PRIMARY,
100, (os_dev_init_func_t)i2s_nrfx_init, (void *)cfg);
}
int
i2s_driver_stop(struct i2s *i2s)
{
struct i2s_sample_buffer *buffer;
if (i2s_nrfx.running) {
i2s_nrfx.running = false;
nrfx_i2s_stop();
}
while (NULL != (buffer = i2s_driver_buffer_get(i2s))) {
i2s_driver_buffer_put(i2s, buffer);
}
return 0;
}
/* Settings are stored for following sampling frequencies:
* 8000, 16000, 22050, 32000, 441000, 48000 */
struct i2s_clock_cfg {
nrf_i2s_mck_t mck_setup;
nrf_i2s_ratio_t ratio;
};
static const uint16_t sample_rates[] = { 8000, 16000, 22050, 32000, 44100, 48000 };
static const struct i2s_clock_cfg mck_for_8_16_bit_samples[] = {
{ NRF_I2S_MCK_32MDIV125, NRF_I2S_RATIO_32X}, /* 8000: 8000 LRCK error 0.0% */
{ NRF_I2S_MCK_32MDIV63, NRF_I2S_RATIO_32X}, /* 16000: 15873.016 LRCK error -0.8% */
{ NRF_I2S_MCK_32MDIV15, NRF_I2S_RATIO_96X}, /* 22050: 22222.222 LRCK error 0.8% */
{ NRF_I2S_MCK_32MDIV31, NRF_I2S_RATIO_32X}, /* 32000: 32258.065 LRCK error 0.8% */
{ NRF_I2S_MCK_32MDIV23, NRF_I2S_RATIO_32X}, /* 44100: 43478.261 LRCK error -1.4% */
{ NRF_I2S_MCK_32MDIV21, NRF_I2S_RATIO_32X} /* 48000: 47619.048 LRCK error -0.8% */
};
static const struct i2s_clock_cfg mck_for_24_bit_samples[] = {
{ NRF_I2S_MCK_32MDIV21, NRF_I2S_RATIO_192X}, /* 8000: 7936.508 LRCK error -0.8% */
{ NRF_I2S_MCK_32MDIV42, NRF_I2S_RATIO_48X}, /* 16000: 15873.016 LRCK error -0.8% */
{ NRF_I2S_MCK_32MDIV30, NRF_I2S_RATIO_48X}, /* 22050: 22222.222 LRCK error 0.8% */
{ NRF_I2S_MCK_32MDIV21, NRF_I2S_RATIO_48X}, /* 32000: 31746.032 LRCK error -0.8% */
{ NRF_I2S_MCK_32MDIV15, NRF_I2S_RATIO_48X}, /* 44100: 44444.444 LRCK error 0.8% */
{ NRF_I2S_MCK_32MDIV15, NRF_I2S_RATIO_48X} /* 48000: 44444.444 LRCK error -7.4% */
};
static void
i2s_nrfx_select_clock_cfg(nrfx_i2s_config_t *cfg, uint32_t sample_rate)
{
int i;
if (cfg->ratio != 0 || cfg->mck_setup != 0) {
/* User provided custom clock setup, no need to use stock values */
return;
}
for (i = 0; i < ARRAY_SIZE(sample_rates); ++i) {
if (sample_rates[i] == sample_rate) {
if (cfg->sample_width == NRF_I2S_SWIDTH_24BIT) {
cfg->ratio = mck_for_24_bit_samples[i].ratio;
cfg->mck_setup = mck_for_24_bit_samples[i].mck_setup;
} else {
cfg->ratio = mck_for_8_16_bit_samples[i].ratio;
cfg->mck_setup = mck_for_8_16_bit_samples[i].mck_setup;
}
break;
}
}
assert(cfg->mck_setup);
}
int
i2s_driver_start(struct i2s *i2s)
{
int rc = 0;
if (!i2s_nrfx.running) {
i2s_nrfx.running = true;
i2s_nrfx_select_clock_cfg(&i2s_nrfx.nrfx_i2s_cfg, i2s->sample_rate);
nrfx_i2s_init(&i2s_nrfx.nrfx_i2s_cfg, i2s_nrfx_data_handler);
assert(i2s_nrfx.nrfx_buffers[0] == NULL);
assert(i2s_nrfx.nrfx_buffers[1] == NULL);
assert(!STAILQ_EMPTY(&i2s->driver_queue));
i2s_nrfx.nrfx_queued_count = 0;
feed_nrfx();
}
return rc;
}
void
i2s_driver_buffer_queued(struct i2s *i2s)
{
if (i2s_nrfx.nrfx_queued_count < 2 && i2s_nrfx.running) {
feed_nrfx();
}
}
int
i2s_driver_suspend(struct i2s *i2s, os_time_t timeout, int arg)
{
return OS_OK;
}
int
i2s_driver_resume(struct i2s *i2s)
{
return OS_OK;
}