d462a64a91
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@18435 a1c6a512-1295-4272-9138-f99709370657
434 lines
12 KiB
C
434 lines
12 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2006 by Michael Sevakis
|
|
* Copyright (C) 2008 by Rob Purchase
|
|
*
|
|
* 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 "logf.h"
|
|
#include "audio.h"
|
|
#include "sound.h"
|
|
#include "pcm.h"
|
|
|
|
/* Just for tests enable it to play simple tone */
|
|
//#define PCM_TELECHIPS_TEST
|
|
|
|
struct dma_data
|
|
{
|
|
/* NOTE: The order of size and p is important if you use assembler
|
|
optimised fiq handler, so don't change it. */
|
|
uint16_t *p;
|
|
size_t size;
|
|
#if NUM_CORES > 1
|
|
unsigned core;
|
|
#endif
|
|
int locked;
|
|
int state;
|
|
};
|
|
|
|
/****************************************************************************
|
|
** Playback DMA transfer
|
|
**/
|
|
struct dma_data dma_play_data SHAREDBSS_ATTR =
|
|
{
|
|
/* Initialize to a locked, stopped state */
|
|
.p = NULL,
|
|
.size = 0,
|
|
#if NUM_CORES > 1
|
|
.core = 0x00,
|
|
#endif
|
|
.locked = 0,
|
|
.state = 0
|
|
};
|
|
|
|
static unsigned long pcm_freq SHAREDDATA_ATTR = HW_SAMPR_DEFAULT; /* 44.1 is default */
|
|
|
|
void pcm_postinit(void)
|
|
{
|
|
#if defined(IAUDIO_7)
|
|
audiohw_postinit(); /* implemented not for all codecs */
|
|
#endif
|
|
pcm_apply_settings();
|
|
}
|
|
|
|
const void * pcm_play_dma_get_peak_buffer(int *count)
|
|
{
|
|
unsigned long addr = (unsigned long)dma_play_data.p;
|
|
size_t cnt = dma_play_data.size;
|
|
*count = cnt >> 2;
|
|
return (void *)((addr + 2) & ~3);
|
|
}
|
|
|
|
void pcm_play_dma_init(void)
|
|
{
|
|
DAVC = 0x0; /* Digital Volume = max */
|
|
#ifdef COWON_D2
|
|
/* Set DAI clock divided from PLL0 (192MHz).
|
|
The best approximation of 256*44.1kHz is 11.291MHz. */
|
|
BCLKCTR &= ~DEV_DAI;
|
|
PCLK_DAI = (1<<28) | 61682; /* DCO mode */
|
|
BCLKCTR |= DEV_DAI;
|
|
|
|
/* Enable DAI block in Master mode, 256fs->32fs, 16bit LSB */
|
|
DAMR = 0x3c8e80;
|
|
#elif defined(IAUDIO_7)
|
|
BCLKCTR &= ~DEV_DAI;
|
|
PCLK_DAI = (0x800b << 16) | (PCLK_DAI & 0xffff);
|
|
BCLKCTR |= DEV_DAI;
|
|
/* Master mode, 256->64fs, 16bit LSB*/
|
|
DAMR = 0x3cce20;
|
|
#else
|
|
#error "Target isn't supported"
|
|
#endif
|
|
/* Set DAI interrupts as FIQs */
|
|
IRQSEL = ~(DAI_RX_IRQ_MASK | DAI_TX_IRQ_MASK);
|
|
|
|
pcm_set_frequency(SAMPR_44);
|
|
|
|
/* Initialize default register values. */
|
|
audiohw_init();
|
|
|
|
/* Power on */
|
|
audiohw_enable_output(true);
|
|
|
|
/* Unmute the master channel (DAC should be at zero point now). */
|
|
audiohw_mute(false);
|
|
|
|
dma_play_data.size = 0;
|
|
#if NUM_CORES > 1
|
|
dma_play_data.core = 0; /* no core in control */
|
|
#endif
|
|
}
|
|
|
|
void pcm_apply_settings(void)
|
|
{
|
|
pcm_curr_sampr = pcm_freq;
|
|
}
|
|
|
|
void pcm_set_frequency(unsigned int frequency)
|
|
{
|
|
(void) frequency;
|
|
pcm_freq = HW_SAMPR_DEFAULT;
|
|
}
|
|
|
|
static void play_start_pcm(void)
|
|
{
|
|
pcm_apply_settings();
|
|
|
|
DAMR &= ~(1<<14); /* disable tx */
|
|
dma_play_data.state = 1;
|
|
|
|
if (dma_play_data.size >= 16)
|
|
{
|
|
DADO_L(0) = *dma_play_data.p++;
|
|
DADO_R(0) = *dma_play_data.p++;
|
|
DADO_L(1) = *dma_play_data.p++;
|
|
DADO_R(1) = *dma_play_data.p++;
|
|
DADO_L(2) = *dma_play_data.p++;
|
|
DADO_R(2) = *dma_play_data.p++;
|
|
DADO_L(3) = *dma_play_data.p++;
|
|
DADO_R(3) = *dma_play_data.p++;
|
|
dma_play_data.size -= 16;
|
|
}
|
|
|
|
DAMR |= (1<<14); /* enable tx */
|
|
}
|
|
|
|
static void play_stop_pcm(void)
|
|
{
|
|
DAMR &= ~(1<<14); /* disable tx */
|
|
dma_play_data.state = 0;
|
|
}
|
|
|
|
void pcm_play_dma_start(const void *addr, size_t size)
|
|
{
|
|
dma_play_data.p = (void *)(((uintptr_t)addr + 2) & ~3);
|
|
dma_play_data.size = (size & ~3);
|
|
|
|
#if NUM_CORES > 1
|
|
/* This will become more important later - and different ! */
|
|
dma_play_data.core = processor_id(); /* save initiating core */
|
|
#endif
|
|
|
|
IEN |= DAI_TX_IRQ_MASK;
|
|
|
|
play_start_pcm();
|
|
}
|
|
|
|
void pcm_play_dma_stop(void)
|
|
{
|
|
play_stop_pcm();
|
|
dma_play_data.size = 0;
|
|
#if NUM_CORES > 1
|
|
dma_play_data.core = 0; /* no core in control */
|
|
#endif
|
|
}
|
|
|
|
void pcm_play_lock(void)
|
|
{
|
|
int status = disable_fiq_save();
|
|
|
|
if (++dma_play_data.locked == 1)
|
|
{
|
|
IEN &= ~DAI_TX_IRQ_MASK;
|
|
}
|
|
|
|
restore_fiq(status);
|
|
}
|
|
|
|
void pcm_play_unlock(void)
|
|
{
|
|
int status = disable_fiq_save();
|
|
|
|
if (--dma_play_data.locked == 0 && dma_play_data.state != 0)
|
|
{
|
|
IEN |= DAI_TX_IRQ_MASK;
|
|
}
|
|
|
|
restore_fiq(status);
|
|
}
|
|
|
|
void pcm_play_dma_pause(bool pause)
|
|
{
|
|
(void) pause;
|
|
}
|
|
|
|
size_t pcm_get_bytes_waiting(void)
|
|
{
|
|
return dma_play_data.size & ~3;
|
|
}
|
|
|
|
#ifdef HAVE_RECORDING
|
|
/* TODO: implement */
|
|
void pcm_rec_dma_init(void)
|
|
{
|
|
}
|
|
|
|
void pcm_rec_dma_close(void)
|
|
{
|
|
}
|
|
|
|
void pcm_rec_dma_start(void *addr, size_t size)
|
|
{
|
|
(void) addr;
|
|
(void) size;
|
|
}
|
|
|
|
void pcm_rec_dma_stop(void)
|
|
{
|
|
}
|
|
|
|
void pcm_rec_lock(void)
|
|
{
|
|
}
|
|
|
|
void pcm_rec_unlock(void)
|
|
{
|
|
}
|
|
|
|
const void * pcm_rec_dma_get_peak_buffer(int *count)
|
|
{
|
|
*count = 0;
|
|
return NULL;
|
|
}
|
|
|
|
void pcm_record_more(void *start, size_t size)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
#if defined(COWON_D2)
|
|
/* TODO: hardcoded hex values differs for tcc7xx and tcc8xx */
|
|
void fiq_handler(void) ICODE_ATTR __attribute__((naked));
|
|
void fiq_handler(void)
|
|
{
|
|
/* r10 contains DADO_L0 base address (set in crt0.S to minimise code in the
|
|
* FIQ handler. r11 contains address of p (also set in crt0.S). Most other
|
|
* addresses we need are generated by using offsets with these two.
|
|
* r8 and r9 contains local copies of p and size respectively.
|
|
* r0-r3 and r12 is a working register.
|
|
*/
|
|
asm volatile (
|
|
"mov r8, #0xc000 \n" /* DAI_TX_IRQ_MASK | DAI_RX_IRQ_MASK */
|
|
"ldr r9, =0xf3001004 \n" /* CREQ */
|
|
"str r8, [r9] \n" /* clear DAI IRQs */
|
|
|
|
"ldmia r11, { r8-r9 } \n" /* r8 = p, r9 = size */
|
|
"cmp r9, #0x10 \n" /* is size <16? */
|
|
"blt .more_data \n" /* if so, ask pcmbuf for more data */
|
|
|
|
".fill_fifo: \n"
|
|
"ldr r12, [r8], #4 \n" /* load two samples */
|
|
"str r12, [r10, #0x0] \n" /* write top sample to DADO_L0 */
|
|
"mov r12, r12, lsr #16 \n" /* put right sample at the bottom */
|
|
"str r12, [r10, #0x4] \n" /* write low sample to DADO_R0*/
|
|
"ldr r12, [r8], #4 \n" /* load two samples */
|
|
"str r12, [r10, #0x8] \n" /* write top sample to DADO_L1 */
|
|
"mov r12, r12, lsr #16 \n" /* put right sample at the bottom */
|
|
"str r12, [r10, #0xc] \n" /* write low sample to DADO_R1*/
|
|
"ldr r12, [r8], #4 \n" /* load two samples */
|
|
"str r12, [r10, #0x10] \n" /* write top sample to DADO_L2 */
|
|
"mov r12, r12, lsr #16 \n" /* put right sample at the bottom */
|
|
"str r12, [r10, #0x14] \n" /* write low sample to DADO_R2*/
|
|
"ldr r12, [r8], #4 \n" /* load two samples */
|
|
"str r12, [r10, #0x18] \n" /* write top sample to DADO_L3 */
|
|
"mov r12, r12, lsr #16 \n" /* put right sample at the bottom */
|
|
"str r12, [r10, #0x1c] \n" /* write low sample to DADO_R3*/
|
|
"sub r9, r9, #0x10 \n" /* 4 words written */
|
|
"stmia r11, { r8-r9 } \n" /* save p and size */
|
|
|
|
".exit: \n"
|
|
"subs pc, lr, #4 \n" /* FIQ specific return sequence */
|
|
|
|
".more_data: \n"
|
|
"stmfd sp!, { r0-r3, lr } \n" /* stack scratch regs and lr */
|
|
"ldr r2, =pcm_callback_for_more \n"
|
|
"ldr r2, [r2] \n" /* get callback address */
|
|
"cmp r2, #0 \n" /* check for null pointer */
|
|
"movne r0, r11 \n" /* r0 = &p */
|
|
"addne r1, r11, #4 \n" /* r1 = &size */
|
|
"blxne r2 \n" /* call pcm_callback_for_more */
|
|
"ldmia r11, { r8-r9 } \n" /* reload p and size */
|
|
"cmp r9, #0x10 \n" /* did we actually get more data? */
|
|
"ldmgefd sp!, { r0-r3, lr } \n"
|
|
"bge .fill_fifo \n" /* yes: fill the fifo */
|
|
"ldr r12, =pcm_play_dma_stop \n"
|
|
"blx r12 \n" /* no: stop playback */
|
|
"ldr r12, =pcm_play_dma_stopped_callback \n"
|
|
"blx r12 \n"
|
|
"ldmfd sp!, { r0-r3, lr } \n"
|
|
"b .exit \n"
|
|
".ltorg \n"
|
|
);
|
|
}
|
|
#else /* C version for reference */
|
|
void fiq_handler(void) ICODE_ATTR __attribute__((naked));
|
|
void fiq_handler(void)
|
|
{
|
|
asm volatile( "stmfd sp!, {r0-r7, ip, lr} \n" /* Store context */
|
|
"sub sp, sp, #8 \n"); /* Reserve stack */
|
|
|
|
register pcm_more_callback_type get_more;
|
|
|
|
if (dma_play_data.size < 16)
|
|
{
|
|
/* 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);
|
|
}
|
|
}
|
|
|
|
if (dma_play_data.size >= 16)
|
|
{
|
|
DADO_L(0) = *dma_play_data.p++;
|
|
DADO_R(0) = *dma_play_data.p++;
|
|
DADO_L(1) = *dma_play_data.p++;
|
|
DADO_R(1) = *dma_play_data.p++;
|
|
DADO_L(2) = *dma_play_data.p++;
|
|
DADO_R(2) = *dma_play_data.p++;
|
|
DADO_L(3) = *dma_play_data.p++;
|
|
DADO_R(3) = *dma_play_data.p++;
|
|
|
|
dma_play_data.size -= 16;
|
|
}
|
|
else
|
|
{
|
|
/* No more data, so disable the FIFO/interrupt */
|
|
pcm_play_dma_stop();
|
|
pcm_play_dma_stopped_callback();
|
|
}
|
|
|
|
/* Clear FIQ status */
|
|
CREQ = DAI_TX_IRQ_MASK | DAI_RX_IRQ_MASK;
|
|
|
|
asm volatile( "add sp, sp, #8 \n" /* Cleanup stack */
|
|
"ldmfd sp!, {r0-r7, ip, lr} \n" /* Restore context */
|
|
"subs pc, lr, #4 \n"); /* Return from FIQ */
|
|
}
|
|
#endif
|
|
|
|
/* TODO: required by wm8531 codec, why not to implement */
|
|
void i2s_reset(void)
|
|
{
|
|
/* DAMR = 0; */
|
|
}
|
|
|
|
#ifdef PCM_TELECHIPS_TEST
|
|
#include "lcd.h"
|
|
#include "sprintf.h"
|
|
#include "backlight-target.h"
|
|
|
|
static int frame = 0;
|
|
static void test_callback_for_more(unsigned char **start, size_t *size)
|
|
{
|
|
static unsigned short data[8];
|
|
static int cntr = 0;
|
|
int i;
|
|
|
|
for (i = 0; i < 8; i ++) {
|
|
unsigned short val;
|
|
|
|
if (0x100 == (cntr & 0x100))
|
|
val = 0x0fff;
|
|
else
|
|
val = 0x0000;
|
|
data[i] = val;
|
|
cntr++;
|
|
}
|
|
|
|
*start = data;
|
|
*size = sizeof(data);
|
|
|
|
frame++;
|
|
}
|
|
|
|
void pcm_telechips_test(void)
|
|
{
|
|
static char buf[100];
|
|
unsigned char *data;
|
|
size_t size;
|
|
|
|
_backlight_on();
|
|
|
|
audiohw_preinit();
|
|
pcm_play_dma_init();
|
|
pcm_postinit();
|
|
|
|
audiohw_mute(false);
|
|
audiohw_set_master_vol(0x7f, 0x7f);
|
|
|
|
pcm_callback_for_more = test_callback_for_more;
|
|
test_callback_for_more(&data, &size);
|
|
pcm_play_dma_start(data, size);
|
|
|
|
while (1) {
|
|
int line = 0;
|
|
lcd_clear_display();
|
|
lcd_puts(0, line++, __func__);
|
|
snprintf(buf, sizeof(buf), "frame: %d", frame);
|
|
lcd_puts(0, line++, buf);
|
|
lcd_update();
|
|
sleep(1);
|
|
}
|
|
}
|
|
#endif
|