| /* |
| * 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; |
| int rc; |
| |
| 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 { |
| rc = i2s_start(i2s); |
| /* |
| * No buffers on opening output I2S stream is to be expected |
| * and is not error preventing device usage. |
| */ |
| if (rc != OS_OK && rc != I2S_ERR_NO_BUFFER) { |
| return rc; |
| } |
| } |
| |
| 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 j; |
| 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) { |
| for (j = 0; j < I2S_BUFFER_GUARD; ++j, ++sample_data) { |
| *(uint8_t *)sample_data = MYNEWT_VAL(I2S_BUFFER_GUARD_PATTERN); |
| } |
| buffers[i].capacity = samples_per_buffer; |
| buffers[i].sample_data = (void *)sample_data; |
| buffers[i].sample_count = 0; |
| sample_data += pool->buffer_size; |
| for (j = 0; j < I2S_BUFFER_GUARD; ++j, ++sample_data) { |
| *(uint8_t *)sample_data = MYNEWT_VAL(I2S_BUFFER_GUARD_PATTERN); |
| } |
| |
| 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) |
| { |
| 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 / i2s->sample_size_in_bytes; |
| if (buffer->capacity < sample_count) { |
| sample_count = buffer->capacity; |
| } |
| |
| sample_buffer_size = sample_count * i2s->sample_size_in_bytes; |
| |
| /* Move data to buffer */ |
| memcpy(buffer->sample_data, samples, sample_buffer_size); |
| buffer->sample_count = sample_count; |
| |
| /* 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; |
| if (i2s->client) { |
| 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; |
| int i; |
| const uint8_t *pre; |
| const uint8_t *post; |
| |
| if (I2S_BUFFER_GUARD) { |
| pre = (const uint8_t *)(buffer->sample_data); |
| post = (const uint8_t *)(buffer->sample_data) + buffer->capacity * i2s->sample_size_in_bytes; |
| for (i = 0; i < I2S_BUFFER_GUARD; ++i) { |
| /* |
| * Assert here means that a memory was corrupted in some way, |
| * no indication that the buffer was overwritten due to buffer size mismatch, |
| * but rather some other memory manipulation went amiss. |
| */ |
| assert(*--pre == MYNEWT_VAL(I2S_BUFFER_GUARD_PATTERN)); |
| /* Assert here usually means that more data was written to the buffer then it's size. */ |
| assert(*post++ == MYNEWT_VAL(I2S_BUFFER_GUARD_PATTERN)); |
| } |
| } |
| /* |
| * 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); |
| } |
| } |
| } |