diff options
author | SIMOND François <francois@lienweb.fr> | 2011-05-20 08:43:01 +0200 |
---|---|---|
committer | KalimochoAz <calimochoazucarado@gmail.com> | 2011-12-19 12:15:55 +0100 |
commit | c389bc2f675449785536543daabda9d5e0bcb305 (patch) | |
tree | 1513c001ff0b76ace26c5d24ac764a52af5f397d /sound/soc/codecs/wm8994_voodoo.c | |
parent | c66fbe8305d97ed8b4d0bff029d38ab57e09b05b (diff) | |
download | kernel_samsung_crespo-c389bc2f675449785536543daabda9d5e0bcb305.zip kernel_samsung_crespo-c389bc2f675449785536543daabda9d5e0bcb305.tar.gz kernel_samsung_crespo-c389bc2f675449785536543daabda9d5e0bcb305.tar.bz2 |
Voodoo sound: driver v9
New features
- advanced logging controllable via debug_log infos: 1, verbose: 2
- implements hardware EQ
- implements hardware stereo expansion effect
- new concept: digital_gain:
makes room for effects with negative gains
avoid saturation using hardware DRC as limiter
act as compresser+limiter with positive gains
(gain unit is mili-decibels, min -71625, max 36000)
negative digital_gain are analog compensated if possible
- super smooth headphone amp volume changes
New supported device
- Compatible with M110S Gingerbread kernel sources
Diffstat (limited to 'sound/soc/codecs/wm8994_voodoo.c')
-rw-r--r-- | sound/soc/codecs/wm8994_voodoo.c | 790 |
1 files changed, 694 insertions, 96 deletions
diff --git a/sound/soc/codecs/wm8994_voodoo.c b/sound/soc/codecs/wm8994_voodoo.c index b10dd0b..1334a89 100644 --- a/sound/soc/codecs/wm8994_voodoo.c +++ b/sound/soc/codecs/wm8994_voodoo.c @@ -31,7 +31,6 @@ #endif #define SUBJECT "wm8994_voodoo.c" -#define VOODOO_SOUND_VERSION 8 #ifdef MODULE #include "tegrak_voodoo_sound.h" @@ -52,9 +51,11 @@ bool bypass_write_hook = false; +short unsigned int debug_log_level = LOG_OFF; + #ifdef CONFIG_SND_VOODOO_HP_LEVEL_CONTROL -unsigned short hplvol = CONFIG_SND_VOODOO_HP_LEVEL; -unsigned short hprvol = CONFIG_SND_VOODOO_HP_LEVEL; +unsigned short hp_level[2] = { CONFIG_SND_VOODOO_HP_LEVEL, + CONFIG_SND_VOODOO_HP_LEVEL };; #endif #ifdef CONFIG_SND_VOODOO_FM @@ -82,26 +83,73 @@ bool fll_tuning = true; bool dac_direct = true; bool mono_downmix = false; +// equalizer + +// digital gain value in mili dB +int digital_gain = 0; + +bool headphone_eq = false; +short eq_gains[5] = { 0, 0, 0, 0, 0 }; +short eq_bands[5] = { 3, 4, 4, 4, 3 }; +char eq_band_coef_names[][2] = { "A", "B", "C", "PG" }; + +unsigned int eq_band_values[5][4] = { + {0x0FCA, 0x0400, 0x00D8}, + {0x1EB5, 0xF145, 0x0B75, 0x01C5}, + {0x1C58, 0xF373, 0x0A54, 0x0558}, + {0x168E, 0xF829, 0x07AD, 0x1103}, + {0x0564, 0x0559, 0x4000} +}; + +// 3D effect +bool stereo_expansion = false; +short unsigned int stereo_expansion_gain = 16; + // keep here a pointer to the codec structure struct snd_soc_codec *codec; -#define DECLARE_BOOL_SHOW(name) \ -static ssize_t name##_show(struct device *dev, \ -struct device_attribute *attr, char *buf) \ -{ \ - return sprintf(buf,"%u\n",(name ? 1 : 0)); \ +#define DECLARE_BOOL_SHOW(name) \ +static ssize_t name##_show(struct device *dev, \ +struct device_attribute *attr, char *buf) \ +{ \ + return sprintf(buf,"%u\n",(name ? 1 : 0)); \ } -#define DECLARE_BOOL_STORE_UPDATE_WITH_MUTE(name, updater, with_mute) \ +#define DECLARE_BOOL_STORE_UPDATE_WITH_MUTE(name, updater, with_mute) \ static ssize_t name##_store(struct device *dev, struct device_attribute *attr, \ - const char *buf, size_t size) \ -{ \ - unsigned short state; \ - if (sscanf(buf, "%hu", &state) == 1) { \ - name = state == 0 ? false : true; \ - updater(with_mute); \ - } \ - return size; \ + const char *buf, size_t size) \ +{ \ + unsigned short state; \ + if (sscanf(buf, "%hu", &state) == 1) { \ + name = state == 0 ? false : true; \ + if (debug_log(LOG_INFOS)) \ + printk("Voodoo sound: %s: %u\n", #updater, state); \ + updater(with_mute); \ + } \ + return size; \ +} + +#define DECLARE_EQ_GAIN_SHOW(band) \ +static ssize_t headphone_eq_b##band##_gain_show(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + return sprintf(buf, "%d\n", eq_gains[band-1]); \ +} + +#define DECLARE_EQ_GAIN_STORE(band) \ +static ssize_t headphone_eq_b##band##_gain_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t size) \ +{ \ + short new_gain; \ + if (sscanf(buf, "%hd", &new_gain) == 1) { \ + if (new_gain >= -12 && new_gain <= 12) { \ + eq_gains[band-1] = new_gain; \ + update_headphone_eq(false); \ + } \ + } \ + return size; \ } #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 35) @@ -110,30 +158,112 @@ static ssize_t name##_store(struct device *dev, struct device_attribute *attr, \ #define DECLARE_WM8994(codec) struct wm8994_priv *wm8994 = codec->private_data; #endif +bool debug_log(short unsigned int level) +{ + if (debug_log_level >= level) + return true; + + return false; +} + #ifdef CONFIG_SND_VOODOO_HP_LEVEL_CONTROL -void update_hpvol() +int hpvol(int channel) +{ + int vol; + + vol = hp_level[channel]; + + if (is_path_media_or_fm_no_call_no_record()) { + // negative digital gain compensation + if (digital_gain < 0) + vol = (vol - ((digital_gain / 100) + 5) / 10); + + if (vol > 62) + return 62; + } + + return vol; +} + +void write_hpvol(unsigned short l, unsigned short r) +{ + unsigned short val; + + // we don't need the Volume Update flag when sending the first volume + val = (WM8994_HPOUT1L_MUTE_N | l); + val |= WM8994_HPOUT1L_ZC; + wm8994_write(codec, WM8994_LEFT_OUTPUT_VOLUME, val); + + // this time we write the right volume plus the Volume Update flag. + // This way, both volume are set at the same time + val = (WM8994_HPOUT1_VU | WM8994_HPOUT1R_MUTE_N | r); + val |= WM8994_HPOUT1L_ZC; + wm8994_write(codec, WM8994_RIGHT_OUTPUT_VOLUME, val); +} + +void update_hpvol(bool with_fade) { unsigned short val; + unsigned short i; + short steps; + unsigned short hp_level_old[2]; + unsigned short hp_level_registers[2] = { WM8994_LEFT_OUTPUT_VOLUME, + WM8994_RIGHT_OUTPUT_VOLUME }; + DECLARE_WM8994(codec); // don't affect headphone amplifier volume // when not on heapdhones or if call is active if (!is_path(HEADPHONES) || (wm8994->codec_state & CALL_ACTIVE)) - return; + return; bypass_write_hook = true; - // we don't need the Volume Update flag when sending the first volume - val = (WM8994_HPOUT1L_MUTE_N | hplvol); - val |= WM8994_HPOUT1L_ZC; - wm8994_write(codec, WM8994_LEFT_OUTPUT_VOLUME, val); + if (!with_fade) { + write_hpvol(hpvol(0), hpvol(1)); + bypass_write_hook = false; + return; + } + + // read previous levels + for (i = 0; i < 2; i++) { + val = wm8994_read(codec, hp_level_registers[i]); + val &= ~(WM8994_HPOUT1_VU_MASK); + val &= ~(WM8994_HPOUT1L_ZC_MASK); + val &= ~(WM8994_HPOUT1L_MUTE_N_MASK); + hp_level_old[i] = val + (digital_gain / 1000); + + if (debug_log(LOG_INFOS)) + printk("Voodoo sound: previous hp_level[%hu]: %hu\n", + i, val); + } + + // calculate number of steps for volume fade + steps = hp_level[0] - hp_level_old[0]; + if (debug_log(LOG_INFOS)) + printk("Voodoo sound: volume change steps: %hd " + "start: %hu, end: %hu\n", + steps, + hp_level_old[0], + hp_level[0]); + + while (steps != 0) { + if (hp_level[0] < hp_level_old[0]) + steps++; + else + steps--; + + if (debug_log(LOG_INFOS)) + printk("Voodoo sound: volume: %hu\n", + (hpvol(0) - steps)); + + write_hpvol(hpvol(0) - steps, hpvol(1) - steps); + + if (steps != 0) + udelay(1000); + } - // this time we write the right volume plus the Volume Update flag. - //This way, both volume are set at the same time - val = (WM8994_HPOUT1_VU | WM8994_HPOUT1R_MUTE_N | hprvol); - val |= WM8994_HPOUT1L_ZC; - wm8994_write(codec, WM8994_RIGHT_OUTPUT_VOLUME, val); bypass_write_hook = false; } #endif @@ -325,7 +455,7 @@ bool is_path(int unified_path) DECLARE_WM8994(codec); switch (unified_path) { - // speaker + // speaker case SPEAKER: #ifdef GALAXY_TAB return (wm8994->cur_path == SPK @@ -342,9 +472,7 @@ bool is_path(int unified_path) #endif #endif - // headphones - // FIXME: be sure dac_direct doesn't break phone calls on TAB - // with these spath detection settings (HP4P) + // headphones case HEADPHONES: #ifdef NEXUS_S @@ -362,7 +490,10 @@ bool is_path(int unified_path) #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 35) return (wm8994->cur_path == HP || wm8994->cur_path == HP_NO_MIC - || wm8994->fmradio_path == FMR_HP); +#ifndef M110S + || wm8994->fmradio_path == FMR_HP +#endif + ); #else return (wm8994->cur_path == HP || wm8994->fmradio_path == FMR_HP); @@ -371,7 +502,7 @@ bool is_path(int unified_path) #endif #endif - // FM Radio on headphones + // FM Radio on headphones case RADIO_HEADPHONES: #ifdef NEXUS_S return false; @@ -389,17 +520,32 @@ bool is_path(int unified_path) #endif #endif - // headphones - // FIXME: be sure dac_direct doesn't break phone calls on TAB - // with these spath detection settings (HP4P) + // Standard recording presets + // for M110S Gingerbread: added check non call case MAIN_MICROPHONE: return (wm8994->codec_state & CAPTURE_ACTIVE) - && (wm8994->rec_path == MAIN); + && (wm8994->rec_path == MAIN) + && !(wm8994->codec_state & CALL_ACTIVE); } return false; } +bool is_path_media_or_fm_no_call_no_record() { + + DECLARE_WM8994(codec); + + if ((is_path(HEADPHONES) + && (wm8994->codec_state & PLAYBACK_ACTIVE) + && (wm8994->stream_state & PCM_STREAM_PLAYBACK) + && !(wm8994->codec_state & CALL_ACTIVE) + && (wm8994->rec_path == MIC_OFF) + ) || is_path(RADIO_HEADPHONES)) + return true; + + return false; +} + #ifdef NEXUS_S void update_speaker_tuning(bool with_mute) { @@ -545,13 +691,7 @@ void update_mono_downmix(bool with_mute) unsigned short dac_direct_get_value(unsigned short val, bool can_reverse) { - DECLARE_WM8994(codec); - - if ((is_path(HEADPHONES) - && (wm8994->codec_state & PLAYBACK_ACTIVE) - && (wm8994->stream_state & PCM_STREAM_PLAYBACK) - && !(wm8994->codec_state & CALL_ACTIVE)) - || is_path(RADIO_HEADPHONES)) { + if (is_path_media_or_fm_no_call_no_record()) { if (dac_direct) { if (val == WM8994_DAC1L_TO_MIXOUTL) @@ -580,18 +720,225 @@ void update_dac_direct(bool with_mute) bypass_write_hook = false; } +unsigned short digital_gain_get_value(unsigned short val) +{ + // AIF gain to 0dB + int aif_gain = 0xC0; + int i; + int step = -375; + + if (is_path_media_or_fm_no_call_no_record()) { + + if (digital_gain <= 0) { + // clear the actual DAC volume for this value + val &= ~(WM8994_DAC1R_VOL_MASK); + + // calculation with round + i = ((digital_gain * 10 / step) + 5) / 10; + aif_gain -= i; + val |= aif_gain; + + if (debug_log(LOG_INFOS)) + printk("Voodoo sound: digital gain: %d mdB, " + "steps: %d, real AIF gain: %d mdB\n", + digital_gain, i, i * step); + } + } + + return val; +} + +void update_digital_gain(bool with_mute) +{ + unsigned short val1, val2; + val1 = digital_gain_get_value(wm8994_read(codec, + WM8994_AIF1_DAC1_LEFT_VOLUME)); + val2 = digital_gain_get_value(wm8994_read(codec, + WM8994_AIF1_DAC1_RIGHT_VOLUME)); + + bypass_write_hook = true; + wm8994_write(codec, WM8994_AIF1_DAC1_LEFT_VOLUME, + WM8994_DAC1_VU | val1); + wm8994_write(codec, WM8994_AIF1_DAC1_RIGHT_VOLUME, + WM8994_DAC1_VU | val2); + bypass_write_hook = false; +} + +void update_headphone_eq(bool with_mute) +{ + int gains_1; + int gains_2; + int i; + int j; + int k = 0; + int first_reg = WM8994_AIF1_DAC1_EQ_BAND_1_A; + + if (!is_path_media_or_fm_no_call_no_record()) { + // don't apply the EQ + return; + } + + if (debug_log(LOG_INFOS)) + printk("Voodoo sound: EQ gains (dB): %hd, %hd, %hd, %hd, %hd\n", + eq_gains[0], eq_gains[1], eq_gains[2], + eq_gains[3], eq_gains[4]); + + gains_1 = + ((eq_gains[0] + 12) << WM8994_AIF1DAC1_EQ_B1_GAIN_SHIFT) | + ((eq_gains[1] + 12) << WM8994_AIF1DAC1_EQ_B2_GAIN_SHIFT) | + ((eq_gains[2] + 12) << WM8994_AIF1DAC1_EQ_B3_GAIN_SHIFT) | + headphone_eq; + + gains_2 = + ((eq_gains[3] + 12) << WM8994_AIF1DAC1_EQ_B4_GAIN_SHIFT) | + ((eq_gains[4] + 12) << WM8994_AIF1DAC1_EQ_B5_GAIN_SHIFT); + + wm8994_write(codec, WM8994_AIF1_DAC1_EQ_GAINS_1, gains_1); + wm8994_write(codec, WM8994_AIF1_DAC1_EQ_GAINS_2, gains_2); + + // don't send EQ configuration if its not enabled + if (!headphone_eq) + return; + + for (i = 0; i < ARRAY_SIZE(eq_band_values); i++) { + if (debug_log(LOG_INFOS)) + printk("Voodoo sound: send EQ Band %d\n", i + 1); + + for (j = 0; j < eq_bands[i]; j++) { + wm8994_write(codec, + first_reg + k, eq_band_values[i][j]); + k++; + } + } +} + +void update_stereo_expansion(bool with_mute) +{ + short unsigned int val; + + val = wm8994_read(codec, WM8994_AIF1_DAC1_FILTERS_2); + if (stereo_expansion) { + val &= ~(WM8994_AIF1DAC1_3D_GAIN_MASK); + val |= (stereo_expansion_gain << WM8994_AIF1DAC1_3D_GAIN_SHIFT); + } + val &= ~(WM8994_AIF1DAC1_3D_ENA_MASK); + val |= (stereo_expansion << WM8994_AIF1DAC1_3D_ENA_SHIFT); + + wm8994_write(codec, WM8994_AIF1_DAC1_FILTERS_2, val); +} + +void load_current_eq_values() +{ + int i; + int j; + int k = 0; + int first_reg = WM8994_AIF1_DAC1_EQ_BAND_1_A; + + for (i = 0; i < ARRAY_SIZE(eq_band_values); i++) + for (j = 0; j < eq_bands[i]; j++) { + eq_band_values[i][j] = + wm8994_read(codec, first_reg + k); + k++; + } +} + +void apply_saturation_prevention_drc() +{ + unsigned short val; + unsigned short drc_gain = 0; + int i; + int step = 750; + + // don't apply the limiter if not playing media + // (exclude FM radio, it has its own DRC settings) + if (!is_path_media_or_fm_no_call_no_record() + || is_path(RADIO_HEADPHONES)) + return; + + // don't apply the limiter without stereo_expansion or headphone_eq + // or a positive digital gain + if (!(stereo_expansion + || headphone_eq + || digital_gain >= 0)) + return; + + // configure the DRC to avoid saturation: not actually compress signal + // gain is unmodified. Should affect only what's higher than 0 dBFS + + // tune Attack & Decacy values + val = wm8994_read(codec, WM8994_AIF1_DRC1_2); + val &= ~(WM8994_AIF1DRC1_ATK_MASK); + val &= ~(WM8994_AIF1DRC1_DCY_MASK); + val |= (0x1 << WM8994_AIF1DRC1_ATK_SHIFT); + val |= (0x4 << WM8994_AIF1DRC1_DCY_SHIFT); + + // set DRC maximum gain to 36 dB + val &= ~(WM8994_AIF1DRC1_MAXGAIN_MASK); + val |= (0x3 << WM8994_AIF1DRC1_MAXGAIN_SHIFT); + + wm8994_write(codec, WM8994_AIF1_DRC1_2, val); + + // Above knee: flat (what really avoid the saturation) + val = wm8994_read(codec, WM8994_AIF1_DRC1_3); + val |= (0x5 << WM8994_AIF1DRC1_HI_COMP_SHIFT); + wm8994_write(codec, WM8994_AIF1_DRC1_3, val); + + val = wm8994_read(codec, WM8994_AIF1_DRC1_1); + // disable Quick Release and Anti Clip + // both do do more harm than good for this particular usage + val &= ~(WM8994_AIF1DRC1_QR_MASK); + val &= ~(WM8994_AIF1DRC1_ANTICLIP_MASK); + + // enable DRC + val &= ~(WM8994_AIF1DAC1_DRC_ENA_MASK); + val |= WM8994_AIF1DAC1_DRC_ENA; + wm8994_write(codec, WM8994_AIF1_DRC1_1, val); + + val = wm8994_read(codec, WM8994_AIF1_DRC1_4); + val &= ~(WM8994_AIF1DRC1_KNEE_IP_MASK); + + if (digital_gain >= 0) { + // deal with positive digital gains + i = ((digital_gain * 10 / step) + 5) / 10; + drc_gain += i; + val |= (drc_gain << WM8994_AIF1DRC1_KNEE_IP_SHIFT); + + if (debug_log(LOG_INFOS)) + printk("Voodoo sound: digital gain: %d mdB, " + "steps: %d, real DRC gain: %d mdB\n", + digital_gain, i, i * step); + + } + wm8994_write(codec, WM8994_AIF1_DRC1_4, val); +} + /* * * Declaring the controling misc devices * */ +static ssize_t debug_log_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%u\n", debug_log_level); +} + +static ssize_t debug_log_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + sscanf(buf, "%hu", &debug_log_level); + return size; +} + #ifdef CONFIG_SND_VOODOO_HP_LEVEL_CONTROL static ssize_t headphone_amplifier_level_show(struct device *dev, struct device_attribute *attr, char *buf) { // output median of left and right headphone amplifier volumes - return sprintf(buf, "%u\n", (hplvol + hprvol) / 2); + return sprintf(buf, "%u\n", (hp_level[0] + hp_level[1]) / 2); } static ssize_t headphone_amplifier_level_store(struct device *dev, @@ -600,15 +947,16 @@ static ssize_t headphone_amplifier_level_store(struct device *dev, { unsigned short vol; if (sscanf(buf, "%hu", &vol) == 1) { - // left and right are set to the same volumes - hplvol = hprvol = vol; + // hard limit to 62 because 63 introduces distortions - if (hplvol > 62) - hplvol = 62; - if (hprvol > 62) - hprvol = 62; + if (vol > 62) + vol = 62; + + // left and right are set to the same volumes by this control + hp_level[0] = hp_level[1] = vol; - update_hpvol(); + update_digital_gain(false); + update_hpvol(true); } return size; } @@ -683,7 +1031,153 @@ DECLARE_BOOL_STORE_UPDATE_WITH_MUTE(dac_direct, update_dac_direct, false); -#ifdef CONFIG_SND_VOODOO_DEBUG +static ssize_t digital_gain_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", digital_gain); +} + +static ssize_t digital_gain_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int new_digital_gain; + if (sscanf(buf, "%d", &new_digital_gain) == 1) { + if (new_digital_gain <= 36000 && new_digital_gain >= -71625) { + if (new_digital_gain > digital_gain) { + // reduce analog volume first + digital_gain = new_digital_gain; +#ifdef CONFIG_SND_VOODOO_HP_LEVEL_CONTROL + update_hpvol(false); +#endif + update_digital_gain(false); + } else { + // reduce digital volume first + digital_gain = new_digital_gain; + update_digital_gain(false); +#ifdef CONFIG_SND_VOODOO_HP_LEVEL_CONTROL + update_hpvol(false); +#endif + } + } + apply_saturation_prevention_drc(); + } + return size; +} + +DECLARE_BOOL_SHOW(headphone_eq); +DECLARE_BOOL_STORE_UPDATE_WITH_MUTE(headphone_eq, + update_headphone_eq, + false); + +DECLARE_EQ_GAIN_SHOW(1); +DECLARE_EQ_GAIN_STORE(1); +DECLARE_EQ_GAIN_SHOW(2); +DECLARE_EQ_GAIN_STORE(2); +DECLARE_EQ_GAIN_SHOW(3); +DECLARE_EQ_GAIN_STORE(3); +DECLARE_EQ_GAIN_SHOW(4); +DECLARE_EQ_GAIN_STORE(4); +DECLARE_EQ_GAIN_SHOW(5); +DECLARE_EQ_GAIN_STORE(5); + +static ssize_t headphone_eq_bands_values_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int i; + int j; + int k = 0; + int first_reg = WM8994_AIF1_DAC1_EQ_BAND_1_A; + int bands_size = ARRAY_SIZE(eq_bands); + char *name; + + for (i = 0; i < bands_size; i++) + for (j = 0; j < eq_bands[i]; j++) { + + // display 3-coef bands properly (hi & lo shelf) + if (j + 1 == eq_bands[i]) + name = eq_band_coef_names[3]; + else + name = eq_band_coef_names[j]; + + sprintf(buf, "%s%d %s 0x%04X\n", buf, + i + 1, name, + wm8994_read(codec, first_reg + k)); + k++; + } + + return sprintf(buf, "%s", buf); +} + +static ssize_t headphone_eq_bands_values_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int i; + short unsigned int val; + short unsigned int band; + char coef_name[2]; + unsigned int bytes_read = 0; + + while (sscanf(buf, "%hu %s %hx%n", + &band, coef_name, &val, &bytes_read) == 3) { + + buf += bytes_read; + + if (band < 1 || band > 5) + continue; + + for (i = 0; i < ARRAY_SIZE(eq_band_coef_names); i++) { + // loop through band coefficient letters + if (strncmp(eq_band_coef_names[i], coef_name, 2) == 0) { + if (eq_bands[band-1] == 3 && i == 3) + // deal with high and low shelves + eq_band_values[band-1][2] = val; + else + // parametric bands + eq_band_values[band-1][i] = val; + + if (debug_log(LOG_INFOS)) + printk("Voodoo sound: read EQ from " + "sysfs: EQ Band %hd %s: 0x%04X\n" + , band, coef_name, val); + break; + } + } + } + + return size; +} + +DECLARE_BOOL_SHOW(stereo_expansion); +DECLARE_BOOL_STORE_UPDATE_WITH_MUTE(stereo_expansion, + update_stereo_expansion, + false); + +static ssize_t stereo_expansion_gain_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%u\n", stereo_expansion_gain); +} + +static ssize_t stereo_expansion_gain_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + short unsigned val; + + if (sscanf(buf, "%hu", &val) == 1) + if (val >= 0 && val < 32) { + stereo_expansion_gain = val; + update_stereo_expansion(false); + } + + return size; +} + +#ifdef CONFIG_SND_VOODOO_DEVELOPMENT static ssize_t show_wm8994_register_dump(struct device *dev, struct device_attribute *attr, char *buf) @@ -775,7 +1269,13 @@ static ssize_t store_wm8994_write(struct device *dev, while (sscanf(buf, "%hx %hx%n", ®, &val, &bytes_read) == 2) { buf += bytes_read; + if (debug_log(LOG_INFOS)); + printk("Voodoo sound: read from sysfs: %X, %X\n", + reg, val); + + bypass_write_hook = true; wm8994_write(codec, reg, val); + bypass_write_hook = false; } return size; } @@ -806,6 +1306,10 @@ static ssize_t enable_store(struct device *dev, } #endif +static DEVICE_ATTR(debug_log, S_IRUGO | S_IWUGO, + debug_log_show, + debug_log_store); + #ifdef CONFIG_SND_VOODOO_HP_LEVEL_CONTROL static DEVICE_ATTR(headphone_amplifier_level, S_IRUGO | S_IWUGO, headphone_amplifier_level_show, @@ -854,11 +1358,51 @@ static DEVICE_ATTR(dac_direct, S_IRUGO | S_IWUGO, dac_direct_show, dac_direct_store); +static DEVICE_ATTR(digital_gain, S_IRUGO | S_IWUGO, + digital_gain_show, + digital_gain_store); + +static DEVICE_ATTR(headphone_eq, S_IRUGO | S_IWUGO, + headphone_eq_show, + headphone_eq_store); + +static DEVICE_ATTR(headphone_eq_b1_gain, S_IRUGO | S_IWUGO, + headphone_eq_b1_gain_show, + headphone_eq_b1_gain_store); + +static DEVICE_ATTR(headphone_eq_b2_gain, S_IRUGO | S_IWUGO, + headphone_eq_b2_gain_show, + headphone_eq_b2_gain_store); + +static DEVICE_ATTR(headphone_eq_b3_gain, S_IRUGO | S_IWUGO, + headphone_eq_b3_gain_show, + headphone_eq_b3_gain_store); + +static DEVICE_ATTR(headphone_eq_b4_gain, S_IRUGO | S_IWUGO, + headphone_eq_b4_gain_show, + headphone_eq_b4_gain_store); + +static DEVICE_ATTR(headphone_eq_b5_gain, S_IRUGO | S_IWUGO, + headphone_eq_b5_gain_show, + headphone_eq_b5_gain_store); + +static DEVICE_ATTR(headphone_eq_bands_values, S_IRUGO | S_IWUGO, + headphone_eq_bands_values_show, + headphone_eq_bands_values_store); + +static DEVICE_ATTR(stereo_expansion, S_IRUGO | S_IWUGO, + stereo_expansion_show, + stereo_expansion_store); + +static DEVICE_ATTR(stereo_expansion_gain, S_IRUGO | S_IWUGO, + stereo_expansion_gain_show, + stereo_expansion_gain_store); + static DEVICE_ATTR(mono_downmix, S_IRUGO | S_IWUGO, mono_downmix_show, mono_downmix_store); -#ifdef CONFIG_SND_VOODOO_DEBUG +#ifdef CONFIG_SND_VOODOO_DEVELOPMENT static DEVICE_ATTR(wm8994_register_dump, S_IRUGO, show_wm8994_register_dump, NULL); @@ -885,6 +1429,7 @@ static DEVICE_ATTR(module, 0, #endif static struct attribute *voodoo_sound_attributes[] = { + &dev_attr_debug_log.attr, #ifdef CONFIG_SND_VOODOO_HP_LEVEL_CONTROL &dev_attr_headphone_amplifier_level.attr, #endif @@ -903,8 +1448,18 @@ static struct attribute *voodoo_sound_attributes[] = { &dev_attr_adc_osr128.attr, &dev_attr_fll_tuning.attr, &dev_attr_dac_direct.attr, + &dev_attr_digital_gain.attr, + &dev_attr_headphone_eq.attr, + &dev_attr_headphone_eq_b1_gain.attr, + &dev_attr_headphone_eq_b2_gain.attr, + &dev_attr_headphone_eq_b3_gain.attr, + &dev_attr_headphone_eq_b4_gain.attr, + &dev_attr_headphone_eq_b5_gain.attr, + &dev_attr_headphone_eq_bands_values.attr, + &dev_attr_stereo_expansion.attr, + &dev_attr_stereo_expansion_gain.attr, &dev_attr_mono_downmix.attr, -#ifdef CONFIG_SND_VOODOO_DEBUG +#ifdef CONFIG_SND_VOODOO_DEVELOPMENT &dev_attr_wm8994_register_dump.attr, &dev_attr_wm8994_write.attr, #endif @@ -958,6 +1513,10 @@ void update_enable() printk("Voodoo sound: initializing driver v%d\n", VOODOO_SOUND_VERSION); +#ifdef CONFIG_SND_VOODOO_DEVELOPMENT + printk("Voodoo sound: codec development tools enabled\n"); +#endif + misc_register(&voodoo_sound_device); if (sysfs_create_group(&voodoo_sound_device.this_device->kobj, &voodoo_sound_group) < 0) { @@ -1044,13 +1603,13 @@ unsigned int voodoo_hook_wm8994_write(struct snd_soc_codec *codec_, value = (WM8994_HPOUT1_VU | WM8994_HPOUT1L_MUTE_N | - hplvol); + hpvol(0)); if (reg == WM8994_RIGHT_OUTPUT_VOLUME) value = (WM8994_HPOUT1_VU | WM8994_HPOUT1R_MUTE_N | - hprvol); + hpvol(1)); } #endif @@ -1091,54 +1650,90 @@ unsigned int voodoo_hook_wm8994_write(struct snd_soc_codec *codec_, || reg == WM8994_OUTPUT_MIXER_2) value = dac_direct_get_value(value, false); + // Digital Headroom virtual hook + if (reg == WM8994_AIF1_DAC1_LEFT_VOLUME + || reg == WM8994_AIF1_DAC1_RIGHT_VOLUME) + value = digital_gain_get_value(value); + + // Headphones EQ & 3D virtual hook + if (reg == WM8994_AIF1_DAC1_FILTERS_1 + || reg == WM8994_AIF1_DAC2_FILTERS_1 + || reg == WM8994_AIF2_DAC_FILTERS_1) { + bypass_write_hook = true; + apply_saturation_prevention_drc(); + update_headphone_eq(false); + update_stereo_expansion(false); + bypass_write_hook = false; + } + } -#ifdef CONFIG_SND_VOODOO_DEBUG_LOG + if (debug_log(LOG_VERBOSE)) // log every write to dmesg #ifdef NEXUS_S - printk("Voodoo sound: codec_state=%u, stream_state=%u, " - "cur_path=%i, rec_path=%i, " - "power_state=%i\n", - wm8994->codec_state, wm8994->stream_state, - wm8994->cur_path, wm8994->rec_path, - wm8994->power_state); + printk("Voodoo sound: codec_state=%u, stream_state=%u, " + "cur_path=%i, rec_path=%i, " + "power_state=%i\n", + wm8994->codec_state, wm8994->stream_state, + wm8994->cur_path, wm8994->rec_path, + wm8994->power_state); #else #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 35) - printk("Voodoo sound: wm8994_write 0x%03X 0x%04X " - "codec_state=%u, stream_state=%u, " - "cur_path=%i, rec_path=%i, " - "fmradio_path=%i, fmr_mix_path=%i, " - "input_source=%i, output_source=%i, " - "power_state=%i\n", - reg, value, - wm8994->codec_state, wm8994->stream_state, - wm8994->fmradio_path, wm8994->fmr_mix_path, - wm8994->cur_path, wm8994->rec_path, - wm8994->input_source, wm8994->output_source, - wm8994->power_state); + printk("Voodoo sound: wm8994_write 0x%03X 0x%04X " + "codec_state=%u, stream_state=%u, " + "cur_path=%i, rec_path=%i, " +#ifndef M110S + "fmradio_path=%i, fmr_mix_path=%i, " +#endif + "input_source=%i, " +#ifndef M110S + "output_source=%i, " +#endif + "power_state=%i\n", + reg, value, + wm8994->codec_state, wm8994->stream_state, +#ifndef M110S + wm8994->fmradio_path, wm8994->fmr_mix_path, +#endif + wm8994->cur_path, wm8994->rec_path, + wm8994->input_source, +#ifndef M110S + wm8994->output_source, +#endif + wm8994->power_state); #else - printk("Voodoo sound: wm8994_write 0x%03X 0x%04X " - "codec_state=%u, stream_state=%u, " - "cur_path=%i, rec_path=%i, " - "fmradio_path=%i, fmr_mix_path=%i, " + printk("Voodoo sound: wm8994_write 0x%03X 0x%04X " + "codec_state=%u, stream_state=%u, " + "cur_path=%i, rec_path=%i, " +#ifndef M110S + "fmradio_path=%i, fmr_mix_path=%i, " +#endif #ifdef CONFIG_S5PC110_KEPLER_BOARD - "call_record_path=%i, call_record_ch=%i, " - "AUDIENCE_state=%i, " - "Fac_SUB_MIC_state=%i, TTY_state=%i, " -#endif - "power_state=%i, " - "recognition_active=%i, ringtone_active=%i\n", - reg, value, - wm8994->codec_state, wm8994->stream_state, - wm8994->cur_path, wm8994->rec_path, - wm8994->fmradio_path, wm8994->fmr_mix_path, + "call_record_path=%i, call_record_ch=%i, " + "AUDIENCE_state=%i, " + "Fac_SUB_MIC_state=%i, TTY_state=%i, " +#endif + "power_state=%i, " +#ifndef M110S + "recognition_active=%i, ringtone_active=%i" +#endif + "\n", + reg, value, + wm8994->codec_state, wm8994->stream_state, + wm8994->cur_path, wm8994->rec_path, +#ifndef M110S + wm8994->fmradio_path, wm8994->fmr_mix_path, +#endif #ifdef CONFIG_S5PC110_KEPLER_BOARD - wm8994->call_record_path, wm8994->call_record_ch, - wm8994->AUDIENCE_state, - wm8994->Fac_SUB_MIC_state, wm8994->TTY_state, + wm8994->call_record_path, wm8994->call_record_ch, + wm8994->AUDIENCE_state, + wm8994->Fac_SUB_MIC_state, wm8994->TTY_state, #endif - wm8994->power_state, - wm8994->recognition_active, wm8994->ringtone_active); + wm8994->power_state +#ifndef M110S + ,wm8994->recognition_active, + wm8994->ringtone_active #endif + ); #endif #endif return value; @@ -1161,4 +1756,7 @@ void voodoo_hook_wm8994_pcm_probe(struct snd_soc_codec *codec_) // make a copy of the codec pointer codec = codec_; + + // initialize eq_band_values[] from default codec EQ values + load_current_eq_values(); } |