/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2002 by wavey@wavey.org * RTC config saving code (C) 2002 by hessu@hes.iki.fi * * 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 #include #include #include "config.h" #include "kernel.h" #include "thread.h" #include "settings.h" #include "disk.h" #include "panic.h" #include "debug.h" #include "button.h" #include "usb.h" #include "backlight.h" #include "lcd.h" #include "audio.h" #include "mp3_playback.h" #include "talk.h" #include "string.h" #include "ata.h" #include "fat.h" #include "power.h" #include "backlight.h" #include "powermgmt.h" #include "status.h" #include "atoi.h" #include "screens.h" #include "ctype.h" #include "file.h" #include "errno.h" #include "system.h" #include "misc.h" #include "timefuncs.h" #ifdef HAVE_LCD_BITMAP #include "icons.h" #include "font.h" #include "peakmeter.h" #include "hwcompat.h" #endif #include "lang.h" #include "language.h" #include "wps-display.h" #include "powermgmt.h" #include "bookmark.h" #include "sprintf.h" #include "keyboard.h" #include "version.h" #include "rtc.h" #include "sound.h" #if CONFIG_HWCODEC == MAS3507D void dac_line_in(bool enable); #endif struct user_settings global_settings; #ifdef HAVE_RECORDING const char rec_base_directory[] = REC_BASE_DIR; #endif #if CONFIG_HWCODEC == MASNONE #include "pcm_playback.h" #endif #define CONFIG_BLOCK_VERSION 22 #define CONFIG_BLOCK_SIZE 512 #define RTC_BLOCK_SIZE 44 #ifdef HAVE_LCD_BITMAP #define MAX_LINES 10 #else #define MAX_LINES 2 #endif #ifdef HAVE_REMOTE_LCD #include "lcd-remote.h" #endif long lasttime = 0; static long config_sector = 0; /* mark uninitialized */ static unsigned char config_block[CONFIG_BLOCK_SIZE]; /* descriptor for a configuration value */ /* (watch the struct packing and member sizes to keep this small) */ struct bit_entry { /* how many bits within the bitfield (1-32), MSB set if value is signed */ unsigned char bit_size; /* min 6+1 bit */ /* how many bytes in the global_settings struct (1,2,4) */ unsigned char byte_size; /* min 3 bits */ /* store position in global_settings struct */ short settings_offset; /* min 9 bit, better 10 */ /* default value */ int default_val; /* min 15 bit */ /* variable name in a .cfg file, NULL if not to be saved */ const char* cfg_name; /* set of values, or NULL for a numerical value */ const char* cfg_val; }; /******************************************** Config block as saved on the battery-packed RTC user RAM memory block of 44 bytes, starting at offset 0x14 of the RTC memory space. offset abs 0x00 0x14 "Roc" header signature: 0x52 0x6f 0x63 0x03 0x17 0x04 0x18 start of bit-table ... 0x28,0x29 unused, not reachable by set_bits() without disturbing the next 2 0x2A,0x2B Config memory is reset to 0xff and initialized with 'factory defaults' if a valid header & checksum is not found. Config version number is only increased when information is _relocated_ or space is _reused_ so that old versions can read and modify configuration changed by new versions. Memory locations not used by a given version should not be modified unless the header & checksum test fails. Rest of config block, only saved to disk: 0x2C start of 2nd bit-table ... 0xB8 (char[20]) WPS file 0xCC (char[20]) Lang file 0xE0 (char[20]) Font file 0xF4-0xFF *************************************/ /* The persistence of the global_settings members is now controlled by the two tables below, rtc_bits and hd_bits. New values can just be added to the end, it will be backwards compatible. If you however change order, bitsize, etc. of existing entries, you need to bump CONFIG_BLOCK_VERSION to break compatibility. */ /* convenience macro for both size and offset of global_settings member */ #define S_O(val) sizeof(global_settings.val), offsetof(struct user_settings, val) #define SIGNED 0x80 /* for bitsize value with signed attribute */ /* some sets of values which are used more than once, to save memory */ static const char off_on[] = "off,on"; static const char off_on_ask[] = "off,on,ask"; static const char off_number_spell_hover[] = "off,number,spell,hover"; #ifdef HAVE_LCD_BITMAP static const char graphic_numeric[] = "graphic,numeric"; #endif #ifdef HAVE_RECORDING /* keep synchronous to trig_durations and trigger_times in settings_apply_trigger */ static const char trig_durations_conf [] = "0s,1s,2s,5s,10s,15s,20s,25s,30s,1min,2min,5min,10min"; #endif /* the part of the settings which ends up in the RTC RAM, where available (those we either need early, save frequently, or without spinup) */ static const struct bit_entry rtc_bits[] = { /* placeholder, containing the size information */ {9, 0, 0, 0, NULL, NULL }, /* 9 bit to tell how far this is populated */ /* # of bits, offset+size, default, .cfg name, .cfg values */ /* sound */ {7, S_O(volume), 70, "volume", NULL }, /* 0...100 */ {8 | SIGNED, S_O(balance), 0, "balance", NULL }, /* -100...100 */ #if CONFIG_HWCODEC != MASNONE /* any MAS */ {5 | SIGNED, S_O(bass), 0, "bass", NULL }, /* -15..+15 / -12..+12 */ {5 | SIGNED, S_O(treble), 0, "treble", NULL }, /* -15..+15 / -12..+12 */ #elif defined HAVE_UDA1380 {5, S_O(bass), 0, "bass", NULL }, /* 0..+24 */ {3, S_O(treble), 0, "treble", NULL }, /* 0..+6 */ #endif #if (CONFIG_HWCODEC == MAS3587F) || (CONFIG_HWCODEC == MAS3539F) {5, S_O(loudness), 0, "loudness", NULL }, /* 0...17 */ {3, S_O(avc), 0, "auto volume", "off,20ms,2,4,8" }, {1, S_O(superbass), false, "superbass", off_on }, #endif {3, S_O(channel_config), 0, "channels", "stereo,mono,custom,mono left,mono right,karaoke" }, {8, S_O(stereo_width), 100, "stereo width", NULL}, /* playback */ {2, S_O(resume), false, "resume", off_on }, {1, S_O(playlist_shuffle), false, "shuffle", off_on }, {16 | SIGNED, S_O(resume_index), -1, NULL, NULL }, {16 | SIGNED, S_O(resume_first_index), 0, NULL, NULL }, {32 | SIGNED, S_O(resume_offset), -1, NULL, NULL }, {32 | SIGNED, S_O(resume_seed), -1, NULL, NULL }, {2, S_O(repeat_mode), REPEAT_ALL, "repeat", "off,all,one,shuffle" }, /* LCD */ {6, S_O(contrast), 40, "contrast", NULL }, #ifdef CONFIG_BACKLIGHT #ifdef HAVE_CHARGING {1, S_O(backlight_on_when_charging), false, "backlight when plugged", off_on }, #endif {5, S_O(backlight_timeout), 5, "backlight timeout", "off,on,1,2,3,4,5,6,7,8,9,10,15,20,25,30,45,60,90" }, #endif /* CONFIG_BACKLIGHT */ #ifdef HAVE_LCD_BITMAP {1, S_O(invert), false, "invert", off_on }, {1, S_O(flip_display), false, "flip display", off_on }, /* display */ {1, S_O(invert_cursor), false, "invert cursor", off_on }, {1, S_O(statusbar), true, "statusbar", off_on }, {1, S_O(scrollbar), true, "scrollbar", off_on }, #if CONFIG_KEYPAD == RECORDER_PAD {1, S_O(buttonbar), true, "buttonbar", off_on }, #endif {1, S_O(volume_type), 0, "volume display", graphic_numeric }, {1, S_O(battery_display), 0, "battery display", graphic_numeric }, {1, S_O(timeformat), 0, "time format", "24hour,12hour" }, #endif {1, S_O(show_icons), true, "show icons", off_on }, /* system */ {4, S_O(poweroff), 10, "idle poweroff", "off,1,2,3,4,5,6,7,8,9,10,15,30,45,60" }, {18, S_O(runtime), 0, NULL, NULL }, {18, S_O(topruntime), 0, NULL, NULL }, {15, S_O(max_files_in_playlist), 10000, "max files in playlist", NULL }, /* 1000...20000 */ {14, S_O(max_files_in_dir), 400, "max files in dir", NULL }, /* 50...10000 */ /* battery */ {12, S_O(battery_capacity), BATTERY_CAPACITY_MIN, "battery capacity", NULL }, /* 1500...3200 for NiMH, 2200...3200 for LiIon, 500...1500 for Alkaline */ #ifdef HAVE_CHARGING {1, S_O(car_adapter_mode), false, "car adapter mode", off_on }, #endif /* tuner */ #ifdef CONFIG_TUNER {1, S_O(fm_force_mono), false, "force fm mono", off_on }, {8, S_O(last_frequency), 0, NULL, NULL }, /* Default: MIN_FREQ */ #endif /* new stuff to be added here */ /* If values are just added to the end, no need to bump the version. */ #if BATTERY_TYPES_COUNT > 1 {1, S_O(battery_type), 0, "battery type", "alkaline,nimh" }, #endif #ifdef HAVE_REMOTE_LCD /* remote lcd */ {6, S_O(remote_contrast), 42, "remote contrast", NULL }, {1, S_O(remote_invert), false, "remote invert", off_on }, {5, S_O(remote_backlight_timeout), 5, "remote backlight timeout", "off,on,1,2,3,4,5,6,7,8,9,10,15,20,25,30,45,60,90" }, {1, S_O(remote_flip_display), false, "remote flip display", off_on }, #endif /* Current sum of bits: 259 (worst case) */ /* Sum of all bit sizes must not grow beyond 288! */ }; /* the part of the settings which ends up in HD sector only */ static const struct bit_entry hd_bits[] = { /* This table starts after the 44 RTC bytes = 352 bits. */ /* Here we need 11 bits to tell how far this is populated. */ /* placeholder, containing the size information */ {11, 0, 0, 0, NULL, NULL }, /* 11 bit to tell how far this is populated */ /* # of bits, offset+size, default, .cfg name, .cfg values */ /* more display */ #ifdef CONFIG_BACKLIGHT {1, S_O(caption_backlight), false, "caption backlight", off_on }, #endif {4, S_O(scroll_speed), 9, "scroll speed", NULL }, /* 0...15 */ {7, S_O(scroll_step), 6, "scroll step", NULL }, /* 1...112 */ {8, S_O(scroll_delay), 100, "scroll delay", NULL }, /* 0...250 */ {8, S_O(bidir_limit), 50, "bidir limit", NULL }, /* 0...200 */ #ifdef HAVE_LCD_CHARCELLS {3, S_O(jump_scroll), 0, "jump scroll", NULL }, /* 0...5 */ {8, S_O(jump_scroll_delay), 50, "jump scroll delay", NULL }, /* 0...250 */ #endif /* more playback */ {1, S_O(play_selected), true, "play selected", off_on }, {1, S_O(fade_on_stop), true, "volume fade", off_on }, {4, S_O(ff_rewind_min_step), FF_REWIND_1000, "scan min step", "1,2,3,4,5,6,8,10,15,20,25,30,45,60" }, {4, S_O(ff_rewind_accel), 3, "scan accel", NULL }, {3, S_O(buffer_margin), 0, "antiskip", NULL }, /* disk */ #ifndef HAVE_MMC #ifdef HAVE_ATA_POWER_OFF {1, S_O(disk_poweroff), false, "disk poweroff", off_on }, #endif {8, S_O(disk_spindown), 5, "disk spindown", NULL }, #endif /* browser */ {3, S_O(dirfilter), SHOW_SUPPORTED, "show files", "all,supported,music,playlists,id3 database" }, {1, S_O(sort_case), false, "sort case", off_on }, {1, S_O(browse_current), false, "follow playlist", off_on }, /* playlist */ {1, S_O(playlist_viewer_icons), true, "playlist viewer icons", off_on }, {1, S_O(playlist_viewer_indices), true, "playlist viewer indices", off_on }, {1, S_O(playlist_viewer_track_display), 0, "playlist viewer track display", "track name,full path" }, {2, S_O(recursive_dir_insert), RECURSE_OFF, "recursive directory insert", off_on_ask }, /* bookmarks */ {3, S_O(autocreatebookmark), BOOKMARK_NO, "autocreate bookmarks", "off,on,ask,recent only - on,recent only - ask" }, {2, S_O(autoloadbookmark), BOOKMARK_NO, "autoload bookmarks", off_on_ask }, {2, S_O(usemrb), BOOKMARK_NO, "use most-recent-bookmarks", "off,on,unique only" }, #ifdef HAVE_LCD_BITMAP /* peak meter */ {5, S_O(peak_meter_clip_hold), 16, "peak meter clip hold", /* 0...25 */ "on,1,2,3,4,5,6,7,8,9,10,15,20,25,30,45,60,90,2min,3min,5min,10min,20min,45min,90min" }, {1, S_O(peak_meter_performance), false, "peak meter busy", off_on }, {5, S_O(peak_meter_hold), 3, "peak meter hold", "off,200ms,300ms,500ms,1,2,3,4,5,6,7,8,9,10,15,20,30,1min" }, {7, S_O(peak_meter_release), 8, "peak meter release", NULL }, /* 0...126 */ {1, S_O(peak_meter_dbfs), true, "peak meter dbfs", off_on }, {7, S_O(peak_meter_min), 60, "peak meter min", NULL }, /* 0...100 */ {7, S_O(peak_meter_max), 0, "peak meter max", NULL }, /* 0...100 */ #endif #if CONFIG_HWCODEC == MAS3587F /* recording */ {1, S_O(rec_editable), false, "editable recordings", off_on }, {4, S_O(rec_timesplit), 0, "rec timesplit", /* 0...15 */ "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), 8, "rec mic gain", NULL }, {3, S_O(rec_quality), 5, "rec quality", NULL }, {2, S_O(rec_source), 0, /* 0=mic */ "rec source", "mic,line,spdif" }, {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 */ "rec left gain", NULL }, /* 0...15 */ {4, S_O(rec_right_gain), 2, /* 0dB */ "rec right gain", NULL }, /* 0...15 */ {5, S_O(rec_prerecord_time), 0, "prerecording time", NULL }, /* 0...30 */ {1, S_O(rec_directory), 0, /* rec_base_directory */ "rec directory", REC_BASE_DIR ",current" }, #endif #if CONFIG_HWCODEC == MAS3507D {1, S_O(line_in), false, "line in", off_on }, #endif /* voice */ {2, S_O(talk_dir), 0, "talk dir", off_number_spell_hover }, {2, S_O(talk_file), 0, "talk file", off_number_spell_hover }, {1, S_O(talk_menu), true, "talk menu", off_on }, /* If values are just added to the end, no need to bump the version. */ {2, S_O(sort_file), 0, "sort files", "alpha,oldest,newest,type" }, {2, S_O(sort_dir), 0, "sort dirs", "alpha,oldest,newest" }, {7, S_O(mdb_strength), 0, "mdb strength", NULL}, {7, S_O(mdb_harmonics), 0, "mdb harmonics", NULL}, {9, S_O(mdb_center), 0, "mdb center", NULL}, {9, S_O(mdb_shape), 0, "mdb shape", NULL}, {1, S_O(mdb_enable), 0, "mdb enable", off_on}, {1, S_O(id3_v1_first), 0, "id3 tag priority", "v2-v1,v1-v2"}, #ifdef HAVE_RECORDING {1, S_O(rec_startup), false, "rec screen on startup", off_on }, /* values for the trigger */ {8 | SIGNED, S_O(rec_start_thres), -35, "trigger start threshold", NULL}, {8 | SIGNED, S_O(rec_stop_thres), -45, "trigger stop threshold", NULL}, {4, S_O(rec_start_duration), 0, "trigger start duration", trig_durations_conf}, {4, S_O(rec_stop_postrec), 2, "trigger stop postrec", trig_durations_conf}, {4, S_O(rec_stop_gap), 1, "trigger min gap", trig_durations_conf}, {4, S_O(rec_trigger_mode ), 1, "trigger mode", "off,no rearm,rearm"}, #endif #if CONFIG_HWCODEC == MASNONE {1, S_O(crossfade), false, "crossfade", off_on}, #endif #if CONFIG_BACKLIGHT == BL_IRIVER /* backlight fading */ {2, S_O(backlight_fade_in), 1, "backlight fade in", "off,500ms,1s,2s"}, {3, S_O(backlight_fade_out), 3, "backlight fade out", "off,500ms,1s,2s,3s,4s,5s,10s"}, #endif #ifdef HAVE_SPDIF_POWER {1, S_O(spdif_enable), false, "spdif enable", off_on}, #endif {1, S_O(next_folder), false, "move to next folder", off_on }, {1, S_O(runtimedb), false, "gather runtime data", off_on }, /* new stuff to be added at the end */ /* Sum of all bit sizes must not grow beyond 0xB8*8 = 1472 */ }; /* helper function to extract n (<=32) bits from an arbitrary position * counting from LSB to MSB */ static unsigned long get_bits( const unsigned long* p, /* the start of the bitfield array */ unsigned int from, /* bit no. to start reading from */ unsigned int size) /* how many bits to read */ { unsigned int long_index = from / 32; unsigned int bit_index = from % 32; unsigned long result; result = p[long_index] >> bit_index; if (bit_index + size > 32) /* crossing longword boundary */ result |= p[long_index+1] << (32 - bit_index); result &= 0xFFFFFFFF >> (32 - size); return result; } /* helper function to set n (<=32) bits to an arbitrary position, * counting from LSB to MSB */ static void set_bits( unsigned long* p, /* the start of the bitfield array */ unsigned int from, /* bit no. to start writing into */ unsigned int size, /* how many bits to change */ unsigned long value) /* content (LSBs will be taken) */ { unsigned int long_index = from / 32; unsigned int bit_index = from % 32; unsigned long mask; mask = 0xFFFFFFFF >> (32 - size); value &= mask; mask <<= bit_index; if (bit_index + size > 32) p[long_index+1] = (p[long_index+1] & (0xFFFFFFFF << (bit_index + size - 32))) | (value >> (32 - bit_index)); p[long_index] = (p[long_index] & ~mask) | (value << bit_index); } /* * Calculates the checksum for the config block and returns it */ static unsigned short calculate_config_checksum(const unsigned char* buf) { unsigned int i; unsigned char cksum[2]; cksum[0] = cksum[1] = 0; for (i=0; i < RTC_BLOCK_SIZE - 2; i+=2 ) { cksum[0] ^= buf[i]; cksum[1] ^= buf[i+1]; } return (cksum[0] << 8) | cksum[1]; } /* * initialize the config block buffer */ static void init_config_buffer( void ) { DEBUGF( "init_config_buffer()\n" ); /* reset to 0 - all unused */ memset(config_block, 0, CONFIG_BLOCK_SIZE); /* insert header */ config_block[0] = 'R'; config_block[1] = 'o'; config_block[2] = 'c'; config_block[3] = CONFIG_BLOCK_VERSION; } /* * save the config block buffer to disk or RTC RAM */ static int save_config_buffer( void ) { unsigned short chksum; #ifdef HAVE_RTC unsigned int i; #endif /* update the checksum in the end of the block before saving */ chksum = calculate_config_checksum(config_block); config_block[ RTC_BLOCK_SIZE - 2 ] = chksum >> 8; config_block[ RTC_BLOCK_SIZE - 1 ] = chksum & 0xff; #ifdef HAVE_RTC /* FIXME: okay, it _would_ be cleaner and faster to implement rtc_write so that it would write a number of bytes at a time since the RTC chip supports that, but this will have to do for now 8-) */ for (i=0; i < RTC_BLOCK_SIZE; i++ ) { int r = rtc_write(0x14+i, config_block[i]); if (r) { DEBUGF( "save_config_buffer: rtc_write failed at addr 0x%02x: %d\n", 14+i, r ); return r; } } #endif if (config_sector != 0) ata_delayed_write( config_sector, config_block); else return -1; return 0; } /* * load the config block buffer from disk or RTC RAM */ static int load_config_buffer(int which) { unsigned short chksum; bool correct = false; DEBUGF( "load_config_buffer()\n" ); if (which & SETTINGS_HD) { if (config_sector != 0) { ata_read_sectors(IF_MV2(0,) config_sector, 1, config_block); /* calculate the checksum, check it and the header */ chksum = calculate_config_checksum(config_block); if (config_block[0] == 'R' && config_block[1] == 'o' && config_block[2] == 'c' && config_block[3] == CONFIG_BLOCK_VERSION && (chksum >> 8) == config_block[RTC_BLOCK_SIZE - 2] && (chksum & 0xff) == config_block[RTC_BLOCK_SIZE - 1]) { DEBUGF( "load_config_buffer: header & checksum test ok\n" ); correct = true; } } } #ifdef HAVE_RTC if(!correct) { /* If the disk sector was incorrect, reinit the buffer */ memset(config_block, 0, CONFIG_BLOCK_SIZE); } if (which & SETTINGS_RTC) { unsigned int i; unsigned char rtc_block[RTC_BLOCK_SIZE]; /* read rtc block */ for (i=0; i < RTC_BLOCK_SIZE; i++ ) rtc_block[i] = rtc_read(0x14+i); chksum = calculate_config_checksum(rtc_block); /* if rtc block is ok, use that */ if (rtc_block[0] == 'R' && rtc_block[1] == 'o' && rtc_block[2] == 'c' && rtc_block[3] == CONFIG_BLOCK_VERSION && (chksum >> 8) == rtc_block[RTC_BLOCK_SIZE - 2] && (chksum & 0xff) == rtc_block[RTC_BLOCK_SIZE - 1]) { memcpy(config_block, rtc_block, RTC_BLOCK_SIZE); correct = true; } } #endif if ( !correct ) { /* if checksum is not valid, clear the config buffer */ DEBUGF( "load_config_buffer: header & checksum test failed\n" ); init_config_buffer(); return -1; } return 0; } /* helper to save content of global_settings into a bitfield, as described per table */ static void save_bit_table(const struct bit_entry* p_table, int count, int bitstart) { unsigned long* p_bitfield = (unsigned long*)config_block; /* 32 bit addr. */ unsigned long value; /* 32 bit content */ int i; const struct bit_entry* p_run = p_table; /* start after the size info */ int curr_bit = bitstart + p_table->bit_size; count--; /* first is excluded from loop */ for (i=0; ibyte_size) { case 1: value = ((unsigned char*)&global_settings)[p_run->settings_offset]; break; case 2: value = ((unsigned short*)&global_settings)[p_run->settings_offset/2]; break; case 4: value = ((unsigned int*)&global_settings)[p_run->settings_offset/4]; break; default: DEBUGF( "illegal size!" ); continue; } set_bits(p_bitfield, curr_bit, p_run->bit_size & 0x3F, value); curr_bit += p_run->bit_size & 0x3F; } set_bits(p_bitfield, bitstart, p_table->bit_size, /* write size */ curr_bit); /* = position after last element */ } /* * figure out the config sector from the partition table and the * mounted file system */ void settings_calc_config_sector(void) { #ifdef SIMULATOR config_sector = 61; #else int i; long partition_start; long sector = 0; if (fat_startsector(IF_MV(0)) != 0) /* There is a partition table */ { sector = 61; for (i = 0; i < 4; i++) { partition_start = disk_partinfo(i)->start; if (partition_start != 0 && (partition_start - 2) < sector) sector = partition_start - 2; } if (sector < 0) sector = 0; } config_sector = sector; #endif } /* * persist all runtime user settings to disk or RTC RAM */ int settings_save( void ) { { int elapsed_secs; elapsed_secs = (current_tick - lasttime) / HZ; global_settings.runtime += elapsed_secs; lasttime += (elapsed_secs * HZ); if ( global_settings.runtime > global_settings.topruntime ) global_settings.topruntime = global_settings.runtime; } /* serialize scalar values into RTC and HD sector, specified via table */ save_bit_table(rtc_bits, sizeof(rtc_bits)/sizeof(rtc_bits[0]), 4*8); save_bit_table(hd_bits, sizeof(hd_bits)/sizeof(hd_bits[0]), RTC_BLOCK_SIZE*8); strncpy(&config_block[0xb8], global_settings.wps_file, MAX_FILENAME); strncpy(&config_block[0xcc], global_settings.lang_file, MAX_FILENAME); strncpy(&config_block[0xe0], global_settings.font_file, MAX_FILENAME); if(save_config_buffer()) { lcd_clear_display(); #ifdef HAVE_LCD_CHARCELLS lcd_puts(0, 0, str(LANG_SETTINGS_SAVE_PLAYER)); lcd_puts(0, 1, str(LANG_SETTINGS_BATTERY_PLAYER)); #else lcd_puts(4, 2, str(LANG_SETTINGS_SAVE_RECORDER)); lcd_puts(2, 4, str(LANG_SETTINGS_BATTERY_RECORDER)); lcd_update(); #endif sleep(HZ*2); return -1; } return 0; } #ifdef HAVE_LCD_BITMAP /** * Applies the range infos stored in global_settings to * the peak meter. */ void settings_apply_pm_range(void) { int pm_min, pm_max; /* depending on the scale mode (dBfs or percent) the values of global_settings.peak_meter_dbfs have different meanings */ if (global_settings.peak_meter_dbfs) { /* convert to dBfs * 100 */ pm_min = -(((int)global_settings.peak_meter_min) * 100); pm_max = -(((int)global_settings.peak_meter_max) * 100); } else { /* percent is stored directly -> no conversion */ pm_min = global_settings.peak_meter_min; pm_max = global_settings.peak_meter_max; } /* apply the range */ peak_meter_init_range(global_settings.peak_meter_dbfs, pm_min, pm_max); } #endif /* HAVE_LCD_BITMAP */ void sound_settings_apply(void) { sound_set(SOUND_BASS, global_settings.bass); sound_set(SOUND_TREBLE, global_settings.treble); sound_set(SOUND_BALANCE, global_settings.balance); sound_set(SOUND_VOLUME, global_settings.volume); sound_set(SOUND_CHANNELS, global_settings.channel_config); sound_set(SOUND_STEREO_WIDTH, global_settings.stereo_width); #if (CONFIG_HWCODEC == MAS3587F) || (CONFIG_HWCODEC == MAS3539F) sound_set(SOUND_LOUDNESS, global_settings.loudness); sound_set(SOUND_AVC, global_settings.avc); sound_set(SOUND_MDB_STRENGTH, global_settings.mdb_strength); sound_set(SOUND_MDB_HARMONICS, global_settings.mdb_harmonics); sound_set(SOUND_MDB_CENTER, global_settings.mdb_center); sound_set(SOUND_MDB_SHAPE, global_settings.mdb_shape); sound_set(SOUND_MDB_ENABLE, global_settings.mdb_enable); sound_set(SOUND_SUPERBASS, global_settings.superbass); #endif } void settings_apply(void) { char buf[64]; sound_settings_apply(); audio_set_buffer_margin(global_settings.buffer_margin); lcd_set_contrast(global_settings.contrast); lcd_scroll_speed(global_settings.scroll_speed); #ifdef HAVE_REMOTE_LCD lcd_remote_set_contrast(global_settings.remote_contrast); lcd_remote_set_invert_display(global_settings.remote_invert); lcd_remote_set_flip(global_settings.remote_flip_display); remote_backlight_set_timeout(global_settings.remote_backlight_timeout); #endif backlight_set_timeout(global_settings.backlight_timeout); backlight_set_on_when_charging(global_settings.backlight_on_when_charging); #if CONFIG_BACKLIGHT == BL_IRIVER backlight_set_fade_in(global_settings.backlight_fade_in); backlight_set_fade_out(global_settings.backlight_fade_out); #endif ata_spindown(global_settings.disk_spindown); #if CONFIG_HWCODEC == MAS3507D dac_line_in(global_settings.line_in); #endif #ifdef HAVE_ATA_POWER_OFF ata_poweroff(global_settings.disk_poweroff); #endif set_poweroff_timeout(global_settings.poweroff); set_battery_capacity(global_settings.battery_capacity); #if BATTERY_TYPES_COUNT > 1 set_battery_type(global_settings.battery_type); #endif #ifdef HAVE_LCD_BITMAP lcd_set_invert_display(global_settings.invert); lcd_set_flip(global_settings.flip_display); button_set_flip(global_settings.flip_display); lcd_update(); /* refresh after flipping the screen */ settings_apply_pm_range(); peak_meter_init_times( global_settings.peak_meter_release, global_settings.peak_meter_hold, global_settings.peak_meter_clip_hold); #endif if ( global_settings.wps_file[0] && global_settings.wps_file[0] != 0xff ) { snprintf(buf, sizeof buf, ROCKBOX_DIR "/%s.wps", global_settings.wps_file); wps_load(buf, false); } else wps_reset(); #ifdef HAVE_LCD_BITMAP if ( global_settings.font_file[0] && global_settings.font_file[0] != 0xff ) { snprintf(buf, sizeof buf, ROCKBOX_DIR FONT_DIR "/%s.fnt", global_settings.font_file); font_load(buf); } else font_reset(); lcd_scroll_step(global_settings.scroll_step); #else lcd_jump_scroll(global_settings.jump_scroll); lcd_jump_scroll_delay(global_settings.jump_scroll_delay * (HZ/10)); #endif lcd_bidir_scroll(global_settings.bidir_limit); lcd_scroll_delay(global_settings.scroll_delay * (HZ/10)); if ( global_settings.lang_file[0] && global_settings.lang_file[0] != 0xff ) { snprintf(buf, sizeof buf, ROCKBOX_DIR LANG_DIR "/%s.lng", global_settings.lang_file); lang_load(buf); talk_init(); /* use voice of same language */ } #if CONFIG_HWCODEC == MASNONE && !defined(SIMULATOR) pcm_crossfade_enable(global_settings.crossfade); #endif #ifdef HAVE_SPDIF_POWER spdif_power_enable(global_settings.spdif_enable); #endif } /* helper to load global_settings from a bitfield, as described per table */ static void load_bit_table(const struct bit_entry* p_table, int count, int bitstart) { unsigned long* p_bitfield = (unsigned long*)config_block; /* 32 bit addr. */ unsigned long value; /* 32 bit content */ int i; int maxbit; /* how many bits are valid in the saved part */ const struct bit_entry* p_run = p_table; /* start after the size info */ count--; /* first is excluded from loop */ maxbit = get_bits(p_bitfield, bitstart, p_table->bit_size); bitstart += p_table->bit_size; for (i=0; ibit_size & 0x3F; /* mask off abused bits */ if (bitstart + size > maxbit) break; /* exit if this is not valid any more in bitfield */ value = get_bits(p_bitfield, bitstart, size); bitstart += size; if (p_run->bit_size & SIGNED) { // sign extend the read value unsigned long mask = 0xFFFFFFFF << (size - 1); if (value & mask) /* true if MSB of value is set */ value |= mask; } /* could do a memcpy, but that would be endian-dependent */ switch(p_run->byte_size) { case 1: ((unsigned char*)&global_settings)[p_run->settings_offset] = (unsigned char)value; break; case 2: ((unsigned short*)&global_settings)[p_run->settings_offset/2] = (unsigned short)value; break; case 4: ((unsigned int*)&global_settings)[p_run->settings_offset/4] = (unsigned int)value; break; default: DEBUGF( "illegal size!" ); continue; } } } /* * load settings from disk or RTC RAM */ void settings_load(int which) { DEBUGF( "reload_all_settings()\n" ); /* load the buffer from the RTC (resets it to all-unused if the block is invalid) and decode the settings which are set in the block */ if (!load_config_buffer(which)) { /* load scalar values from RTC and HD sector, specified via table */ if (which & SETTINGS_RTC) { load_bit_table(rtc_bits, sizeof(rtc_bits)/sizeof(rtc_bits[0]), 4*8); } if (which & SETTINGS_HD) { load_bit_table(hd_bits, sizeof(hd_bits)/sizeof(hd_bits[0]), RTC_BLOCK_SIZE*8); } if ( global_settings.contrast < MIN_CONTRAST_SETTING ) global_settings.contrast = lcd_default_contrast(); strncpy(global_settings.wps_file, &config_block[0xb8], MAX_FILENAME); strncpy(global_settings.lang_file, &config_block[0xcc], MAX_FILENAME); strncpy(global_settings.font_file, &config_block[0xe0], MAX_FILENAME); } } void set_file(char* filename, char* setting, int maxlen) { char* fptr = strrchr(filename,'/'); int len; int extlen = 0; char* ptr; if (!fptr) return; *fptr = 0; fptr++; len = strlen(fptr); ptr = fptr + len; while (*ptr != '.') { extlen++; ptr--; } if (strncasecmp(ROCKBOX_DIR, filename ,strlen(ROCKBOX_DIR)) || (len-extlen > maxlen)) return; strncpy(setting, fptr, len-extlen); setting[len-extlen]=0; settings_save(); } /* helper to sort a .cfg file entry into a global_settings member, as described per table. Returns the position if found, else 0. */ static int load_cfg_table( const struct bit_entry* p_table, /* the table which describes the entries */ int count, /* number of entries in the table, including the first */ const char* name, /* the item to be searched */ const char* value, /* the value which got loaded for that item */ int hint) /* position to start looking */ { int i = hint; do { if (p_table[i].cfg_name != NULL && !strcasecmp(name, p_table[i].cfg_name)) { /* found */ int val = 0; if (p_table[i].cfg_val == NULL) { /* numerical value, just convert the string */ val = atoi(value); } else { /* set of string values, find the index */ const char* item; const char* run; int len = strlen(value); item = run = p_table[i].cfg_val; while(1) { /* count the length of the field */ while (*run != ',' && *run != '\0') run++; if (!strncasecmp(value, item, MAX(run-item, len))) break; /* match, exit the search */ if (*run == '\0') /* reached the end of the choices */ return i; /* return the position, but don't update */ val++; /* count the item up */ run++; /* behind the ',' */ item = run; } } /* could do a memcpy, but that would be endian-dependent */ switch(p_table[i].byte_size) { case 1: ((unsigned char*)&global_settings)[p_table[i].settings_offset] = (unsigned char)val; break; case 2: ((unsigned short*)&global_settings)[p_table[i].settings_offset/2] = (unsigned short)val; break; case 4: ((unsigned int*)&global_settings)[p_table[i].settings_offset/4] = (unsigned int)val; break; default: DEBUGF( "illegal size!" ); continue; } return i; /* return the position */ } i++; if (i==count) i=1; /* wraparound */ } while (i != hint); /* back where we started, all searched */ return 0; /* indicate not found */ } bool settings_load_config(const char* file) { int fd; char line[128]; fd = open(file, O_RDONLY); if (fd < 0) return false; while (read_line(fd, line, sizeof line) > 0) { char* name; char* value; const struct bit_entry* table[2] = { rtc_bits, hd_bits }; const int ta_size[2] = { sizeof(rtc_bits)/sizeof(rtc_bits[0]), sizeof(hd_bits)/sizeof(hd_bits[0]) }; int last_table = 0; /* which table was used last round */ int last_pos = 1; /* at which position did we succeed */ int pos; /* currently returned position */ if (!settings_parseline(line, &name, &value)) continue; /* check for the string values */ if (!strcasecmp(name, "wps")) { if (wps_load(value,false)) set_file(value, global_settings.wps_file, MAX_FILENAME); } else if (!strcasecmp(name, "lang")) { if (!lang_load(value)) { set_file(value, global_settings.lang_file, MAX_FILENAME); talk_init(); /* use voice of same language */ } } #ifdef HAVE_LCD_BITMAP else if (!strcasecmp(name, "font")) { if (font_load(value)) set_file(value, global_settings.font_file, MAX_FILENAME); } #endif /* check for scalar values, using the two tables */ pos = load_cfg_table(table[last_table], ta_size[last_table], name, value, last_pos); if (pos) /* success */ { last_pos = pos; /* remember as a position hint for next round */ continue; } last_table = 1-last_table; /* try other table */ last_pos = 1; /* search from start */ pos = load_cfg_table(table[last_table], ta_size[last_table], name, value, last_pos); if (pos) /* success */ { last_pos = pos; /* remember as a position hint for next round */ continue; } } close(fd); settings_apply(); settings_save(); return true; } /* helper to save content of global_settings into a file, as described per table */ static void save_cfg_table(const struct bit_entry* p_table, int count, int fd) { long value; /* 32 bit content */ int i; const struct bit_entry* p_run = p_table; /* start after the size info */ count--; /* first is excluded from loop */ for (i=0; icfg_name == NULL) continue; /* this value is not to be saved */ /* could do a memcpy, but that would be endian-dependent */ switch(p_run->byte_size) { case 1: if (p_run->bit_size & SIGNED) /* signed? */ value = ((char*)&global_settings)[p_run->settings_offset]; else value = ((unsigned char*)&global_settings)[p_run->settings_offset]; break; case 2: if (p_run->bit_size & SIGNED) /* signed? */ value = ((short*)&global_settings)[p_run->settings_offset/2]; else value = ((unsigned short*)&global_settings)[p_run->settings_offset/2]; break; case 4: value = ((unsigned int*)&global_settings)[p_run->settings_offset/4]; break; default: DEBUGF( "illegal size!" ); continue; } if (p_run->cfg_val == NULL) /* write as number */ { fdprintf(fd, "%s: %ld\r\n", p_run->cfg_name, value); } else /* write as item */ { const char* p = p_run->cfg_val; fdprintf(fd, "%s: ", p_run->cfg_name); while(value >= 0) { char c = *p++; /* currently processed char */ if (c == ',') /* separator */ value--; else if (c == '\0') /* end of string */ break; /* not found */ else if (value == 0) /* the right place */ write(fd, &c, 1); /* char by char, this is lame, OK */ } fdprintf(fd, "\r\n"); if (p_run->cfg_val != off_on) /* explaination for non-bool */ fdprintf(fd, "# (possible values: %s)\r\n", p_run->cfg_val); } } } bool settings_save_config(void) { bool done = false; int fd, i; char filename[MAX_PATH]; /* find unused filename */ for (i=0; ; i++) { snprintf(filename, sizeof filename, ROCKBOX_DIR "/config%02d.cfg", i); fd = open(filename, O_RDONLY); if (fd < 0) break; close(fd); } /* allow user to modify filename */ while (!done) { if (!kbd_input(filename, sizeof filename)) { fd = creat(filename,0); if (fd < 0) { lcd_clear_display(); lcd_puts(0,0,str(LANG_FAILED)); lcd_update(); sleep(HZ); } else done = true; } else break; } /* abort if file couldn't be created */ if (!done) { lcd_clear_display(); lcd_puts(0,0,str(LANG_RESET_DONE_CANCEL)); lcd_update(); sleep(HZ); return false; } fdprintf(fd, "# .cfg file created by rockbox %s - ", appsversion); fdprintf(fd, "http://www.rockbox.org\r\n#\r\n"); fdprintf(fd, "#\r\n# wps / language / font \r\n#\r\n"); if (global_settings.wps_file[0] != 0) fdprintf(fd, "wps: %s/%s.wps\r\n", ROCKBOX_DIR, global_settings.wps_file); if (global_settings.lang_file[0] != 0) fdprintf(fd, "lang: %s/%s.lng\r\n", ROCKBOX_DIR LANG_DIR, global_settings.lang_file); #ifdef HAVE_LCD_BITMAP if (global_settings.font_file[0] != 0) fdprintf(fd, "font: %s/%s.fnt\r\n", ROCKBOX_DIR FONT_DIR, global_settings.font_file); #endif /* here's the action: write values to file, specified via table */ save_cfg_table(rtc_bits, sizeof(rtc_bits)/sizeof(rtc_bits[0]), fd); save_cfg_table(hd_bits, sizeof(hd_bits)/sizeof(hd_bits[0]), fd); close(fd); lcd_clear_display(); lcd_puts(0,0,str(LANG_SETTINGS_SAVED1)); lcd_puts(0,1,str(LANG_SETTINGS_SAVED2)); lcd_update(); sleep(HZ); return true; } /* helper to load defaults from table into global_settings members */ static void default_table(const struct bit_entry* p_table, int count) { int i; for (i=1; i max ) *variable = max; if(*variable < min ) *variable = min; if ( function && button != BUTTON_NONE) function(*variable); } lcd_stop_scroll(); return false; } /* NOTE: the 'type' parameter specifies the actual type of the variable that 'variable' points to. not the value within. Only variables with type 'bool' should use parameter BOOL. The type separation is necessary since int and bool are fundamentally different and bit-incompatible types and can not share the same access code. */ bool set_option(const char* string, void* variable, enum optiontype type, const struct opt_items* options, int numoptions, void (*function)(int)) { bool done = false; int button; int* intvar = (int*)variable; bool* boolvar = (bool*)variable; int oldval = 0; int index, oldindex = -1; /* remember what we said */ if (type==INT) oldval=*intvar; else oldval=*boolvar; #ifdef HAVE_LCD_BITMAP if(global_settings.statusbar) lcd_setmargins(0, STATUSBAR_HEIGHT); else lcd_setmargins(0, 0); #endif lcd_clear_display(); lcd_puts_scroll(0, 0, string); while ( !done ) { index = type==INT ? *intvar : (int)*boolvar; lcd_puts(0, 1, P2STR(options[index].string)); if (global_settings.talk_menu && index != oldindex) { talk_id(options[index].voice_id, false); oldindex = index; } #ifdef HAVE_LCD_BITMAP status_draw(true); #endif lcd_update(); button = button_get_w_tmo(HZ/2); switch (button) { case SETTINGS_INC: case SETTINGS_INC | BUTTON_REPEAT: if (type == INT) { if ( *intvar < (numoptions-1) ) (*intvar)++; else (*intvar) -= (numoptions-1); } else *boolvar = !*boolvar; break; case SETTINGS_DEC: case SETTINGS_DEC | BUTTON_REPEAT: if (type == INT) { if ( *intvar > 0 ) (*intvar)--; else (*intvar) += (numoptions-1); } else *boolvar = !*boolvar; break; case SETTINGS_OK: #ifdef SETTINGS_OK2 case SETTINGS_OK2: #endif done = true; break; case SETTINGS_CANCEL: #ifdef SETTINGS_CANCEL2 case SETTINGS_CANCEL2: #endif if (((type==INT) && (*intvar != oldval)) || ((type==BOOL) && (*boolvar != (bool)oldval))) { if (type==INT) *intvar=oldval; else *boolvar=oldval; lcd_stop_scroll(); lcd_puts(0, 0, str(LANG_MENU_SETTING_CANCEL)); lcd_update(); sleep(HZ/2); } done = true; break; default: if(default_event_handler(button) == SYS_USB_CONNECTED) return true; break; } if ( function && button != BUTTON_NONE) { if (type == INT) function(*intvar); else function(*boolvar); } } lcd_stop_scroll(); return false; } #ifdef HAVE_RECORDING /* This array holds the record timer interval lengths, in seconds */ static const unsigned long rec_timer_seconds[] = { 0, /* 0 means OFF */ 5*60, /* 00:05 */ 10*60, /* 00:10 */ 15*60, /* 00:15 */ 30*60, /* 00:30 */ 60*60, /* 01:00 */ 74*60, /* 74:00 */ 80*60, /* 80:00 */ 2*60*60, /* 02:00 */ 4*60*60, /* 04:00 */ 6*60*60, /* 06:00 */ 8*60*60, /* 08:00 */ 10L*60*60, /* 10:00 */ 12L*60*60, /* 12:00 */ 18L*60*60, /* 18:00 */ 24L*60*60 /* 24:00 */ }; unsigned int rec_timesplit_seconds(void) { return rec_timer_seconds[global_settings.rec_timesplit]; } /* * Time strings used for the trigger durations. * Keep synchronous to trigger_times in settings_apply_trigger */ char *trig_durations[TRIG_DURATION_COUNT] = { "0s", "1s", "2s", "5s", "10s", "15s", "20s", "25s", "30s", "1min", "2min", "5min", "10min" }; void settings_apply_trigger(void) { /* Keep synchronous to trig_durations and trig_durations_conf*/ static const long trigger_times[TRIG_DURATION_COUNT] = { 0, HZ, 2*HZ, 5*HZ, 10*HZ, 15*HZ, 20*HZ, 25*HZ, 30*HZ, 60*HZ, 2*60*HZ, 5*60*HZ, 10*60*HZ }; peak_meter_define_trigger( global_settings.rec_start_thres, trigger_times[global_settings.rec_start_duration], MIN(trigger_times[global_settings.rec_start_duration] / 2, 2*HZ), global_settings.rec_stop_thres, trigger_times[global_settings.rec_stop_postrec], trigger_times[global_settings.rec_stop_gap] ); } #endif