diff --git a/apps/SOURCES b/apps/SOURCES index 2ea41c2..58e38a2 100644 --- a/apps/SOURCES +++ b/apps/SOURCES @@ -15,7 +15,7 @@ menus/display_menu.c menus/theme_menu.c #if CONFIG_CODEC == SWCODEC menus/eq_menu.c -buffering.c +buffering_flash.c voice_thread.c replaygain.c #else /* !SWCODEC */ diff --git a/apps/buffering_flash.c b/apps/buffering_flash.c new file mode 100644 index 0000000..50bf53b --- /dev/null +++ b/apps/buffering_flash.c @@ -0,0 +1,444 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007 Nicolas Pennequin + * + * 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 "config.h" +#include +#include +#include +#include +#include "buffering.h" + +#include "file.h" +#include "debug.h" +#include "metadata.h" +#include "thread.h" +#include "kernel.h" +#include "events.h" +#include "system.h" + +/* Define LOGF_ENABLE to enable logf output in this file */ +#define LOGF_ENABLE +#include "logf.h" + +/* macros to enable logf for queues + logging on SYS_TIMEOUT can be disabled */ +#ifdef SIMULATOR +/* Define this for logf output of all queuing except SYS_TIMEOUT */ +#define BUFFERING_LOGQUEUES +/* Define this to logf SYS_TIMEOUT messages */ +/* #define BUFFERING_LOGQUEUES_SYS_TIMEOUT */ +#endif + +#ifdef BUFFERING_LOGQUEUES +#define LOGFQUEUE logf +#else +#define LOGFQUEUE(...) +#endif + +#ifdef BUFFERING_LOGQUEUES_SYS_TIMEOUT +#define LOGFQUEUE_SYS_TIMEOUT logf +#else +#define LOGFQUEUE_SYS_TIMEOUT(...) +#endif + + +#define MAX_HANDLES 32 + +#define BUF_HANDLE_MASK 0x7FFFFFFF + +#define BUFFER_SIZE 192*1024 + +/* The buffer bufgetdata uses to hold a file chunk */ +static char buffer[BUFFER_SIZE]; + +/* The id of the handle for which we have data */ +static int buffer_hid = -1; + +/* The read offset in the buffer */ +static size_t buffer_offset = 0; + +/* How much fresh data is left in the buffer */ +static size_t buffer_avail = 0; + +/* The position in the file where the buffered data starts */ +static size_t buffer_file_offset = 0; + + +struct memory_handle { + int id; /* A unique ID for the handle */ + enum data_type type; /* Type of data buffered with this handle */ + char path[MAX_PATH]; /* Path if data originated in a file */ + size_t filepos; /* where we are in the file */ + size_t filesize; /* File total length */ + size_t offset; /* Where we are in the file */ + volatile size_t ridx; /* Read pointer, relative to the buffer */ + struct mp3entry id3data; +}; + +enum { + Q_HANDLE_ADDED, +}; + +static struct memory_handle handles[MAX_HANDLES]; + +static unsigned int num_handles = 0; + + +/* Buffering thread */ +static void buffering_thread(void); +static long buffering_stack[(DEFAULT_STACK_SIZE + 0x2000)/sizeof(long)]; +static const char buffering_thread_name[] = "buffering"; +static struct thread_entry *buffering_thread_p; +static struct event_queue buffering_queue; +static struct queue_sender_list buffering_queue_sender_list; + + + + +static struct memory_handle *find_handle(int handle_id) +{ + if (handle_id < 0) + return NULL; + + int i; + for (i = 0; i < MAX_HANDLES; i++) { + if (handles[i].id == handle_id) + return &handles[i]; + } + + return NULL; +} + +static struct memory_handle *add_handle(void) +{ + /* gives each handle a unique id */ + static int cur_handle_id = 0; + + /* find an unused handle slot */ + int i = 0; + while (i < MAX_HANDLES && handles[i].id != -1) + i++; + + if (handles[i].id == -1) { + handles[i].id = cur_handle_id; + + /* Wrap signed int is safe and 0 doesn't happen */ + cur_handle_id = (cur_handle_id + 1) & BUF_HANDLE_MASK; + + num_handles++; + + return &handles[i]; + } + else + return NULL; +} + +static bool rm_handle(struct memory_handle *h) +{ + if (h == NULL) + return true; + + num_handles--; + memset(h, 0, sizeof(struct memory_handle)); + h->id = -1; + + return true; +} + +int bufopen(const char *file, size_t offset, enum data_type type) +{ + if (num_handles >= MAX_HANDLES) + return ERR_BUFFER_FULL; + + int fd = open(file, O_RDONLY); + if (fd < 0) + return ERR_FILE_ERROR; + + struct memory_handle *h = add_handle(); + if (!h) + { + DEBUGF("bufopen: failed to add handle\n"); + close(fd); + return ERR_BUFFER_FULL; + } + + strncpy(h->path, file, MAX_PATH); + h->type = type; + h->filesize = filesize(fd); + h->filepos = offset; + h->offset = offset; + h->ridx = 0; + + if (type == TYPE_ID3) + { + if (!get_metadata(&h->id3data, fd, file)) { + DEBUGF("failed to get metadata\n"); + } + } + + close(fd); + + /* Inform the buffering thread that we added a handle */ + LOGFQUEUE("buffering > Q_HANDLE_ADDED %d", h->id); + queue_post(&buffering_queue, Q_HANDLE_ADDED, h->id); + + return h->id; +} + +int bufalloc(const void *src, size_t size, enum data_type type) +{ + (void)src; + (void)size; + (void)type; + DEBUGF("bufalloc not implemented\n"); + return 0; +} + +bool bufclose(int handle_id) +{ + struct memory_handle *h = find_handle(handle_id); + + /* If the handle is not found, it is closed */ + if (!h) + return true; + + /* Currently rm_handle always returns true */ + return rm_handle(h); +} + +int bufseek(int handle_id, size_t newpos) +{ + //DEBUGF("bufseek(%d, %ld)\n", handle_id, newpos); + + struct memory_handle *h = find_handle(handle_id); + if (!h) + return ERR_HANDLE_NOT_FOUND; + + if (newpos > h->filesize) { + /* access beyond the end of the file */ + return ERR_INVALID_VALUE; + } + else { + h->offset = newpos; + } + return 0; +} + +int bufadvance(int handle_id, off_t offset) +{ + //DEBUGF("bufadvance(%d, %ld)\n", handle_id, offset); + + struct memory_handle *h = find_handle(handle_id); + if (!h) + return ERR_HANDLE_NOT_FOUND; + + if ((size_t)(h->offset + offset) > h->filesize || + (off_t)(h->offset + offset) < 0) + { + /* access beyond the limits of the file */ + return ERR_INVALID_VALUE; + } + else { + buffer_offset += offset; + buffer_avail -= offset; + h->offset += offset; + h->ridx += offset; + } + return 0; +} + +ssize_t bufread(int handle_id, size_t size, void *dest) +{ + //DEBUGF("bufread(%d)\n", handle_id); + struct memory_handle *h = find_handle(handle_id); + if (!h) + return ERR_HANDLE_NOT_FOUND; + + int fd = open(h->path, O_RDONLY); + if (fd < 0) + return ERR_FILE_ERROR; + + lseek(fd, h->offset, SEEK_SET); + h->filepos = h->offset; + + ssize_t ret = read(fd, dest, size); + if (ret < 0) { + DEBUGF("read failed (%ld)\n", ret); + } else { + h->filepos += ret; + } + + close(fd); + return ret; +} + +ssize_t bufgetdata(int handle_id, size_t size, void **data) +{ + //DEBUGF("bufgetdata(%d, %ld)\n", handle_id, size); + (void)size; + + struct memory_handle *h = find_handle(handle_id); + if (!h) + return ERR_HANDLE_NOT_FOUND; + + if (h->type == TYPE_ID3) { + *data = &h->id3data; + return sizeof (struct mp3entry); + } else if (h->type == TYPE_PACKET_AUDIO) { + + /* If there is still enough data left in the buffer, we can use that */ + if (size < buffer_avail && buffer_file_offset + buffer_offset == h->offset) { + //DEBUGF("bufgetdata: buffer hit (%ld left)\n", buffer_avail); + *data = buffer + buffer_offset; + return buffer_avail; + } + + if (buffer_file_offset + buffer_offset != h->offset) { + DEBUGF("offset mismatch\n"); + } + + //DEBUGF("bufgetdata: buffer miss (%ld left)\n", buffer_avail); + + int fd = open(h->path, O_RDONLY); + if (fd < 0) + return ERR_FILE_ERROR; + + lseek(fd, h->offset, SEEK_SET); + h->filepos = h->offset; + buffer_file_offset = h->offset; + + //size = (size == 0) ? BUFFER_SIZE : MIN(size, BUFFER_SIZE); + ssize_t ret = read(fd, buffer, BUFFER_SIZE); + if (ret < 0) { + DEBUGF("read failed (%ld)\n", ret); + } else { + buffer_offset = 0; + buffer_avail = ret; + h->filepos += ret; + h->ridx = 0; + } + + close(fd); + *data = buffer; + return ret; + } else { + *data = NULL; + return 0; + } +} + +ssize_t bufgettail(int handle_id, size_t size, void **data) +{ + +} + +ssize_t bufcuttail(int handle_id, size_t size) +{ + +} + +ssize_t buf_get_offset(int handle_id, void *ptr) +{ + const struct memory_handle *h = find_handle(handle_id); + if (!h) + return ERR_HANDLE_NOT_FOUND; + + return ptr - (void *)&buffer[h->ridx]; +} + +ssize_t buf_handle_offset(int handle_id) +{ + +} + +void buf_request_buffer_handle(int handle_id) +{ + +} + +void buf_set_base_handle(int handle_id) +{ + +} + +size_t buf_used(void) +{ + +} + +void buf_set_watermark(size_t bytes) +{ + +} + +void buffering_get_debugdata(struct buffering_debug *dbgdata) +{ + +} + +void buffering_init(void) +{ + queue_init(&buffering_queue, true); + buffering_thread_p = create_thread( buffering_thread, buffering_stack, + sizeof(buffering_stack), CREATE_THREAD_FROZEN, + buffering_thread_name IF_PRIO(, PRIORITY_BUFFERING) + IF_COP(, CPU)); + + queue_enable_queue_send(&buffering_queue, &buffering_queue_sender_list, + buffering_thread_p); +} + +bool buffering_reset(char *buf, size_t buflen) +{ + int i; + for (i = 0; i < MAX_HANDLES; i++) { + handles[i].id = -1; + } + + num_handles = 0; + buffer_hid = -1; + buffer_offset = 0; + + thread_thaw(buffering_thread_p); + return true; +} + +void buffering_thread(void) +{ + struct queue_event ev; + + while (true) + { + queue_wait_w_tmo(&buffering_queue, &ev, 5); + + switch (ev.id) + { + case Q_HANDLE_ADDED: + { + LOGFQUEUE("buffering < Q_HANDLE_ADDED %d", (int)ev.data); + struct memory_handle *h = find_handle((int)ev.data); + if (h && h->type == TYPE_ID3) { + send_event(BUFFER_EVENT_FINISHED, &h->id); + } + } + } + } +} diff --git a/apps/plugins/plugin.lds b/apps/plugins/plugin.lds index 52ec938..0c5c1e8 100644 --- a/apps/plugins/plugin.lds +++ b/apps/plugins/plugin.lds @@ -37,6 +37,8 @@ OUTPUT_FORMAT(elf32-sh) #include "imx31l.h" /* Reserve 1mb for LCD buffer/TTB as in app.lds */ #define DRAMSIZE (MEMORYSIZE * 0x100000 - 0x100000) - PLUGIN_BUFFER_SIZE - STUBOFFSET - CODEC_SIZE +#elif CONFIG_CPU==AS3525 && MEMORYSIZE <= 2 +#define DRAMSIZE (MEMORYSIZE * 0x100000) - PLUGIN_BUFFER_SIZE - STUBOFFSET #else #define DRAMSIZE (MEMORYSIZE * 0x100000) - PLUGIN_BUFFER_SIZE - STUBOFFSET - CODEC_SIZE #endif @@ -87,17 +89,29 @@ OUTPUT_FORMAT(elf32-sh) #define IRAM DRAM #define IRAMSIZE 0 #elif CONFIG_CPU==AS3525 +#if MEMORYSIZE <= 2 +#define IRAMSIZE 0 /* simulates no IRAM since codec is already entirely in IRAM */ +#define CODEC_ORIGIN (0x50000 - CODEC_SIZE) +#define PLUGIN_ORIGIN (DRAMORIG + DRAMSIZE) +#else #define IRAMORIG 0x0 #define IRAMSIZE 0x50000 +#endif #define DRAMORIG 0x30000000 + #else #define DRAMORIG 0x09000000 + STUBOFFSET #endif #define PLUGIN_LENGTH PLUGIN_BUFFER_SIZE +#ifndef CODEC_ORIGIN /* targets can specify another origin */ #define CODEC_ORIGIN (DRAMORIG + (DRAMSIZE)) +#endif + +#ifndef PLUGIN_ORIGIN /* targets can specify another origin */ #define PLUGIN_ORIGIN (CODEC_ORIGIN + CODEC_SIZE) +#endif #ifdef CODEC #define THIS_LENGTH CODEC_SIZE diff --git a/firmware/drivers/audio/as3514.c b/firmware/drivers/audio/as3514.c index 41577a3..18eaa14 100644 --- a/firmware/drivers/audio/as3514.c +++ b/firmware/drivers/audio/as3514.c @@ -194,7 +194,10 @@ void audiohw_postinit(void) /* wait until outputs have stabilized */ sleep(HZ/4); +/* TODO do other targets really need this? Sansa clip needs HPCM */ +#ifndef SANSA_CLIP as3514_write(AS3514_AUDIOSET3, AUDIOSET3_HPCM_off); +#endif #ifdef CPU_PP ascodec_suppressor_on(false); diff --git a/firmware/export/config-clip.h b/firmware/export/config-clip.h index 0f4b93b..40630bb 100644 --- a/firmware/export/config-clip.h +++ b/firmware/export/config-clip.h @@ -95,10 +95,10 @@ #define HAVE_FAT16SUPPORT /* The number of bytes reserved for loadable codecs */ -#define CODEC_SIZE 0x80000 /* TODO : check if we can use IRAM */ +#define CODEC_SIZE 0x48000 /* in IRAM */ /* The number of bytes reserved for loadable plugins */ -#define PLUGIN_BUFFER_SIZE 0x80000 +#define PLUGIN_BUFFER_SIZE 0x60000 #define AB_REPEAT_ENABLE 1 diff --git a/firmware/export/config-m200v4.h b/firmware/export/config-m200v4.h index b5c4f56..ec264d0 100644 --- a/firmware/export/config-m200v4.h +++ b/firmware/export/config-m200v4.h @@ -73,14 +73,10 @@ #define HAVE_SW_POWEROFF /* The number of bytes reserved for loadable codecs */ -#define CODEC_SIZE 0x100000 +#define CODEC_SIZE 0x48000 /* in IRAM */ /* The number of bytes reserved for loadable plugins */ -#if 0 /* The plugin buffer doesn't fit in the 2MB memory */ -#define PLUGIN_BUFFER_SIZE 0x80000 -#else -#define PLUGIN_BUFFER_SIZE 0 -#endif +#define PLUGIN_BUFFER_SIZE 0x60000 #define AB_REPEAT_ENABLE 1 diff --git a/firmware/target/arm/as3525/app.lds b/firmware/target/arm/as3525/app.lds index bf8e68f..1856afb 100644 --- a/firmware/target/arm/as3525/app.lds +++ b/firmware/target/arm/as3525/app.lds @@ -6,6 +6,11 @@ OUTPUT_FORMAT(elf32-littlearm) OUTPUT_ARCH(arm) STARTUP(target/arm/crt0.o) +#if MEMORYSIZE <= 2 +/* we put the codec buffer in IRAM */ +#define LOWMEM +#endif + #define PLUGINSIZE PLUGIN_BUFFER_SIZE #define CODECSIZE CODEC_SIZE @@ -16,8 +21,15 @@ STARTUP(target/arm/crt0.o) #endif #include "cpu.h" + #define IRAMSIZE 0x50000 + +#ifdef LOWMEM +#define DRAMSIZE (MEMORYSIZE * 0x100000) - STUBOFFSET - PLUGINSIZE +#define CODECORIG (IRAMORIG + IRAMSIZE - CODEC_SIZE) +#else #define DRAMSIZE (MEMORYSIZE * 0x100000) - STUBOFFSET - PLUGINSIZE - CODECSIZE +#endif #define IRAMORIG 0x0 #define DRAMORIG 0x30000000 + STUBOFFSET @@ -26,11 +38,20 @@ STARTUP(target/arm/crt0.o) #define ENDAUDIOADDR (DRAMORIG + DRAMSIZE) /* Where the codec buffer ends, and the plugin buffer starts */ +#ifdef LOWMEM +#define ENDADDR (ENDAUDIOADDR) +#else #define ENDADDR (ENDAUDIOADDR + CODECSIZE) +#endif MEMORY { +#ifdef LOWMEM + IRAM : ORIGIN = IRAMORIG, LENGTH = (IRAMSIZE - CODEC_SIZE) + CODEC_IRAM : ORIGIN = CODECORIG, LENGTH = CODEC_SIZE +#else IRAM : ORIGIN = IRAMORIG, LENGTH = IRAMSIZE +#endif DRAM : ORIGIN = DRAMORIG, LENGTH = DRAMSIZE } @@ -69,8 +90,6 @@ SECTIONS *(.eh_frame) } - _initdata_end =.; - .vectors IRAMORIG: { _vectors_start = .; @@ -79,16 +98,7 @@ SECTIONS _vectorscopy = LOADADDR(.vectors); - .ibss (NOLOAD) : - { - _iedata = .; - *(.qharray) - *(.ibss) - . = ALIGN(0x4); - _iend = .; - } > IRAM - - .iram _iend : + .iram : { _iramstart = .; *(.icode) @@ -98,6 +108,15 @@ SECTIONS _iramend = .; } > IRAM AT> DRAM + .ibss (NOLOAD) : + { + _iedata = .; + *(.qharray) + *(.ibss) + . = ALIGN(0x4); + _iend = .; + } > IRAM + _iramcopy = LOADADDR(.iram); .stack (NOLOAD) : @@ -130,15 +149,19 @@ SECTIONS _audiobufend = .; } > DRAM - .codec ENDAUDIOADDR (NOLOAD) : + .codec CODECORIG (NOLOAD) : { codecbuf = .; _codecbuf = .; - } +#ifdef LOWMEM + } > CODEC_IRAM +#else + } > DRAM +#endif .plugin ENDADDR (NOLOAD) : { _pluginbuf = .; pluginbuf = .; - } + } > DRAM } diff --git a/firmware/target/arm/as3525/pcm-as3525.c b/firmware/target/arm/as3525/pcm-as3525.c index d91450c..0386cc3 100644 --- a/firmware/target/arm/as3525/pcm-as3525.c +++ b/firmware/target/arm/as3525/pcm-as3525.c @@ -21,58 +21,140 @@ #include "system.h" #include "audio.h" #include "string.h" - -/* TODO */ - +#include "as3525.h" +#include "pl081.h" +#include "dma-target.h" +#include "clock-target.h" +#include "panic.h" +#include "as3514.h" +#include "audiohw.h" + +#define MAX_TRANSFER (4*((1<<11)-1)) /* maximum data we can transfer via DMA + * i.e. 32 bits at once (size of I2SO_DATA) + * and the number of 32bits words has to + * fit in 11 bits of DMA register */ + +static unsigned char *dma_start_addr; +static size_t dma_size; /* in 4*32 bits */ +static void dma_callback(void); +static int locked = 0; + +/* Mask the DMA interrupt */ void pcm_play_lock(void) { + if(++locked == 1) + VIC_INT_EN_CLEAR |= INTERRUPT_DMAC; } +/* Unmask the DMA interrupt if enabled */ void pcm_play_unlock(void) { + if(--locked == 0) + VIC_INT_ENABLE |= INTERRUPT_DMAC; } -void pcm_play_dma_start(const void *addr, size_t size) +static void play_start_pcm(void) { + const unsigned char* addr = dma_start_addr; + size_t size = dma_size; + if(size > MAX_TRANSFER) + size = MAX_TRANSFER; + + if((unsigned int)dma_start_addr & 3) + panicf("unaligned pointer!"); + + dma_size -= size; + dma_start_addr += size; + + dma_enable_channel(1, (void*)addr, (void*)I2SOUT_DATA, DMA_PERI_I2SOUT, + DMAC_FLOWCTRL_DMAC_MEM_TO_PERI, true, false, size >> 2, DMA_S1, + dma_callback); } -void pcm_play_dma_stop(void) +static void dma_callback(void) { + if(!dma_size) + { + register pcm_more_callback_type get_more = pcm_callback_for_more; + if(get_more) + get_more(&dma_start_addr, &dma_size); + } + + if(!dma_size) + pcm_play_dma_stop(); + else + play_start_pcm(); } -void pcm_play_dma_pause(bool pause) +void pcm_play_dma_start(const void *addr, size_t size) { + dma_size = size; + dma_start_addr = (unsigned char*)addr; + + play_start_pcm(); } -unsigned long physical_address(void *p) +void pcm_play_dma_stop(void) +{ + dma_disable_channel(1); + dma_size = 0; +} + +void pcm_play_dma_pause(bool pause) { - return 0; + if(pause) + dma_disable_channel(1); + else + play_start_pcm(); } void pcm_play_dma_init(void) { + CGU_PERI |= CGU_I2SOUT_APB_CLOCK_ENABLE; + + /* enable I2SO_MCLK, clock source PLLA, minimal frequency */ + CGU_AUDIO |= (1<<11) | (511<<2) | (1<<0); + + I2SOUT_CONTROL |= (1<<6) ; /* enable dma */ + I2SOUT_CONTROL |= (1<<3) ; /* stereo */ + I2SOUT_CONTROL &= ~(1<<2); /* 16 bit samples */ + + audiohw_preinit(); } void pcm_postinit(void) { + audiohw_postinit(); + pcm_apply_settings(); } void pcm_set_frequency(unsigned int frequency) { + const int divider = (((AS3525_PLLA_FREQ/128) + (frequency/2)) / frequency) - 1; + if(divider < 0 || divider > 511) + panicf("unsupported frequency %d", frequency); + + CGU_AUDIO &= ~(((511 ^ divider) << 2) /* I2SOUT */ + /*| ((511 ^ divider) << 14) */ /* I2SIN */ + ); + + pcm_curr_sampr = frequency; } void pcm_apply_settings(void) { + pcm_set_frequency(HW_SAMPR_DEFAULT); } size_t pcm_get_bytes_waiting(void) { - return 0; + return dma_size; } const void * pcm_play_dma_get_peak_buffer(int *count) { - return NULL; + *count = dma_size >> 2; + return (const void*)dma_start_addr; }