From a4bfe37c6cdcc2e70b2b6d5d486531fa2986370b Mon Sep 17 00:00:00 2001 From: Thom Johansen Date: Thu, 11 May 2006 22:55:24 +0000 Subject: Optical S/PDIF recording and monitoring for Iriver H1x0. Removed unsupported recording options on Iriver. Sample rate displayed in recording screen reflects the real S/PDIF sample rate when doing S/PDIF recording. Testing would be appreciated. Thanks to Jens Arnold for fixing the DMA hang issue. Will reset settings! git-svn-id: svn://svn.rockbox.org/rockbox/trunk@9916 a1c6a512-1295-4272-9138-f99709370657 --- apps/recorder/recording.c | 23 +++++- apps/settings.c | 6 +- apps/sound_menu.c | 20 +++-- firmware/export/audio.h | 2 + firmware/export/config-h100.h | 3 + firmware/export/config-h120.h | 3 + firmware/pcm_record.c | 188 +++++++++++++++++++++++++++++++++++++----- 7 files changed, 214 insertions(+), 31 deletions(-) diff --git a/apps/recorder/recording.c b/apps/recorder/recording.c index cf361eea76..854e979ade 100644 --- a/apps/recorder/recording.c +++ b/apps/recorder/recording.c @@ -577,6 +577,11 @@ bool recording_screen(void) #endif talk_buffer_steal(); /* will use the mp3 buffer */ +#ifdef HAVE_SPDIF_POWER + /* Tell recording whether we want S/PDIF power enabled at all times */ + audio_set_spdif_power_setting(global_settings.spdif_enable); +#endif + audio_set_recording_options(global_settings.rec_frequency, global_settings.rec_quality, global_settings.rec_source, @@ -1011,6 +1016,7 @@ bool recording_screen(void) unsigned int dseconds, dhours, dminutes; unsigned long num_recorded_bytes; int pos = 0; + char spdif_sfreq[8]; update_countdown = 5; last_seconds = seconds; @@ -1259,12 +1265,21 @@ bool recording_screen(void) 2+PM_HEIGHT, true); } } - +/* Can't measure S/PDIF sample rate on Archos yet */ +#if CONFIG_CODEC != MAS3587F && defined(HAVE_SPDIF_IN) + if (global_settings.rec_source == SOURCE_SPDIF) + snprintf(spdif_sfreq, 8, "%dHz", audio_get_spdif_sample_rate()); +#else + (void)spdif_sfreq; +#endif snprintf(buf, 32, "%s %s", +#if CONFIG_CODEC != MAS3587F && defined(HAVE_SPDIF_IN) + global_settings.rec_source == SOURCE_SPDIF ? + spdif_sfreq : +#endif freq_str[global_settings.rec_frequency], - global_settings.rec_channels? - str(LANG_CHANNEL_MONO):str(LANG_CHANNEL_STEREO)); - + global_settings.rec_channels ? + str(LANG_CHANNEL_MONO) : str(LANG_CHANNEL_STEREO)); FOR_NB_SCREENS(i) screens[i].puts(0, 5+PM_HEIGHT, buf); diff --git a/apps/settings.c b/apps/settings.c index ad1ec70c07..ccf0acd8f9 100644 --- a/apps/settings.c +++ b/apps/settings.c @@ -94,7 +94,7 @@ const char rec_base_directory[] = REC_BASE_DIR; #include "dsp.h" #endif -#define CONFIG_BLOCK_VERSION 41 +#define CONFIG_BLOCK_VERSION 42 #define CONFIG_BLOCK_SIZE 512 #define RTC_BLOCK_SIZE 44 @@ -504,7 +504,11 @@ static const struct bit_entry hd_bits[] = "off,00:05,00:10,00:15,00:30,01:00,01:14,01:20,02:00,04:00,06:00,08:00,10:00,12:00,18:00,24:00" }, {1, S_O(rec_channels), 0, "rec channels", "stereo,mono" }, {4, S_O(rec_mic_gain), 4, "rec mic gain", NULL }, +#ifdef HAVE_SPDIF_IN + {2, S_O(rec_source), 0 /* 0=mic */, "rec source", "mic,line,spdif" }, +#else {1, S_O(rec_source), 0 /* 0=mic */, "rec source", "mic,line" }, +#endif {3, S_O(rec_frequency), 0, /* 0=44.1kHz */ "rec frequency", "44,48,32,22,24,16" }, {4, S_O(rec_left_gain), 2, /* 0dB */ diff --git a/apps/sound_menu.c b/apps/sound_menu.c index 1d389f30de..d3377a3efa 100644 --- a/apps/sound_menu.c +++ b/apps/sound_menu.c @@ -302,6 +302,8 @@ static bool recsource(void) sizeof(names)/sizeof(struct opt_items), NULL ); } +/* To be removed when we add support for sample rates and channel settings */ +#ifndef HAVE_UDA1380 static bool recfrequency(void) { static const struct opt_items names[] = { @@ -327,12 +329,11 @@ static bool recchannels(void) &global_settings.rec_channels, INT, names, 2, NULL ); } +#endif +#if CONFIG_CODEC == MAS3587F static bool recquality(void) { -#ifdef HAVE_UDA1380 - (void)recquality(); -#endif return set_int(str(LANG_RECORDING_QUALITY), "", UNIT_INT, &global_settings.rec_quality, NULL, 1, 0, 7, NULL ); @@ -343,7 +344,7 @@ static bool receditable(void) return set_bool(str(LANG_RECORDING_EDITABLE), &global_settings.rec_editable); } - +#endif static bool rectimesplit(void) { @@ -901,20 +902,29 @@ bool recording_menu(bool no_source) struct menu_item items[13]; bool result; -#ifndef HAVE_UDA1380 +#if CONFIG_CODEC == MAS3587F items[i].desc = ID2P(LANG_RECORDING_QUALITY); items[i++].function = recquality; #endif +/* We don't support frequency selection for UDA1380 yet. Let it just stay at + the default 44100 Hz. */ +#ifndef HAVE_UDA1380 items[i].desc = ID2P(LANG_RECORDING_FREQUENCY); items[i++].function = recfrequency; +#endif if(!no_source) { items[i].desc = ID2P(LANG_RECORDING_SOURCE); items[i++].function = recsource; } +/* We don't support other configurations than stereo yet either */ +#ifndef HAVE_UDA1380 items[i].desc = ID2P(LANG_RECORDING_CHANNELS); items[i++].function = recchannels; +#endif +#if CONFIG_CODEC == MAS3587F items[i].desc = ID2P(LANG_RECORDING_EDITABLE); items[i++].function = receditable; +#endif items[i].desc = ID2P(LANG_RECORD_TIMESPLIT); items[i++].function = rectimesplit; items[i].desc = ID2P(LANG_RECORD_PRERECORD_TIME); diff --git a/firmware/export/audio.h b/firmware/export/audio.h index 6922dab1f6..b44bb91378 100644 --- a/firmware/export/audio.h +++ b/firmware/export/audio.h @@ -100,6 +100,8 @@ void audio_set_recording_options(int frequency, int quality, void audio_set_recording_gain(int left, int right, int type); unsigned long audio_recorded_time(void); unsigned long audio_num_recorded_bytes(void); +void audio_set_spdif_power_setting(bool on); +unsigned long audio_get_spdif_sample_rate(void); diff --git a/firmware/export/config-h100.h b/firmware/export/config-h100.h index 942f18adf3..d53adcec14 100644 --- a/firmware/export/config-h100.h +++ b/firmware/export/config-h100.h @@ -125,6 +125,9 @@ #endif +/* Define this for S/PDIF input available */ +#define HAVE_SPDIF_IN + /* Define this for S/PDIF output available */ #define HAVE_SPDIF_OUT diff --git a/firmware/export/config-h120.h b/firmware/export/config-h120.h index 186203c648..34f5059b69 100644 --- a/firmware/export/config-h120.h +++ b/firmware/export/config-h120.h @@ -120,6 +120,9 @@ #endif +/* Define this for S/PDIF input available */ +#define HAVE_SPDIF_IN + /* Define this for S/PDIF output available */ #define HAVE_SPDIF_OUT diff --git a/firmware/pcm_record.c b/firmware/pcm_record.c index a7d8bc707c..f97995df72 100644 --- a/firmware/pcm_record.c +++ b/firmware/pcm_record.c @@ -30,6 +30,7 @@ #include "cpu.h" #include "i2c.h" +#include "power.h" #include "uda1380.h" #include "system.h" #include "usb.h" @@ -61,6 +62,8 @@ 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 unsigned int sample_rate; /* Sample rate at time of recording start */ +static int rec_source; /* Current recording source */ static int wav_file; static char recording_filename[MAX_PATH]; @@ -193,51 +196,144 @@ unsigned long audio_num_recorded_bytes(void) return 0; } +#ifdef HAVE_SPDIF_IN +/* Only the last six of these are standard rates, but all sample rates are + * possible, so we support some other common ones as well. + */ +static unsigned long spdif_sample_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, + 32000, 44100, 48000, 64000, 88200, 96000 +}; + +/* Return SPDIF sample rate. Since we base our reading on the actual SPDIF + * sample rate (which might be a bit inaccurate), we round off to the closest + * sample rate that is supported by SPDIF. + */ +unsigned long audio_get_spdif_sample_rate(void) +{ + int i = 0; + unsigned long measured_rate; + const int upper_bound = sizeof(spdif_sample_rates)/sizeof(long) - 1; + + /* The following formula is specified in MCF5249 user's manual section + * 17.6.1. The 3*(1 << 13) part will need changing if the setup of the + * PHASECONFIG register is ever changed. The 128 divide is because of the + * fact that the SPDIF clock is the sample rate times 128. + */ + measured_rate = (unsigned long)((unsigned long long)FREQMEAS*CPU_FREQ/ + ((1 << 15)*3*(1 << 13))/128); + /* Find which SPDIF sample rate we're closest to. */ + while (spdif_sample_rates[i] < measured_rate && i < upper_bound) ++i; + if (i > 0 && i < upper_bound) + { + long diff1 = measured_rate - spdif_sample_rates[i - 1]; + long diff2 = spdif_sample_rates[i] - measured_rate; + + if (diff2 > diff1) --i; + } + return spdif_sample_rates[i]; +} +#endif + +#ifdef HAVE_SPDIF_POWER +static bool spdif_power_setting; + +void audio_set_spdif_power_setting(bool on) +{ + spdif_power_setting = on; +} +#endif /** * 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) + * @param source 0=mic, 1=line-in, 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! */ + /* NOTE: Coldfire UDA based recording does not yet support anything other + * than 44.1kHz sampling rate, so we limit it to that case here now. SPDIF + * based recording will overwrite this value with the proper sample rate in + * audio_record(), and will not be affected by this. + */ + frequency = 44100; pre_record_ticks = prerecord_time * HZ; - pre_record_chunks = ((44100 * prerecord_time * 4)/CHUNK_SIZE)+1; + pre_record_chunks = ((frequency * 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; + pre_record_ticks = ((pre_record_chunks * CHUNK_SIZE)/(4*frequency)) * HZ; } //logf("pcmrec: src=%d", source); + rec_source = source; +#ifdef HAVE_SPDIF_POWER + /* Check if S/PDIF output power should be switched off or on. NOTE: assumes + both optical in and out is controlled by the same power source, which is + the case on H1x0. */ + spdif_power_enable((source == 2) || spdif_power_setting); +#endif switch (source) { /* mic */ case 0: + /* Generate int. when 6 samples in FIFO, PDIR2 src = IIS1recv */ + DATAINCONTROL = 0xc020; uda1380_enable_recording(true); break; /* line-in */ case 1: + /* Generate int. when 6 samples in FIFO, PDIR2 src = IIS1recv */ + DATAINCONTROL = 0xc020; uda1380_enable_recording(false); break; +#ifdef HAVE_SPDIF_IN + /* SPDIF */ + case 2: + /* Int. when 6 samples in FIFO. PDIR2 source = ebu1RcvData */ + DATAINCONTROL = 0xc038; + EBU1CONFIG = 0; /* Normal operation, source is EBU in 1 */ + /* We can't use the EBU clock to drive the IIS interface, so we + * need to use the clock the UDA provides, which is 44.1kHz as of + * now. This is the reason S/PDIF monitoring distorts for all other + * sample rates. Enable record to enable clock gen. + */ + uda1380_enable_recording(true); + break; +#endif } - + + sample_rate = frequency; + +#ifdef HAVE_SPDIF_IN + /* Turn on UDA based monitoring when UDA is used as input. */ + if (source == 2) { + uda1380_set_monitor(false); + IIS2CONFIG = 0x800; /* Reset before reprogram */ + /* SCLK follow IIS1 (UDA clock), TXSRC = EBU1rcv, 64 bclk/wclk */ + IIS2CONFIG = (8 << 12) | (7 << 8) | (4 << 2); + } + else + { + uda1380_set_monitor(true); + IIS2CONFIG = 0x800; /* Stop the S/PDIF monitoring if it's active */ + } +#else uda1380_set_monitor(true); +#endif } @@ -271,6 +367,11 @@ void audio_record(const char *filename) strncpy(recording_filename, filename, MAX_PATH - 1); recording_filename[MAX_PATH - 1] = 0; +#ifdef HAVE_SPDIF_IN + if (rec_source == 2) + sample_rate = audio_get_spdif_sample_rate(); +#endif + record_done = false; queue_post(&pcmrec_queue, PCMREC_START, 0); @@ -381,7 +482,7 @@ void pcm_rec_get_peaks(int *left, int *right) * */ -static void pcmrec_callback(bool flush) __attribute__ ((section (".icode"))); +static void pcmrec_callback(bool flush) ICODE_ATTR; static void pcmrec_callback(bool flush) { int num_ready, num_free, num_new; @@ -493,6 +594,10 @@ static void pcmrec_dma_start(void) /* Start the DMA transfer.. */ DCR1 = DMA_INT | DMA_EEXT | DMA_CS | DMA_DINC | DMA_START; +#ifdef HAVE_SPDIF_IN + INTERRUPTCLEAR = 0x03c00000; +#endif + /* pre-recording: buffer count */ buffered_chunks = 0; @@ -512,14 +617,36 @@ void DMA1(void) { 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 + DAR1 = (unsigned long)GET_CHUNK(write_index); /* Destination address */ + BCR1 = CHUNK_SIZE; + DCR1 = DMA_INT | DMA_EEXT | DMA_CS | DMA_DINC | DMA_START; + } +#ifdef HAVE_SPDIF_IN + else if ((rec_source == 2) && (INTERRUPTSTAT & 0x01c00000)) /* valnogood, symbolerr, parityerr */ + { + INTERRUPTCLEAR = 0x03c00000; + error_count++; + + logf("spdif err"); + + if (is_stopping) + { + DCR1 = 0; /* Stop DMA transfer */ + is_stopping = false; + + logf("dma1 stopping"); + } + else + { + DAR1 = (unsigned long)GET_CHUNK(write_index); /* Destination address */ + BCR1 = CHUNK_SIZE; + } + } +#endif + else { write_index++; if (write_index >= num_chunks) @@ -535,15 +662,16 @@ void DMA1(void) is_stopping = false; logf("dma1 stopping"); - - } else if (write_index == read_index) + } + else if (write_index == read_index) { DCR1 = 0; /* Stop DMA transfer */ is_recording = false; logf("dma1 overrun"); - } else + } + else { DAR1 = (unsigned long)GET_CHUNK(write_index); /* Destination address */ BCR1 = CHUNK_SIZE; @@ -560,10 +688,11 @@ 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, + 0x10,0,0,0,1,0,2,0,0,0,0,0,0,0,0,0, 4,0,0x10,0,'d','a','t','a',0,0,0,0 }; - + unsigned long avg_bytes_per_sec; + wav_file = open(recording_filename, O_RDWR|O_CREAT|O_TRUNC); if (wav_file < 0) { @@ -572,7 +701,18 @@ static int start_wave(void) is_error = true; return -1; } - + /* Now set the sample rate field of the WAV header to what it should be */ + header[24] = (unsigned char)(sample_rate & 0xff); + header[25] = (unsigned char)(sample_rate >> 8); + header[26] = (unsigned char)(sample_rate >> 16); + header[27] = (unsigned char)(sample_rate >> 24); + /* And then the average bytes per second field */ + avg_bytes_per_sec = sample_rate*4; /* Hard coded to 16 bit stereo */ + header[28] = (unsigned char)(avg_bytes_per_sec & 0xff); + header[29] = (unsigned char)(avg_bytes_per_sec >> 8); + header[30] = (unsigned char)(avg_bytes_per_sec >> 16); + header[31] = (unsigned char)(avg_bytes_per_sec >> 24); + if (sizeof(header) != write(wav_file, header, sizeof(header))) { close(wav_file); @@ -634,7 +774,7 @@ static void pcmrec_start(void) { /* not enough good chunks available - limit pre-record time */ pre_chunks = buffered_chunks; - pre_ticks = ((buffered_chunks * CHUNK_SIZE)/(4*44100)) * HZ; + pre_ticks = ((buffered_chunks * CHUNK_SIZE)/(4*sample_rate)) * HZ; } record_start_time = current_tick - pre_ticks; @@ -792,7 +932,6 @@ static void pcmrec_resume(void) logf("pcmrec_resume done"); } - /** * audio_init_recording calls this function using PCMREC_INIT * @@ -834,7 +973,6 @@ static void pcmrec_init(void) 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 */ @@ -842,6 +980,9 @@ static void pcmrec_init(void) ICR7 = 0x1c; /* Enable interrupt at level 7, priority 0 */ IMR &= ~(1<<15); /* bit 15 is DMA1 */ +#ifdef HAVE_SPDIF_IN + PHASECONFIG = 0x34; /* Gain = 3*2^13, source = EBUIN */ +#endif pcmrec_dma_start(); init_done = 1; @@ -851,10 +992,15 @@ static void pcmrec_close(void) { uda1380_disable_recording(); +#ifdef HAVE_SPDIF_POWER + spdif_power_enable(spdif_power_setting); +#endif DMAROUTE = (DMAROUTE & 0xffff00ff); ICR7 = 0x00; /* Disable interrupt */ IMR |= (1<<15); /* bit 15 is DMA1 */ + /* Reset PDIR2 data flow */ + DATAINCONTROL = 0x200; close_done = true; } -- cgit v1.2.3