diff --git a/platform/drivers/audio/Cx000_dac.cpp b/platform/drivers/audio/Cx000_dac.cpp new file mode 100644 index 00000000..f328b31b --- /dev/null +++ b/platform/drivers/audio/Cx000_dac.cpp @@ -0,0 +1,234 @@ +/*************************************************************************** + * Copyright (C) 2024 by Silvano Seva IU2KWO * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include +#include +#include +#include "Cx000_dac.h" + +#include + +static HR_C6000 *c6000; +static bool running = false; +static bool syncPoint = false; +static bool stopReq = false; +static size_t readPos; +static struct streamCtx *stream; +static pthread_mutex_t mutex; +static pthread_cond_t wakeup_cond; + +static inline void stopStream() +{ + running = false; + stream->running = 0; + + // Clear the "OpenMusic" bit + c6000->writeCfgRegister(0x06, 0x20); +} + +void Cx000dac_init(HR_C6000 *device) +{ + c6000 = device; + c6000->init(); + + pthread_mutex_init(&mutex, NULL); + pthread_cond_init(&wakeup_cond, NULL); +} + +void Cx000dac_terminate() +{ + pthread_mutex_destroy(&mutex); + pthread_cond_destroy(&wakeup_cond); +} + +void Cx000dac_task() +{ + if(running == false) + return; + + // Check if FIFO is empty + uint8_t reg = c6000->readCfgRegister(0x88); + if((reg & 0x01) == 1) + return; + + // Need to refill the FIFO + bool isSyncPoint = false; + size_t prevRdPos = readPos; + uint8_t *sound = (uint8_t *)(&stream->buffer[readPos]); + + c6000->sendAudio(sound); + readPos += 32; + + // For circular buffer mode, check if the half of the buffer has been + // crossed: this is a thread sync point. + if(stream->bufMode == BUF_CIRC_DOUBLE) + { + size_t half = stream->bufSize / 2; + if((prevRdPos < half) && (readPos >= half)) + isSyncPoint = true; + } + + // Check if buffer end has been reached, this is a thread sync point for + // both linear and circular buffer modes. When in linear mode, transfer + // ends. + if(readPos >= stream->bufSize) + { + isSyncPoint = true; + readPos = 0; + + if(stream->bufMode == BUF_LINEAR) + stopReq = true; + } + + // Wake up thread(s) waiting to be synced with the stream. + if(isSyncPoint == true) + { + pthread_mutex_lock(&mutex); + syncPoint = true; + + if(stopReq == true) + stopStream(); + + pthread_cond_signal(&wakeup_cond); + pthread_mutex_unlock(&mutex); + } +} + + +static int Cx000dac_start(const uint8_t instance, const void *config, + struct streamCtx *ctx) +{ + (void) config; + (void) instance; + + if((ctx == NULL) || (ctx->running != 0)) + return -EINVAL; + + // Require that buffer size is an integer multiple of 32 samples. + if((ctx->bufSize % 32) != 0) + return -EINVAL; + + if(running == true) + return -EBUSY; + + // Stream not running and thread idle, set up a new stream + pthread_mutex_lock(&mutex); + ctx->running = 1; + pthread_mutex_unlock(&mutex); + + stopReq = false; + syncPoint = false; + readPos = 0; + stream = ctx; + + // HR_Cx000 DAC requires data to be in big endian format + for(size_t i = 0; i < ctx->bufSize; i++) + { + stream_sample_t tmp = ctx->buffer[i]; + ctx->buffer[i] = __builtin_bswap16(tmp); + } + + // Set the "OpenMusic" bit + c6000->writeCfgRegister(0x06, 0x22); + running = true; + + return 0; +} + +static int Cx000dac_idleBuf(struct streamCtx *ctx, stream_sample_t **buf) +{ + // Idle buffer is present only in circular mode + if(ctx->bufMode != BUF_CIRC_DOUBLE) + { + *buf = NULL; + return 0; + } + + // If reading the first half, the second half is free and vice-versa + if(readPos < (ctx->bufSize/2)) + *buf = ctx->buffer + (ctx->bufSize/2); + else + *buf = ctx->buffer; + + return ctx->bufSize/2; +} + +static int Cx000dac_sync(struct streamCtx *ctx, uint8_t dirty) +{ + if(ctx->running == 0) + return -1; + + // HR_Cx000 DAC requires data to be in big endian format + if((ctx->bufMode == BUF_CIRC_DOUBLE) && (dirty != 0)) + { + stream_sample_t *ptr; + Cx000dac_idleBuf(ctx, &ptr); + + for(size_t i = 0; i < ctx->bufSize/2; i++) + { + stream_sample_t tmp = ptr[i]; + ptr[i] = __builtin_bswap16(tmp); + } + } + + pthread_mutex_lock(&mutex); + + // Check for buffer overruns + if(syncPoint == true) + { + syncPoint = false; + pthread_mutex_unlock(&mutex); + return -1; + } + + // Wait for sync point + while(syncPoint == false) + { + pthread_cond_wait(&wakeup_cond, &mutex); + } + + syncPoint = false; + pthread_mutex_unlock(&mutex); + return 0; +} + +static void Cx000dac_stop(struct streamCtx *ctx) +{ + if(ctx->running == 0) + return; + + stopReq = true; +} + +static void Cx000dac_halt(struct streamCtx *ctx) +{ + if(ctx->running == 0) + return; + + stopStream(); +} + +#pragma GCC diagnostic ignored "-Wpedantic" +const struct audioDriver Cx000_dac_audio_driver = +{ + .start = Cx000dac_start, + .data = Cx000dac_idleBuf, + .sync = Cx000dac_sync, + .stop = Cx000dac_stop, + .terminate = Cx000dac_halt +}; +#pragma GCC diagnostic pop diff --git a/platform/drivers/audio/Cx000_dac.h b/platform/drivers/audio/Cx000_dac.h new file mode 100644 index 00000000..53c1e66c --- /dev/null +++ b/platform/drivers/audio/Cx000_dac.h @@ -0,0 +1,58 @@ +/*************************************************************************** + * Copyright (C) 2024 by Silvano Seva IU2KWO * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef Cx000_DAC_H +#define Cx000_DAC_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Driver to use the HR_Cx000 internal DAC as audio output stream device. + * Input data format is signed 16-bit and only a single instance of this driver + * is allowed. + */ + +extern const struct audioDriver Cx000_dac_audio_driver; + +#ifdef __cplusplus +} // extern "C" + +/** + * Initialize the driver. + */ +void Cx000dac_init(HR_C6000 *device); + +/** + * Shutdown the driver. + */ +void Cx000dac_terminate(); + +/** + * Driver task function, to be called at least once every 4ms to ensure a + * proper operation. + */ +void Cx000dac_task(); + +#endif + +#endif /* Cx000_DAC_H */