This is an automated email from the ASF dual-hosted git repository. jerzy pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/mynewt-core.git
commit 6eac7e5002a49dcbfc695fba3471bee610a854f9 Author: Jerzy Kasenberg <jerzy.kasenb...@codecoup.pl> AuthorDate: Fri Mar 27 13:17:56 2020 +0100 hw/drivers/i2s: Add I2S device implementation This adds common code for I2S device handling. --- hw/drivers/i2s/src/i2s.c | 378 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 378 insertions(+) diff --git a/hw/drivers/i2s/src/i2s.c b/hw/drivers/i2s/src/i2s.c new file mode 100644 index 0000000..4d430bd --- /dev/null +++ b/hw/drivers/i2s/src/i2s.c @@ -0,0 +1,378 @@ +/* + * 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 <assert.h> + +#include <os/os_eventq.h> +#include <os/os_sem.h> + +#include <i2s/i2s.h> +#include <i2s/i2s_driver.h> + +/* Function called from i2s_open/os_dev_open */ +static int +i2s_open_handler(struct os_dev *dev, uint32_t timout, void *arg) +{ + struct i2s *i2s; + struct i2s_client *client = (struct i2s_client *)arg; + struct i2s_sample_buffer *buffer; + + if (dev->od_flags & OS_DEV_F_STATUS_OPEN) { + return OS_EBUSY; + } + + i2s = (struct i2s *)dev; + + assert(client == NULL || + (client->sample_buffer_ready_cb != NULL && + client->state_changed_cb != NULL)); + i2s->client = client; + if (client && client->sample_rate) { + i2s->sample_rate = client->sample_rate; + } + + if (i2s->direction == I2S_IN) { + while (NULL != (buffer = i2s_buffer_get(i2s, 0))) { + i2s_buffer_put(i2s, buffer); + } + } else { + i2s_start(i2s); + } + + return OS_OK; +} + +/* Function called from i2s_close/os_dev_close */ +static int +i2s_close_handler(struct os_dev *dev) +{ + struct i2s *i2s; + + i2s = (struct i2s *)dev; + i2s_stop(i2s); + i2s->client = NULL; + + return OS_OK; +} + +static int +i2s_suspend_handler(struct os_dev *dev, os_time_t timeout, int arg) +{ + return i2s_driver_suspend((struct i2s *)dev, timeout, arg); +} + +static int +i2s_resume_handler(struct os_dev *dev) +{ + return i2s_driver_resume((struct i2s *)dev); +} + +static void +i2s_add_to_user_queue(struct i2s *i2s, struct i2s_sample_buffer *buffer) +{ + STAILQ_INSERT_TAIL(&i2s->user_queue, buffer, next_buffer); + os_sem_release(&i2s->user_queue_buffer_count); +} + +static void +i2s_add_to_driver_queue(struct i2s *i2s, struct i2s_sample_buffer *buffer) +{ + STAILQ_INSERT_TAIL(&i2s->driver_queue, buffer, next_buffer); + if (i2s->state != I2S_STATE_STOPPED) { + i2s_driver_buffer_queued(i2s); + } +} + +static void +i2s_buffers_from_pool(struct i2s *i2s, struct i2s_buffer_pool *pool) +{ + int i; + int sr; + struct i2s_sample_buffer *buffers; + uintptr_t sample_data; + uint32_t samples_per_buffer; + + if (i2s->direction != I2S_IN && pool != NULL) { + os_sem_init(&i2s->user_queue_buffer_count, pool->buffer_count); + } else { + os_sem_init(&i2s->user_queue_buffer_count, 0); + } + + i2s->buffer_pool = pool; + if (pool == NULL) { + return; + } + + buffers = (struct i2s_sample_buffer *)pool->buffers; + + samples_per_buffer = pool->buffer_size / i2s->sample_size_in_bytes; + sample_data = (uintptr_t)&buffers[pool->buffer_count]; + + for (i = 0; i < pool->buffer_count; ++i) { + buffers[i].capacity = samples_per_buffer; + buffers[i].sample_data = (void *)sample_data; + buffers[i].sample_count = 0; + sample_data += pool->buffer_size; + + OS_ENTER_CRITICAL(sr); + if (i2s->direction == I2S_IN) { + i2s_add_to_driver_queue(i2s, &buffers[i]); + } else { + STAILQ_INSERT_TAIL(&i2s->user_queue, &buffers[i], next_buffer); + } + OS_EXIT_CRITICAL(sr); + } +} + +int +i2s_init(struct i2s *i2s, struct i2s_buffer_pool *pool) +{ + STAILQ_INIT(&i2s->driver_queue); + STAILQ_INIT(&i2s->user_queue); + + i2s->state = I2S_STATE_STOPPED; + + i2s_buffers_from_pool(i2s, pool); + + i2s->dev.od_handlers.od_open = i2s_open_handler; + i2s->dev.od_handlers.od_close = i2s_close_handler; + i2s->dev.od_handlers.od_suspend = i2s_suspend_handler; + i2s->dev.od_handlers.od_resume = i2s_resume_handler; + + return 0; +} + +struct i2s * +i2s_open(const char *name, uint32_t timeout, struct i2s_client *client) +{ + return (struct i2s *)os_dev_open(name, timeout, client); +} + +int +i2s_close(struct i2s *i2s) +{ + return os_dev_close(&i2s->dev); +} + +int +i2s_write(struct i2s *i2s, void *samples, uint32_t sample_buffer_size) +{ + uint32_t sample_pair_size = (i2s->sample_size_in_bytes * 2); + size_t sample_count; + struct i2s_sample_buffer *buffer; + + assert(i2s->direction == I2S_OUT); + + buffer = i2s_buffer_get(i2s, OS_WAIT_FOREVER); + + if (buffer == NULL) { + return -1; + } + + /* Calculate buffer size */ + sample_count = sample_buffer_size / sample_pair_size; + if (buffer->capacity < sample_count) { + sample_count = buffer->capacity; + } + + sample_buffer_size = sample_count * sample_pair_size; + + /* Move data to buffer */ + memcpy(buffer->sample_data, samples, sample_buffer_size); + + /* Pass buffer to output device */ + i2s_buffer_put(i2s, buffer); + + return sample_buffer_size; +} + +int +i2s_read(struct i2s *i2s, void *samples, uint32_t sample_buffer_size) +{ + int sr; + uint32_t sample_pair_size = (i2s->sample_size_in_bytes * 2); + size_t sample_capacity = sample_buffer_size / sample_pair_size; + struct i2s_sample_buffer *buffer; + + assert(i2s->direction == I2S_IN); + + if (i2s->state == I2S_STATE_STOPPED) { + i2s_start(i2s); + } + + buffer = i2s_buffer_get(i2s, OS_WAIT_FOREVER); + + if (buffer == NULL) { + return 0; + } + + if (sample_capacity > buffer->sample_count) { + sample_capacity = buffer->sample_count; + } + sample_buffer_size = sample_capacity * sample_pair_size; + memcpy(samples, buffer->sample_data, sample_buffer_size); + if (sample_capacity < buffer->sample_count) { + /* Not all data consumed, modify buffer and put buffer at the front again */ + memmove(buffer->sample_data, buffer->sample_data, + (buffer->sample_count - sample_capacity) * sample_pair_size); + OS_ENTER_CRITICAL(sr); + STAILQ_INSERT_HEAD(&i2s->user_queue, buffer, next_buffer); + OS_EXIT_CRITICAL(sr); + } + + return sample_buffer_size; +} + +int +i2s_start(struct i2s *i2s) +{ + int rc = I2S_OK; + + if (i2s->state != I2S_STATE_RUNNING) { + if (STAILQ_EMPTY(&i2s->driver_queue)) { + i2s->state = I2S_STATE_OUT_OF_BUFFERS; + rc = I2S_ERR_NO_BUFFER; + } else { + rc = i2s_driver_start(i2s); + if (rc == I2S_OK) { + i2s->state = I2S_STATE_RUNNING; + i2s->client->state_changed_cb(i2s, I2S_STATE_RUNNING); + } + } + } + return rc; +} + +int +i2s_stop(struct i2s *i2s) +{ + struct i2s_sample_buffer *buffer; + + i2s_driver_stop(i2s); + + i2s->state = I2S_STATE_STOPPED; + if (i2s->client) { + i2s->client->state_changed_cb(i2s, i2s->state); + } + + if (i2s->direction == I2S_IN) { + while (NULL != (buffer = i2s_buffer_get(i2s, 0))) { + i2s_add_to_driver_queue(i2s, buffer); + } + } else { + while (!STAILQ_EMPTY(&i2s->driver_queue)) { + buffer = STAILQ_FIRST(&i2s->driver_queue); + STAILQ_REMOVE_HEAD(&i2s->driver_queue, next_buffer); + i2s_add_to_user_queue(i2s, buffer); + } + } + return 0; +} + +int +i2s_available_buffers(struct i2s *i2s) +{ + return os_sem_get_count(&i2s->user_queue_buffer_count); +} + +struct i2s_sample_buffer * +i2s_buffer_get(struct i2s *i2s, os_time_t timeout) +{ + int sr; + struct i2s_sample_buffer *buffer = NULL; + + if (OS_OK == os_sem_pend(&i2s->user_queue_buffer_count, timeout)) { + OS_ENTER_CRITICAL(sr); + buffer = STAILQ_FIRST(&i2s->user_queue); + assert(buffer); + STAILQ_REMOVE_HEAD(&i2s->user_queue, next_buffer); + OS_EXIT_CRITICAL(sr); + assert(buffer->capacity > 0); + } + + return buffer; +} + +int +i2s_buffer_put(struct i2s *i2s, struct i2s_sample_buffer *buffer) +{ + int sr; + int rc = I2S_OK; + + /* + * Output sample buffer without samples? + * Don't bother driver, just put in client queue. + */ + if (i2s->direction == I2S_OUT && buffer->sample_count == 0) { + i2s_driver_buffer_put(i2s, buffer); + } else { + OS_ENTER_CRITICAL(sr); + i2s_add_to_driver_queue(i2s, buffer); + OS_EXIT_CRITICAL(sr); + + if (i2s->state == I2S_STATE_OUT_OF_BUFFERS) { + rc = i2s_driver_start(i2s); + } + } + return rc; +} + +struct i2s_sample_buffer * +i2s_driver_buffer_get(struct i2s *i2s) +{ + int sr; + struct i2s_sample_buffer *buffer; + + OS_ENTER_CRITICAL(sr); + buffer = STAILQ_FIRST(&i2s->driver_queue); + if (buffer) { + STAILQ_REMOVE_HEAD(&i2s->driver_queue, next_buffer); + } + OS_EXIT_CRITICAL(sr); + + return buffer; +} + +void +i2s_driver_buffer_put(struct i2s *i2s, struct i2s_sample_buffer *buffer) +{ + int sr; + + assert(buffer != NULL && i2s != NULL); + if (i2s->client) { + /* If callback returns 1, buffer is not added to the pool */ + if (i2s->client->sample_buffer_ready_cb(i2s, buffer) == 1) { + return; + } + } + OS_ENTER_CRITICAL(sr); + i2s_add_to_user_queue(i2s, buffer); + OS_EXIT_CRITICAL(sr); +} + +void +i2s_driver_state_changed(struct i2s *i2s, enum i2s_state state) +{ + if (i2s->state != state) { + i2s->state = state; + if (i2s->client) { + i2s->client->state_changed_cb(i2s, state); + } + } +}