mirror of
https://github.com/Rockbox/rockbox.git
synced 2026-10-10 08:03:04 -04:00
rk27xx: recording over I2S and HDMA channel 1
pcm-rk27xx.c had no recording at all. Add it for targets with HAVE_RECORDING: HDMA channel 1 moves the I2S Rx FIFO into the recording buffer - hdreq 7, fixed source, incrementing destination, 32-bit inc8 slices, the mirror of the playback channel and the setup the Samsung YP-CP3's original firmware uses. Playback keeps channel 0, so the two can run together. - HDMA_ISR holds both channels' masks and flags; playback used to write all of it, clearing whatever the other channel had. Each channel now updates only its own bits, and INT_HDMA serves each channel's count down flag. Both channels start masked and clear. - Recording cannot mask the shared interrupt, so pcm_rec_lock() defers a completed buffer to pcm_rec_unlock() instead. - In master mode the I2S Rx side runs only while recording: started with nothing reading its FIFO it upsets playback. I2S_RXCTL gets the Tx frame format, plus bit 24 as the YP-CP3's original firmware sets it. - audio-rk27xx.c routes inputs through audiohw_set_recsrc() on targets that record. Only partly tested, on a YP-CP3: playback and FM radio still work after the interrupt changes, and the recording screen's peak meter follows the microphone, so samples arrive through the DMA. Not yet tested: playing back a recorded file, recording FM, long recordings, recording while playing. A known risk: a flag set by the hardware in the few cycles of the read-modify-write of HDMA_ISR would be lost and stall that channel. Other rk27xx targets build; their playback was not retested on hardware. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Change-Id: Idc64bce8075d0fc628c29767fc33beda4fff4342
This commit is contained in:
parent
c922ef98c3
commit
a33b0c7360
2 changed files with 211 additions and 28 deletions
|
|
@ -29,10 +29,15 @@ static int output_source = AUDIO_SRC_PLAYBACK;
|
|||
|
||||
static void select_audio_path(void)
|
||||
{
|
||||
#ifdef HAVE_RECORDING
|
||||
/* the codec routes the source for listening and recording both */
|
||||
audiohw_set_recsrc(input_source, input_flags & SRCF_RECORDING);
|
||||
#else
|
||||
if(input_source == AUDIO_SRC_PLAYBACK)
|
||||
audiohw_set_monitor(false);
|
||||
else
|
||||
audiohw_set_monitor(true);
|
||||
#endif
|
||||
}
|
||||
|
||||
void audio_input_mux(int source, unsigned flags)
|
||||
|
|
|
|||
|
|
@ -31,6 +31,30 @@
|
|||
|
||||
static int locked = 0;
|
||||
|
||||
/* HDMA_ISR holds the interrupt masks (bits 8-13) and flags (bits 0-5) of
|
||||
* both channels - channel 0 plays, channel 1 records - so each channel
|
||||
* updates only its own bits. A flag is cleared by writing 0. */
|
||||
#define HDMA_CH0_ISR_BITS ((1<<12) | (1<<10) | (1<<9) | \
|
||||
(1<<4) | (1<<2) | (1<<0))
|
||||
#define HDMA_CH1_ISR_BITS ((1<<13) | (1<<11) | (1<<8) | \
|
||||
(1<<5) | (1<<3) | (1<<1))
|
||||
/* running: only the page count down interrupt unmasked, flags clear */
|
||||
#define HDMA_CH0_ISR_RUN ((1<<12) | (1<<9))
|
||||
#define HDMA_CH1_ISR_RUN ((1<<13) | (1<<8))
|
||||
/* stopped: all masked, flags clear */
|
||||
#define HDMA_CH0_ISR_STOP ((1<<12) | (1<<10) | (1<<9))
|
||||
#define HDMA_CH1_ISR_STOP ((1<<13) | (1<<11) | (1<<8))
|
||||
/* count down to zero */
|
||||
#define HDMA_CH0_DONE (1<<2)
|
||||
#define HDMA_CH1_DONE (1<<3)
|
||||
|
||||
static void hdma_isr_update(unsigned long bits, unsigned long val)
|
||||
{
|
||||
int old = disable_irq_save();
|
||||
HDMA_ISR = (HDMA_ISR & ~bits) | val;
|
||||
restore_irq(old);
|
||||
}
|
||||
|
||||
/* Mask the DMA interrupt */
|
||||
static void sink_lock(void)
|
||||
{
|
||||
|
|
@ -56,7 +80,7 @@ static void sink_unlock(void)
|
|||
static void sink_dma_stop(void)
|
||||
{
|
||||
HDMA_CON0 = 0x00;
|
||||
HDMA_ISR = 0x00;
|
||||
hdma_isr_update(HDMA_CH0_ISR_BITS, HDMA_CH0_ISR_STOP);
|
||||
|
||||
locked = 1;
|
||||
}
|
||||
|
|
@ -74,18 +98,9 @@ static void hdma_i2s_transfer(const void *addr, size_t size)
|
|||
* (zero based)
|
||||
*/
|
||||
|
||||
HDMA_ISR = ((1<<13) | /* mask ch1 accumulation overflow irq */
|
||||
(1<<12) | /* mask ch0 accumulation overflow irq */
|
||||
(1<<11) | /* mask ch1 page count down irq */
|
||||
(0<<10) | /* UNMASK ch0 page count down irq */
|
||||
(1<<9) | /* mask ch0 transfer irq */
|
||||
(1<<8) | /* mask ch1 transfer irq */
|
||||
(0<<5) | /* clear ch1 accumulation overflow flag */
|
||||
(0<<4) | /* clear ch0 accumulation overflow flag */
|
||||
(0<<3) | /* clear ch1 count down to zero flag */
|
||||
(0<<2) | /* clear ch0 count down to zero flag */
|
||||
(0<<1) | /* clear ch1 active flag */
|
||||
(0<<0)); /* clear ch0 active flag */
|
||||
/* mask ch0 accumulation overflow and transfer irqs, unmask its page
|
||||
* count down irq, clear its flags */
|
||||
hdma_isr_update(HDMA_CH0_ISR_BITS, HDMA_CH0_ISR_RUN);
|
||||
|
||||
HDMA_ISCNT0 = 0x07; /* slice size in transfer size units (zero base) */
|
||||
|
||||
|
|
@ -114,6 +129,18 @@ static void sink_dma_start(const void *addr, size_t size)
|
|||
hdma_i2s_transfer(addr, size);
|
||||
}
|
||||
|
||||
#define I2S_FORMAT ((1<<16) | /* LRCK/SCLK = 64 */ \
|
||||
(4<<8) | /* MCLK/SCLK = 4 */ \
|
||||
(1<<4) | /* 16bit samples */ \
|
||||
(0<<3) | /* stereo */ \
|
||||
(0<<1) | /* I2S IF */ \
|
||||
I2S_MASTER)
|
||||
#ifdef CODEC_SLAVE
|
||||
#define I2S_MASTER (1<<0) /* master mode */
|
||||
#else
|
||||
#define I2S_MASTER (0<<0) /* slave mode */
|
||||
#endif
|
||||
|
||||
static void i2s_init(void)
|
||||
{
|
||||
#if defined(HAVE_RK27XX_CODEC)
|
||||
|
|
@ -140,15 +167,10 @@ static void i2s_init(void)
|
|||
/* configure I2S module */
|
||||
I2S_IER = 0; /* disable all i2s interrupts */
|
||||
|
||||
I2S_TXCTL = (1<<16) | /* LRCK/SCLK = 64 */
|
||||
(4<<8) | /* MCLK/SCLK = 4 */
|
||||
(1<<4) | /* 16bit samples */
|
||||
(0<<3) | /* stereo */
|
||||
(0<<1) | /* I2S IF */
|
||||
#ifdef CODEC_SLAVE
|
||||
(1<<0); /* master mode */
|
||||
#else
|
||||
(0<<0); /* slave mode */
|
||||
I2S_TXCTL = I2S_FORMAT;
|
||||
#ifdef HAVE_RECORDING
|
||||
/* bit 24 as the original firmware of the Samsung YP-CP3 sets it */
|
||||
I2S_RXCTL = I2S_FORMAT | (1<<24);
|
||||
#endif
|
||||
|
||||
/* the fifo is 16x32bits according to my tests
|
||||
|
|
@ -231,6 +253,10 @@ static void sink_dma_init(void)
|
|||
INTC_IMR |= IRQ_ARM_HDMA;
|
||||
INTC_IECR |= IRQ_ARM_HDMA;
|
||||
|
||||
/* both channels stopped, with the HDMA clocked for the write */
|
||||
SCU_CLKCFG &= ~CLKCFG_HDMA;
|
||||
HDMA_ISR = HDMA_CH0_ISR_STOP | HDMA_CH1_ISR_STOP;
|
||||
|
||||
audiohw_preinit();
|
||||
|
||||
i2s_init();
|
||||
|
|
@ -245,17 +271,46 @@ static void sink_set_freq(uint16_t freq)
|
|||
audiohw_set_frequency(freq);
|
||||
}
|
||||
|
||||
#ifdef HAVE_RECORDING
|
||||
static void rec_dma_done(void);
|
||||
static volatile int rec_locked = 0;
|
||||
static volatile bool rec_pending = false;
|
||||
#endif
|
||||
|
||||
/* audio DMA ISR called when chunk from callers buffer has been transfered */
|
||||
void INT_HDMA(void)
|
||||
{
|
||||
const void *start;
|
||||
size_t size;
|
||||
unsigned long isr = HDMA_ISR;
|
||||
|
||||
if (pcm_play_dma_complete_callback(PCM_DMAST_OK, &start, &size))
|
||||
if (isr & HDMA_CH0_DONE)
|
||||
{
|
||||
hdma_i2s_transfer(start, size);
|
||||
pcm_play_dma_status_callback(PCM_DMAST_STARTED);
|
||||
const void *start;
|
||||
size_t size;
|
||||
|
||||
hdma_isr_update(HDMA_CH0_DONE, 0);
|
||||
|
||||
if (pcm_play_dma_complete_callback(PCM_DMAST_OK, &start, &size))
|
||||
{
|
||||
hdma_i2s_transfer(start, size);
|
||||
pcm_play_dma_status_callback(PCM_DMAST_STARTED);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_RECORDING
|
||||
if (isr & HDMA_CH1_DONE)
|
||||
{
|
||||
hdma_isr_update(HDMA_CH1_DONE, 0);
|
||||
|
||||
if (rec_locked)
|
||||
{
|
||||
rec_pending = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
rec_dma_done();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
struct pcm_sink builtin_pcm_sink = {
|
||||
|
|
@ -279,4 +334,127 @@ struct pcm_sink builtin_pcm_sink = {
|
|||
/****************************************************************************
|
||||
** Recording DMA transfer
|
||||
**/
|
||||
/* TODO */
|
||||
#ifdef HAVE_RECORDING
|
||||
/* HDMA channel 1 from the I2S Rx FIFO. Playback and recording share the
|
||||
* HDMA interrupt, so locking recording cannot mask it; a buffer completed
|
||||
* while locked is handed over at unlock instead. */
|
||||
|
||||
/* I2S_OPR while playing only, and while recording too. In master mode the
|
||||
* Rx side runs only while recording: started with nothing reading its FIFO
|
||||
* it upsets playback. */
|
||||
#ifdef CODEC_SLAVE
|
||||
#define I2S_OPR_START_TX (1<<1)
|
||||
#define I2S_OPR_START_RX (1<<0)
|
||||
#else
|
||||
#define I2S_OPR_START_TX 0 /* not used in slave mode */
|
||||
#define I2S_OPR_START_RX 0
|
||||
#endif
|
||||
#define I2S_OPR_PLAY ((0<<6) | /* HDMA Req1 enable */ \
|
||||
(1<<5) | /* HDMA Req2 disable */ \
|
||||
(0<<4) | /* Req1 for Tx fifo */ \
|
||||
(1<<3) | /* Req2 for Rx fifo */ \
|
||||
I2S_OPR_START_TX)
|
||||
#define I2S_OPR_RECORD ((I2S_OPR_PLAY & ~(1<<5)) | I2S_OPR_START_RX)
|
||||
|
||||
static void hdma_i2s_rec_transfer(void *addr, size_t size)
|
||||
{
|
||||
SCU_CLKCFG &= ~CLKCFG_HDMA; /* enable HDMA clock */
|
||||
|
||||
/* the DMA writes the buffer: no line of it may be written back over
|
||||
* the samples later */
|
||||
commit_discard_dcache_range(addr, size);
|
||||
|
||||
HDMA_ISRC1 = (uint32_t)&I2S_RXR; /* i2s rx fifo */
|
||||
HDMA_IDST1 = (uint32_t)addr; /* destination address */
|
||||
HDMA_ICNT1 = (uint16_t)((size>>2) - 1); /* number of dma transactions
|
||||
* of transfer size bytes
|
||||
* (zero based)
|
||||
*/
|
||||
|
||||
hdma_isr_update(HDMA_CH1_ISR_BITS, HDMA_CH1_ISR_RUN);
|
||||
|
||||
HDMA_ISCNT1 = 0x07; /* slice size in transfer size units (zero base) */
|
||||
|
||||
HDMA_IPNCNTD1 = 0x01; /* page count */
|
||||
|
||||
HDMA_CON1 = ((0<<23) | /* page mode */
|
||||
(1<<22) | /* slice mode */
|
||||
(1<<21) | /* DMA enable */
|
||||
(1<<18) | /* generate interrupt */
|
||||
(0<<16) | /* on-the-fly is not supported by rk27xx */
|
||||
(5<<13) | /* transfer mode inc8 */
|
||||
(7<<9) | /* external hdreq from i2s rx */
|
||||
(1<<7) | /* fixed source address */
|
||||
(0<<5) | /* increment destination address */
|
||||
(2<<3) | /* transfer size = 32bits word */
|
||||
(0<<1) | /* command of software DMA (not relevant) */
|
||||
(1<<0)); /* hardware trigger DMA mode */
|
||||
}
|
||||
|
||||
static void rec_dma_done(void)
|
||||
{
|
||||
void *start;
|
||||
size_t size;
|
||||
|
||||
if (pcm_rec_dma_complete_callback(PCM_DMAST_OK, &start, &size))
|
||||
{
|
||||
hdma_i2s_rec_transfer(start, size);
|
||||
pcm_rec_dma_status_callback(PCM_DMAST_STARTED);
|
||||
}
|
||||
}
|
||||
|
||||
void pcm_rec_lock(void)
|
||||
{
|
||||
int old = disable_irq_save();
|
||||
++rec_locked;
|
||||
restore_irq(old);
|
||||
}
|
||||
|
||||
void pcm_rec_unlock(void)
|
||||
{
|
||||
int old = disable_irq_save();
|
||||
|
||||
if (--rec_locked == 0 && rec_pending)
|
||||
{
|
||||
rec_pending = false;
|
||||
rec_dma_done();
|
||||
}
|
||||
|
||||
restore_irq(old);
|
||||
}
|
||||
|
||||
void pcm_rec_dma_stop(void)
|
||||
{
|
||||
HDMA_CON1 = 0x00;
|
||||
hdma_isr_update(HDMA_CH1_ISR_BITS, HDMA_CH1_ISR_STOP);
|
||||
rec_pending = false;
|
||||
|
||||
I2S_OPR = I2S_OPR_PLAY;
|
||||
}
|
||||
|
||||
void pcm_rec_dma_start(void *addr, size_t size)
|
||||
{
|
||||
pcm_rec_dma_stop();
|
||||
|
||||
I2S_OPR = I2S_OPR_PLAY | (1<<16); /* reset Rx */
|
||||
I2S_OPR = I2S_OPR_RECORD;
|
||||
|
||||
hdma_i2s_rec_transfer(addr, size);
|
||||
}
|
||||
|
||||
void pcm_rec_dma_init(void)
|
||||
{
|
||||
pcm_rec_dma_stop();
|
||||
}
|
||||
|
||||
void pcm_rec_dma_close(void)
|
||||
{
|
||||
pcm_rec_dma_stop();
|
||||
}
|
||||
|
||||
const void * pcm_rec_dma_get_peak_buffer(void)
|
||||
{
|
||||
/* where the DMA writes now */
|
||||
return (const void *)(HDMA_CDST1 & ~3);
|
||||
}
|
||||
#endif /* HAVE_RECORDING */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue