summaryrefslogtreecommitdiff
path: root/firmware/target/arm/imx31/gigabeat-s/pcm-gigabeat-s.c
diff options
context:
space:
mode:
Diffstat (limited to 'firmware/target/arm/imx31/gigabeat-s/pcm-gigabeat-s.c')
-rw-r--r--firmware/target/arm/imx31/gigabeat-s/pcm-gigabeat-s.c555
1 files changed, 555 insertions, 0 deletions
diff --git a/firmware/target/arm/imx31/gigabeat-s/pcm-gigabeat-s.c b/firmware/target/arm/imx31/gigabeat-s/pcm-gigabeat-s.c
new file mode 100644
index 0000000000..6cec3ecdd3
--- /dev/null
+++ b/firmware/target/arm/imx31/gigabeat-s/pcm-gigabeat-s.c
@@ -0,0 +1,555 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2008 by Michael Sevakis
+ *
+ * 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdlib.h>
+#include "system.h"
+#include "kernel.h"
+#include "audio.h"
+#include "sound.h"
+#include "ccm-imx31.h"
+#include "sdma-imx31.h"
+#include "mmu-imx31.h"
+
+#define DMA_PLAY_CH_NUM 2
+#define DMA_REC_CH_NUM 1
+#define DMA_PLAY_CH_PRIORITY 6
+#define DMA_REC_CH_PRIORITY 6
+
+static struct buffer_descriptor dma_play_bd DEVBSS_ATTR;
+static struct channel_descriptor dma_play_cd DEVBSS_ATTR;
+
+struct dma_data
+{
+ 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 */
+ .locked = 0,
+ .callback_pending = 0,
+ .state = 0
+};
+
+static void play_dma_callback(void)
+{
+ unsigned char *start;
+ size_t size = 0;
+ pcm_more_callback_type get_more = pcm_callback_for_more;
+
+ if (dma_play_data.locked != 0)
+ {
+ /* Callback is locked out */
+ dma_play_data.callback_pending = dma_play_data.state;
+ return;
+ }
+
+ if (dma_play_bd.mode.status & BD_RROR)
+ {
+ /* Stop on error */
+ }
+ else if (get_more != NULL && (get_more(&start, &size), size != 0))
+ {
+ 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);
+ return;
+ }
+
+ /* Error, callback missing or no more DMA to do */
+ pcm_play_dma_stop();
+ pcm_play_dma_stopped_callback();
+}
+
+void pcm_play_lock(void)
+{
+ if (++dma_play_data.locked == 1)
+ imx31_regclr32(&SSI_SIER2, SSI_SIER_TDMAE);
+}
+
+void pcm_play_unlock(void)
+{
+ if (--dma_play_data.locked == 0 && dma_play_data.state != 0)
+ {
+ int oldstatus = disable_irq_save();
+ int pending = dma_play_data.callback_pending;
+ dma_play_data.callback_pending = 0;
+ SSI_SIER2 |= SSI_SIER_TDMAE;
+ restore_irq(oldstatus);
+
+ /* 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 != 0)
+ play_dma_callback();
+ }
+}
+
+void pcm_dma_apply_settings(void)
+{
+ audiohw_set_frequency(pcm_fsel);
+}
+
+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_SSI2_TX1;
+ dma_play_cd.wml = SDMA_SSI_TXFIFO_WML*2;
+ dma_play_cd.per_type = SDMA_PER_SSI_SHP; /* SSI2 shared with SDMA core */
+ dma_play_cd.tran_type = SDMA_TRAN_EMI_2_PER;
+ dma_play_cd.event_id1 = SDMA_REQ_SSI2_TX1;
+
+ sdma_channel_init(DMA_PLAY_CH_NUM, &dma_play_cd, &dma_play_bd);
+ sdma_channel_set_priority(DMA_PLAY_CH_NUM, DMA_PLAY_CH_PRIORITY);
+
+ ccm_module_clock_gating(CG_SSI1, CGM_ON_RUN_WAIT);
+ ccm_module_clock_gating(CG_SSI2, CGM_ON_RUN_WAIT);
+
+ /* Reset & disable SSIs */
+ SSI_SCR1 &= ~SSI_SCR_SSIEN;
+ SSI_SCR2 &= ~SSI_SCR_SSIEN;
+
+ SSI_SIER1 = 0;
+ SSI_SIER2 = 0;
+
+ /* Set up audio mux */
+
+ /* Port 2 (internally connected to SSI2)
+ * All clocking is output sourced from port 4 */
+ AUDMUX_PTCR2 = AUDMUX_PTCR_TFS_DIR | AUDMUX_PTCR_TFSEL_PORT4 |
+ AUDMUX_PTCR_TCLKDIR | AUDMUX_PTCR_TCSEL_PORT4 |
+ AUDMUX_PTCR_SYN;
+
+ /* Receive data from port 4 */
+ AUDMUX_PDCR2 = AUDMUX_PDCR_RXDSEL_PORT4;
+ /* All clock lines are inputs sourced from the master mode codec and
+ * sent back to SSI2 through port 2 */
+ AUDMUX_PTCR4 = AUDMUX_PTCR_SYN;
+
+ /* Receive data from port 2 */
+ AUDMUX_PDCR4 = AUDMUX_PDCR_RXDSEL_PORT2;
+
+ /* PORT1 (internally connected to SSI1) routes clocking to PORT5 to
+ * provide MCLK to the codec */
+ /* TX clocks are inputs taken from SSI2 */
+ /* RX clocks are outputs taken from PORT4 */
+ AUDMUX_PTCR1 = AUDMUX_PTCR_RFS_DIR | AUDMUX_PTCR_RFSSEL_PORT4 |
+ AUDMUX_PTCR_RCLKDIR | AUDMUX_PTCR_RCSEL_PORT4;
+ /* RX data taken from PORT4 */
+ AUDMUX_PDCR1 = AUDMUX_PDCR_RXDSEL_PORT4;
+
+ /* PORT5 outputs TCLK sourced from PORT1 (SSI1) */
+ AUDMUX_PTCR5 = AUDMUX_PTCR_TCLKDIR | AUDMUX_PTCR_TCSEL_PORT1;
+ AUDMUX_PDCR5 = 0;
+
+ /* Setup SSIs */
+
+ /* SSI2 - SoC software interface for all I2S data out */
+ SSI_SCR2 = SSI_SCR_SYN | SSI_SCR_I2S_MODE_SLAVE;
+ SSI_STCR2 = SSI_STCR_TXBIT0 | SSI_STCR_TSCKP | SSI_STCR_TFSI |
+ SSI_STCR_TEFS | SSI_STCR_TFEN0;
+
+ /* 16 bits per word, 2 words per frame */
+ SSI_STCCR2 = SSI_STRCCR_WL16 | ((2-1) << SSI_STRCCR_DC_POS) |
+ ((4-1) << SSI_STRCCR_PM_POS);
+
+ /* Transmit low watermark */
+ SSI_SFCSR2 = (SSI_SFCSR2 & ~SSI_SFCSR_TFWM0) |
+ ((8-SDMA_SSI_TXFIFO_WML) << SSI_SFCSR_TFWM0_POS);
+ SSI_STMSK2 = 0;
+
+ /* SSI1 - provides MCLK to codec. Receives data from codec. */
+ SSI_STCR1 = SSI_STCR_TXDIR;
+
+ /* f(INT_BIT_CLK) =
+ * f(SYS_CLK) / [(DIV2 + 1)*(7*PSR + 1)*(PM + 1)*2] =
+ * 677737600 / [(1 + 1)*(7*0 + 1)*(0 + 1)*2] =
+ * 677737600 / 4 = 169344000 Hz
+ *
+ * 45.4.2.2 DIV2, PSR, and PM Bit Description states:
+ * Bits DIV2, PSR, and PM should not be all set to zero at the same
+ * time.
+ *
+ * The hardware seems to force a divide by 4 even if all bits are
+ * zero but comply by setting DIV2 and the others to zero.
+ */
+ SSI_STCCR1 = SSI_STRCCR_DIV2 | ((1-1) << SSI_STRCCR_PM_POS);
+
+ /* SSI1 - receive - asynchronous clocks */
+ SSI_SCR1 = SSI_SCR_I2S_MODE_SLAVE;
+
+ SSI_SRCR1 = SSI_SRCR_RXBIT0 | SSI_SRCR_RSCKP | SSI_SRCR_RFSI |
+ SSI_SRCR_REFS;
+
+ /* 16 bits per word, 2 words per frame */
+ SSI_SRCCR1 = SSI_STRCCR_WL16 | ((2-1) << SSI_STRCCR_DC_POS) |
+ ((4-1) << SSI_STRCCR_PM_POS);
+
+ /* Receive high watermark */
+ SSI_SFCSR1 = (SSI_SFCSR1 & ~SSI_SFCSR_RFWM0) |
+ (SDMA_SSI_RXFIFO_WML << SSI_SFCSR_RFWM0_POS);
+ SSI_SRMSK1 = 0;
+
+ /* Enable SSI1 (codec clock) */
+ SSI_SCR1 |= SSI_SCR_SSIEN;
+
+ audiohw_init();
+}
+
+void pcm_postinit(void)
+{
+ audiohw_postinit();
+}
+
+static void play_start_pcm(void)
+{
+ /* Stop transmission (if in progress) */
+ SSI_SCR2 &= ~SSI_SCR_TE;
+
+ SSI_SCR2 |= SSI_SCR_SSIEN; /* Enable SSI */
+ SSI_STCR2 |= SSI_STCR_TFEN0; /* Enable TX FIFO */
+
+ dma_play_data.state = 1; /* Enable DMA requests on unlock */
+
+ /* Do prefill to prevent swapped channels (see TLSbo61214 in MCIMX31CE).
+ * No actual solution was offered but this appears to work. */
+ SSI_STX0_2 = 0;
+ SSI_STX0_2 = 0;
+ SSI_STX0_2 = 0;
+ SSI_STX0_2 = 0;
+
+ SSI_SCR2 |= SSI_SCR_TE; /* Start transmitting */
+}
+
+static void play_stop_pcm(void)
+{
+ /* Wait for FIFO to empty */
+ while (SSI_SFCSR_TFCNT0 & SSI_SFCSR2);
+
+ /* Disable transmission */
+ SSI_STCR2 &= ~SSI_STCR_TFEN0;
+ SSI_SCR2 &= ~(SSI_SCR_TE | SSI_SCR_SSIEN);
+
+ /* Set state before pending to prevent race with interrupt */
+ /* 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)
+{
+ sdma_channel_stop(DMA_PLAY_CH_NUM);
+
+ /* Disable transmission */
+ SSI_STCR2 &= ~SSI_STCR_TFEN0;
+ SSI_SCR2 &= ~(SSI_SCR_TE | SSI_SCR_SSIEN);
+
+ addr = (void *)(((unsigned long)addr + 3) & ~3);
+ size &= ~3;
+
+ if (size <= 0)
+ return;
+
+ if (!sdma_channel_reset(DMA_PLAY_CH_NUM))
+ return;
+
+ 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_run(DMA_PLAY_CH_NUM);
+}
+
+void pcm_play_dma_stop(void)
+{
+ sdma_channel_stop(DMA_PLAY_CH_NUM);
+ play_stop_pcm();
+}
+
+void pcm_play_dma_pause(bool pause)
+{
+ if (pause)
+ {
+ sdma_channel_pause(DMA_PLAY_CH_NUM);
+ play_stop_pcm();
+ }
+ else
+ {
+ 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)
+{
+ 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)
+{
+ 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 */
+ .locked = 0,
+ .callback_pending = 0,
+ .state = 0
+};
+
+static void rec_dma_callback(void)
+{
+ pcm_more_callback_type2 more_ready;
+ int status = 0;
+
+ if (dma_rec_data.locked != 0)
+ {
+ dma_rec_data.callback_pending = dma_rec_data.state;
+ 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(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)
+ imx31_regclr32(&SSI_SIER1, SSI_SIER_RDMAE);
+}
+
+void pcm_rec_unlock(void)
+{
+ if (--dma_rec_data.locked == 0 && dma_rec_data.state != 0)
+ {
+ int oldstatus = disable_irq_save();
+ int pending = dma_rec_data.callback_pending;
+ dma_rec_data.callback_pending = 0;
+ SSI_SIER1 |= 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 != 0)
+ rec_dma_callback();
+ }
+}
+
+void pcm_record_more(void *start, size_t size)
+{
+ start = (void *)(((unsigned long)start + 3) & ~3);
+ size &= ~3;
+
+ /* Invalidate - buffer must be coherent */
+ dump_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_SIER1, SSI_SIER_RDMAE);
+
+ SSI_SCR1 &= ~SSI_SCR_RE; /* Disable RX */
+ SSI_SRCR1 &= ~SSI_SRCR_RFEN0; /* Disable RX FIFO */
+
+ /* Set state before pending to prevent race with interrupt */
+ /* Do not enable DMA requests on unlock */
+ dma_rec_data.state = 0;
+ 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;
+
+ if (size <= 0)
+ return;
+
+ if (!sdma_channel_reset(DMA_REC_CH_NUM))
+ return;
+
+ /* Invalidate - buffer must be coherent */
+ dump_dcache_range(addr, size);
+
+ addr = (void *)addr_virt_to_phys((unsigned long)addr);
+ pcm_rec_peak_addr = addr;
+ 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;
+
+ SSI_SRCR1 |= SSI_SRCR_RFEN0; /* Enable RX FIFO */
+
+ /* Ensure clear FIFO */
+ while (SSI_SFCSR1 & SSI_SFCSR_RFCNT0)
+ SSI_SRX0_1;
+
+ /* Enable receive */
+ SSI_SCR1 |= SSI_SCR_RE;
+ sdma_channel_run(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_SSI1_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_SSI1_RX1;
+
+ sdma_channel_init(DMA_REC_CH_NUM, &dma_rec_cd, &dma_rec_bd);
+ sdma_channel_set_priority(DMA_REC_CH_NUM, DMA_REC_CH_PRIORITY);
+}
+
+const void * pcm_rec_dma_get_peak_buffer(int *count)
+{
+ 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 */