From 15e3d37110f1674ec7d52c2f055ebfad1d77b5da Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Tue, 11 Jan 2022 13:56:46 +0000 Subject: [PATCH] x1000: core PCM recording support Change-Id: I71883272cc3bffadc1235b0931c3f42bb38e4c1e --- firmware/export/x1000.h | 5 + .../target/mips/ingenic_x1000/debug-x1000.c | 6 + .../target/mips/ingenic_x1000/pcm-x1000.c | 181 ++++++++++++------ 3 files changed, 136 insertions(+), 56 deletions(-) diff --git a/firmware/export/x1000.h b/firmware/export/x1000.h index 102d4ec978..de3d394c02 100644 --- a/firmware/export/x1000.h +++ b/firmware/export/x1000.h @@ -57,6 +57,11 @@ #define X1000_STACKSIZE 0x1e00 #define X1000_IRQSTACKSIZE 0x300 +/* Required for pcm_rec_dma_get_peak_buffer(), doesn't do anything + * except on targets with recording. */ +#define HAVE_PCM_DMA_ADDRESS +#define HAVE_PCM_REC_DMA_ADDRESS + /* Convert kseg0 address to physical address or uncached address */ #define PHYSADDR(x) ((unsigned long)(x) & 0x1fffffff) #define UNCACHEDADDR(x) (PHYSADDR(x) | 0xa0000000) diff --git a/firmware/target/mips/ingenic_x1000/debug-x1000.c b/firmware/target/mips/ingenic_x1000/debug-x1000.c index 98b8f95fb5..236442a880 100644 --- a/firmware/target/mips/ingenic_x1000/debug-x1000.c +++ b/firmware/target/mips/ingenic_x1000/debug-x1000.c @@ -118,12 +118,18 @@ static bool dbg_gpios(void) } extern volatile unsigned aic_tx_underruns; +#ifdef HAVE_RECORDING +extern volatile unsigned aic_rx_overruns; +#endif static bool dbg_audio(void) { do { lcd_clear_display(); lcd_putsf(0, 0, "TX underruns: %u", aic_tx_underruns); +#ifdef HAVE_RECORDING + lcd_putsf(0, 1, "RX overruns: %u", aic_rx_overruns); +#endif lcd_update(); } while(get_action(CONTEXT_STD, HZ) != ACTION_STD_CANCEL); diff --git a/firmware/target/mips/ingenic_x1000/pcm-x1000.c b/firmware/target/mips/ingenic_x1000/pcm-x1000.c index ef54d45e62..7d1c83a35a 100644 --- a/firmware/target/mips/ingenic_x1000/pcm-x1000.c +++ b/firmware/target/mips/ingenic_x1000/pcm-x1000.c @@ -7,7 +7,7 @@ * \/ \/ \/ \/ \/ * $Id$ * - * Copyright (C) 2021 Aidan MacDonald + * Copyright (C) 2021-2022 Aidan MacDonald * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -31,28 +31,31 @@ #include "x1000/aic.h" #include "x1000/cpm.h" -#define AIC_STATE_STOPPED 0 -#define AIC_STATE_PLAYING 1 +#define AIC_STATE_STOPPED 0x00 +#define AIC_STATE_PLAYING 0x01 +#define AIC_STATE_RECORDING 0x02 volatile unsigned aic_tx_underruns = 0; static int aic_state = AIC_STATE_STOPPED; -static int aic_lock = 0; -static volatile int aic_dma_pending_event = DMA_EVENT_NONE; - -static dma_desc aic_dma_desc; - +static int play_lock = 0; +static volatile int play_dma_pending_event = DMA_EVENT_NONE; +static dma_desc play_dma_desc; static void pcm_play_dma_int_cb(int event); + #ifdef HAVE_RECORDING +volatile unsigned aic_rx_overruns = 0; +static int rec_lock = 0; +static volatile int rec_dma_pending_event = DMA_EVENT_NONE; +static dma_desc rec_dma_desc; + static void pcm_rec_dma_int_cb(int event); #endif void pcm_play_dma_init(void) { - /* Ungate clock, assign pins. NB this overlaps with pins labeled "sa0-sa4" - * on Ingenic's datasheets but I'm not sure what they are. Probably safe to - * assume they are not useful to Rockbox... */ + /* Ungate clock */ jz_writef(CPM_CLKGR, AIC(0)); /* Configure AIC with some sane defaults */ @@ -79,7 +82,7 @@ void pcm_play_dma_init(void) #endif /* Set DMA settings */ - jz_writef(AIC_CFG, TFTH(16), RFTH(16)); + jz_writef(AIC_CFG, TFTH(16), RFTH(15)); dma_set_callback(DMA_CHANNEL_AUDIO, pcm_play_dma_int_cb); #ifdef HAVE_RECORDING dma_set_callback(DMA_CHANNEL_RECORD, pcm_rec_dma_int_cb); @@ -106,23 +109,23 @@ void pcm_dma_apply_settings(void) audiohw_set_frequency(pcm_fsel); } -static void pcm_dma_start(const void* addr, size_t size) +static void play_dma_start(const void* addr, size_t size) { - aic_dma_desc.cm = jz_orf(DMA_CHN_CM, SAI(1), DAI(0), RDIL(9), - SP_V(32BIT), DP_V(32BIT), TSZ_V(AUTO), - STDE(0), TIE(1), LINK(0)); - aic_dma_desc.sa = PHYSADDR(addr); - aic_dma_desc.ta = PHYSADDR(JA_AIC_DR); - aic_dma_desc.tc = size; - aic_dma_desc.sd = 0; - aic_dma_desc.rt = jz_orf(DMA_CHN_RT, TYPE_V(I2S_TX)); - aic_dma_desc.pad0 = 0; - aic_dma_desc.pad1 = 0; + play_dma_desc.cm = jz_orf(DMA_CHN_CM, SAI(1), DAI(0), RDIL(9), + SP_V(32BIT), DP_V(32BIT), TSZ_V(AUTO), + STDE(0), TIE(1), LINK(0)); + play_dma_desc.sa = PHYSADDR(addr); + play_dma_desc.ta = PHYSADDR(JA_AIC_DR); + play_dma_desc.tc = size; + play_dma_desc.sd = 0; + play_dma_desc.rt = jz_orf(DMA_CHN_RT, TYPE_V(I2S_TX)); + play_dma_desc.pad0 = 0; + play_dma_desc.pad1 = 0; - commit_dcache_range(&aic_dma_desc, sizeof(dma_desc)); + commit_dcache_range(&play_dma_desc, sizeof(dma_desc)); commit_dcache_range(addr, size); - REG_DMA_CHN_DA(DMA_CHANNEL_AUDIO) = PHYSADDR(&aic_dma_desc); + REG_DMA_CHN_DA(DMA_CHANNEL_AUDIO) = PHYSADDR(&play_dma_desc); jz_writef(DMA_CHN_CS(DMA_CHANNEL_AUDIO), DES8(1), NDES(0)); jz_set(DMA_DB, 1 << DMA_CHANNEL_AUDIO); jz_writef(DMA_CHN_CS(DMA_CHANNEL_AUDIO), CTE(1)); @@ -130,13 +133,13 @@ static void pcm_dma_start(const void* addr, size_t size) pcm_play_dma_status_callback(PCM_DMAST_STARTED); } -static void pcm_dma_handle_event(int event) +static void play_dma_handle_event(int event) { if(event == DMA_EVENT_COMPLETE) { const void* addr; size_t size; if(pcm_play_dma_complete_callback(PCM_DMAST_OK, &addr, &size)) - pcm_dma_start(addr, size); + play_dma_start(addr, size); } else if(event == DMA_EVENT_NONE) { /* ignored, so callers don't need to check for this */ } else { @@ -146,20 +149,20 @@ static void pcm_dma_handle_event(int event) static void pcm_play_dma_int_cb(int event) { - if(aic_lock) { - aic_dma_pending_event = event; + if(play_lock) { + play_dma_pending_event = event; return; } else { - pcm_dma_handle_event(event); + play_dma_handle_event(event); } } void pcm_play_dma_start(const void* addr, size_t size) { - aic_dma_pending_event = DMA_EVENT_NONE; - aic_state = AIC_STATE_PLAYING; + play_dma_pending_event = DMA_EVENT_NONE; + aic_state |= AIC_STATE_PLAYING; - pcm_dma_start(addr, size); + play_dma_start(addr, size); jz_writef(AIC_CCR, TDMS(1), ETUR(1), ERPL(1)); } @@ -168,21 +171,23 @@ void pcm_play_dma_stop(void) jz_writef(AIC_CCR, TDMS(0), ETUR(0), ERPL(0)); jz_writef(AIC_CCR, TFLUSH(1)); - aic_dma_pending_event = DMA_EVENT_NONE; - aic_state = AIC_STATE_STOPPED; + play_dma_pending_event = DMA_EVENT_NONE; + aic_state &= ~AIC_STATE_PLAYING; } void pcm_play_lock(void) { - ++aic_lock; + int irq = disable_irq_save(); + ++play_lock; + restore_irq(irq); } void pcm_play_unlock(void) { int irq = disable_irq_save(); - if(--aic_lock == 0 && aic_state == AIC_STATE_PLAYING) { - pcm_dma_handle_event(aic_dma_pending_event); - aic_dma_pending_event = DMA_EVENT_NONE; + if(--play_lock == 0 && (aic_state & AIC_STATE_PLAYING)) { + play_dma_handle_event(play_dma_pending_event); + play_dma_pending_event = DMA_EVENT_NONE; } restore_irq(irq); @@ -193,11 +198,56 @@ void pcm_play_unlock(void) * Recording */ -/* FIXME need to implement this!! */ +static void rec_dma_start(void* addr, size_t size) +{ + /* NOTE: Rockbox always records in stereo and the AIC pushes in the + * sample for each channel separately. One frame therefore requires + * two 16-bit transfers from the AIC. */ + rec_dma_desc.cm = jz_orf(DMA_CHN_CM, SAI(0), DAI(1), RDIL(6), + SP_V(16BIT), DP_V(16BIT), TSZ_V(16BIT), + STDE(0), TIE(1), LINK(0)); + rec_dma_desc.sa = PHYSADDR(JA_AIC_DR); + rec_dma_desc.ta = PHYSADDR(addr); + rec_dma_desc.tc = size / 2; + rec_dma_desc.sd = 0; + rec_dma_desc.rt = jz_orf(DMA_CHN_RT, TYPE_V(I2S_RX)); + rec_dma_desc.pad0 = 0; + rec_dma_desc.pad1 = 0; + + commit_dcache_range(&rec_dma_desc, sizeof(dma_desc)); + if((unsigned long)addr < 0xa0000000ul) + discard_dcache_range(addr, size); + + REG_DMA_CHN_DA(DMA_CHANNEL_RECORD) = PHYSADDR(&rec_dma_desc); + jz_writef(DMA_CHN_CS(DMA_CHANNEL_RECORD), DES8(1), NDES(0)); + jz_set(DMA_DB, 1 << DMA_CHANNEL_RECORD); + jz_writef(DMA_CHN_CS(DMA_CHANNEL_RECORD), CTE(1)); + + pcm_rec_dma_status_callback(PCM_DMAST_STARTED); +} + +static void rec_dma_handle_event(int event) +{ + if(event == DMA_EVENT_COMPLETE) { + void* addr; + size_t size; + if(pcm_rec_dma_complete_callback(PCM_DMAST_OK, &addr, &size)) + rec_dma_start(addr, size); + } else if(event == DMA_EVENT_NONE) { + /* ignored, so callers don't need to check for this */ + } else { + pcm_rec_dma_status_callback(PCM_DMAST_ERR_DMA); + } +} static void pcm_rec_dma_int_cb(int event) { - (void)event; + if(rec_lock) { + rec_dma_pending_event = event; + return; + } else { + rec_dma_handle_event(event); + } } void pcm_rec_dma_init(void) @@ -210,45 +260,64 @@ void pcm_rec_dma_close(void) void pcm_rec_dma_start(void* addr, size_t size) { - (void)addr; - (void)size; + rec_dma_pending_event = DMA_EVENT_NONE; + aic_state |= AIC_STATE_RECORDING; + + rec_dma_start(addr, size); + jz_writef(AIC_CCR, RDMS(1), EROR(1), EREC(1)); } void pcm_rec_dma_stop(void) { + jz_writef(AIC_CCR, RDMS(0), EROR(0), EREC(0)); + jz_writef(AIC_CCR, RFLUSH(1)); + + rec_dma_pending_event = DMA_EVENT_NONE; + aic_state &= ~AIC_STATE_RECORDING; } void pcm_rec_lock(void) { - + int irq = disable_irq_save(); + ++rec_lock; + restore_irq(irq); } void pcm_rec_unlock(void) { + int irq = disable_irq_save(); + if(--rec_lock == 0 && (aic_state & AIC_STATE_RECORDING)) { + rec_dma_handle_event(rec_dma_pending_event); + rec_dma_pending_event = DMA_EVENT_NONE; + } + restore_irq(irq); } const void* pcm_rec_dma_get_peak_buffer(void) { - return NULL; -} - -void audio_set_output_source(int source) -{ - (void)source; -} - -void audio_input_mux(int source, unsigned flags) -{ - (void)source; - (void)flags; + return (const void*)UNCACHEDADDR(REG_DMA_CHN_TA(DMA_CHANNEL_RECORD)); } #endif /* HAVE_RECORDING */ +#ifdef HAVE_PCM_DMA_ADDRESS +void* pcm_dma_addr(void* addr) +{ + return (void*)UNCACHEDADDR(addr); +} +#endif + void AIC(void) { if(jz_readf(AIC_SR, TUR)) { aic_tx_underruns += 1; jz_writef(AIC_SR, TUR(0)); } + +#ifdef HAVE_RECORDING + if(jz_readf(AIC_SR, ROR)) { + aic_rx_overruns += 1; + jz_writef(AIC_SR, ROR(0)); + } +#endif /* HAVE_RECORDING */ }