diff options
-rw-r--r-- | apps/enc_config.c | 432 | ||||
-rw-r--r-- | apps/enc_config.h | 73 | ||||
-rw-r--r-- | firmware/enc_base.c | 46 | ||||
-rw-r--r-- | firmware/export/enc_base.h | 270 | ||||
-rw-r--r-- | firmware/export/general.h | 38 | ||||
-rw-r--r-- | firmware/export/pcm_sampr.h | 310 | ||||
-rw-r--r-- | firmware/general.c | 77 | ||||
-rw-r--r-- | firmware/pcm_sampr.c | 76 | ||||
-rw-r--r-- | firmware/target/coldfire/pcm-coldfire.c | 738 |
9 files changed, 2060 insertions, 0 deletions
diff --git a/apps/enc_config.c b/apps/enc_config.c new file mode 100644 index 0000000000..384e679c42 --- /dev/null +++ b/apps/enc_config.c @@ -0,0 +1,432 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2006 Michael Sevakis + * + * 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 <stdio.h> +#include <sprintf.h> +#include <string.h> +#include "config.h" +#include "atoi.h" +#include "lang.h" +#include "misc.h" +#include "talk.h" +#include "general.h" +#include "codecs.h" +#include "menu.h" +#include "statusbar.h" +#include "settings.h" +#include "audio.h" +#include "pcm_record.h" +#include "enc_config.h" + +#define MENU_ITEM_FN(fn) \ + ((bool (*)(void))fn) + +#define ENC_MENU_ITEM_FN(fn) \ + ((bool (*)(struct encoder_config *))fn) + +#define CALL_FN_(fn, ...) \ + if (fn) fn(__VA_ARGS__) + +static bool enc_run_menu(int m, const struct menu_item items[], + struct encoder_config *cfg); +static bool enc_no_config_menu(struct encoder_config *cfg); + +/** Function definitions for each codec - add these to enc_data + list following the definitions **/ + +/** mp3_enc.codec **/ +/* mp3_enc: return encoder capabilities */ +static void mp3_enc_get_caps(const struct encoder_config *cfg, + struct encoder_caps *caps, + bool for_config) +{ + int i; + unsigned long bitr; + + if (!for_config) + { + /* Overall encoder capabilities */ + caps->samplerate_caps = MPEG1_SAMPR_CAPS | MPEG2_SAMPR_CAPS; + caps->channel_caps = CHN_CAP_ALL; + return; + } + + /* Restrict caps based on config */ + i = round_value_to_list32(cfg->mp3_enc.bitrate, mp3_enc_bitr, + MP3_ENC_NUM_BITR, false); + bitr = mp3_enc_bitr[i]; + + /* sample rate caps */ + + /* check if MPEG1 sample rates are available */ + if ((bitr >= 32 && bitr <= 128) || bitr >= 160) + caps->samplerate_caps |= MPEG1_SAMPR_CAPS; + + /* check if MPEG2 sample rates and mono are available */ + if (bitr <= 160) + { + caps->samplerate_caps |= MPEG2_SAMPR_CAPS; + caps->channel_caps |= CHN_CAP_MONO; + } + + /* check if stereo is available */ + if (bitr >= 32) + caps->channel_caps |= CHN_CAP_STEREO; +} /* mp3_enc_get_caps */ + +/* mp3_enc: return the default configuration */ +static void mp3_enc_default_config(struct encoder_config *cfg) +{ + cfg->mp3_enc.bitrate = 128; /* default that works for all types */ +} /* mp3_enc_default_config */ + +static void mp3_enc_convert_config(struct encoder_config *cfg, + bool to_encoder) +{ + if (to_encoder) + { + if ((unsigned)global_settings.mp3_enc_config.bitrate > MP3_ENC_NUM_BITR) + global_settings.mp3_enc_config.bitrate = MP3_ENC_BITRATE_CFG_DEFAULT; + cfg->mp3_enc.bitrate = mp3_enc_bitr[global_settings.mp3_enc_config.bitrate]; + } + else + { + global_settings.mp3_enc_config.bitrate = + round_value_to_list32(cfg->mp3_enc.bitrate, mp3_enc_bitr, + MP3_ENC_NUM_BITR, false); + } +} /* mp3_enc_convert_config */ + +/* mp3_enc: show the bitrate setting options */ +static bool mp3_enc_bitrate(struct encoder_config *cfg) +{ + static const struct opt_items items[] = + { + /* Available in MPEG Version: */ +#ifdef HAVE_MPEG2_SAMPR +#if 0 + /* this sounds awful no matter what */ + { "8 kBit/s", TALK_ID(8, UNIT_KBIT) }, /* 2 */ +#endif + /* mono only */ + { "16 kBit/s", TALK_ID(16, UNIT_KBIT) }, /* 2 */ + { "24 kBit/s", TALK_ID(24, UNIT_KBIT) }, /* 2 */ +#endif + /* stereo/mono */ + { "32 kBit/s", TALK_ID(32, UNIT_KBIT) }, /* 1,2 */ + { "40 kBit/s", TALK_ID(40, UNIT_KBIT) }, /* 1,2 */ + { "48 kBit/s", TALK_ID(48, UNIT_KBIT) }, /* 1,2 */ + { "56 kBit/s", TALK_ID(56, UNIT_KBIT) }, /* 1,2 */ + { "64 kBit/s", TALK_ID(64, UNIT_KBIT) }, /* 1,2 */ + { "80 kBit/s", TALK_ID(80, UNIT_KBIT) }, /* 1,2 */ + { "96 kBit/s", TALK_ID(96, UNIT_KBIT) }, /* 1,2 */ + { "112 kBit/s", TALK_ID(112, UNIT_KBIT) }, /* 1,2 */ + { "128 kBit/s", TALK_ID(128, UNIT_KBIT) }, /* 1,2 */ +#if 0 + /* oddball MPEG2-only rate stuck in the middle */ + { "144 kBit/s", TALK_ID(144, UNIT_KBIT) }, /* 2 */ +#endif + { "160 kBit/s", TALK_ID(160, UNIT_KBIT) }, /* 1,2 */ + /* stereo only */ + { "192 kBit/s", TALK_ID(192, UNIT_KBIT) }, /* 1 */ + { "224 kBit/s", TALK_ID(224, UNIT_KBIT) }, /* 1 */ + { "256 kBit/s", TALK_ID(256, UNIT_KBIT) }, /* 1 */ + { "320 kBit/s", TALK_ID(320, UNIT_KBIT) }, /* 1 */ + }; + + unsigned long rate_list[ARRAYLEN(items)]; + + /* This is rather constant based upon the build but better than + storing and maintaining yet another list of numbers */ + int n_rates = make_list_from_caps32( + MPEG1_BITR_CAPS | MPEG2_BITR_CAPS, mp3_enc_bitr, + MPEG1_BITR_CAPS +#ifdef HAVE_MPEG2_SAMPR + | (MPEG2_BITR_CAPS & ~(MP3_BITR_CAP_144 | MP3_BITR_CAP_8)), +#endif + rate_list); + + int index = round_value_to_list32(cfg->mp3_enc.bitrate, rate_list, + n_rates, false); + bool res = set_option(str(LANG_BITRATE), &index, INT, + items, n_rates, NULL); + index = round_value_to_list32(rate_list[index], mp3_enc_bitr, + MP3_ENC_NUM_BITR, false); + cfg->mp3_enc.bitrate = mp3_enc_bitr[index]; + + return res; +} /* mp3_enc_bitrate */ + +/* mp3_enc: show the configuration menu */ +static bool mp3_enc_menu(struct encoder_config *cfg) +{ + static const struct menu_item items[] = + { + { ID2P(LANG_BITRATE), MENU_ITEM_FN(mp3_enc_bitrate) } + }; + + bool result; + int m = menu_init(items, ARRAYLEN(items), NULL, NULL, NULL, NULL); + result = enc_run_menu(m, items, cfg); + menu_exit(m); + return result; +} /* mp3_enc_menu */ + +/** wav_enc.codec **/ +/* wav_enc: show the configuration menu */ +#if 0 +static bool wav_enc_menu(struct encoder_config *cfg); +#endif + +/** wavpack_enc.codec **/ +/* wavpack_enc: show the configuration menu */ +#if 0 +static bool wavpack_enc_menu(struct encoder_config *cfg); +#endif + +/** config function pointers and/or data for each codec **/ +static const struct encoder_data +{ + void (*get_caps)(const struct encoder_config *, struct encoder_caps *, + bool); + void (*default_cfg)(struct encoder_config *); + void (*convert_cfg)(struct encoder_config *, bool to_encoder); + bool (*menu)(struct encoder_config *); +} enc_data[REC_NUM_FORMATS] = +{ + /* mp3_enc.codec */ + [REC_FORMAT_MPA_L3] = { + mp3_enc_get_caps, + mp3_enc_default_config, + mp3_enc_convert_config, + mp3_enc_menu, + }, + /* wav_enc.codec */ + [REC_FORMAT_PCM_WAV] = { + NULL, + NULL, + NULL, + enc_no_config_menu, + }, + /* wavpack_enc.codec */ + [REC_FORMAT_WAVPACK] = { + NULL, + NULL, + NULL, + enc_no_config_menu, + }, +}; + +static inline bool rec_format_ok(int rec_format) +{ + return (unsigned)rec_format < REC_NUM_FORMATS; +} + +static bool enc_run_menu(int m, const struct menu_item items[], + struct encoder_config *cfg) +{ + int selected; + while (1) + { + switch (selected=menu_show(m)) + { + case MENU_SELECTED_EXIT: + return false; + + case MENU_ATTACHED_USB: + return true; + + default: + if (items[selected].function && + ENC_MENU_ITEM_FN(items[selected].function)(cfg)) + return true; + gui_syncstatusbar_draw(&statusbars, true); + } + } +} /* enc_run_menu */ + +/* menu created when encoder has no configuration options */ +static bool enc_no_config_menu(struct encoder_config *cfg) +{ + static const struct menu_item items[] = + { + { ID2P(LANG_NO_SETTINGS), NULL } + }; + int m; + bool result; + + m = menu_init(items, ARRAYLEN(items), NULL, NULL, NULL, NULL); + result = enc_run_menu(m, items, NULL); + menu_exit(m); + + return result; + (void)cfg; +} /* enc_no_config_menu */ + +/* update settings dependent upon encoder settings */ +static void enc_rec_settings_changed(struct encoder_config *cfg) +{ + struct encoder_config enc_config; + struct encoder_caps caps; + long table[MAX(CHN_NUM_MODES, REC_NUM_FREQ)]; + int n; + + if (cfg == NULL) + { + cfg = &enc_config; + cfg->rec_format = global_settings.rec_format; + global_to_encoder_config(cfg); + } + + /* have to sync other settings when encoder settings change */ + if (!enc_get_caps(cfg, &caps, true)) + return; + + /* rec_channels */ + n = make_list_from_caps32(CHN_CAP_ALL, NULL, + caps.channel_caps, table); + + /* no zero check needed: encoder must support at least one + sample rate that recording supports or it shouldn't be in + available in the recording options */ + n = round_value_to_list32(global_settings.rec_channels, + table, n, true); + global_settings.rec_channels = table[n]; + + /* rec_frequency */ + n = make_list_from_caps32(REC_SAMPR_CAPS, rec_freq_sampr, + caps.samplerate_caps, table); + + n = round_value_to_list32( + rec_freq_sampr[global_settings.rec_frequency], + table, n, false); + + global_settings.rec_frequency = round_value_to_list32( + table[n], rec_freq_sampr, REC_NUM_FREQ, false); +} /* enc_rec_settings_changed */ + +/** public stuff **/ +void global_to_encoder_config(struct encoder_config *cfg) +{ + const struct encoder_data *data = &enc_data[cfg->rec_format]; + CALL_FN_(data->convert_cfg, cfg, true); +} /* global_to_encoder_config */ + +void encoder_config_to_global(const struct encoder_config *cfg) +{ + const struct encoder_data *data = &enc_data[cfg->rec_format]; + CALL_FN_(data->convert_cfg, (struct encoder_config *)cfg, false); +} /* encoder_config_to_global */ + +bool enc_get_caps(const struct encoder_config *cfg, + struct encoder_caps *caps, + bool for_config) +{ + /* get_caps expects caps to be zeroed first */ + memset(caps, 0, sizeof (*caps)); + + if (!rec_format_ok(cfg->rec_format)) + return false; + + if (enc_data[cfg->rec_format].get_caps) + { + enc_data[cfg->rec_format].get_caps(cfg, caps, for_config); + } + else + { + /* If no function provided...defaults to all */ + caps->samplerate_caps = SAMPR_CAP_ALL; + caps->channel_caps = CHN_CAP_ALL; + } + + return true; +} /* enc_get_caps */ + +/* Initializes the config struct with default values */ +bool enc_init_config(struct encoder_config *cfg) +{ + if (!rec_format_ok(cfg->rec_format)) + return false; + CALL_FN_(enc_data[cfg->rec_format].default_cfg, cfg); + return true; +} /* enc_init_config */ + +/** Encoder Menus **/ +bool enc_config_menu(struct encoder_config *cfg) +{ + if (!rec_format_ok(cfg->rec_format)) + return false; + return enc_data[cfg->rec_format].menu(cfg); +} /* enc_config_menu */ + +/** Global Settings **/ + +/* Reset all codecs to defaults */ +void enc_global_settings_reset(void) +{ + struct encoder_config cfg; + cfg.rec_format = 0; + + do + { + global_to_encoder_config(&cfg); + enc_init_config(&cfg); + encoder_config_to_global(&cfg); + if (cfg.rec_format == global_settings.rec_format) + enc_rec_settings_changed(&cfg); + } + while (++cfg.rec_format < REC_NUM_FORMATS); +} /* enc_global_settings_reset */ + +/* Apply new settings */ +void enc_global_settings_apply(void) +{ + struct encoder_config cfg; + if (!rec_format_ok(global_settings.rec_format)) + global_settings.rec_format = REC_FORMAT_DEFAULT; + + cfg.rec_format = global_settings.rec_format; + global_to_encoder_config(&cfg); + enc_rec_settings_changed(&cfg); + encoder_config_to_global(&cfg); +} /* enc_global_settings_apply */ + +/* Show an encoder's config menu based on the global_settings. + Modified settings are placed in global_settings.enc_config. */ +bool enc_global_config_menu(void) +{ + struct encoder_config cfg; + + bool res; + + if (!rec_format_ok(global_settings.rec_format)) + global_settings.rec_format = REC_FORMAT_DEFAULT; + + cfg.rec_format = global_settings.rec_format; + + global_to_encoder_config(&cfg); + + res = enc_config_menu(&cfg); + if (!res) + { + enc_rec_settings_changed(&cfg); + encoder_config_to_global(&cfg); + } + + return res; +} /* enc_global_config_menu */ diff --git a/apps/enc_config.h b/apps/enc_config.h new file mode 100644 index 0000000000..53fa7638e9 --- /dev/null +++ b/apps/enc_config.h @@ -0,0 +1,73 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2006 Michael Sevakis + * + * 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. + * + ****************************************************************************/ +#ifndef ENC_CONFIG_H +#define ENC_CONFIG_H + +#include "misc.h" +#include "enc_base.h" + +/** Capabilities **/ + +/* Capabilities returned by enc_get_caps that depend upon encoder settings */ +struct encoder_caps +{ + unsigned long samplerate_caps; /* Mask composed of SAMPR_CAP_* flags */ + unsigned long channel_caps; /* Mask composed of CHN_CAP_* flags */ +}; + +/* for_config: + * true- the capabilities returned should be contextual based upon the + * settings in the config structure + * false- the overall capabilities are being requested + */ +bool enc_get_caps(const struct encoder_config *cfg, + struct encoder_caps *caps, + bool for_config); + +/** Configuration **/ + +/* These translate to a back between the global format and the per- + instance format */ +void global_to_encoder_config(struct encoder_config *cfg); +void encoder_config_to_global(const struct encoder_config *cfg); + +/* Initializes the config struct with default values. + set afmt member before calling. */ +bool enc_init_config(struct encoder_config *cfg); + +/** Encoder Menus **/ + +/* Shows an encoder's config menu given an encoder config returned by one + of the enc_api functions. Modified settings are not saved to disk but + instead are placed in the structure. Call enc_save_config to commit + the data. */ +bool enc_config_menu(struct encoder_config *cfg); + +/** Global Settings **/ + +/* Reset all codecs to defaults */ +void enc_global_settings_reset(void); + +/* Apply new settings */ +void enc_global_settings_apply(void); + +/* Show an encoder's config menu based on the global_settings. + Modified settings are placed in global_settings.enc_config. */ +bool enc_global_config_menu(void); +#endif /* ENC_CONFIG_H */ diff --git a/firmware/enc_base.c b/firmware/enc_base.c new file mode 100644 index 0000000000..e346064fe2 --- /dev/null +++ b/firmware/enc_base.c @@ -0,0 +1,46 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2006 Michael Sevakis + * + * 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 "audio.h" + +/** mp3_enc.codec **/ + +/* These are in descending order rather than in MPEG frequency index + order */ +const unsigned long mp3_enc_sampr[MP3_ENC_NUM_SAMPR] = +{ + 48000, 44100, 32000, /* MPEG 1 */ + 24000, 22050, 16000, /* MPEG 2 */ +#if 0 + 12000, 11025, 8000, /* MPEG 2.5 */ +#endif +}; + +/* All bitrates used in the MPA L3 standard */ +const unsigned long mp3_enc_bitr[MP3_ENC_NUM_BITR] = +{ + 8, 16, 24, 32, 40, 48, 56, 64, 80, + 96, 112, 128, 144, 160, 192, 224, 256, 320 +}; + +/** wav_enc.codec **/ + +/** wavpack_enc.codec **/ + +/** public functions **/ diff --git a/firmware/export/enc_base.h b/firmware/export/enc_base.h new file mode 100644 index 0000000000..85101ac7fd --- /dev/null +++ b/firmware/export/enc_base.h @@ -0,0 +1,270 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Base declarations for working with software encoders + * + * Copyright (C) 2006 Michael Sevakis + * + * 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. + * + ****************************************************************************/ + +#ifndef ENC_BASE_H +#define ENC_BASE_H + +/** encoder config structures **/ + +/** mp3_enc.codec **/ +#define MP3_BITR_CAP_8 (1 << 0) +#define MP3_BITR_CAP_16 (1 << 1) +#define MP3_BITR_CAP_24 (1 << 2) +#define MP3_BITR_CAP_32 (1 << 3) +#define MP3_BITR_CAP_40 (1 << 4) +#define MP3_BITR_CAP_48 (1 << 5) +#define MP3_BITR_CAP_56 (1 << 6) +#define MP3_BITR_CAP_64 (1 << 7) +#define MP3_BITR_CAP_80 (1 << 8) +#define MP3_BITR_CAP_96 (1 << 9) +#define MP3_BITR_CAP_112 (1 << 10) +#define MP3_BITR_CAP_128 (1 << 11) +#define MP3_BITR_CAP_144 (1 << 12) +#define MP3_BITR_CAP_160 (1 << 13) +#define MP3_BITR_CAP_192 (1 << 14) +#define MP3_BITR_CAP_224 (1 << 15) +#define MP3_BITR_CAP_256 (1 << 16) +#define MP3_BITR_CAP_320 (1 << 17) +#define MP3_ENC_NUM_BITR 18 + +/* MPEG 1 */ +#define MPEG1_SAMPR_CAPS (SAMPR_CAP_32 | SAMPR_CAP_48 | SAMPR_CAP_44) +#define MPEG1_BITR_CAPS (MP3_BITR_CAP_32 | MP3_BITR_CAP_40 | MP3_BITR_CAP_48 | \ + MP3_BITR_CAP_56 | MP3_BITR_CAP_64 | MP3_BITR_CAP_80 | \ + MP3_BITR_CAP_96 | MP3_BITR_CAP_112 | MP3_BITR_CAP_128 | \ + MP3_BITR_CAP_160 | MP3_BITR_CAP_192 | MP3_BITR_CAP_224 | \ + MP3_BITR_CAP_256 | MP3_BITR_CAP_320) + +/* MPEG 2 */ +#define MPEG2_SAMPR_CAPS (SAMPR_CAP_22 | SAMPR_CAP_24 | SAMPR_CAP_16) +#define MPEG2_BITR_CAPS (MP3_BITR_CAP_8 | MP3_BITR_CAP_16 | MP3_BITR_CAP_24 | \ + MP3_BITR_CAP_32 | MP3_BITR_CAP_40 | MP3_BITR_CAP_48 | \ + MP3_BITR_CAP_56 | MP3_BITR_CAP_64 | MP3_BITR_CAP_80 | \ + MP3_BITR_CAP_96 | MP3_BITR_CAP_112 | MP3_BITR_CAP_128 | \ + MP3_BITR_CAP_144 | MP3_BITR_CAP_160) + +#if 0 +/* MPEG 2.5 */ +#define MPEG2_5_SAMPR_CAPS (SAMPR_CAP_8 | SAMPR_CAP_12 | SAMPR_CAP_11) +#define MPEG2_5_BITR_CAPS MPEG2_BITR_CAPS +#endif + +/* Assume 44100 is always available and therefore MPEG1 */ + +/* HAVE_MPEG* defines mainly apply to the bitrate menu */ +#if (REC_SAMPR_CAPS & MPEG2_SAMPR_CAPS) || defined (HAVE_SPDIF_IN) +#define HAVE_MPEG2_SAMPR +#endif + +#if 0 +#if (REC_SAMPR_CAPS & MPEG2_5_SAMPR_CAPS) || defined (HAVE_SPDIF_IN) +#define HAVE_MPEG2_5_SAMPR +#endif +#endif /* 0 */ + +#define MP3_ENC_SAMPR_CAPS (MPEG1_SAMPR_CAPS | MPEG2_SAMPR_CAPS) + +/* This number is count of full encoder set */ +#define MP3_ENC_NUM_SAMPR 6 + +extern const unsigned long mp3_enc_sampr[MP3_ENC_NUM_SAMPR]; +extern const unsigned long mp3_enc_bitr[MP3_ENC_NUM_BITR]; + +struct mp3_enc_config +{ + unsigned long bitrate; +}; + +#define MP3_ENC_BITRATE_CFG_DEFAULT 11 /* 128 */ +#define MP3_ENC_BITRATE_CFG_VALUE_LIST "8,16,24,32,40,48,56,64,80,96," \ + "112,128,144,160,192,224,256,320" + +/** wav_enc.codec **/ +#define WAV_ENC_SAMPR_CAPS SAMPR_CAP_ALL + +struct wav_enc_config +{ +#if 0 + unsigned long sample_depth; +#endif +}; + +/** wavpack_enc.codec **/ +#define WAVPACK_ENC_SAMPR_CAPS SAMPR_CAP_ALL + +struct wavpack_enc_config +{ +#if 0 + unsigned long sample_depth; +#endif +}; + +struct encoder_config +{ + union + { + /* states which *_enc_config member is valid */ + int rec_format; /* REC_FORMAT_* value */ + int afmt; /* AFMT_* value */ + }; + + union + { + struct mp3_enc_config mp3_enc; + struct wavpack_enc_config wavpack_enc; + struct wav_enc_config wav_enc; + }; +}; + +/** Encoder chunk macros and definitions **/ +#define CHUNKF_START_FILE 0x0001 /* This chunk starts a new file */ +#define CHUNKF_END_FILE 0x0002 /* This chunk ends the current file */ +#define CHUNKF_PRERECORD 0x0010 /* This chunk is prerecord data, + a new file could start anytime */ +#define CHUNKF_ABORT 0x0020 /* Encoder should not finish this + chunk */ +#define CHUNKF_ERROR 0x80000000 /* An error has occured (passed to/ + from encoder). Use the sign bit to + check (long)flags < 0. */ + +/* Header at the beginning of every encoder chunk */ +struct enc_chunk_hdr +{ + unsigned long flags; /* in/out: flags used by encoder and file + writing */ + size_t enc_size; /* out: amount of encoder data written to + chunk */ + unsigned long num_pcm; /* out: number of PCM samples eaten during + processing + (<= size of allocated buffer) */ + unsigned char *enc_data; /* out: pointer to enc_size_written bytes + of encoded audio data in chunk */ + /* Encoder defined data follows header. Can be audio data + any other + stuff the encoder needs to handle on a per chunk basis */ +}; + +/* Paranoia: be sure header size is 4-byte aligned */ +#define ENC_CHUNK_HDR_SIZE \ + ALIGN_UP_P2(sizeof (struct enc_chunk_hdr), 2) +/* Skip the chunk header and return data */ +#define ENC_CHUNK_SKIP_HDR(t, hdr) \ + ((typeof (t))((char *)hdr + ENC_CHUNK_HDR_SIZE)) +/* Cast p to struct enc_chunk_hdr * */ +#define ENC_CHUNK_HDR(p) \ + ((struct enc_chunk_hdr *)(p)) + +enum enc_events +{ + /* File writing events - data points to enc_file_event_data */ + ENC_START_FILE = 0, /* a new file has been opened and no data has yet + been written */ + ENC_WRITE_CHUNK, /* write the current chunk to disk */ + ENC_END_FILE, /* current file about to be closed and all valid + data has been written */ + /* Encoder buffer events - data points to enc_buffer_event_data */ + ENC_REC_NEW_STREAM, /* Take steps to finish current stream and start + new */ +}; + +/** + * encoder can write extra data to the file such as headers or more encoded + * samples and must update sizes and samples accordingly. + */ +struct enc_file_event_data +{ + struct enc_chunk_hdr *chunk; /* Current chunk */ + size_t new_enc_size; /* New size of chunk */ + unsigned long new_num_pcm; /* New number of pcm in chunk */ + const char *filename; /* filename to open if ENC_START_FILE */ + int rec_file; /* Current file or < 0 if none */ + unsigned long num_pcm_samples; /* Current pcm sample count written to + file so far. */ +}; + +/** + * encoder may add some data to the end of the last and start of the next + * but must never yield when called so any encoding done should be absolutely + * minimal. + */ +struct enc_buffer_event_data +{ + unsigned long flags; /* in: One or more of: + * CHUNKF_PRERECORD + * CHUNKF_END_FILE + * CHUNKF_START_FILE + */ + struct enc_chunk_hdr *pre_chunk; /* in: pointer to first prerecord + * chunk + */ + struct enc_chunk_hdr *chunk; /* in,out: chunk were split occurs - + * first chunk of start + */ +}; + +/** Callbacks called by encoder codec **/ + +/* parameters passed to encoder by enc_get_inputs */ +struct enc_inputs +{ + unsigned long sample_rate; /* out - pcm frequency */ + int num_channels; /* out - number of audio channels */ + struct encoder_config *config; /* out - encoder settings */ +}; + +void enc_get_inputs(struct enc_inputs *inputs); + +/* parameters pass from encoder to enc_set_parameters */ +struct enc_parameters +{ + /* IN parameters */ + int afmt; /* AFMT_* id - sanity checker */ + size_t chunk_size; /* max chunk size required */ + unsigned long enc_sample_rate; /* actual sample rate used by encoder + (for recorded time calculation) */ + size_t reserve_bytes; /* number of bytes to reserve immediately + following chunks */ + void (*events_callback)(enum enc_events event, + void *data); /* pointer to events callback */ + /* OUT parameters */ + unsigned char *enc_buffer; /* pointer to enc_buffer */ + size_t buf_chunk_size; /* size of chunks in enc_buffer */ + int num_chunks; /* number of chunks allotted to encoder */ + unsigned char *reserve_buffer; /* pointer to reserve_bytes bytes */ +}; + +/* set the encoder dimensions - called by encoder codec at initialization + and termination */ +void enc_set_parameters(struct enc_parameters *params); +/* returns pointer to next write chunk in circular buffer */ +struct enc_chunk_hdr * enc_get_chunk(void); +/* releases the current chunk into the available chunks */ +void enc_finish_chunk(void); +/* checks near empty state on pcm input buffer */ +int enc_pcm_buf_near_empty(void); + +#define PCM_MAX_FEED_SIZE 20000 /* max pcm size passed to encoder */ + +/* passes a pointer to next chunk of unprocessed wav data */ +unsigned char * enc_get_pcm_data(size_t size); +/* puts some pcm data back in the queue */ +size_t enc_unget_pcm_data(size_t size); + +#endif /* ENC_BASE_H */ diff --git a/firmware/export/general.h b/firmware/export/general.h new file mode 100644 index 0000000000..427e2773b8 --- /dev/null +++ b/firmware/export/general.h @@ -0,0 +1,38 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2006 by Michael Sevakis + * + * 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. + * + ****************************************************************************/ + +#ifndef GENERAL_H +#define GENERAL_H + +#include <stdbool.h> + +/* round a signed/unsigned 32bit value to the closest of a list of values */ +/* returns the index of the closest value */ +int round_value_to_list32(unsigned long value, + const unsigned long list[], + int count, + bool signd); + +int make_list_from_caps32(unsigned long src_mask, + const unsigned long *src_list, + unsigned long caps_mask, + unsigned long *caps_list); + + +#endif /* GENERAL_H */ diff --git a/firmware/export/pcm_sampr.h b/firmware/export/pcm_sampr.h new file mode 100644 index 0000000000..c4a399b62f --- /dev/null +++ b/firmware/export/pcm_sampr.h @@ -0,0 +1,310 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2006 by Michael Sevakis + * + * 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. + * + ****************************************************************************/ + +#ifndef PCM_SAMPR_H +#define PCM_SAMPR_H + +/* These must be macros for comparison with SAMPR_CAP_* flags by the + preprocessor. Add samplerate index in descending order renumbering + the ones later in the list if any */ +#define FREQ_96 0 +#define FREQ_88 1 +#define FREQ_64 2 +#define FREQ_48 3 +#define FREQ_44 4 +#define FREQ_32 5 +#define FREQ_24 6 +#define FREQ_22 7 +#define FREQ_16 8 +#define FREQ_12 9 +#define FREQ_11 10 +#define FREQ_8 11 +#define SAMPR_NUM_FREQ 12 + +/* sample rate values in HZ */ +#define SAMPR_96 96000 +#define SAMPR_88 88200 +#define SAMPR_64 64000 +#define SAMPR_48 48000 +#define SAMPR_44 44100 +#define SAMPR_32 32000 +#define SAMPR_24 24000 +#define SAMPR_22 22050 +#define SAMPR_16 16000 +#define SAMPR_12 12000 +#define SAMPR_11 11025 +#define SAMPR_8 8000 + +/* sample rate capability bits */ +#define SAMPR_CAP_96 (1 << FREQ_96) +#define SAMPR_CAP_88 (1 << FREQ_88) +#define SAMPR_CAP_64 (1 << FREQ_64) +#define SAMPR_CAP_48 (1 << FREQ_48) +#define SAMPR_CAP_44 (1 << FREQ_44) +#define SAMPR_CAP_32 (1 << FREQ_32) +#define SAMPR_CAP_24 (1 << FREQ_24) +#define SAMPR_CAP_22 (1 << FREQ_22) +#define SAMPR_CAP_16 (1 << FREQ_16) +#define SAMPR_CAP_12 (1 << FREQ_12) +#define SAMPR_CAP_11 (1 << FREQ_11) +#define SAMPR_CAP_8 (1 << FREQ_8) +#define SAMPR_CAP_ALL (SAMPR_CAP_96 | SAMPR_CAP_88 | SAMPR_CAP_64 | \ + SAMPR_CAP_48 | SAMPR_CAP_44 | SAMPR_CAP_32 | \ + SAMPR_CAP_24 | SAMPR_CAP_22 | SAMPR_CAP_16 | \ + SAMPR_CAP_12 | SAMPR_CAP_11 | SAMPR_CAP_8) + +/* Master list of all "standard" rates supported. */ +extern const unsigned long audio_master_sampr_list[SAMPR_NUM_FREQ]; + +/** Hardware sample rates **/ + +/* Enumeration of supported frequencies where 0 is the highest rate + supported and REC_NUM_FREQUENCIES is the number available */ +enum hw_freq_indexes +{ + __HW_FREQ_START_INDEX = -1, /* Make sure first in list is 0 */ + +/* 96000 */ +#if (HW_SAMPR_CAPS & SAMPR_CAP_96) /* Macros and enums for each FREQ: */ + HW_FREQ_96, /* Index in enumeration */ +#define HW_HAVE_96 /* Defined if this FREQ is defined */ +#define HW_HAVE_96_(...) __VA_ARGS__ /* Output its parameters for this FREQ */ +#else +#define HW_HAVE_96_(...) /* Discards its parameters for this FREQ */ +#endif +/* 88200 */ +#if (HW_SAMPR_CAPS & SAMPR_CAP_88) + HW_FREQ_88, +#define HW_HAVE_88 +#define HW_HAVE_88_(...) __VA_ARGS__ +#else +#define HW_HAVE_88_(...) +#endif +/* 64000 */ +#if (HW_SAMPR_CAPS & SAMPR_CAP_64) + HW_FREQ_64, +#define HW_HAVE_64 +#define HW_HAVE_64_(...) __VA_ARGS__ +#else +#define HW_HAVE_64_(...) +#endif +/* 48000 */ +#if (HW_SAMPR_CAPS & SAMPR_CAP_48) + HW_FREQ_48, +#define HW_HAVE_48 +#define HW_HAVE_48_(...) __VA_ARGS__ +#else +#define HW_HAVE_48_(...) +#endif +/* 44100 */ + HW_FREQ_44, +#define HW_HAVE_44 +#define HW_HAVE_44_(...) __VA_ARGS__ +/* 32000 */ +#if (HW_SAMPR_CAPS & SAMPR_CAP_32) + HW_FREQ_32, +#define HW_HAVE_32 +#define HW_HAVE_32_(...) __VA_ARGS__ +#else +#define HW_HAVE_32_(...) +#endif +/* 24000 */ +#if (HW_SAMPR_CAPS & SAMPR_CAP_24) + HW_FREQ_24, +#define HW_HAVE_24 +#define HW_HAVE_24_(...) __VA_ARGS__ +#else +#define HW_HAVE_24_(...) +#endif +/* 22050 */ +#if (HW_SAMPR_CAPS & SAMPR_CAP_22) + HW_FREQ_22, +#define HW_HAVE_22 +#define HW_HAVE_22_(...) __VA_ARGS__ +#else +#define HW_HAVE_22_(...) +#endif +/* 16000 */ +#if (HW_SAMPR_CAPS & SAMPR_CAP_16) + HW_FREQ_16, +#define HW_HAVE_16 +#define HW_HAVE_16_(...) __VA_ARGS__ +#else +#define HW_HAVE_16_(...) +#endif +/* 12000 */ +#if (HW_SAMPR_CAPS & SAMPR_CAP_12) + HW_FREQ_12, +#define HW_HAVE_12 +#define HW_HAVE_12_(...) __VA_ARGS__ +#else +#define HW_HAVE_12_(...) +#endif +/* 11025 */ +#if (HW_SAMPR_CAPS & SAMPR_CAP_11) + HW_FREQ_11, +#define HW_HAVE_11 +#define HW_HAVE_11_(...) __VA_ARGS__ +#else +#define HW_HAVE_11_(...) +#endif +/* 8000 */ +#if (HW_SAMPR_CAPS & SAMPR_CAP_8 ) + HW_FREQ_8, +#define HW_HAVE_8 +#define HW_HAVE_8_(...) __VA_ARGS__ +#else +#define HW_HAVE_8_(...) +#endif + HW_NUM_FREQ, + HW_FREQ_DEFAULT = HW_FREQ_44, + HW_SAMPR_DEFAULT = SAMPR_44, +}; /* enum hw_freq_indexes */ + +/* list of hardware sample rates */ +extern const unsigned long hw_freq_sampr[HW_NUM_FREQ]; + +#ifdef HAVE_RECORDING +/* Enumeration of supported frequencies where 0 is the highest rate + supported and REC_NUM_FREQUENCIES is the number available */ +enum rec_freq_indexes +{ + __REC_FREQ_START_INDEX = -1, /* Make sure first in list is 0 */ + +/* 96000 */ +#if (REC_SAMPR_CAPS & SAMPR_CAP_96) /* Macros and enums for each FREQ: */ + REC_FREQ_96, /* Index in enumeration */ +#define REC_HAVE_96 /* Defined if this FREQ is defined */ +#define REC_HAVE_96_(...) __VA_ARGS__ /* Output its parameters for this FREQ */ +#else +#define REC_HAVE_96_(...) /* Discards its parameters for this FREQ */ +#endif +/* 88200 */ +#if (REC_SAMPR_CAPS & SAMPR_CAP_88) + REC_FREQ_88, +#define REC_HAVE_88 +#define REC_HAVE_88_(...) __VA_ARGS__ +#else +#define REC_HAVE_88_(...) +#endif +/* 64000 */ +#if (REC_SAMPR_CAPS & SAMPR_CAP_64) + REC_FREQ_64, +#define REC_HAVE_64 +#define REC_HAVE_64_(...) __VA_ARGS__ +#else +#define REC_HAVE_64_(...) +#endif +/* 48000 */ +#if (REC_SAMPR_CAPS & SAMPR_CAP_48) + REC_FREQ_48, +#define REC_HAVE_48 +#define REC_HAVE_48_(...) __VA_ARGS__ +#else +#define REC_HAVE_48_(...) +#endif +/* 44100 */ +#if (REC_SAMPR_CAPS & SAMPR_CAP_44) + REC_FREQ_44, +#define REC_HAVE_44 +#define REC_HAVE_44_(...) __VA_ARGS__ +#else +#define REC_HAVE_44_(...) +#endif +/* 32000 */ +#if (REC_SAMPR_CAPS & SAMPR_CAP_32) + REC_FREQ_32, +#define REC_HAVE_32 +#define REC_HAVE_32_(...) __VA_ARGS__ +#else +#define REC_HAVE_32_(...) +#endif +/* 24000 */ +#if (REC_SAMPR_CAPS & SAMPR_CAP_24) + REC_FREQ_24, +#define REC_HAVE_24 +#define REC_HAVE_24_(...) __VA_ARGS__ +#else +#define REC_HAVE_24_(...) +#endif +/* 22050 */ +#if (REC_SAMPR_CAPS & SAMPR_CAP_22) + REC_FREQ_22, +#define REC_HAVE_22 +#define REC_HAVE_22_(...) __VA_ARGS__ +#else +#define REC_HAVE_22_(...) +#endif +/* 16000 */ +#if (REC_SAMPR_CAPS & SAMPR_CAP_16) + REC_FREQ_16, +#define REC_HAVE_16 +#define REC_HAVE_16_(...) __VA_ARGS__ +#else +#define REC_HAVE_16_(...) +#endif +/* 12000 */ +#if (REC_SAMPR_CAPS & SAMPR_CAP_12) + REC_FREQ_12, +#define REC_HAVE_12 +#define REC_HAVE_12_(...) __VA_ARGS__ +#else +#define REC_HAVE_12_(...) +#endif +/* 11025 */ +#if (REC_SAMPR_CAPS & SAMPR_CAP_11) + REC_FREQ_11, +#define REC_HAVE_11 +#define REC_HAVE_11_(...) __VA_ARGS__ +#else +#define REC_HAVE_11_(...) +#endif +/* 8000 */ +#if (REC_SAMPR_CAPS & SAMPR_CAP_8 ) + REC_FREQ_8, +#define REC_HAVE_8 +#define REC_HAVE_8_(...) __VA_ARGS__ +#else +#define REC_HAVE_8_(...) +#endif + REC_NUM_FREQ, + /* This should always come out I reckon */ + REC_FREQ_DEFAULT = REC_FREQ_44, + /* Get the minimum bitcount needed to save the range of values */ + REC_FREQ_CFG_NUM_BITS = (REC_NUM_FREQ > 8 ? + 4 : (REC_NUM_FREQ > 4 ? + 3 : (REC_NUM_FREQ > 2 ? + 2 : 1 + ) + ) + ), +}; /* enum rec_freq_indexes */ + +#define REC_FREQ_CFG_VAL_LIST &REC_HAVE_96_(",96") REC_HAVE_88_(",88") \ + REC_HAVE_64_(",64") REC_HAVE_48_(",48") \ + REC_HAVE_44_(",44") REC_HAVE_32_(",32") \ + REC_HAVE_24_(",24") REC_HAVE_22_(",22") \ + REC_HAVE_16_(",16") REC_HAVE_12_(",12") \ + REC_HAVE_11_(",11") REC_HAVE_8_(",8")[1] + +/* List of recording supported sample rates (set or subset of master list) */ +extern const unsigned long rec_freq_sampr[REC_NUM_FREQ]; +#endif /* HAVE_RECORDING */ + +#endif /* PCM_SAMPR_H */ diff --git a/firmware/general.c b/firmware/general.c new file mode 100644 index 0000000000..7f4348046c --- /dev/null +++ b/firmware/general.c @@ -0,0 +1,77 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2006 by Michael Sevakis + * + * 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 <limits.h> +#include "config.h" +#include "general.h" + +int round_value_to_list32(unsigned long value, + const unsigned long list[], + int count, + bool signd) +{ + unsigned long dmin = ULONG_MAX; + int idmin = -1, i; + + for (i = 0; i < count; i++) + { + unsigned long diff; + + if (list[i] == value) + { + idmin = i; + break; + } + + if (signd ? ((long)list[i] < (long)value) : (list[i] < value)) + diff = value - list[i]; + else + diff = list[i] - value; + + if (diff < dmin) + { + dmin = diff; + idmin = i; + } + } + + return idmin; +} /* round_value_to_list32 */ + +/* Number of bits set in src_mask should equal src_list length */ +int make_list_from_caps32(unsigned long src_mask, + const unsigned long *src_list, + unsigned long caps_mask, + unsigned long *caps_list) +{ + int i, count; + unsigned long mask; + + for (mask = src_mask, count = 0, i = 0; + mask != 0; + src_mask = mask, i++) + { + unsigned long test_bit; + mask &= mask - 1; /* Zero lowest bit set */ + test_bit = mask ^ src_mask; /* Isolate the bit */ + if (test_bit & caps_mask) /* Add item if caps has test bit set */ + caps_list[count++] = src_list ? src_list[i] : (unsigned long)i; + } + + return count; +} /* make_list_from_caps32 */ diff --git a/firmware/pcm_sampr.c b/firmware/pcm_sampr.c new file mode 100644 index 0000000000..cceb4b7399 --- /dev/null +++ b/firmware/pcm_sampr.c @@ -0,0 +1,76 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2006 Michael Sevakis + * + * 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 "pcm_sampr.h" + +/* Master list of all "standard" rates supported. */ +const unsigned long audio_master_sampr_list[SAMPR_NUM_FREQ] = +{ + [0 ... SAMPR_NUM_FREQ-1] = -1, /* any gaps set to -1 */ + [FREQ_96] = SAMPR_96, + [FREQ_88] = SAMPR_88, + [FREQ_64] = SAMPR_64, + [FREQ_48] = SAMPR_48, + [FREQ_44] = SAMPR_44, + [FREQ_32] = SAMPR_32, + [FREQ_24] = SAMPR_24, + [FREQ_22] = SAMPR_22, + [FREQ_16] = SAMPR_16, + [FREQ_12] = SAMPR_12, + [FREQ_11] = SAMPR_11, + [FREQ_8 ] = SAMPR_8, +}; + +/* List of all hardware rates supported (set or subset of master list) */ +const unsigned long hw_freq_sampr[HW_NUM_FREQ] = +{ + [0 ... HW_NUM_FREQ-1] = -1, + HW_HAVE_96_([HW_FREQ_96] = SAMPR_96,) + HW_HAVE_88_([HW_FREQ_88] = SAMPR_88,) + HW_HAVE_64_([HW_FREQ_64] = SAMPR_64,) + HW_HAVE_48_([HW_FREQ_48] = SAMPR_48,) + HW_HAVE_44_([HW_FREQ_44] = SAMPR_44,) + HW_HAVE_32_([HW_FREQ_32] = SAMPR_32,) + HW_HAVE_24_([HW_FREQ_24] = SAMPR_24,) + HW_HAVE_22_([HW_FREQ_22] = SAMPR_22,) + HW_HAVE_16_([HW_FREQ_16] = SAMPR_16,) + HW_HAVE_12_([HW_FREQ_12] = SAMPR_12,) + HW_HAVE_11_([HW_FREQ_11] = SAMPR_11,) + HW_HAVE_8_( [HW_FREQ_8 ] = SAMPR_8 ,) +}; + +#ifdef HAVE_RECORDING +/* List of recording supported sample rates (set or subset of master list) */ +const unsigned long rec_freq_sampr[REC_NUM_FREQ] = +{ + [0 ... REC_NUM_FREQ-1] = -1, + REC_HAVE_96_([REC_FREQ_96] = SAMPR_96,) + REC_HAVE_88_([REC_FREQ_88] = SAMPR_88,) + REC_HAVE_64_([REC_FREQ_64] = SAMPR_64,) + REC_HAVE_48_([REC_FREQ_48] = SAMPR_48,) + REC_HAVE_44_([REC_FREQ_44] = SAMPR_44,) + REC_HAVE_32_([REC_FREQ_32] = SAMPR_32,) + REC_HAVE_24_([REC_FREQ_24] = SAMPR_24,) + REC_HAVE_22_([REC_FREQ_22] = SAMPR_22,) + REC_HAVE_16_([REC_FREQ_16] = SAMPR_16,) + REC_HAVE_12_([REC_FREQ_12] = SAMPR_12,) + REC_HAVE_11_([REC_FREQ_11] = SAMPR_11,) + REC_HAVE_8_( [REC_FREQ_8 ] = SAMPR_8 ,) +}; +#endif /* HAVE_RECORDING */ diff --git a/firmware/target/coldfire/pcm-coldfire.c b/firmware/target/coldfire/pcm-coldfire.c new file mode 100644 index 0000000000..6b92f9cc14 --- /dev/null +++ b/firmware/target/coldfire/pcm-coldfire.c @@ -0,0 +1,738 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2006 by Michael Sevakis + * + * 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 <stdlib.h> +#include "system.h" +#include "kernel.h" +#include "logf.h" +#include "audio.h" +#if defined(HAVE_UDA1380) +#include "uda1380.h" +#elif defined(HAVE_TLV320) +#include "tlv320.h" +#endif + +/* Avoid further #ifdef's for some codec functions */ +#if defined(HAVE_UDA1380) +#define ac_init uda1380_init +#define ac_mute uda1380_mute +#define ac_set_frequency uda1380_set_frequency +#elif defined(HAVE_TLV320) +#define ac_init tlv320_init +#define ac_mute tlv320_mute +#define ac_set_frequency tlv320_set_frequency +#endif + +/** Semi-private shared symbols **/ + +/* the registered callback function to ask for more pcm data */ +extern pcm_more_callback_type pcm_callback_for_more; +extern bool pcm_playing; +extern bool pcm_paused; + +/* the registered callback function for when more data is available */ +extern pcm_more_callback_type pcm_callback_more_ready; +extern bool pcm_recording; + +/* peaks */ +static int play_peak_left, play_peak_right; +static unsigned long *rec_peak_addr; +static int rec_peak_left, rec_peak_right; + +#define IIS_DEFPARM ( (freq_ent[FPARM_CLOCKSEL] << 12) | \ + (pcm_txsrc_select[pcm_monitor+1] << 8) | \ + (4 << 2) ) /* 64 bit clocks / word clock */ +#define IIS_RESET 0x800 + +#ifdef IAUDIO_X5 +#define SET_IIS_CONFIG(x) IIS1CONFIG = (x); +#define IIS_CONFIG IIS1CONFIG +#define PLLCR_SET_AUDIO_BITS_DEFPARM \ + ((freq_ent[FPARM_CLSEL] << 28) | (1 << 22)) +#else +#define SET_IIS_CONFIG(x) IIS2CONFIG = (x); +#define IIS_CONFIG IIS2CONFIG +#define PLLCR_SET_AUDIO_BITS_DEFPARM \ + ((freq_ent[FPARM_CLSEL] << 28) | (3 << 22)) + +#ifdef HAVE_SPDIF_OUT +#define EBU_DEFPARM ((7 << 12) | (3 << 8) | (1 << 5) | (5 << 2)) +#endif +#endif + +/** Sample rates **/ +#define FPARM_CLOCKSEL 0 +#define FPARM_CLSEL 1 +#define FPARM_FSEL 2 +#if CONFIG_CPU == MCF5249 && defined(HAVE_UDA1380) +static const unsigned char pcm_freq_parms[HW_NUM_FREQ][3] = +{ + [HW_FREQ_88] = { 0x0c, 0x01, 0x03 }, + [HW_FREQ_44] = { 0x06, 0x01, 0x02 }, + [HW_FREQ_22] = { 0x04, 0x02, 0x01 }, + [HW_FREQ_11] = { 0x02, 0x02, 0x00 }, +}; +#endif + +#if CONFIG_CPU == MCF5250 && defined(HAVE_TLV320) +static const unsigned char pcm_freq_parms[HW_NUM_FREQ][3] = +{ + [HW_FREQ_88] = { 0x0c, 0x01, 0x02 }, + [HW_FREQ_44] = { 0x06, 0x01, 0x01 }, + [HW_FREQ_22] = { 0x04, 0x01, 0x00 }, + [HW_FREQ_11] = { 0x02, 0x02, 0x00 }, +}; +#endif + +static int pcm_freq = HW_SAMPR_DEFAULT; /* 44.1 is default */ +static const unsigned char *freq_ent = pcm_freq_parms[HW_FREQ_DEFAULT]; + +/* set frequency used by the audio hardware */ +void pcm_set_frequency(unsigned int frequency) +{ + int index; + + switch(frequency) + { + case SAMPR_11: + index = HW_FREQ_11; + break; + case SAMPR_22: + index = HW_FREQ_22; + break; + default: + case SAMPR_44: + index = HW_FREQ_44; + break; + case SAMPR_88: + index = HW_FREQ_88; + break; + } + + /* remember table entry and rate */ + freq_ent = pcm_freq_parms[index]; + pcm_freq = hw_freq_sampr[index]; +} /* pcm_set_frequency */ + +/** monitoring/source selection **/ +static int pcm_monitor = AUDIO_SRC_PLAYBACK; + +static const unsigned char pcm_txsrc_select[AUDIO_NUM_SOURCES+1] = +{ + [AUDIO_SRC_PLAYBACK+1] = 3, /* PDOR3 */ + [AUDIO_SRC_MIC+1] = 4, /* IIS1 RcvData */ + [AUDIO_SRC_LINEIN+1] = 4, /* IIS1 RcvData */ +#ifdef HAVE_FMRADIO_IN + [AUDIO_SRC_FMRADIO+1] = 4, /* IIS1 RcvData */ +#endif +#ifdef HAVE_SPDIF_IN + [AUDIO_SRC_SPDIF+1] = 7, /* EBU1 RcvData */ +#endif +}; + +static const unsigned short pcm_dataincontrol[AUDIO_NUM_SOURCES+1] = +{ + [AUDIO_SRC_PLAYBACK+1] = 0x0200, /* Reset PDIR2 data flow */ + [AUDIO_SRC_MIC+1] = 0xc020, /* Int. when 6 samples in FIFO, + PDIR2 src = ebu1RcvData */ + [AUDIO_SRC_LINEIN+1] = 0xc020, /* Int. when 6 samples in FIFO, + PDIR2 src = ebu1RcvData */ +#ifdef HAVE_FMRADIO_IN + [AUDIO_SRC_FMRADIO+1] = 0xc020, /* Int. when 6 samples in FIFO, + PDIR2 src = ebu1RcvData */ +#endif +#ifdef HAVE_SPDIF_IN + [AUDIO_SRC_SPDIF+1] = 0xc038, /* Int. when 6 samples in FIFO, + PDIR2 src = ebu1RcvData */ +#endif +}; + +static int pcm_rec_src = AUDIO_SRC_PLAYBACK; + +void pcm_set_monitor(int monitor) +{ + if ((unsigned)monitor >= AUDIO_NUM_SOURCES) + monitor = AUDIO_SRC_PLAYBACK; + pcm_monitor = monitor; +} /* pcm_set_monitor */ + +void pcm_set_rec_source(int source) +{ + if ((unsigned)source >= AUDIO_NUM_SOURCES) + source = AUDIO_SRC_PLAYBACK; + pcm_rec_src = source; +} /* pcm_set_rec_source */ + +/* apply audio settings */ +void pcm_apply_settings(bool reset) +{ + static int last_pcm_freq = HW_SAMPR_DEFAULT; +#if 0 + static int last_pcm_monitor = AUDIO_SRC_PLAYBACK; +#endif + static int last_pcm_rec_src = AUDIO_SRC_PLAYBACK; + + /* Playback must prevent pops and record monitoring won't work at all + adding IIS_RESET when setting IIS_CONFIG. Use a different method for + each. */ + if (reset && (pcm_monitor != AUDIO_SRC_PLAYBACK)) + { + /* Not playback - reset first */ + SET_IIS_CONFIG(IIS_RESET); + reset = false; + } + + if (pcm_rec_src != last_pcm_rec_src) + { + last_pcm_rec_src = pcm_rec_src; + DATAINCONTROL = pcm_dataincontrol[pcm_rec_src+1]; + } + + if (pcm_freq != last_pcm_freq) + { + last_pcm_freq = pcm_freq; + ac_set_frequency(freq_ent[FPARM_FSEL]); + coldfire_set_pllcr_audio_bits(PLLCR_SET_AUDIO_BITS_DEFPARM); + } + + SET_IIS_CONFIG(IIS_DEFPARM | (reset ? IIS_RESET : 0)); +} /* pcm_apply_settings */ + +/** DMA **/ + +/**************************************************************************** + ** Playback DMA transfer + **/ + +/* Set up the DMA transfer that kicks in when the audio FIFO gets empty */ +void pcm_play_dma_start(const void *addr, size_t size) +{ + logf("pcm_play_dma_start"); + + addr = (void *)((unsigned long)addr & ~3); /* Align data */ + size &= ~3; /* Size must be multiple of 4 */ + + pcm_playing = true; + + /* Reset the audio FIFO */ +#ifdef HAVE_SPDIF_OUT + EBU1CONFIG = IIS_RESET | EBU_DEFPARM; +#endif + + /* Set up DMA transfer */ + SAR0 = (unsigned long)addr; /* Source address */ + DAR0 = (unsigned long)&PDOR3; /* Destination address */ + BCR0 = size; /* Bytes to transfer */ + + /* Enable the FIFO and force one write to it */ + pcm_apply_settings(false); + + /* Also send the audio to S/PDIF */ +#ifdef HAVE_SPDIF_OUT + EBU1CONFIG = EBU_DEFPARM; +#endif + + DCR0 = DMA_INT | DMA_EEXT | DMA_CS | DMA_AA | + DMA_SINC | DMA_SSIZE(3) | DMA_START; +} /* pcm_play_dma_start */ + +/* Stops the DMA transfer and interrupt */ +void pcm_play_dma_stop(void) +{ + logf("pcm_play_dma_stop"); + + pcm_playing = false; + + DCR0 = 0; + DSR0 = 1; + + /* Reset the FIFO */ + pcm_apply_settings(false); + +#ifdef HAVE_SPDIF_OUT + EBU1CONFIG = IIS_RESET | EBU_DEFPARM; +#endif +} /* pcm_play_dma_stop */ + +void pcm_init(void) +{ + logf("pcm_init"); + + pcm_playing = false; + pcm_paused = false; + pcm_callback_for_more = NULL; + + MPARK = 0x81; /* PARK[1,0]=10 + BCR24BIT */ + DIVR0 = 54; /* DMA0 is mapped into vector 54 in system.c */ + DMAROUTE = (DMAROUTE & 0xffffff00) | DMA0_REQ_AUDIO_1; + DMACONFIG = 1; /* DMA0Req = PDOR3, DMA1Req = PDIR2 */ + + /* Reset the audio FIFO */ + SET_IIS_CONFIG(IIS_RESET); + + pcm_set_frequency(-1); + pcm_set_monitor(-1); + + /* Prevent pops (resets DAC to zero point) */ + SET_IIS_CONFIG(IIS_DEFPARM | IIS_RESET); + + /* Initialize default register values. */ + ac_init(); + +#if defined(HAVE_UDA1380) + /* Sleep a while so the power can stabilize (especially a long + delay is needed for the line out connector). */ + sleep(HZ); + /* Power on FSDAC and HP amp. */ + uda1380_enable_output(true); +#elif defined(HAVE_TLV320) + sleep(HZ/4); +#endif + + /* UDA1380: Unmute the master channel + (DAC should be at zero point now). */ + ac_mute(false); + + /* Call pcm_play_dma_stop to initialize everything. */ + pcm_play_dma_stop(); + + /* Enable interrupt at level 7, priority 0 */ + ICR6 = (7 << 2); + IMR &= ~(1 << 14); /* bit 14 is DMA0 */ +} /* pcm_init */ + +size_t pcm_get_bytes_waiting(void) +{ + return BCR0 & 0xffffff; +} /* pcm_get_bytes_waiting */ + +/* DMA0 Interrupt is called when the DMA has finished transfering a chunk + from the caller's buffer */ +void DMA0(void) __attribute__ ((interrupt_handler, section(".icode"))); +void DMA0(void) +{ + int res = DSR0; + + DSR0 = 1; /* Clear interrupt */ + DCR0 &= ~DMA_EEXT; + + /* Stop on error */ + if ((res & 0x70) == 0) + { + pcm_more_callback_type get_more = pcm_callback_for_more; + unsigned char *next_start; + size_t next_size = 0; + + if (get_more) + get_more(&next_start, &next_size); + + if (next_size > 0) + { + SAR0 = (unsigned long)next_start; /* Source address */ + BCR0 = next_size; /* Bytes to transfer */ + DCR0 |= DMA_EEXT; + return; + } + else + { + /* Finished playing */ +#if 0 + /* int. logfs can trash the display */ + logf("DMA0 No Data:0x%04x", res); +#endif + } + } + else + { + logf("DMA Error:0x%04x", res); + } + + pcm_play_dma_stop(); +} /* DMA0 */ + +/**************************************************************************** + ** Recording DMA transfer + **/ +void pcm_rec_dma_start(const void *addr, size_t size) +{ + logf("pcm_rec_dma_start"); + + addr = (void *)((unsigned long)addr & ~3); /* Align data */ + size &= ~3; /* Size must be multiple of 4 */ + + pcm_recording = true; + + DAR1 = (unsigned long)addr; /* Destination address */ + SAR1 = (unsigned long)&PDIR2; /* Source address */ + BCR1 = size; /* Bytes to transfer */ + + rec_peak_addr = (unsigned long *)addr; + + pcm_apply_settings(false); + + /* Start the DMA transfer.. */ +#ifdef HAVE_SPDIF_IN + INTERRUPTCLEAR = 0x03c00000; +#endif + + DCR1 = DMA_INT | DMA_EEXT | DMA_CS | DMA_DINC | + DMA_DSIZE(3) | DMA_START; +} /* pcm_dma_start */ + +void pcm_rec_dma_stop(void) +{ + logf("pcm_rec_dma_stop"); + + pcm_recording = false; + + DCR1 = 0; + DSR1 = 1; /* Clear interrupt */ +} /* pcm_dma_stop */ + +void pcm_init_recording(void) +{ + logf("pcm_init_recording"); + + pcm_recording = false; + pcm_callback_more_ready = NULL; + + AUDIOGLOB |= 0x180; /* IIS1 fifo auto sync = on, PDIR2 auto sync = on */ + + DIVR1 = 55; /* DMA1 is mapped into vector 55 in system.c */ + DMACONFIG = 1; /* DMA0Req = PDOR3, DMA1Req = PDIR2 */ + DMAROUTE = (DMAROUTE & 0xffff00ff) | DMA1_REQ_AUDIO_2; + +#ifdef HAVE_SPDIF_IN + /* PHASECONFIG setup: gain = 3*2^13, source = EBUIN */ + PHASECONFIG = (6 << 3) | (4 << 0); +#endif + + pcm_rec_dma_stop(); + + ICR7 = (7 << 2); /* Enable interrupt at level 7, priority 0 */ + IMR &= ~(1 << 15); /* bit 15 is DMA1 */ +} /* pcm_init_recording */ + +void pcm_close_recording(void) +{ + logf("pcm_close_recording"); + + pcm_rec_dma_stop(); + + DMAROUTE &= 0xffff00ff; + ICR7 = 0x00; /* Disable interrupt */ + IMR |= (1 << 15); /* bit 15 is DMA1 */ +} /* pcm_close_recording */ + +/* DMA1 Interrupt is called when the DMA has finished transfering a chunk + into the caller's buffer */ +void DMA1(void) __attribute__ ((interrupt_handler, section(".icode"))); +void DMA1(void) +{ + int res = DSR1; + pcm_more_callback_type more_ready; + unsigned char *next_start; + ssize_t next_size = 0; /* passing <> 0 is indicates + an error condition */ + + DSR1 = 1; /* Clear interrupt */ + DCR1 &= ~DMA_EEXT; + + if (res & 0x70) + { + next_size = DMA_REC_ERROR_DMA; + logf("DMA1 err: 0x%x", res); + } +#ifdef HAVE_SPDIF_IN + else if (pcm_rec_src == AUDIO_SRC_SPDIF && + (INTERRUPTSTAT & 0x01c00000)) /* valnogood, symbolerr, parityerr */ + { + INTERRUPTCLEAR = 0x03c00000; + next_size = DMA_REC_ERROR_SPDIF; + logf("spdif err"); + } +#endif + + more_ready = pcm_callback_more_ready; + + if (more_ready) + more_ready(&next_start, &next_size); + + if (next_size > 0) + { + /* Start peaking at dest */ + rec_peak_addr = (unsigned long *)next_start; + DAR1 = (unsigned long)next_start; /* Destination address */ + BCR1 = (unsigned long)next_size; /* Bytes to transfer */ + DCR1 |= DMA_EEXT; + return; + } + else + { +#if 0 + /* int. logfs can trash the display */ + logf("DMA1 No Data:0x%04x", res); +#endif + } + + /* Finished recording */ + pcm_rec_dma_stop(); +} /* DMA1 */ + +void pcm_mute(bool mute) +{ + ac_mute(mute); + if (mute) + sleep(HZ/16); +} /* pcm_mute */ + +void pcm_play_pause_pause(void) +{ + /* Disable DMA peripheral request. */ + DCR0 &= ~DMA_EEXT; + pcm_apply_settings(true); +#ifdef HAVE_SPDIF_OUT + EBU1CONFIG = EBU_DEFPARM; +#endif +} /* pcm_play_pause_pause */ + +void pcm_play_pause_unpause(void) +{ + /* Enable the FIFO and force one write to it */ + pcm_apply_settings(false); +#ifdef HAVE_SPDIF_OUT + EBU1CONFIG = EBU_DEFPARM; +#endif + DCR0 |= DMA_EEXT | DMA_START; +} /* pcm_play_pause_unpause */ + +/** + * Return playback peaks - Peaks ahead in the DMA buffer based upon the + * calling period to attempt to compensate for + * delay. + */ +void pcm_calculate_peaks(int *left, int *right) +{ + unsigned long samples; + unsigned long *addr, *end; + long peak_p, peak_n; + int level; + + static unsigned long last_peak_tick = 0; + static unsigned long frame_period = 0; + + /* Throttled peak ahead based on calling period */ + unsigned long period = current_tick - last_peak_tick; + + /* Keep reasonable limits on period */ + if (period < 1) + period = 1; + else if (period > HZ/5) + period = HZ/5; + + frame_period = (3*frame_period + period) >> 2; + + last_peak_tick = current_tick; + + if (!pcm_playing || pcm_paused) + { + play_peak_left = play_peak_right = 0; + goto peak_done; + } + + /* prevent interrupt from setting up next transfer and + be sure SAR0 and BCR0 refer to current transfer */ + level = set_irq_level(HIGHEST_IRQ_LEVEL); + + addr = (long *)(SAR0 & ~3); + samples = (BCR0 & 0xffffff) >> 2; + + set_irq_level(level); + + samples = MIN(frame_period*pcm_freq/HZ, samples); + end = addr + samples; + peak_p = peak_n = 0; + + if (left && right) + { + if (samples > 0) + { + long peak_rp = 0, peak_rn = 0; + + do + { + long value = *addr; + long ch; + + ch = value >> 16; + if (ch > peak_p) peak_p = ch; + else if (ch < peak_n) peak_n = ch; + + ch = (short)value; + if (ch > peak_rp) peak_rp = ch; + else if (ch < peak_rn) peak_rn = ch; + + addr += 4; + } + while (addr < end); + + play_peak_left = MAX(peak_p, -peak_n); + play_peak_right = MAX(peak_rp, -peak_rn); + } + } + else if (left || right) + { + if (samples > 0) + { + if (left) + { + /* Put left channel in low word */ + addr = (long *)((short *)addr - 1); + end = (long *)((short *)end - 1); + } + + do + { + long value = *(short *)addr; + + if (value > peak_p) peak_p = value; + else if (value < peak_n) peak_n = value; + + addr += 4; + } + while (addr < end); + + if (left) + play_peak_left = MAX(peak_p, -peak_n); + else + play_peak_right = MAX(peak_p, -peak_n); + } + } + +peak_done: + if (left) + *left = play_peak_left; + + if (right) + *right = play_peak_right; +} /* pcm_calculate_peaks */ + +/** + * Return recording peaks - Looks at every 4th sample from last peak up to + * current write position. + */ +void pcm_calculate_rec_peaks(int *left, int *right) +{ + unsigned long *pkaddr, *addr, *end; + long peak_lp, peak_ln; /* L +,- */ + long peak_rp, peak_rn; /* R +,- */ + int level; + + if (!pcm_recording) + { + rec_peak_left = rec_peak_right = 0; + goto peak_done; + } + + /* read these atomically or each value may not refer to the + same data transfer */ + level = set_irq_level(HIGHEST_IRQ_LEVEL); + + pkaddr = rec_peak_addr; + addr = pkaddr; + end = (unsigned long *)(DAR1 & ~3); + + set_irq_level(level); + + if (addr < end) + { + peak_lp = peak_ln = + peak_rp = peak_rn = 0; + + /* peak one sample per line */ + do + { + long value = *addr; + long ch; + + ch = value >> 16; + if (ch < peak_ln) + peak_ln = ch; + else if (ch > peak_lp) + peak_lp = ch; + + ch = (short)value; + if (ch > peak_rp) + peak_rp = ch; + else if (ch < peak_rn) + peak_rn = ch; + + addr += 4; + } + while (addr < end); + + /* only update rec_peak_addr if a DMA interrupt hasn't already + done so */ + level = set_irq_level(HIGHEST_IRQ_LEVEL); + + if (pkaddr == rec_peak_addr) + rec_peak_addr = end; + + set_irq_level(level); + + /* save peaks */ + rec_peak_left = MAX(peak_lp, -peak_ln); + rec_peak_right = MAX(peak_rp, -peak_rn); + } + +peak_done: + if (left) + *left = rec_peak_left; + + if (right) + *right = rec_peak_right; +} /* pcm_calculate_rec_peaks */ + +/** + * Select VINL & VINR source: 0=Line-in, 1=FM Radio + */ +/* All use GPIO */ +#if defined(IAUDIO_X5) + #define REC_MUX_BIT (1 << 29) + #define REC_MUX_SET_LINE() or_l(REC_MUX_BIT, &GPIO_OUT) + #define REC_MUX_SET_FM() and_l(~REC_MUX_BIT, &GPIO_OUT) +#else +#if defined(IRIVER_H100_SERIES) + #define REC_MUX_BIT (1 << 23) +#elif defined(IRIVER_H300_SERIES) + #define REC_MUX_BIT (1 << 30) +#endif + #define REC_MUX_SET_LINE() and_l(~REC_MUX_BIT, &GPIO_OUT) + #define REC_MUX_SET_FM() or_l(REC_MUX_BIT, &GPIO_OUT) +#endif + +void pcm_rec_mux(int source) +{ + if (source == 0) + REC_MUX_SET_LINE(); /* Line In */ + else + REC_MUX_SET_FM(); /* FM radio */ + + or_l(REC_MUX_BIT, &GPIO_ENABLE); + or_l(REC_MUX_BIT, &GPIO_FUNCTION); +} /* pcm_rec_mux */ |