2021-02-02 20:14:39 -05:00
|
|
|
/*
|
|
|
|
* This file is part of the MicroPython project, http://micropython.org/
|
|
|
|
*
|
|
|
|
* The MIT License (MIT)
|
|
|
|
*
|
|
|
|
* Copyright (c) 2021 Scott Shawcroft for Adafruit Industries
|
|
|
|
*
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
|
|
* in the Software without restriction, including without limitation the rights
|
|
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
|
|
* furnished to do so, subject to the following conditions:
|
|
|
|
*
|
|
|
|
* The above copyright notice and this permission notice shall be included in
|
|
|
|
* all copies or substantial portions of the Software.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
|
* THE SOFTWARE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "audio_dma.h"
|
|
|
|
|
|
|
|
#include "shared-bindings/audiocore/RawSample.h"
|
|
|
|
#include "shared-bindings/audiocore/WaveFile.h"
|
2021-08-10 22:00:09 -04:00
|
|
|
#include "shared-bindings/microcontroller/__init__.h"
|
2021-02-02 20:14:39 -05:00
|
|
|
#include "supervisor/background_callback.h"
|
|
|
|
|
|
|
|
#include "py/mpstate.h"
|
|
|
|
#include "py/runtime.h"
|
|
|
|
|
|
|
|
#include "src/rp2_common/hardware_irq/include/hardware/irq.h"
|
|
|
|
|
|
|
|
#if CIRCUITPY_AUDIOPWMIO || CIRCUITPY_AUDIOBUSIO
|
|
|
|
|
2021-02-23 18:50:00 -05:00
|
|
|
void audio_dma_reset(void) {
|
2021-08-03 19:12:14 -04:00
|
|
|
for (size_t channel = 0; channel < NUM_DMA_CHANNELS; channel++) {
|
2021-02-23 18:50:00 -05:00
|
|
|
if (MP_STATE_PORT(playing_audio)[channel] == NULL) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
audio_dma_stop(MP_STATE_PORT(playing_audio)[channel]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-29 13:26:10 -04:00
|
|
|
|
2021-08-10 22:00:09 -04:00
|
|
|
STATIC void audio_dma_convert_samples(
|
|
|
|
audio_dma_t *dma,
|
|
|
|
uint8_t *input, uint32_t input_length,
|
|
|
|
uint8_t *available_output_buffer, uint32_t available_output_buffer_length,
|
|
|
|
uint8_t **output, uint32_t *output_length) {
|
2021-07-29 13:26:10 -04:00
|
|
|
|
2021-02-02 20:14:39 -05:00
|
|
|
#pragma GCC diagnostic push
|
|
|
|
#pragma GCC diagnostic ignored "-Wcast-align"
|
2021-08-10 22:00:09 -04:00
|
|
|
|
|
|
|
// Check whether a conversion is necessary
|
2021-02-02 20:14:39 -05:00
|
|
|
if (dma->signed_to_unsigned ||
|
2021-03-15 09:57:36 -04:00
|
|
|
dma->unsigned_to_signed ||
|
|
|
|
dma->sample_spacing > 1 ||
|
|
|
|
(dma->sample_resolution != dma->output_resolution)) {
|
2021-08-10 22:00:09 -04:00
|
|
|
|
|
|
|
// Must convert.
|
|
|
|
// Write the conversion into the passed-in output buffer
|
|
|
|
*output = available_output_buffer;
|
|
|
|
*output_length = input_length / dma->sample_spacing;
|
|
|
|
|
|
|
|
if (*output_length > available_output_buffer_length) {
|
|
|
|
mp_raise_RuntimeError(translate("Internal audio buffer too small"));
|
|
|
|
}
|
|
|
|
|
2021-02-02 20:14:39 -05:00
|
|
|
uint32_t out_i = 0;
|
|
|
|
if (dma->sample_resolution <= 8 && dma->output_resolution > 8) {
|
2021-08-10 22:00:09 -04:00
|
|
|
// reading bytes, writing 16-bit words, so output buffer will be bigger.
|
|
|
|
|
|
|
|
*output_length = *output_length * 2;
|
|
|
|
if (*output_length > available_output_buffer_length) {
|
2021-07-29 13:26:10 -04:00
|
|
|
mp_raise_RuntimeError(translate("Internal audio buffer too small"));
|
|
|
|
}
|
|
|
|
|
2021-02-02 20:14:39 -05:00
|
|
|
size_t shift = dma->output_resolution - dma->sample_resolution;
|
|
|
|
|
2021-08-10 22:00:09 -04:00
|
|
|
for (uint32_t i = 0; i < input_length; i += dma->sample_spacing) {
|
2021-02-02 20:14:39 -05:00
|
|
|
if (dma->signed_to_unsigned) {
|
2021-08-10 22:00:09 -04:00
|
|
|
((uint16_t *)*output)[out_i] = ((uint16_t)((int8_t *)input)[i] + 0x80) << shift;
|
2021-02-02 20:14:39 -05:00
|
|
|
} else if (dma->unsigned_to_signed) {
|
2021-08-10 22:00:09 -04:00
|
|
|
((int16_t *)*output)[out_i] = ((int16_t)((uint8_t *)input)[i] - 0x80) << shift;
|
2021-02-02 20:14:39 -05:00
|
|
|
} else {
|
2021-08-10 22:00:09 -04:00
|
|
|
((uint16_t *)*output)[out_i] = ((uint16_t)((uint8_t *)input)[i]) << shift;
|
2021-02-02 20:14:39 -05:00
|
|
|
}
|
|
|
|
out_i += 1;
|
|
|
|
}
|
|
|
|
} else if (dma->sample_resolution <= 8 && dma->output_resolution <= 8) {
|
2021-08-10 22:00:09 -04:00
|
|
|
for (uint32_t i = 0; i < input_length; i += dma->sample_spacing) {
|
2021-02-02 20:14:39 -05:00
|
|
|
if (dma->signed_to_unsigned) {
|
2021-08-10 22:00:09 -04:00
|
|
|
((uint8_t *)*output)[out_i] = ((int8_t *)input)[i] + 0x80;
|
2021-02-02 20:14:39 -05:00
|
|
|
} else if (dma->unsigned_to_signed) {
|
2021-08-10 22:00:09 -04:00
|
|
|
((int8_t *)*output)[out_i] = ((uint8_t *)input)[i] - 0x80;
|
2021-02-02 20:14:39 -05:00
|
|
|
} else {
|
2021-08-10 22:00:09 -04:00
|
|
|
((uint8_t *)*output)[out_i] = ((uint8_t *)input)[i];
|
2021-02-02 20:14:39 -05:00
|
|
|
}
|
|
|
|
out_i += 1;
|
|
|
|
}
|
|
|
|
} else if (dma->sample_resolution > 8 && dma->output_resolution > 8) {
|
|
|
|
size_t shift = 16 - dma->output_resolution;
|
2021-08-10 22:00:09 -04:00
|
|
|
for (uint32_t i = 0; i < input_length / 2; i += dma->sample_spacing) {
|
2021-02-02 20:14:39 -05:00
|
|
|
if (dma->signed_to_unsigned) {
|
2021-08-10 22:00:09 -04:00
|
|
|
((uint16_t *)*output)[out_i] = ((int16_t *)input)[i] + 0x8000;
|
2021-03-15 09:57:36 -04:00
|
|
|
} else if (dma->unsigned_to_signed) {
|
2021-08-10 22:00:09 -04:00
|
|
|
((int16_t *)*output)[out_i] = ((uint16_t *)input)[i] - 0x8000;
|
2021-02-02 20:14:39 -05:00
|
|
|
} else {
|
2021-08-10 22:00:09 -04:00
|
|
|
((uint16_t *)*output)[out_i] = ((uint16_t *)input)[i];
|
2021-02-02 20:14:39 -05:00
|
|
|
}
|
|
|
|
if (dma->output_resolution < 16) {
|
|
|
|
if (dma->output_signed) {
|
2021-08-10 22:00:09 -04:00
|
|
|
((int16_t *)*output)[out_i] = ((int16_t *)*output)[out_i] >> shift;
|
2021-02-02 20:14:39 -05:00
|
|
|
} else {
|
2021-08-10 22:00:09 -04:00
|
|
|
((uint16_t *)*output)[out_i] = ((uint16_t *)*output)[out_i] >> shift;
|
2021-02-02 20:14:39 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
out_i += 1;
|
|
|
|
}
|
2021-07-29 13:26:10 -04:00
|
|
|
} else {
|
|
|
|
// (dma->sample_resolution > 8 && dma->output_resolution <= 8)
|
|
|
|
// Not currently used, but might be in the future.
|
|
|
|
mp_raise_RuntimeError(translate("Audio conversion not implemented"));
|
2021-02-02 20:14:39 -05:00
|
|
|
}
|
|
|
|
} else {
|
2021-08-10 22:00:09 -04:00
|
|
|
// No conversion necessary. Designate the input buffer as the output buffer.
|
|
|
|
*output = input;
|
|
|
|
*output_length = input_length;
|
2021-02-02 20:14:39 -05:00
|
|
|
}
|
|
|
|
#pragma GCC diagnostic pop
|
|
|
|
}
|
|
|
|
|
2021-08-10 22:00:09 -04:00
|
|
|
// channel_idx is 0 or 1.
|
|
|
|
STATIC void audio_dma_load_next_block(audio_dma_t *dma, size_t buffer_idx) {
|
|
|
|
size_t dma_channel = dma->channel[buffer_idx];
|
2021-02-02 20:14:39 -05:00
|
|
|
|
|
|
|
audioio_get_buffer_result_t get_buffer_result;
|
2021-08-10 22:00:09 -04:00
|
|
|
uint8_t *sample_buffer;
|
|
|
|
uint32_t sample_buffer_length;
|
2021-02-02 20:14:39 -05:00
|
|
|
get_buffer_result = audiosample_get_buffer(dma->sample,
|
2021-08-10 22:00:09 -04:00
|
|
|
dma->single_channel_output, dma->audio_channel, &sample_buffer, &sample_buffer_length);
|
2021-02-02 20:14:39 -05:00
|
|
|
|
|
|
|
if (get_buffer_result == GET_BUFFER_ERROR) {
|
|
|
|
audio_dma_stop(dma);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-08-10 22:00:09 -04:00
|
|
|
// Convert the sample format resolution and signedness, as necessary.
|
|
|
|
// The input sample buffer is what was read from a file or a raw sample buffer.
|
|
|
|
// The output buffer is one of the DMA buffers (passed in), or if no conversion was done,
|
|
|
|
// the original sample buffer (to save copying).
|
2021-02-02 20:14:39 -05:00
|
|
|
|
2021-08-10 22:00:09 -04:00
|
|
|
// audio_dma_convert_samples() will write the converted samples into the given output
|
|
|
|
// buffer if necessary. If no conversion was needed, it will return the sample buffer
|
|
|
|
// as the output buffer.
|
|
|
|
uint8_t *output_buffer;
|
|
|
|
uint32_t output_buffer_length;
|
|
|
|
|
|
|
|
audio_dma_convert_samples(dma, sample_buffer, sample_buffer_length,
|
|
|
|
dma->buffer[buffer_idx], dma->buffer_length[buffer_idx],
|
|
|
|
&output_buffer, &output_buffer_length);
|
2021-02-02 20:14:39 -05:00
|
|
|
|
|
|
|
dma_channel_set_read_addr(dma_channel, output_buffer, false /* trigger */);
|
2021-08-10 22:00:09 -04:00
|
|
|
dma_channel_set_trans_count(dma_channel, output_buffer_length / dma->output_size, false /* trigger */);
|
2021-07-30 13:54:33 -04:00
|
|
|
|
2021-02-02 20:14:39 -05:00
|
|
|
if (get_buffer_result == GET_BUFFER_DONE) {
|
|
|
|
if (dma->loop) {
|
2021-06-30 13:11:54 -04:00
|
|
|
audiosample_reset_buffer(dma->sample, dma->single_channel_output, dma->audio_channel);
|
2021-02-02 20:14:39 -05:00
|
|
|
} else {
|
2021-08-10 22:00:09 -04:00
|
|
|
// Set channel trigger to ourselves so we don't keep going.
|
|
|
|
dma_channel_hw_t *c = &dma_hw->ch[dma_channel];
|
|
|
|
c->al1_ctrl =
|
|
|
|
(c->al1_ctrl & ~DMA_CH0_CTRL_TRIG_CHAIN_TO_BITS) |
|
|
|
|
(dma_channel << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB);
|
|
|
|
|
2021-07-30 13:54:33 -04:00
|
|
|
if (output_buffer_length == 0 &&
|
|
|
|
!dma_channel_is_busy(dma->channel[0]) &&
|
|
|
|
!dma_channel_is_busy(dma->channel[1])) {
|
|
|
|
// No data has been read, and both DMA channels have now finished, so it's safe to stop.
|
|
|
|
audio_dma_stop(dma);
|
2021-08-03 19:12:14 -04:00
|
|
|
dma->playing_in_progress = false;
|
2021-07-30 13:54:33 -04:00
|
|
|
}
|
2021-02-02 20:14:39 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Playback should be shutdown before calling this.
|
2021-08-10 22:00:09 -04:00
|
|
|
audio_dma_result audio_dma_setup_playback(
|
|
|
|
audio_dma_t *dma,
|
2021-03-15 09:57:36 -04:00
|
|
|
mp_obj_t sample,
|
|
|
|
bool loop,
|
2021-06-30 13:11:54 -04:00
|
|
|
bool single_channel_output,
|
2021-03-15 09:57:36 -04:00
|
|
|
uint8_t audio_channel,
|
|
|
|
bool output_signed,
|
|
|
|
uint8_t output_resolution,
|
|
|
|
uint32_t output_register_address,
|
|
|
|
uint8_t dma_trigger_source) {
|
2021-08-10 22:00:09 -04:00
|
|
|
|
2021-06-30 13:11:54 -04:00
|
|
|
// Use two DMA channels to play because the DMA can't wrap to itself without the
|
2021-02-02 20:14:39 -05:00
|
|
|
// buffer being power of two aligned.
|
2021-07-08 08:14:37 -04:00
|
|
|
int dma_channel_0_maybe = dma_claim_unused_channel(false);
|
|
|
|
if (dma_channel_0_maybe < 0) {
|
2021-02-02 20:14:39 -05:00
|
|
|
return AUDIO_DMA_DMA_BUSY;
|
|
|
|
}
|
|
|
|
|
2021-07-08 08:14:37 -04:00
|
|
|
int dma_channel_1_maybe = dma_claim_unused_channel(false);
|
|
|
|
if (dma_channel_1_maybe < 0) {
|
|
|
|
dma_channel_unclaim((uint)dma_channel_0_maybe);
|
|
|
|
return AUDIO_DMA_DMA_BUSY;
|
|
|
|
}
|
|
|
|
|
|
|
|
dma->channel[0] = (uint8_t)dma_channel_0_maybe;
|
|
|
|
dma->channel[1] = (uint8_t)dma_channel_1_maybe;
|
|
|
|
|
2021-02-02 20:14:39 -05:00
|
|
|
dma->sample = sample;
|
|
|
|
dma->loop = loop;
|
2021-06-30 13:11:54 -04:00
|
|
|
dma->single_channel_output = single_channel_output;
|
2021-02-02 20:14:39 -05:00
|
|
|
dma->audio_channel = audio_channel;
|
|
|
|
dma->signed_to_unsigned = false;
|
|
|
|
dma->unsigned_to_signed = false;
|
|
|
|
dma->output_signed = output_signed;
|
|
|
|
dma->sample_spacing = 1;
|
|
|
|
dma->output_resolution = output_resolution;
|
|
|
|
dma->sample_resolution = audiosample_bits_per_sample(sample);
|
2021-07-30 13:54:33 -04:00
|
|
|
dma->output_register_address = output_register_address;
|
|
|
|
|
2021-06-30 13:11:54 -04:00
|
|
|
audiosample_reset_buffer(sample, single_channel_output, audio_channel);
|
2021-02-02 20:14:39 -05:00
|
|
|
|
2021-08-10 22:00:09 -04:00
|
|
|
|
|
|
|
bool single_buffer; // True if data fits in one single buffer.
|
|
|
|
|
2021-02-02 20:14:39 -05:00
|
|
|
bool samples_signed;
|
|
|
|
uint32_t max_buffer_length;
|
2021-06-30 13:11:54 -04:00
|
|
|
audiosample_get_buffer_structure(sample, single_channel_output, &single_buffer, &samples_signed,
|
2021-03-15 09:57:36 -04:00
|
|
|
&max_buffer_length, &dma->sample_spacing);
|
2021-07-30 13:54:33 -04:00
|
|
|
|
2021-02-02 20:14:39 -05:00
|
|
|
// Check to see if we have to scale the resolution up.
|
|
|
|
if (dma->sample_resolution <= 8 && dma->output_resolution > 8) {
|
|
|
|
max_buffer_length *= 2;
|
|
|
|
}
|
|
|
|
if (output_signed != samples_signed ||
|
2021-03-15 09:57:36 -04:00
|
|
|
dma->sample_spacing > 1 ||
|
|
|
|
(dma->sample_resolution != dma->output_resolution)) {
|
2021-02-02 20:14:39 -05:00
|
|
|
max_buffer_length /= dma->sample_spacing;
|
2021-07-28 22:19:41 -04:00
|
|
|
}
|
2021-02-02 20:14:39 -05:00
|
|
|
|
2021-08-10 22:00:09 -04:00
|
|
|
dma->buffer[0] = (uint8_t *)m_realloc(dma->buffer[0], max_buffer_length);
|
|
|
|
dma->buffer_length[0] = max_buffer_length;
|
|
|
|
if (dma->buffer[0] == NULL) {
|
2021-07-28 22:19:41 -04:00
|
|
|
return AUDIO_DMA_MEMORY_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!single_buffer) {
|
2021-08-10 22:00:09 -04:00
|
|
|
dma->buffer[1] = (uint8_t *)m_realloc(dma->buffer[1], max_buffer_length);
|
|
|
|
dma->buffer_length[1] = max_buffer_length;
|
|
|
|
if (dma->buffer[1] == NULL) {
|
2021-07-28 22:19:41 -04:00
|
|
|
return AUDIO_DMA_MEMORY_ERROR;
|
2021-02-02 20:14:39 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-28 22:19:41 -04:00
|
|
|
dma->signed_to_unsigned = !output_signed && samples_signed;
|
|
|
|
dma->unsigned_to_signed = output_signed && !samples_signed;
|
|
|
|
|
2021-02-02 20:14:39 -05:00
|
|
|
if (output_resolution > 8) {
|
|
|
|
dma->output_size = 2;
|
|
|
|
} else {
|
|
|
|
dma->output_size = 1;
|
|
|
|
}
|
|
|
|
// Transfer both channels at once.
|
2021-06-30 13:11:54 -04:00
|
|
|
if (!single_channel_output && audiosample_channel_count(sample) == 2) {
|
2021-02-02 20:14:39 -05:00
|
|
|
dma->output_size *= 2;
|
|
|
|
}
|
|
|
|
enum dma_channel_transfer_size dma_size = DMA_SIZE_8;
|
|
|
|
if (dma->output_size == 2) {
|
|
|
|
dma_size = DMA_SIZE_16;
|
|
|
|
} else if (dma->output_size == 4) {
|
|
|
|
dma_size = DMA_SIZE_32;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (size_t i = 0; i < 2; i++) {
|
|
|
|
dma_channel_config c = dma_channel_get_default_config(dma->channel[i]);
|
|
|
|
channel_config_set_transfer_data_size(&c, dma_size);
|
|
|
|
channel_config_set_dreq(&c, dma_trigger_source);
|
|
|
|
channel_config_set_read_increment(&c, true);
|
|
|
|
channel_config_set_write_increment(&c, false);
|
2021-08-10 22:00:09 -04:00
|
|
|
|
2021-02-02 20:14:39 -05:00
|
|
|
// Chain to the other channel by default.
|
|
|
|
channel_config_set_chain_to(&c, dma->channel[(i + 1) % 2]);
|
|
|
|
dma_channel_set_config(dma->channel[i], &c, false /* trigger */);
|
2021-08-10 22:00:09 -04:00
|
|
|
|
2021-03-15 09:57:36 -04:00
|
|
|
dma_channel_set_write_addr(dma->channel[i], (void *)output_register_address, false /* trigger */);
|
2021-02-02 20:14:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// We keep the audio_dma_t for internal use and the sample as a root pointer because it
|
|
|
|
// contains the audiodma structure.
|
|
|
|
MP_STATE_PORT(playing_audio)[dma->channel[0]] = dma;
|
|
|
|
MP_STATE_PORT(playing_audio)[dma->channel[1]] = dma;
|
|
|
|
|
|
|
|
// Load the first two blocks up front.
|
2021-08-10 22:00:09 -04:00
|
|
|
audio_dma_load_next_block(dma, 0);
|
2021-02-02 20:14:39 -05:00
|
|
|
if (!single_buffer) {
|
2021-08-10 22:00:09 -04:00
|
|
|
audio_dma_load_next_block(dma, 1);
|
2021-02-02 20:14:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Special case the DMA for a single buffer. It's commonly used for a single wave length of sound
|
|
|
|
// and may be short. Therefore, we use DMA chaining to loop quickly without involving interrupts.
|
|
|
|
// On the RP2040 we chain by having a second DMA writing to the config registers of the first.
|
|
|
|
// Read and write addresses change with DMA so we need to reset the read address back to the
|
|
|
|
// start of the sample.
|
|
|
|
if (single_buffer) {
|
|
|
|
dma_channel_config c = dma_channel_get_default_config(dma->channel[1]);
|
|
|
|
channel_config_set_transfer_data_size(&c, DMA_SIZE_32);
|
|
|
|
channel_config_set_dreq(&c, 0x3f); // dma as fast as possible
|
|
|
|
channel_config_set_read_increment(&c, false);
|
|
|
|
channel_config_set_write_increment(&c, false);
|
|
|
|
channel_config_set_chain_to(&c, dma->channel[1]); // Chain to ourselves so we stop.
|
|
|
|
dma_channel_configure(dma->channel[1], &c,
|
|
|
|
&dma_hw->ch[dma->channel[0]].al3_read_addr_trig, // write address
|
2021-08-10 22:00:09 -04:00
|
|
|
&dma->buffer[0], // read address
|
2021-02-02 20:14:39 -05:00
|
|
|
1, // transaction count
|
|
|
|
false); // trigger
|
|
|
|
} else {
|
2021-08-10 22:00:09 -04:00
|
|
|
// Enable our DMA channels on DMA_IRQ_0 to the CPU. This will wake us up when
|
2021-02-02 20:14:39 -05:00
|
|
|
// we're WFI.
|
|
|
|
dma_hw->inte0 |= (1 << dma->channel[0]) | (1 << dma->channel[1]);
|
|
|
|
irq_set_mask_enabled(1 << DMA_IRQ_0, true);
|
|
|
|
}
|
|
|
|
|
2021-08-03 19:12:14 -04:00
|
|
|
dma->playing_in_progress = true;
|
2021-02-02 20:14:39 -05:00
|
|
|
dma_channel_start(dma->channel[0]);
|
|
|
|
|
|
|
|
return AUDIO_DMA_OK;
|
|
|
|
}
|
|
|
|
|
2021-03-15 09:57:36 -04:00
|
|
|
void audio_dma_stop(audio_dma_t *dma) {
|
2021-02-02 20:14:39 -05:00
|
|
|
// Disable our interrupts.
|
2021-08-03 19:12:14 -04:00
|
|
|
uint32_t channel_mask = 0;
|
|
|
|
if (dma->channel[0] < NUM_DMA_CHANNELS) {
|
|
|
|
channel_mask |= 1 << dma->channel[0];
|
|
|
|
}
|
|
|
|
if (dma->channel[1] < NUM_DMA_CHANNELS) {
|
|
|
|
channel_mask |= 1 << dma->channel[1];
|
|
|
|
}
|
|
|
|
dma_hw->inte0 &= ~channel_mask;
|
2021-02-02 20:14:39 -05:00
|
|
|
irq_set_mask_enabled(1 << DMA_IRQ_0, false);
|
|
|
|
|
|
|
|
// Run any remaining audio tasks because we remove ourselves from
|
|
|
|
// playing_audio.
|
|
|
|
RUN_BACKGROUND_TASKS;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < 2; i++) {
|
|
|
|
size_t channel = dma->channel[i];
|
2021-08-03 19:12:14 -04:00
|
|
|
if (channel == NUM_DMA_CHANNELS) {
|
|
|
|
// Channel not in use.
|
|
|
|
continue;
|
|
|
|
}
|
2021-02-02 20:14:39 -05:00
|
|
|
|
2021-02-23 18:50:00 -05:00
|
|
|
dma_channel_config c = dma_channel_get_default_config(dma->channel[i]);
|
|
|
|
channel_config_set_enable(&c, false);
|
|
|
|
dma_channel_set_config(channel, &c, false /* trigger */);
|
|
|
|
|
2021-02-02 20:14:39 -05:00
|
|
|
if (dma_channel_is_busy(channel)) {
|
|
|
|
dma_channel_abort(channel);
|
|
|
|
}
|
2021-07-30 13:54:33 -04:00
|
|
|
|
|
|
|
// Write a zero as the last sample. This stops any PWM output.
|
|
|
|
if (dma->output_resolution <= 8) {
|
|
|
|
*((uint8_t *)dma->output_register_address) = 0;
|
|
|
|
} else {
|
|
|
|
*((uint16_t *)dma->output_register_address) = 0;
|
|
|
|
}
|
|
|
|
|
2021-02-23 18:50:00 -05:00
|
|
|
dma_channel_set_read_addr(channel, NULL, false /* trigger */);
|
|
|
|
dma_channel_set_write_addr(channel, NULL, false /* trigger */);
|
|
|
|
dma_channel_set_trans_count(channel, 0, false /* trigger */);
|
2021-02-02 20:14:39 -05:00
|
|
|
dma_channel_unclaim(channel);
|
|
|
|
MP_STATE_PORT(playing_audio)[channel] = NULL;
|
|
|
|
dma->channel[i] = NUM_DMA_CHANNELS;
|
|
|
|
}
|
2021-08-03 19:12:14 -04:00
|
|
|
dma->playing_in_progress = false;
|
2021-02-02 20:14:39 -05:00
|
|
|
|
|
|
|
// Hold onto our buffers.
|
|
|
|
}
|
|
|
|
|
|
|
|
// To pause we simply stop the DMA. It is the responsibility of the output peripheral
|
|
|
|
// to hold the previous value.
|
2021-03-15 09:57:36 -04:00
|
|
|
void audio_dma_pause(audio_dma_t *dma) {
|
2021-02-02 20:14:39 -05:00
|
|
|
dma_hw->ch[dma->channel[0]].al1_ctrl &= ~DMA_CH0_CTRL_TRIG_EN_BITS;
|
2021-06-30 13:11:54 -04:00
|
|
|
dma_hw->ch[dma->channel[1]].al1_ctrl &= ~DMA_CH1_CTRL_TRIG_EN_BITS;
|
2021-02-02 20:14:39 -05:00
|
|
|
}
|
|
|
|
|
2021-03-15 09:57:36 -04:00
|
|
|
void audio_dma_resume(audio_dma_t *dma) {
|
2021-02-02 20:14:39 -05:00
|
|
|
// Always re-enable the non-busy channel first so it's ready to continue when the busy channel
|
|
|
|
// finishes and chains to it. (An interrupt could make the time between enables long.)
|
|
|
|
if (dma_channel_is_busy(dma->channel[0])) {
|
2021-06-30 13:11:54 -04:00
|
|
|
dma_hw->ch[dma->channel[1]].al1_ctrl |= DMA_CH1_CTRL_TRIG_EN_BITS;
|
|
|
|
dma_hw->ch[dma->channel[0]].al1_ctrl |= DMA_CH0_CTRL_TRIG_EN_BITS;
|
|
|
|
} else {
|
|
|
|
dma_hw->ch[dma->channel[0]].al1_ctrl |= DMA_CH0_CTRL_TRIG_EN_BITS;
|
|
|
|
dma_hw->ch[dma->channel[1]].al1_ctrl |= DMA_CH1_CTRL_TRIG_EN_BITS;
|
2021-02-02 20:14:39 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-15 09:57:36 -04:00
|
|
|
bool audio_dma_get_paused(audio_dma_t *dma) {
|
2021-08-03 19:12:14 -04:00
|
|
|
if (dma->channel[0] >= NUM_DMA_CHANNELS) {
|
2021-02-02 20:14:39 -05:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
uint32_t control = dma_hw->ch[dma->channel[0]].ctrl_trig;
|
|
|
|
|
|
|
|
return (control & DMA_CH0_CTRL_TRIG_EN_BITS) == 0;
|
|
|
|
}
|
|
|
|
|
2021-03-15 09:57:36 -04:00
|
|
|
void audio_dma_init(audio_dma_t *dma) {
|
2021-08-10 22:00:09 -04:00
|
|
|
dma->buffer[0] = NULL;
|
|
|
|
dma->buffer[1] = NULL;
|
|
|
|
|
2021-05-29 12:55:10 -04:00
|
|
|
dma->channel[0] = NUM_DMA_CHANNELS;
|
|
|
|
dma->channel[1] = NUM_DMA_CHANNELS;
|
2021-02-02 20:14:39 -05:00
|
|
|
}
|
|
|
|
|
2021-03-15 09:57:36 -04:00
|
|
|
void audio_dma_deinit(audio_dma_t *dma) {
|
2021-08-10 22:00:09 -04:00
|
|
|
m_free(dma->buffer[0]);
|
|
|
|
dma->buffer[0] = NULL;
|
2021-02-02 20:14:39 -05:00
|
|
|
|
2021-08-10 22:00:09 -04:00
|
|
|
m_free(dma->buffer[1]);
|
|
|
|
dma->buffer[1] = NULL;
|
2021-02-02 20:14:39 -05:00
|
|
|
}
|
|
|
|
|
2021-03-15 09:57:36 -04:00
|
|
|
bool audio_dma_get_playing(audio_dma_t *dma) {
|
2021-02-02 20:14:39 -05:00
|
|
|
if (dma->channel[0] == NUM_DMA_CHANNELS) {
|
|
|
|
return false;
|
|
|
|
}
|
2021-08-03 19:12:14 -04:00
|
|
|
return dma->playing_in_progress;
|
2021-02-02 20:14:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// WARN(tannewt): DO NOT print from here, or anything it calls. Printing calls
|
|
|
|
// background tasks such as this and causes a stack overflow.
|
2021-06-30 13:11:54 -04:00
|
|
|
// NOTE(dhalbert): I successfully printed from here while debugging.
|
|
|
|
// So it's possible, but be careful.
|
2021-02-02 20:14:39 -05:00
|
|
|
STATIC void dma_callback_fun(void *arg) {
|
2021-03-15 09:57:36 -04:00
|
|
|
audio_dma_t *dma = arg;
|
2021-02-02 20:14:39 -05:00
|
|
|
if (dma == NULL) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-08-10 22:00:09 -04:00
|
|
|
common_hal_mcu_disable_interrupts();
|
|
|
|
uint32_t channels_to_load_mask = dma->channels_to_load_mask;
|
|
|
|
dma->channels_to_load_mask = 0;
|
|
|
|
common_hal_mcu_enable_interrupts();
|
|
|
|
|
|
|
|
// Load the blocks for the requested channels.
|
|
|
|
uint32_t channel = 0;
|
|
|
|
while (channels_to_load_mask) {
|
|
|
|
if (channels_to_load_mask & 1) {
|
|
|
|
if (dma->channel[0] == channel) {
|
|
|
|
audio_dma_load_next_block(dma, 0);
|
|
|
|
}
|
|
|
|
if (dma->channel[1] == channel) {
|
|
|
|
audio_dma_load_next_block(dma, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
channels_to_load_mask >>= 1;
|
|
|
|
channel++;
|
|
|
|
}
|
2021-02-02 20:14:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void isr_dma_0(void) {
|
|
|
|
for (size_t i = 0; i < NUM_DMA_CHANNELS; i++) {
|
|
|
|
uint32_t mask = 1 << i;
|
|
|
|
if ((dma_hw->intr & mask) != 0 && MP_STATE_PORT(playing_audio)[i] != NULL) {
|
2021-03-15 09:57:36 -04:00
|
|
|
audio_dma_t *dma = MP_STATE_PORT(playing_audio)[i];
|
2021-08-10 22:00:09 -04:00
|
|
|
// Record all channels whose DMA has completed; they need loading.
|
|
|
|
dma->channels_to_load_mask |= mask;
|
2021-03-15 09:57:36 -04:00
|
|
|
background_callback_add(&dma->callback, dma_callback_fun, (void *)dma);
|
2021-02-02 20:14:39 -05:00
|
|
|
dma_hw->ints0 = mask;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|