2007-09-21 15:51:53 +00:00
|
|
|
/***************************************************************************
|
|
|
|
* __________ __ ___.
|
|
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
|
|
* \/ \/ \/ \/ \/
|
|
|
|
* $Id$
|
|
|
|
*
|
2008-05-03 15:14:52 +00:00
|
|
|
* Copyright (C) 2008 by Michael Sevakis
|
2007-09-21 15:51:53 +00:00
|
|
|
*
|
2008-06-28 18:10:04 +00:00
|
|
|
* 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.
|
2007-09-21 15:51:53 +00:00
|
|
|
*
|
|
|
|
* 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"
|
2008-05-03 15:14:52 +00:00
|
|
|
#include "avic-imx31.h"
|
|
|
|
#include "clkctl-imx31.h"
|
2007-09-21 15:51:53 +00:00
|
|
|
|
2008-05-03 15:14:52 +00:00
|
|
|
/* This isn't DMA-based at the moment and is handled like Portal Player but
|
|
|
|
* will suffice for starters. */
|
|
|
|
|
|
|
|
struct dma_data
|
|
|
|
{
|
|
|
|
uint16_t *p;
|
|
|
|
size_t size;
|
|
|
|
int locked;
|
|
|
|
int state;
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct dma_data dma_play_data =
|
|
|
|
{
|
|
|
|
/* Initialize to a locked, stopped state */
|
|
|
|
.p = NULL,
|
|
|
|
.size = 0,
|
|
|
|
.locked = 0,
|
|
|
|
.state = 0
|
|
|
|
};
|
2007-10-06 22:27:27 +00:00
|
|
|
|
|
|
|
void pcm_play_lock(void)
|
|
|
|
{
|
2008-05-03 15:14:52 +00:00
|
|
|
if (++dma_play_data.locked == 1)
|
|
|
|
{
|
|
|
|
/* Atomically disable transmit interrupt */
|
2008-12-19 15:30:30 +00:00
|
|
|
imx31_regclr32(&SSI_SIER1, SSI_SIER_TIE);
|
2008-05-03 15:14:52 +00:00
|
|
|
}
|
2007-10-06 22:27:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void pcm_play_unlock(void)
|
|
|
|
{
|
2008-05-03 15:14:52 +00:00
|
|
|
if (--dma_play_data.locked == 0 && dma_play_data.state != 0)
|
|
|
|
{
|
|
|
|
/* Atomically enable transmit interrupt */
|
2008-12-19 15:30:30 +00:00
|
|
|
imx31_regset32(&SSI_SIER1, SSI_SIER_TIE);
|
2008-05-03 15:14:52 +00:00
|
|
|
}
|
2007-10-06 22:27:27 +00:00
|
|
|
}
|
|
|
|
|
2008-05-03 15:14:52 +00:00
|
|
|
static void __attribute__((interrupt("IRQ"))) SSI1_HANDLER(void)
|
|
|
|
{
|
|
|
|
register pcm_more_callback_type get_more;
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
while (dma_play_data.size > 0)
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* p is empty, get some more data */
|
|
|
|
get_more = pcm_callback_for_more;
|
|
|
|
|
|
|
|
if (get_more)
|
|
|
|
{
|
|
|
|
get_more((unsigned char **)&dma_play_data.p,
|
|
|
|
&dma_play_data.size);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
while (dma_play_data.size > 0);
|
|
|
|
|
|
|
|
/* No more data, so disable the FIFO/interrupt */
|
|
|
|
pcm_play_dma_stop();
|
|
|
|
pcm_play_dma_stopped_callback();
|
2007-09-21 15:51:53 +00:00
|
|
|
}
|
|
|
|
|
2008-12-12 11:01:07 +00:00
|
|
|
void pcm_dma_apply_settings(void)
|
2007-09-21 15:51:53 +00:00
|
|
|
{
|
2008-12-12 11:01:07 +00:00
|
|
|
audiohw_set_frequency(pcm_fsel);
|
2007-09-21 15:51:53 +00:00
|
|
|
}
|
|
|
|
|
2007-10-06 22:27:27 +00:00
|
|
|
void pcm_play_dma_init(void)
|
2007-09-21 15:51:53 +00:00
|
|
|
{
|
2008-05-03 15:14:52 +00:00
|
|
|
imx31_clkctl_module_clock_gating(CG_SSI1, CGM_ON_ALL);
|
|
|
|
imx31_clkctl_module_clock_gating(CG_SSI2, CGM_ON_ALL);
|
|
|
|
|
|
|
|
/* Reset & disable SSIs */
|
|
|
|
SSI_SCR2 &= ~SSI_SCR_SSIEN;
|
|
|
|
SSI_SCR1 &= ~SSI_SCR_SSIEN;
|
|
|
|
|
2008-12-31 01:54:26 +00:00
|
|
|
SSI_SIER1 = SSI_SIER_TFE0; /* TX0 can issue an interrupt */
|
|
|
|
SSI_SIER2 = SSI_SIER_RFF0; /* RX0 can issue an interrupt */
|
|
|
|
|
2008-05-03 15:14:52 +00:00
|
|
|
/* Set up audio mux */
|
|
|
|
|
|
|
|
/* Port 1 (internally connected to SSI1)
|
|
|
|
* All clocking is output sourced from port 4 */
|
|
|
|
AUDMUX_PTCR1 = AUDMUX_PTCR_TFS_DIR | AUDMUX_PTCR_TFSEL_PORT4 |
|
|
|
|
AUDMUX_PTCR_TCLKDIR | AUDMUX_PTCR_TCSEL_PORT4 |
|
|
|
|
AUDMUX_PTCR_SYN;
|
|
|
|
|
|
|
|
/* Receive data from port 4 */
|
|
|
|
AUDMUX_PDCR1 = AUDMUX_PDCR_RXDSEL_PORT4;
|
|
|
|
/* All clock lines are inputs sourced from the master mode codec and
|
|
|
|
* sent back to SSI1 through port 1 */
|
|
|
|
AUDMUX_PTCR4 = AUDMUX_PTCR_SYN;
|
|
|
|
|
|
|
|
/* Receive data from port 1 */
|
|
|
|
AUDMUX_PDCR4 = AUDMUX_PDCR_RXDSEL_PORT1;
|
|
|
|
|
2008-12-31 01:38:44 +00:00
|
|
|
/* PORT2 (internally connected to SSI2) routes clocking to PORT5 to
|
2008-05-03 15:14:52 +00:00
|
|
|
* provide MCLK to the codec */
|
2008-12-31 01:38:44 +00:00
|
|
|
/* TX clocks are inputs taken from SSI2 */
|
|
|
|
/* RX clocks are outputs taken from PORT4 */
|
|
|
|
AUDMUX_PTCR2 = AUDMUX_PTCR_RFS_DIR | AUDMUX_PTCR_RFSSEL_PORT4 |
|
|
|
|
AUDMUX_PTCR_RCLKDIR | AUDMUX_PTCR_RCSEL_PORT4;
|
|
|
|
/* RX data taken from PORT4 */
|
|
|
|
AUDMUX_PDCR2 = AUDMUX_PDCR_RXDSEL_PORT4;
|
|
|
|
|
|
|
|
/* PORT5 outputs TCLK sourced from PORT2 (SSI2) */
|
2008-05-03 15:14:52 +00:00
|
|
|
AUDMUX_PTCR5 = AUDMUX_PTCR_TCLKDIR | AUDMUX_PTCR_TCSEL_PORT2;
|
|
|
|
AUDMUX_PDCR5 = 0;
|
|
|
|
|
|
|
|
/* Setup SSIs */
|
|
|
|
|
2008-12-31 01:38:44 +00:00
|
|
|
/* SSI1 - SoC software interface for all I2S data out */
|
2008-05-03 15:14:52 +00:00
|
|
|
SSI_SCR1 = SSI_SCR_SYN | SSI_SCR_I2S_MODE_SLAVE;
|
|
|
|
SSI_STCR1 = SSI_STCR_TXBIT0 | SSI_STCR_TSCKP | SSI_STCR_TFSI |
|
|
|
|
SSI_STCR_TEFS | SSI_STCR_TFEN0;
|
|
|
|
|
|
|
|
/* 16 bits per word, 2 words per frame */
|
|
|
|
SSI_STCCR1 = SSI_STRCCR_WL16 | SSI_STRCCR_DCw(2-1) |
|
|
|
|
SSI_STRCCR_PMw(4-1);
|
|
|
|
|
2008-12-31 01:38:44 +00:00
|
|
|
/* Transmit low watermark - 2 samples in FIFO */
|
|
|
|
SSI_SFCSR1 = SSI_SFCSR_TFWM1w(1) | SSI_SFCSR_TFWM0w(2);
|
2008-05-03 15:14:52 +00:00
|
|
|
SSI_STMSK1 = 0;
|
|
|
|
|
2008-12-31 01:38:44 +00:00
|
|
|
/* SSI2 - provides MCLK to codec. Receives data from codec. */
|
2008-05-03 15:14:52 +00:00
|
|
|
SSI_STCR2 = SSI_STCR_TXDIR;
|
|
|
|
|
2008-11-22 12:17:26 +00:00
|
|
|
/* 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_STCCR2 = SSI_STRCCR_DIV2 | SSI_STRCCR_PMw(1-1);
|
|
|
|
|
2008-12-31 01:38:44 +00:00
|
|
|
/* SSI2 - receive - asynchronous clocks */
|
|
|
|
SSI_SCR2 = SSI_SCR_I2S_MODE_SLAVE;
|
|
|
|
|
|
|
|
SSI_SRCR2 = SSI_SRCR_RXBIT0 | SSI_SRCR_RSCKP | SSI_SRCR_RFSI |
|
|
|
|
SSI_SRCR_REFS;
|
|
|
|
|
|
|
|
/* 16 bits per word, 2 words per frame */
|
|
|
|
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);
|
|
|
|
SSI_SRMSK2 = 0;
|
|
|
|
|
2008-11-22 12:17:26 +00:00
|
|
|
/* Enable SSI2 (codec clock) */
|
2008-05-03 15:14:52 +00:00
|
|
|
SSI_SCR2 |= SSI_SCR_SSIEN;
|
|
|
|
|
2008-04-27 10:30:54 +00:00
|
|
|
audiohw_init();
|
2007-09-21 15:51:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void pcm_postinit(void)
|
|
|
|
{
|
2008-04-27 10:30:54 +00:00
|
|
|
audiohw_postinit();
|
2008-05-03 15:14:52 +00:00
|
|
|
avic_enable_int(SSI1, IRQ, 8, SSI1_HANDLER);
|
2007-09-21 15:51:53 +00:00
|
|
|
}
|
|
|
|
|
2007-10-06 22:27:27 +00:00
|
|
|
static void play_start_pcm(void)
|
2007-09-21 15:51:53 +00:00
|
|
|
{
|
2008-05-03 15:14:52 +00:00
|
|
|
/* 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 */
|
2008-12-31 01:38:44 +00:00
|
|
|
SSI_SCR1 |= SSI_SCR_SSIEN; /* Enable SSI */
|
|
|
|
SSI_STCR1 |= SSI_STCR_TFEN0; /* Enable TX FIFO */
|
2008-11-19 03:12:34 +00:00
|
|
|
|
2008-05-03 15:14:52 +00:00
|
|
|
while (1)
|
|
|
|
{
|
|
|
|
if (SSI_SFCSR_TFCNT0r(SSI_SFCSR1) > 6 || dma_play_data.size == 0)
|
|
|
|
{
|
2008-11-19 03:12:34 +00:00
|
|
|
SSI_SCR1 |= SSI_SCR_TE; /* Start transmitting */
|
2008-05-03 15:14:52 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
SSI_STX0_1 = *dma_play_data.p++;
|
|
|
|
SSI_STX0_1 = *dma_play_data.p++;
|
|
|
|
dma_play_data.size -= 4;
|
|
|
|
}
|
2007-09-21 15:51:53 +00:00
|
|
|
}
|
|
|
|
|
2007-10-06 22:27:27 +00:00
|
|
|
static void play_stop_pcm(void)
|
2007-09-21 15:51:53 +00:00
|
|
|
{
|
2008-05-03 15:14:52 +00:00
|
|
|
/* Disable interrupt */
|
|
|
|
SSI_SIER1 &= ~SSI_SIER_TIE;
|
|
|
|
|
|
|
|
/* Wait for FIFO to empty */
|
|
|
|
while (SSI_SFCSR_TFCNT0r(SSI_SFCSR1) > 0);
|
|
|
|
|
|
|
|
/* Disable transmission */
|
2008-12-31 01:38:44 +00:00
|
|
|
SSI_STCR1 &= ~SSI_STCR_TFEN0;
|
2008-05-03 15:14:52 +00:00
|
|
|
SSI_SCR1 &= ~(SSI_SCR_TE | SSI_SCR_SSIEN);
|
|
|
|
|
|
|
|
/* Do not enable interrupt on unlock */
|
|
|
|
dma_play_data.state = 0;
|
2007-09-21 15:51:53 +00:00
|
|
|
}
|
|
|
|
|
2007-10-06 22:27:27 +00:00
|
|
|
void pcm_play_dma_start(const void *addr, size_t size)
|
2007-09-21 15:51:53 +00:00
|
|
|
{
|
2008-05-03 15:14:52 +00:00
|
|
|
dma_play_data.p = (void *)(((uintptr_t)addr + 3) & ~3);
|
|
|
|
dma_play_data.size = (size & ~3);
|
|
|
|
|
|
|
|
play_start_pcm();
|
2007-09-21 15:51:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void pcm_play_dma_stop(void)
|
|
|
|
{
|
2008-05-03 15:14:52 +00:00
|
|
|
play_stop_pcm();
|
|
|
|
dma_play_data.size = 0;
|
2007-09-21 15:51:53 +00:00
|
|
|
}
|
|
|
|
|
2007-10-06 22:27:27 +00:00
|
|
|
void pcm_play_dma_pause(bool pause)
|
2007-09-21 15:51:53 +00:00
|
|
|
{
|
2008-05-03 15:14:52 +00:00
|
|
|
if (pause)
|
|
|
|
{
|
|
|
|
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();
|
|
|
|
}
|
2007-09-21 15:51:53 +00:00
|
|
|
}
|
|
|
|
|
2007-10-06 22:27:27 +00:00
|
|
|
/* Return the number of bytes waiting - full L-R sample pairs only */
|
2007-09-21 15:51:53 +00:00
|
|
|
size_t pcm_get_bytes_waiting(void)
|
|
|
|
{
|
2008-05-03 15:14:52 +00:00
|
|
|
return dma_play_data.size & ~3;
|
2007-09-21 15:51:53 +00:00
|
|
|
}
|
|
|
|
|
2007-10-06 22:27:27 +00:00
|
|
|
/* Return a pointer to the samples and the number of them in *count */
|
|
|
|
const void * pcm_play_dma_get_peak_buffer(int *count)
|
2007-09-21 15:51:53 +00:00
|
|
|
{
|
2008-05-03 15:14:52 +00:00
|
|
|
uint32_t addr = (uint32_t)dma_play_data.p;
|
|
|
|
size_t cnt = dma_play_data.size;
|
|
|
|
*count = cnt >> 2;
|
|
|
|
return (void *)((addr + 2) & ~3);
|
2007-09-21 15:51:53 +00:00
|
|
|
}
|
|
|
|
|
2008-12-31 01:38:44 +00:00
|
|
|
#ifdef HAVE_RECORDING
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
register pcm_more_callback_type2 more_ready;
|
|
|
|
|
|
|
|
while (dma_rec_data.size > 0)
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void pcm_rec_lock(void)
|
|
|
|
{
|
|
|
|
if (++dma_rec_data.locked == 1)
|
|
|
|
{
|
|
|
|
/* Atomically disable receive interrupt */
|
|
|
|
imx31_regclr32(&SSI_SIER2, SSI_SIER_RIE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 */
|
|
|
|
}
|
|
|
|
|
|
|
|
void pcm_rec_dma_stop(void)
|
|
|
|
{
|
|
|
|
/* Stop receiving data */
|
|
|
|
SSI_SCR2 &= ~SSI_SCR_RE; /* Disable RX */
|
|
|
|
SSI_SRCR2 &= ~SSI_SRCR_RFEN0; /* Disable RX FIFO */
|
|
|
|
|
|
|
|
dma_rec_data.state = 0;
|
|
|
|
|
|
|
|
avic_disable_int(SSI2);
|
|
|
|
}
|
|
|
|
|
|
|
|
void pcm_rec_dma_start(void *addr, size_t size)
|
|
|
|
{
|
|
|
|
pcm_rec_dma_stop();
|
|
|
|
|
|
|
|
pcm_rec_peak_addr = addr;
|
|
|
|
dma_rec_data.p = addr;
|
|
|
|
dma_rec_data.size = size;
|
|
|
|
|
|
|
|
dma_rec_data.state = 1;
|
|
|
|
|
|
|
|
avic_enable_int(SSI2, IRQ, 9, SSI2_HANDLER);
|
|
|
|
|
|
|
|
SSI_SRCR2 |= SSI_SRCR_RFEN0; /* Enable RX FIFO */
|
|
|
|
|
|
|
|
/* Ensure clear FIFO */
|
|
|
|
while (SSI_SFCSR2 & SSI_SFCSR_RFCNT0)
|
|
|
|
SSI_SRX0_2;
|
|
|
|
|
|
|
|
/* Enable receive */
|
|
|
|
SSI_SCR2 |= SSI_SCR_RE;
|
|
|
|
}
|
|
|
|
|
|
|
|
void pcm_rec_dma_close(void)
|
|
|
|
{
|
|
|
|
pcm_rec_dma_stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void pcm_rec_dma_init(void)
|
|
|
|
{
|
|
|
|
pcm_rec_dma_stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* HAVE_RECORDING */
|