aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sound/soc/codecs/Kconfig.voodoo12
-rw-r--r--sound/soc/codecs/wm8994_voodoo.c790
-rw-r--r--sound/soc/codecs/wm8994_voodoo.h19
3 files changed, 714 insertions, 107 deletions
diff --git a/sound/soc/codecs/Kconfig.voodoo b/sound/soc/codecs/Kconfig.voodoo
index dfb3250..9d6882d 100644
--- a/sound/soc/codecs/Kconfig.voodoo
+++ b/sound/soc/codecs/Kconfig.voodoo
@@ -45,22 +45,16 @@ config SND_VOODOO_FM
config SND_VOODOO_MODULE
tristate "Build also as module (incomplete)"
- depends on SND_VOODOO && m
+ depends on SND_VOODOO && m && n
default n
help
requires additionnal source
-config SND_VOODOO_DEBUG
- bool "Codec development tools (unsafe and introduce sound skipping)"
+config SND_VOODOO_DEVELOPMENT
+ bool "Codec development tools (unsafe)"
depends on SND_VOODOO
default n
help
Allow to codec dump registers and load register-address/value batchs
Powerful but also dangerous tool
-config SND_VOODOO_DEBUG_LOG
- bool "Log every wm8994_write"
- depends on SND_VOODOO_DEBUG
- default n
- help
- Log codec writes
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", &reg, &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();
}
diff --git a/sound/soc/codecs/wm8994_voodoo.h b/sound/soc/codecs/wm8994_voodoo.h
index 6e7d23f..ef73e28 100644
--- a/sound/soc/codecs/wm8994_voodoo.h
+++ b/sound/soc/codecs/wm8994_voodoo.h
@@ -6,6 +6,8 @@
* published by the Free Software Foundation.
*/
+#define VOODOO_SOUND_VERSION 9
+
#if defined(CONFIG_MACH_HERRING) || defined (CONFIG_SAMSUNG_GALAXYS) \
|| defined (CONFIG_SAMSUNG_GALAXYSB) \
|| defined (CONFIG_SAMSUNG_CAPTIVATE) \
@@ -23,10 +25,17 @@
#define M110S
#endif
+#ifdef CONFIG_TDMB_T3700
+#define M110S
+#endif
+
+enum debug_log { LOG_OFF, LOG_INFOS, LOG_VERBOSE };
+bool debug_log(short unsigned int level);
+
enum unified_path { HEADPHONES, RADIO_HEADPHONES, SPEAKER, MAIN_MICROPHONE };
bool is_path(int unified_path);
-unsigned short tune_fll_value(unsigned short val);
+bool is_path_media_or_fm_no_call_no_record(void);
unsigned int voodoo_hook_wm8994_write(struct snd_soc_codec *codec,
unsigned int reg, unsigned int value);
void voodoo_hook_fmradio_headset(void);
@@ -35,7 +44,10 @@ void voodoo_hook_wm8994_pcm_remove(void);
void voodoo_hook_record_main_mic(void);
void voodoo_hook_playback_speaker(void);
-void update_hpvol(void);
+void load_current_eq_values(void);
+void apply_saturation_prevention_drc(void);
+
+void update_hpvol(bool with_fade);
void update_fm_radio_headset_restore_freqs(bool with_mute);
void update_fm_radio_headset_normalize_gain(bool with_mute);
void update_recording_preset(bool with_mute);
@@ -44,4 +56,7 @@ void update_osr128(bool with_mute);
void update_fll_tuning(bool with_mute);
void update_mono_downmix(bool with_mute);
void update_dac_direct(bool with_mute);
+void update_digital_gain(bool with_mute);
+void update_stereo_expansion(bool with_mute);
+void update_headphone_eq(bool with_mute);
void update_enable(void);