/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2005 by Linus Nielsen Feltzing * * All files in this archive are subject to the GNU General Public License. * See the file COPYING in the source tree root for full license agreement. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include "config.h" #include "debug.h" #include "panic.h" #include "thread.h" #include #include #include #include #include #include "cpu.h" #include "i2c.h" #include "uda1380.h" #include "system.h" #include "usb.h" #include "buffer.h" #include "audio.h" #include "button.h" #include "file.h" #include "sprintf.h" #include "logf.h" #include "button.h" #include "lcd.h" #include "lcd-remote.h" #include "pcm_playback.h" #include "pcm_record.h" /***************************************************************************/ static volatile bool is_recording; /* We are recording */ static volatile bool is_stopping; /* Are we going to stop */ static volatile bool is_paused; /* We have paused */ static volatile bool is_error; /* An error has occured */ static volatile unsigned long num_rec_bytes; /* Num bytes recorded */ static volatile unsigned long num_file_bytes; /* Num bytes written to current file */ static volatile int error_count; /* Number of DMA errors */ static long record_start_time; /* Value of current_tick when recording was started */ static long pause_start_time; /* Value of current_tick when pause was started */ static volatile int buffered_chunks; /* number of valid chunks in buffer */ static int wav_file; static char recording_filename[MAX_PATH]; static volatile bool init_done, close_done, record_done, stop_done, pause_done, resume_done, new_file_done; static short peak_left, peak_right; /***************************************************************************/ /* Some estimates: Normal recording rate: 44100 HZ * 4 = 176 KB/s Total buffer size: 32 MB / 176 KB/s = 181s before writing to disk */ #define CHUNK_SIZE 8192 /* Multiple of 4 */ #define WRITE_THRESHOLD 250 /* (2 MB) Write when this many chunks (or less) until buffer full */ #define GET_CHUNK(x) (short*)(&rec_buffer[CHUNK_SIZE*(x)]) static unsigned char *rec_buffer; /* Circular recording buffer */ static int num_chunks; /* Number of chunks available in rec_buffer */ /* Overrun occures when DMA needs to write a new chunk and write_index == read_index Solution to this is to optimize pcmrec_callback, use cpu_boost or save to disk more often. */ static volatile int write_index; /* Current chunk the DMA is writing to */ static volatile int read_index; /* Oldest chunk that is not written to disk */ static volatile int read2_index; /* Latest chunk that has not been converted to little endian */ static long pre_record_ticks; /* pre-record time expressed in ticks */ static int pre_record_chunks; /* pre-record time expressed in chunks */ /***************************************************************************/ static struct event_queue pcmrec_queue; static long pcmrec_stack[(DEFAULT_STACK_SIZE + 0x1000)/sizeof(long)]; static const char pcmrec_thread_name[] = "pcmrec"; static void pcmrec_thread(void); static void pcmrec_dma_start(void); static void pcmrec_dma_stop(void); /* Event IDs */ #define PCMREC_INIT 1 /* Enable recording */ #define PCMREC_CLOSE 2 #define PCMREC_START 3 /* Start a new recording */ #define PCMREC_STOP 4 /* Stop the current recording */ #define PCMREC_PAUSE 10 #define PCMREC_RESUME 11 #define PCMREC_NEW_FILE 12 #define PCMREC_SET_GAIN 13 /*******************************************************************/ /* Functions that are not executing in the pcmrec_thread first */ /*******************************************************************/ /* Creates pcmrec_thread */ void pcm_rec_init(void) { queue_init(&pcmrec_queue); create_thread(pcmrec_thread, pcmrec_stack, sizeof(pcmrec_stack), pcmrec_thread_name); } /* Initializes recording: * - Set up the UDA1380 for recording * - Prepare for DMA transfers */ void audio_init_recording(void) { init_done = false; queue_post(&pcmrec_queue, PCMREC_INIT, 0); while(!init_done) sleep_thread(); wake_up_thread(); } void audio_close_recording(void) { close_done = false; queue_post(&pcmrec_queue, PCMREC_CLOSE, 0); while(!close_done) sleep_thread(); wake_up_thread(); } unsigned long pcm_rec_status(void) { unsigned long ret = 0; if (is_recording) ret |= AUDIO_STATUS_RECORD; if (is_paused) ret |= AUDIO_STATUS_PAUSE; if (is_error) ret |= AUDIO_STATUS_ERROR; return ret; } unsigned long audio_recorded_time(void) { if (is_recording) { if (is_paused) return pause_start_time - record_start_time; else return current_tick - record_start_time; } return 0; } unsigned long audio_num_recorded_bytes(void) { if (is_recording) return num_rec_bytes; return 0; } /** * Sets the audio source * * This functions starts feeding the CPU with audio data over the I2S bus * * @param source 0=mic, 1=line-in, (todo: 2=spdif) */ void audio_set_recording_options(int frequency, int quality, int source, int channel_mode, bool editable, int prerecord_time) { /* TODO: */ (void)frequency; (void)quality; (void)channel_mode; (void)editable; /* WARNING: calculation below uses fixed frequency! */ pre_record_ticks = prerecord_time * HZ; pre_record_chunks = ((44100 * prerecord_time * 4)/CHUNK_SIZE)+1; if(pre_record_chunks >= (num_chunks-250)) { /* we can't prerecord more than our buffersize minus treshold to write to disk! */ pre_record_chunks = num_chunks-250; /* don't forget to recalculate that time! */ pre_record_ticks = ((pre_record_chunks * CHUNK_SIZE)/(4*44100)) * HZ; } //logf("pcmrec: src=%d", source); switch (source) { /* mic */ case 0: uda1380_enable_recording(true); break; /* line-in */ case 1: uda1380_enable_recording(false); break; } uda1380_set_monitor(true); } /** * Note that microphone is mono, only left value is used * See uda1380_set_recvol() for exact ranges. * * @param type 0=line-in (radio), 1=mic, 2=ADC * */ void audio_set_recording_gain(int left, int right, int type) { //logf("rcmrec: t=%d l=%d r=%d", type, left, right); uda1380_set_recvol(left, right, type); } /** * Start recording * * Use audio_set_recording_options first to select recording options */ void audio_record(const char *filename) { if (is_recording) { logf("record while recording"); return; } strncpy(recording_filename, filename, MAX_PATH - 1); recording_filename[MAX_PATH - 1] = 0; record_done = false; queue_post(&pcmrec_queue, PCMREC_START, 0); while(!record_done) sleep_thread(); wake_up_thread(); } void audio_new_file(const char *filename) { logf("pcm_new_file"); new_file_done = false; strncpy(recording_filename, filename, MAX_PATH - 1); recording_filename[MAX_PATH - 1] = 0; queue_post(&pcmrec_queue, PCMREC_NEW_FILE, 0); while(!new_file_done) sleep_thread(); wake_up_thread(); logf("pcm_new_file done"); } /** * */ void audio_stop_recording(void) { if (!is_recording) return; logf("pcm_stop"); stop_done = false; queue_post(&pcmrec_queue, PCMREC_STOP, 0); while(!stop_done) sleep_thread(); wake_up_thread(); logf("pcm_stop done"); } void audio_pause_recording(void) { if (!is_recording) { logf("pause when not recording"); return; } if (is_paused) { logf("pause when paused"); return; } pause_done = false; queue_post(&pcmrec_queue, PCMREC_PAUSE, 0); while(!pause_done) sleep_thread(); wake_up_thread(); } void audio_resume_recording(void) { if (!is_paused) { logf("resume when not paused"); return; } resume_done = false; queue_post(&pcmrec_queue, PCMREC_RESUME, 0); while(!resume_done) sleep_thread(); wake_up_thread(); } /* return peaks as int, so convert from short first note that peak values are always positive */ void pcm_rec_get_peaks(int *left, int *right) { if (left) *left = (int)peak_left; if (right) *right = (int)peak_right; } /***************************************************************************/ /* Functions that executes in the context of pcmrec_thread */ /***************************************************************************/ /** * Process the chunks using read_index and write_index. * * This function is called when queue_get_w_tmo times out. * * Other functions can also call this function with flush = true when * they want to save everything in the buffers to disk. * */ static void pcmrec_callback(bool flush) __attribute__ ((section (".icode"))); static void pcmrec_callback(bool flush) { int num_ready, num_free, num_new; unsigned short *ptr; int i, j, w; short value; w = write_index; num_new = w - read2_index; if (num_new < 0) num_new += num_chunks; peak_left = 0; peak_right = 0; for (i=0; i peak_left) peak_left = value; else if (-value > peak_left) peak_left = -value; *ptr = htole16(value); ptr++; value = *ptr; if(value > peak_right) peak_right = value; else if (-value > peak_right) peak_right = -value; *ptr = htole16(value); ptr++; } num_rec_bytes += CHUNK_SIZE; read2_index++; if (read2_index >= num_chunks) read2_index = 0; } if ((!is_recording || is_paused) && !flush) { /* not recording = no saving to disk, fake buffer clearing */ read_index = write_index; return; } num_ready = w - read_index; if (num_ready < 0) num_ready += num_chunks; num_free = num_chunks - num_ready; if (num_free <= WRITE_THRESHOLD || flush) { logf("writing: %d (%d)", num_ready, flush); for (i=0; i= num_chunks) read_index = 0; yield(); } logf("done"); } } /* Abort dma transfer */ static void pcmrec_dma_stop(void) { DCR1 = 0; is_error = true; is_recording = false; error_count++; logf("dma1 stopped"); } static void pcmrec_dma_start(void) { DAR1 = (unsigned long)GET_CHUNK(write_index); /* Destination address */ SAR1 = (unsigned long)&PDIR2; /* Source address */ BCR1 = CHUNK_SIZE; /* Bytes to transfer */ /* Start the DMA transfer.. */ DCR1 = DMA_INT | DMA_EEXT | DMA_CS | DMA_DINC | DMA_START; /* pre-recording: buffer count */ buffered_chunks = 0; logf("dma1 started"); } /* DMA1 Interrupt is called when the DMA has finished transfering a chunk */ void DMA1(void) __attribute__ ((interrupt_handler, section(".icode"))); void DMA1(void) { int res = DSR1; DSR1 = 1; /* Clear interrupt */ if (res & 0x70) { DCR1 = 0; /* Stop DMA transfer */ error_count++; is_recording = false; logf("dma1 err: 0x%x", res); /* Flush recorded data to disk and stop recording */ queue_post(&pcmrec_queue, PCMREC_STOP, NULL); } else { write_index++; if (write_index >= num_chunks) write_index = 0; /* update number of valid chunks for pre-recording */ if(buffered_chunks < num_chunks) buffered_chunks++; if (is_stopping) { DCR1 = 0; /* Stop DMA transfer */ is_stopping = false; logf("dma1 stopping"); } else if (write_index == read_index) { DCR1 = 0; /* Stop DMA transfer */ is_recording = false; logf("dma1 overrun"); } else { DAR1 = (unsigned long)GET_CHUNK(write_index); /* Destination address */ BCR1 = CHUNK_SIZE; } } IPR |= (1<<15); /* Clear pending interrupt request */ } /* Create WAVE file and write header */ /* Sets returns 0 if success, -1 on failure */ static int start_wave(void) { unsigned char header[44] = { 'R','I','F','F',0,0,0,0,'W','A','V','E','f','m','t',' ', 0x10,0,0,0,1,0,2,0,0x44,0xac,0,0,0x10,0xb1,2,0, 4,0,0x10,0,'d','a','t','a',0,0,0,0 }; wav_file = open(recording_filename, O_RDWR|O_CREAT|O_TRUNC); if (wav_file < 0) { wav_file = -1; logf("rec: create failed: %d", wav_file); is_error = true; return -1; } if (sizeof(header) != write(wav_file, header, sizeof(header))) { close(wav_file); wav_file = -1; logf("rec: write failed"); is_error = true; return -1; } return 0; } /* Update header and set correct length values */ static void close_wave(void) { long l; if (wav_file != -1) { l = htole32(num_file_bytes + 36); lseek(wav_file, 4, SEEK_SET); write(wav_file, &l, 4); l = htole32(num_file_bytes); lseek(wav_file, 40, SEEK_SET); write(wav_file, &l, 4); close(wav_file); wav_file = -1; } } static void pcmrec_start(void) { int pre_chunks = pre_record_chunks; /* recalculate every time! */ long pre_ticks = pre_record_ticks; /* recalculate every time! */ logf("pcmrec_start"); if (is_recording) { logf("already recording"); record_done = true; return; } if (wav_file != -1) close(wav_file); if (start_wave() != 0) { /* failed to create the file */ record_done = true; return; } /* pre-recording calculation */ if(buffered_chunks < pre_chunks) { /* not enough good chunks available - limit pre-record time */ pre_chunks = buffered_chunks; pre_ticks = ((buffered_chunks * CHUNK_SIZE)/(4*44100)) * HZ; } record_start_time = current_tick - pre_ticks; read_index = write_index - pre_chunks; if(read_index < 0) { read_index += num_chunks; } read2_index = read_index; peak_left = 0; peak_right = 0; num_rec_bytes = 0; num_file_bytes = 0; pause_start_time = 0; is_stopping = false; is_paused = false; is_recording = true; record_done = true; } static void pcmrec_stop(void) { logf("pcmrec_stop"); if (!is_recording) { stop_done = true; return; } if (!is_paused) { /* wait for recording to finish */ is_stopping = true; while (is_stopping && is_recording) sleep_thread(); wake_up_thread(); is_stopping = false; } is_recording = false; /* Flush buffers to file */ pcmrec_callback(true); close_wave(); stop_done = true; /* Finally start dma again for peakmeters and pre-recoding to work. */ pcmrec_dma_start(); logf("pcmrec_stop done"); } static void pcmrec_new_file(void) { logf("pcmrec_new_file"); if (!is_recording) { logf("not recording"); new_file_done = true; return; } /* Since pcmrec_callback() blocks until the data has been written, here is a good approximation when recording to the new file starts */ record_start_time = current_tick; num_rec_bytes = 0; if (is_paused) pause_start_time = record_start_time; /* Flush what we got in buffers to file */ pcmrec_callback(true); close_wave(); num_file_bytes = 0; /* start the new file */ if (start_wave() != 0) { logf("new_file failed"); pcmrec_stop(); } new_file_done = true; logf("pcmrec_new_file done"); } static void pcmrec_pause(void) { logf("pcmrec_pause"); if (!is_recording) { logf("pause: not recording"); pause_done = true; return; } /* Abort DMA transfer and flush to file? */ is_stopping = true; while (is_stopping && is_recording) sleep_thread(); wake_up_thread(); pause_start_time = current_tick; is_paused = true; /* Flush what we got in buffers to file */ pcmrec_callback(true); pause_done = true; logf("pcmrec_pause done"); } static void pcmrec_resume(void) { logf("pcmrec_resume"); if (!is_paused) { logf("resume: not paused"); resume_done = true; return; } is_paused = false; is_recording = true; /* Compensate for the time we have been paused */ if (pause_start_time) { record_start_time += current_tick - pause_start_time; pause_start_time = 0; } pcmrec_dma_start(); resume_done = true; logf("pcmrec_resume done"); } /** * audio_init_recording calls this function using PCMREC_INIT * */ static void pcmrec_init(void) { unsigned long buffer_size; wav_file = -1; read_index = 0; read2_index = 0; write_index = 0; pre_record_chunks = 0; pre_record_ticks = 0; peak_left = 0; peak_right = 0; num_rec_bytes = 0; num_file_bytes = 0; record_start_time = 0; pause_start_time = 0; buffered_chunks = 0; is_recording = false; is_stopping = false; is_paused = false; is_error = false; rec_buffer = (unsigned char*)(((unsigned long)audiobuf) & ~3); buffer_size = (long)audiobufend - (long)audiobuf - 16; logf("buf size: %d kb", buffer_size/1024); num_chunks = buffer_size / CHUNK_SIZE; logf("num_chunks: %d", num_chunks); IIS1CONFIG = 0x800; /* Stop any playback */ AUDIOGLOB |= 0x180; /* IIS1 fifo auto sync = on, PDIR2 auto sync = on */ DATAINCONTROL = 0xc000; /* Generate Interrupt when 6 samples in fifo */ DATAINCONTROL |= 0x20; /* PDIR2 source = IIS1recv */ DIVR1 = 55; /* DMA1 is mapped into vector 55 in system.c */ DMACONFIG = 1; /* DMA0Req = PDOR3, DMA1Req = PDIR2 */ DMAROUTE = (DMAROUTE & 0xffff00ff) | DMA1_REQ_AUDIO_2; ICR7 = 0x1c; /* Enable interrupt at level 7, priority 0 */ IMR &= ~(1<<15); /* bit 15 is DMA1 */ pcmrec_dma_start(); init_done = 1; } static void pcmrec_close(void) { uda1380_disable_recording(); DMAROUTE = (DMAROUTE & 0xffff00ff); ICR7 = 0x00; /* Disable interrupt */ IMR |= (1<<15); /* bit 15 is DMA1 */ close_done = true; } static void pcmrec_thread(void) { struct event ev; logf("thread pcmrec start"); error_count = 0; while (1) { queue_wait_w_tmo(&pcmrec_queue, &ev, HZ / 40); switch (ev.id) { case PCMREC_INIT: pcmrec_init(); break; case PCMREC_CLOSE: pcmrec_close(); break; case PCMREC_START: pcmrec_start(); break; case PCMREC_STOP: pcmrec_stop(); break; case PCMREC_PAUSE: pcmrec_pause(); break; case PCMREC_RESUME: pcmrec_resume(); break; case PCMREC_NEW_FILE: pcmrec_new_file(); break; case SYS_TIMEOUT: pcmrec_callback(false); break; case SYS_USB_CONNECTED: if (!is_recording && !is_stopping) { usb_acknowledge(SYS_USB_CONNECTED_ACK); usb_wait_for_disconnect(&pcmrec_queue); } break; } } logf("thread pcmrec done"); } /* Select VINL & VINR source: 0=Line-in, 1=FM Radio */ void pcm_rec_mux(int source) { #ifdef IRIVER_H300_SERIES if(source == 0) and_l(~0x40000000, &GPIO_OUT); /* Line In */ else or_l(0x40000000, &GPIO_OUT); /* FM radio */ or_l(0x40000000, &GPIO_ENABLE); or_l(0x40000000, &GPIO_FUNCTION); #else if(source == 0) and_l(~0x00800000, &GPIO_OUT); /* Line In */ else or_l(0x00800000, &GPIO_OUT); /* FM radio */ or_l(0x00800000, &GPIO_ENABLE); or_l(0x00800000, &GPIO_FUNCTION); #endif }