rockbox/firmware/target/arm/imx31/gigabeat-s/pcm-imx31.c
Daniel Stenberg 2acc0ac542 Updated our source code header to explicitly mention that we are GPL v2 or
later. We still need to hunt down snippets used that are not. 1324 modified
files...
http://www.rockbox.org/mail/archive/rockbox-dev-archive-2008-06/0060.shtml


git-svn-id: svn://svn.rockbox.org/rockbox/trunk@17847 a1c6a512-1295-4272-9138-f99709370657
2008-06-28 18:10:04 +00:00

300 lines
7.8 KiB
C

/***************************************************************************
* __________ __ ___.
* 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 "avic-imx31.h"
#include "clkctl-imx31.h"
/* 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 unsigned long pcm_freq = HW_SAMPR_DEFAULT; /* 44.1 is default */
static struct dma_data dma_play_data =
{
/* Initialize to a locked, stopped state */
.p = NULL,
.size = 0,
.locked = 0,
.state = 0
};
void pcm_play_lock(void)
{
if (++dma_play_data.locked == 1)
{
/* Atomically disable transmit interrupt */
imx31_regmod32(&SSI_SIER1, 0, SSI_SIER_TIE);
}
}
void pcm_play_unlock(void)
{
if (--dma_play_data.locked == 0 && dma_play_data.state != 0)
{
/* Atomically enable transmit interrupt */
imx31_regmod32(&SSI_SIER1, SSI_SIER_TIE, SSI_SIER_TIE);
}
}
static void _pcm_apply_settings(void)
{
if (pcm_freq != pcm_curr_sampr)
{
pcm_curr_sampr = pcm_freq;
// TODO: audiohw_set_frequency(sr_ctrl);
}
}
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();
}
void pcm_apply_settings(void)
{
int oldstatus = disable_fiq_save();
_pcm_apply_settings();
restore_fiq(oldstatus);
}
void pcm_play_dma_init(void)
{
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;
SSI_SIER1 = SSI_SIER_TFE0;
SSI_SIER2 = 0;
/* 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_RFSDIR | AUDMUX_PTCR_RFSSEL_PORT4 |
AUDMUX_PTCR_RCLKDIR | AUDMUX_PTCR_RCSEL_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;
/* Port 2 (internally connected to SSI2) routes clocking to port 5 to
* provide MCLK to the codec */
/* All port 2 clocks are inputs taken from SSI2 */
AUDMUX_PTCR2 = 0;
AUDMUX_PDCR2 = 0;
/* Port 5 outputs TCLK sourced from port 2 */
AUDMUX_PTCR5 = AUDMUX_PTCR_TCLKDIR | AUDMUX_PTCR_TCSEL_PORT2;
AUDMUX_PDCR5 = 0;
/* Setup SSIs */
/* SSI1 - interface for all I2S data */
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);
SSI_STMSK1 = 0;
/* Receive */
SSI_SRCR1 = SSI_SRCR_RXBIT0 | SSI_SRCR_RSCKP | SSI_SRCR_RFSI |
SSI_SRCR_REFS | SSI_SRCR_RFEN0;
/* 16 bits per word, 2 words per frame */
SSI_SRCCR1 = SSI_STRCCR_WL16 | SSI_STRCCR_DCw(2-1) |
SSI_STRCCR_PMw(4-1);
/* Receive high watermark - 6 samples in FIFO
* Transmit low watermark - 2 samples in FIFO */
SSI_SFCSR1 = SSI_SFCSR_RFWM1w(8) | SSI_SFCSR_TFWM1w(1) |
SSI_SFCSR_RFWM0w(6) | SSI_SFCSR_TFWM0w(2);
SSI_SRMSK1 = 0;
/* SSI2 - provides MCLK only */
SSI_SCR2 = 0;
SSI_SRCR2 = 0;
SSI_STCR2 = SSI_STCR_TXDIR;
SSI_STCCR2 = SSI_STRCCR_PMw(0);
/* Enable SSIs */
SSI_SCR2 |= SSI_SCR_SSIEN;
audiohw_init();
}
void pcm_postinit(void)
{
audiohw_postinit();
avic_enable_int(SSI1, IRQ, 8, SSI1_HANDLER);
}
static void play_start_pcm(void)
{
/* Stop transmission (if in progress) */
SSI_SCR1 &= ~SSI_SCR_TE;
/* Apply new settings */
_pcm_apply_settings();
/* Enable interrupt on unlock */
dma_play_data.state = 1;
/* Fill the FIFO or start when data is used up */
while (1)
{
if (SSI_SFCSR_TFCNT0r(SSI_SFCSR1) > 6 || dma_play_data.size == 0)
{
SSI_SCR1 |= (SSI_SCR_TE | SSI_SCR_SSIEN); /* Start transmitting */
return;
}
SSI_STX0_1 = *dma_play_data.p++;
SSI_STX0_1 = *dma_play_data.p++;
dma_play_data.size -= 4;
}
}
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);
/* Disable transmission */
SSI_SCR1 &= ~(SSI_SCR_TE | SSI_SCR_SSIEN);
/* Do not enable interrupt on unlock */
dma_play_data.state = 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);
play_start_pcm();
}
void pcm_play_dma_stop(void)
{
play_stop_pcm();
dma_play_data.size = 0;
}
void pcm_play_dma_pause(bool pause)
{
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();
}
}
/* Set the pcm frequency hardware will use when play is next started or
when pcm_apply_settings is called. Do not apply the setting to the
hardware here but simply cache it. */
void pcm_set_frequency(unsigned int frequency)
{
/* TODO */
(void)frequency;
}
/* 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;
}
/* 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);
}
/* Any recording functionality should be implemented similarly */