From 94537f954e67d44fdd9703c062c4ee53599e0e74 Mon Sep 17 00:00:00 2001 From: Michael Sevakis Date: Sun, 8 Feb 2009 22:32:41 +0000 Subject: Gigabeat S: Implement an SDMA API and use it in the PCM driver. Some other miscellaneous adjustments to recording and PCM buffer to accomodate use of physical addresses and cache coherency. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@19949 a1c6a512-1295-4272-9138-f99709370657 --- firmware/target/arm/imx31/gigabeat-s/pcm-imx31.c | 366 ++++++++++++++++------- 1 file changed, 255 insertions(+), 111 deletions(-) (limited to 'firmware/target/arm/imx31/gigabeat-s/pcm-imx31.c') diff --git a/firmware/target/arm/imx31/gigabeat-s/pcm-imx31.c b/firmware/target/arm/imx31/gigabeat-s/pcm-imx31.c index f53d98aee9..60801262b4 100644 --- a/firmware/target/arm/imx31/gigabeat-s/pcm-imx31.c +++ b/firmware/target/arm/imx31/gigabeat-s/pcm-imx31.c @@ -23,78 +23,93 @@ #include "kernel.h" #include "audio.h" #include "sound.h" -#include "avic-imx31.h" #include "clkctl-imx31.h" +#include "sdma-imx31.h" +#include "mmu-imx31.h" -/* This isn't DMA-based at the moment and is handled like Portal Player but - * will suffice for starters. */ +#define DMA_PLAY_CH_NUM 2 +#define DMA_REC_CH_NUM 1 + +static struct buffer_descriptor dma_play_bd DEVBSS_ATTR; +static struct channel_descriptor dma_play_cd DEVBSS_ATTR; struct dma_data { - uint16_t *p; - size_t size; int locked; + int callback_pending; /* DMA interrupt happened while locked */ int state; }; static struct dma_data dma_play_data = { /* Initialize to a locked, stopped state */ - .p = NULL, - .size = 0, .locked = 0, + .callback_pending = 0, .state = 0 }; -void pcm_play_lock(void) +static void play_dma_callback(void) { - if (++dma_play_data.locked == 1) + unsigned char *start; + size_t size; + pcm_more_callback_type get_more = pcm_callback_for_more; + + if (dma_play_data.locked) { - /* Atomically disable transmit interrupt */ - imx31_regclr32(&SSI_SIER1, SSI_SIER_TIE); + /* Callback is locked out */ + dma_play_data.callback_pending = 1; + return; } -} -void pcm_play_unlock(void) -{ - if (--dma_play_data.locked == 0 && dma_play_data.state != 0) + if (get_more == NULL || (get_more(&start, &size), size == 0)) { - /* Atomically enable transmit interrupt */ - imx31_regset32(&SSI_SIER1, SSI_SIER_TIE); + /* Callback missing or no more DMA to do */ + pcm_play_dma_stop(); + pcm_play_dma_stopped_callback(); + } + else + { + start = (void*)(((unsigned long)start + 3) & ~3); + size &= ~3; + + /* Flush any pending cache writes */ + clean_dcache_range(start, size); + dma_play_bd.buf_addr = (void *)addr_virt_to_phys((unsigned long)start); + dma_play_bd.mode.count = size; + dma_play_bd.mode.command = TRANSFER_16BIT; + dma_play_bd.mode.status = BD_DONE | BD_WRAP | BD_INTR; + sdma_channel_run(DMA_PLAY_CH_NUM); } } -static void __attribute__((interrupt("IRQ"))) SSI1_HANDLER(void) +void pcm_play_lock(void) { - register pcm_more_callback_type get_more; + if (++dma_play_data.locked == 1) + imx31_regclr32(&SSI_SIER1, SSI_SIER_TDMAE); +} - do +void pcm_play_unlock(void) +{ + if (--dma_play_data.locked == 0 && dma_play_data.state != 0) { - while (dma_play_data.size > 0) + bool pending = false; + int oldstatus = disable_irq_save(); + + if (dma_play_data.callback_pending) { - if (SSI_SFCSR_TFCNT0r(SSI_SFCSR1) > 6) - { - return; - } - SSI_STX0_1 = *dma_play_data.p++; - SSI_STX0_1 = *dma_play_data.p++; - dma_play_data.size -= 4; + pending = true; + dma_play_data.callback_pending = 0; } - /* p is empty, get some more data */ - get_more = pcm_callback_for_more; + SSI_SIER1 |= SSI_SIER_TDMAE; + restore_irq(oldstatus); - if (get_more) - { - get_more((unsigned char **)&dma_play_data.p, - &dma_play_data.size); - } + /* Should an interrupt be forced instead? The upper pcm layer can + * call producer's callback in thread context so technically this is + * acceptable. */ + if (pending) + play_dma_callback(); } - while (dma_play_data.size > 0); - - /* No more data, so disable the FIFO/interrupt */ - pcm_play_dma_stop(); - pcm_play_dma_stopped_callback(); } void pcm_dma_apply_settings(void) @@ -104,6 +119,17 @@ void pcm_dma_apply_settings(void) void pcm_play_dma_init(void) { + /* Init channel information */ + dma_play_cd.bd_count = 1; + dma_play_cd.callback = play_dma_callback; + dma_play_cd.shp_addr = SDMA_PER_ADDR_SSI1_TX1; + dma_play_cd.wml = SDMA_SSI_TXFIFO_WML*2; + dma_play_cd.per_type = SDMA_PER_SSI; + dma_play_cd.tran_type = SDMA_TRAN_EMI_2_PER; + dma_play_cd.event_id1 = SDMA_REQ_SSI1_TX1; + + sdma_channel_init(DMA_PLAY_CH_NUM, &dma_play_cd, &dma_play_bd); + imx31_clkctl_module_clock_gating(CG_SSI1, CGM_ON_ALL); imx31_clkctl_module_clock_gating(CG_SSI2, CGM_ON_ALL); @@ -111,8 +137,8 @@ void pcm_play_dma_init(void) SSI_SCR2 &= ~SSI_SCR_SSIEN; SSI_SCR1 &= ~SSI_SCR_SSIEN; - SSI_SIER1 = SSI_SIER_TFE0; /* TX0 can issue an interrupt */ - SSI_SIER2 = SSI_SIER_RFF0; /* RX0 can issue an interrupt */ + SSI_SIER1 = 0; + SSI_SIER2 = 0; /* Set up audio mux */ @@ -155,8 +181,9 @@ void pcm_play_dma_init(void) SSI_STCCR1 = SSI_STRCCR_WL16 | SSI_STRCCR_DCw(2-1) | SSI_STRCCR_PMw(4-1); - /* Transmit low watermark - 2 samples in FIFO */ - SSI_SFCSR1 = SSI_SFCSR_TFWM1w(1) | SSI_SFCSR_TFWM0w(2); + /* Transmit low watermark */ + SSI_SFCSR1 = (SSI_SFCSR1 & ~SSI_SFCSR_TFWM0) | + SSI_SFCSR_TFWM0w(8-SDMA_SSI_TXFIFO_WML); SSI_STMSK1 = 0; /* SSI2 - provides MCLK to codec. Receives data from codec. */ @@ -186,8 +213,9 @@ void pcm_play_dma_init(void) SSI_SRCCR2 = SSI_STRCCR_WL16 | SSI_STRCCR_DCw(2-1) | SSI_STRCCR_PMw(4-1); - /* Receive high watermark - 6 samples in FIFO */ - SSI_SFCSR2 = SSI_SFCSR_RFWM1w(8) | SSI_SFCSR_RFWM0w(6); + /* Receive high watermark */ + SSI_SFCSR2 = (SSI_SFCSR2 & ~SSI_SFCSR_RFWM0) | + SSI_SFCSR_RFWM0w(SDMA_SSI_RXFIFO_WML); SSI_SRMSK2 = 0; /* Enable SSI2 (codec clock) */ @@ -199,7 +227,6 @@ void pcm_play_dma_init(void) void pcm_postinit(void) { audiohw_postinit(); - avic_enable_int(SSI1, IRQ, 8, SSI1_HANDLER); } static void play_start_pcm(void) @@ -207,32 +234,23 @@ static void play_start_pcm(void) /* Stop transmission (if in progress) */ SSI_SCR1 &= ~SSI_SCR_TE; - /* Enable interrupt on unlock */ - dma_play_data.state = 1; - - /* Fill the FIFO or start when data is used up */ SSI_SCR1 |= SSI_SCR_SSIEN; /* Enable SSI */ SSI_STCR1 |= SSI_STCR_TFEN0; /* Enable TX FIFO */ - while (1) - { - if (SSI_SFCSR_TFCNT0r(SSI_SFCSR1) > 6 || dma_play_data.size == 0) - { - SSI_SCR1 |= SSI_SCR_TE; /* Start transmitting */ - return; - } + dma_play_data.state = 1; /* Enable DMA requests on unlock */ - SSI_STX0_1 = *dma_play_data.p++; - SSI_STX0_1 = *dma_play_data.p++; - dma_play_data.size -= 4; - } + /* Do prefill to prevent swapped channels (see TLSbo61214 in MCIMX31CE). + * No actual solution was offered but this appears to work. */ + SSI_STX0_1 = 0; + SSI_STX0_1 = 0; + SSI_STX0_1 = 0; + SSI_STX0_1 = 0; + + SSI_SCR1 |= SSI_SCR_TE; /* Start transmitting */ } static void play_stop_pcm(void) { - /* Disable interrupt */ - SSI_SIER1 &= ~SSI_SIER_TIE; - /* Wait for FIFO to empty */ while (SSI_SFCSR_TFCNT0r(SSI_SFCSR1) > 0); @@ -240,135 +258,227 @@ static void play_stop_pcm(void) SSI_STCR1 &= ~SSI_STCR_TFEN0; SSI_SCR1 &= ~(SSI_SCR_TE | SSI_SCR_SSIEN); - /* Do not enable interrupt on unlock */ + /* Do not enable DMA requests on unlock */ dma_play_data.state = 0; + dma_play_data.callback_pending = 0; } void pcm_play_dma_start(const void *addr, size_t size) { - dma_play_data.p = (void *)(((uintptr_t)addr + 3) & ~3); - dma_play_data.size = (size & ~3); + sdma_channel_stop(DMA_PLAY_CH_NUM); + + /* Disable transmission */ + SSI_STCR1 &= ~SSI_STCR_TFEN0; + SSI_SCR1 &= ~(SSI_SCR_TE | SSI_SCR_SSIEN); + + addr = (void *)(((unsigned long)addr + 3) & ~3); + size &= ~3; + + clean_dcache_range(addr, size); + dma_play_bd.buf_addr = + (void *)addr_virt_to_phys((unsigned long)(void *)addr); + dma_play_bd.mode.count = size; + dma_play_bd.mode.command = TRANSFER_16BIT; + dma_play_bd.mode.status = BD_DONE | BD_WRAP | BD_INTR; play_start_pcm(); + sdma_channel_start(DMA_PLAY_CH_NUM); } void pcm_play_dma_stop(void) { + sdma_channel_stop(DMA_PLAY_CH_NUM); play_stop_pcm(); - dma_play_data.size = 0; } void pcm_play_dma_pause(bool pause) { if (pause) { + sdma_channel_pause(DMA_PLAY_CH_NUM); play_stop_pcm(); } else { - uint32_t addr = (uint32_t)dma_play_data.p; - dma_play_data.p = (void *)((addr + 2) & ~3); - dma_play_data.size &= ~3; play_start_pcm(); + sdma_channel_run(DMA_PLAY_CH_NUM); } } /* Return the number of bytes waiting - full L-R sample pairs only */ size_t pcm_get_bytes_waiting(void) { - return dma_play_data.size & ~3; + static unsigned long dsa DEVBSS_ATTR; + long offs, size; + int oldstatus; + + /* read burst dma source address register in channel context */ + sdma_read_words(&dsa, CHANNEL_CONTEXT_ADDR(DMA_PLAY_CH_NUM)+0x0b, 1); + + oldstatus = disable_irq_save(); + offs = dsa - (unsigned long)dma_play_bd.buf_addr; + size = dma_play_bd.mode.count; + restore_irq(oldstatus); + + /* Be addresses are coherent (no buffer change during read) */ + if (offs >= 0 && offs < size) + { + return (size - offs) & ~3; + } + + return 0; } /* Return a pointer to the samples and the number of them in *count */ const void * pcm_play_dma_get_peak_buffer(int *count) { - uint32_t addr = (uint32_t)dma_play_data.p; - size_t cnt = dma_play_data.size; - *count = cnt >> 2; - return (void *)((addr + 2) & ~3); + static unsigned long dsa DEVBSS_ATTR; + unsigned long addr; + long offs, size; + int oldstatus; + + /* read burst dma source address register in channel context */ + sdma_read_words(&dsa, CHANNEL_CONTEXT_ADDR(DMA_PLAY_CH_NUM)+0x0b, 1); + + oldstatus = disable_irq_save(); + addr = dsa; + offs = addr - (unsigned long)dma_play_bd.buf_addr; + size = dma_play_bd.mode.count; + restore_irq(oldstatus); + + /* Be addresses are coherent (no buffer change during read) */ + if (offs >= 0 && offs < size) + { + *count = (size - offs) >> 2; + return (void *)((addr + 2) & ~3); + } + + *count = 0; + return NULL; +} + +void * pcm_dma_addr(void *addr) +{ + return (void *)addr_virt_to_phys((unsigned long)addr); } #ifdef HAVE_RECORDING +static struct buffer_descriptor dma_rec_bd DEVBSS_ATTR; +static struct channel_descriptor dma_rec_cd DEVBSS_ATTR; + static struct dma_data dma_rec_data = { /* Initialize to a locked, stopped state */ - .p = NULL, - .size = 0, .locked = 0, .state = 0 }; -static void __attribute__((interrupt("IRQ"))) SSI2_HANDLER(void) +static void rec_dma_callback(void) { - register pcm_more_callback_type2 more_ready; + pcm_more_callback_type2 more_ready; + int status = 0; - while (dma_rec_data.size > 0) + if (dma_rec_data.locked) { - if (SSI_SFCSR_RFCNT0r(SSI_SFCSR2) < 2) - return; - - *dma_rec_data.p++ = SSI_SRX0_2; - *dma_rec_data.p++ = SSI_SRX0_2; - dma_rec_data.size -= 4; + dma_rec_data.callback_pending = 1; + return; /* Callback is locked out */ } + if (dma_rec_bd.mode.status & BD_RROR) + status = DMA_REC_ERROR_DMA; + more_ready = pcm_callback_more_ready; - if (more_ready == NULL || more_ready(0) < 0) { - /* Finished recording */ - pcm_rec_dma_stop(); - pcm_rec_dma_stopped_callback(); + if (more_ready != NULL && more_ready(status) >= 0) + { + sdma_channel_run(DMA_REC_CH_NUM); + return; } + + /* Finished recording */ + pcm_rec_dma_stop(); + pcm_rec_dma_stopped_callback(); } void pcm_rec_lock(void) { if (++dma_rec_data.locked == 1) - { - /* Atomically disable receive interrupt */ - imx31_regclr32(&SSI_SIER2, SSI_SIER_RIE); - } + imx31_regclr32(&SSI_SIER2, SSI_SIER_RDMAE); } void pcm_rec_unlock(void) { if (--dma_rec_data.locked == 0 && dma_rec_data.state != 0) { - /* Atomically enable receive interrupt */ - imx31_regset32(&SSI_SIER2, SSI_SIER_RIE); + bool pending = false; + int oldstatus = disable_irq_save(); + + if (dma_rec_data.callback_pending) + { + pending = true; + dma_rec_data.callback_pending = 0; + } + + SSI_SIER2 |= SSI_SIER_RDMAE; + restore_irq(oldstatus); + + /* Should an interrupt be forced instead? The upper pcm layer can + * call consumer's callback in thread context so technically this is + * acceptable. */ + if (pending) + rec_dma_callback(); } } void pcm_record_more(void *start, size_t size) { - pcm_rec_peak_addr = start; /* Start peaking at dest */ - dma_rec_data.p = start; /* Start of RX buffer */ - dma_rec_data.size = size; /* Bytes to transfer */ + start = (void *)(((unsigned long)start + 3) & ~3); + size &= ~3; + + /* Write back and invalidate - buffer must be coherent */ + invalidate_dcache_range(start, size); + + start = (void *)addr_virt_to_phys((unsigned long)start); + + pcm_rec_peak_addr = start; + dma_rec_bd.buf_addr = start; + dma_rec_bd.mode.count = size; + dma_rec_bd.mode.command = TRANSFER_16BIT; + dma_rec_bd.mode.status = BD_DONE | BD_WRAP | BD_INTR; } void pcm_rec_dma_stop(void) { /* Stop receiving data */ + sdma_channel_stop(DMA_REC_CH_NUM); + + imx31_regclr32(&SSI_SIER2, SSI_SIER_RDMAE); + SSI_SCR2 &= ~SSI_SCR_RE; /* Disable RX */ SSI_SRCR2 &= ~SSI_SRCR_RFEN0; /* Disable RX FIFO */ dma_rec_data.state = 0; - - avic_disable_int(SSI2); + dma_rec_data.callback_pending = 0; } void pcm_rec_dma_start(void *addr, size_t size) { pcm_rec_dma_stop(); + addr = (void *)(((unsigned long)addr + 3) & ~3); + size &= ~3; + + invalidate_dcache_range(addr, size); + + addr = (void *)addr_virt_to_phys((unsigned long)addr); pcm_rec_peak_addr = addr; - dma_rec_data.p = addr; - dma_rec_data.size = size; + dma_rec_bd.buf_addr = addr; + dma_rec_bd.mode.count = size; + dma_rec_bd.mode.command = TRANSFER_16BIT; + dma_rec_bd.mode.status = BD_DONE | BD_WRAP | BD_INTR; dma_rec_data.state = 1; - avic_enable_int(SSI2, IRQ, 9, SSI2_HANDLER); - SSI_SRCR2 |= SSI_SRCR_RFEN0; /* Enable RX FIFO */ /* Ensure clear FIFO */ @@ -377,24 +487,58 @@ void pcm_rec_dma_start(void *addr, size_t size) /* Enable receive */ SSI_SCR2 |= SSI_SCR_RE; + + sdma_channel_start(DMA_REC_CH_NUM); } void pcm_rec_dma_close(void) { pcm_rec_dma_stop(); + sdma_channel_close(DMA_REC_CH_NUM); } void pcm_rec_dma_init(void) { pcm_rec_dma_stop(); + + /* Init channel information */ + dma_rec_cd.bd_count = 1; + dma_rec_cd.callback = rec_dma_callback; + dma_rec_cd.shp_addr = SDMA_PER_ADDR_SSI2_RX1; + dma_rec_cd.wml = SDMA_SSI_RXFIFO_WML*2; + dma_rec_cd.per_type = SDMA_PER_SSI; + dma_rec_cd.tran_type = SDMA_TRAN_PER_2_EMI; + dma_rec_cd.event_id1 = SDMA_REQ_SSI2_RX1; + + sdma_channel_init(DMA_REC_CH_NUM, &dma_rec_cd, &dma_rec_bd); } const void * pcm_rec_dma_get_peak_buffer(int *count) { - unsigned long addr = (uint32_t)pcm_rec_peak_addr; - unsigned long end = (uint32_t)dma_rec_data.p; - *count = (end >> 2) - (addr >> 2); - return (void *)(addr & ~3); + static unsigned long pda DEVBSS_ATTR; + unsigned long buf, addr, end, bufend; + int oldstatus; + + /* read burst dma destination address register in channel context */ + sdma_read_words(&pda, CHANNEL_CONTEXT_ADDR(DMA_REC_CH_NUM)+0x0a, 1); + + oldstatus = disable_irq_save(); + end = pda; + buf = (unsigned long)dma_rec_bd.buf_addr; + addr = (unsigned long)pcm_rec_peak_addr; + bufend = buf + dma_rec_bd.mode.count; + restore_irq(oldstatus); + + /* Be addresses are coherent (no buffer change during read) */ + if (addr >= buf && addr < bufend && + end >= buf && end < bufend) + { + *count = (end >> 2) - (addr >> 2); + return (void *)(addr & ~3); + } + + *count = 0; + return NULL; } #endif /* HAVE_RECORDING */ -- cgit v1.2.3