ASoC: wm8994: Provide VMID mode control and fix default sequence
The optimal management of VMID depends on a number of factors which vary
dynamically at runtime, for example the connection to a system docking
station. In some circumstances it is desirable to keep VMID enabled all
the time, in others it is desirable to aggressively power it up and down.
Provide a callback allowing machine driver to configure either the normal
power up/down mode (WM8994_VMID_MODE_NORMAL) or to maintain VMID even
when idle (WM8994_VMID_MODE_FORCE). This callback, wm8994_vmid_mode(),
should be called with the CODEC lock.
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c
index b7f3cfc..9685dff 100644
--- a/sound/soc/codecs/wm8994.c
+++ b/sound/soc/codecs/wm8994.c
@@ -777,36 +777,68 @@
if (wm8994->vmid_refcount == 1) {
snd_soc_update_bits(codec, WM8994_ANTIPOP_1,
- WM8994_LINEOUT_VMID_BUF_ENA |
WM8994_LINEOUT1_DISCH |
- WM8994_LINEOUT2_DISCH,
- WM8994_LINEOUT_VMID_BUF_ENA);
+ WM8994_LINEOUT2_DISCH, 0);
wm_hubs_vmid_ena(codec);
- /* Startup bias, VMID ramp & buffer */
- snd_soc_update_bits(codec, WM8994_ANTIPOP_2,
- WM8994_BIAS_SRC |
- WM8994_VMID_DISCH |
- WM8994_STARTUP_BIAS_ENA |
- WM8994_VMID_BUF_ENA |
- WM8994_VMID_RAMP_MASK,
- WM8994_BIAS_SRC |
- WM8994_STARTUP_BIAS_ENA |
- WM8994_VMID_BUF_ENA |
- (0x2 << WM8994_VMID_RAMP_SHIFT));
+ switch (wm8994->vmid_mode) {
+ default:
+ WARN_ON(0 == "Invalid VMID mode");
+ case WM8994_VMID_NORMAL:
+ /* Startup bias, VMID ramp & buffer */
+ snd_soc_update_bits(codec, WM8994_ANTIPOP_2,
+ WM8994_BIAS_SRC |
+ WM8994_VMID_DISCH |
+ WM8994_STARTUP_BIAS_ENA |
+ WM8994_VMID_BUF_ENA |
+ WM8994_VMID_RAMP_MASK,
+ WM8994_BIAS_SRC |
+ WM8994_STARTUP_BIAS_ENA |
+ WM8994_VMID_BUF_ENA |
+ (0x3 << WM8994_VMID_RAMP_SHIFT));
- /* Main bias enable, VMID=2x40k */
- snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1,
- WM8994_BIAS_ENA |
- WM8994_VMID_SEL_MASK,
- WM8994_BIAS_ENA | 0x2);
+ /* Main bias enable, VMID=2x40k */
+ snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1,
+ WM8994_BIAS_ENA |
+ WM8994_VMID_SEL_MASK,
+ WM8994_BIAS_ENA | 0x2);
- msleep(50);
+ msleep(50);
- snd_soc_update_bits(codec, WM8994_ANTIPOP_2,
- WM8994_VMID_RAMP_MASK | WM8994_BIAS_SRC,
- 0);
+ snd_soc_update_bits(codec, WM8994_ANTIPOP_2,
+ WM8994_VMID_RAMP_MASK |
+ WM8994_BIAS_SRC,
+ 0);
+ break;
+
+ case WM8994_VMID_FORCE:
+ /* Startup bias, slow VMID ramp & buffer */
+ snd_soc_update_bits(codec, WM8994_ANTIPOP_2,
+ WM8994_BIAS_SRC |
+ WM8994_VMID_DISCH |
+ WM8994_STARTUP_BIAS_ENA |
+ WM8994_VMID_BUF_ENA |
+ WM8994_VMID_RAMP_MASK,
+ WM8994_BIAS_SRC |
+ WM8994_STARTUP_BIAS_ENA |
+ WM8994_VMID_BUF_ENA |
+ (0x2 << WM8994_VMID_RAMP_SHIFT));
+
+ /* Main bias enable, VMID=2x40k */
+ snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1,
+ WM8994_BIAS_ENA |
+ WM8994_VMID_SEL_MASK,
+ WM8994_BIAS_ENA | 0x2);
+
+ msleep(400);
+
+ snd_soc_update_bits(codec, WM8994_ANTIPOP_2,
+ WM8994_VMID_RAMP_MASK |
+ WM8994_BIAS_SRC,
+ 0);
+ break;
+ }
}
}
@@ -820,34 +852,55 @@
wm8994->vmid_refcount);
if (wm8994->vmid_refcount == 0) {
- /* Switch over to startup biases */
+ if (wm8994->hubs.lineout1_se)
+ snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_3,
+ WM8994_LINEOUT1N_ENA |
+ WM8994_LINEOUT1P_ENA,
+ WM8994_LINEOUT1N_ENA |
+ WM8994_LINEOUT1P_ENA);
+
+ if (wm8994->hubs.lineout2_se)
+ snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_3,
+ WM8994_LINEOUT2N_ENA |
+ WM8994_LINEOUT2P_ENA,
+ WM8994_LINEOUT2N_ENA |
+ WM8994_LINEOUT2P_ENA);
+
+ /* Start discharging VMID */
snd_soc_update_bits(codec, WM8994_ANTIPOP_2,
WM8994_BIAS_SRC |
- WM8994_STARTUP_BIAS_ENA |
- WM8994_VMID_BUF_ENA |
- WM8994_VMID_RAMP_MASK,
+ WM8994_VMID_DISCH,
WM8994_BIAS_SRC |
- WM8994_STARTUP_BIAS_ENA |
- WM8994_VMID_BUF_ENA |
- (1 << WM8994_VMID_RAMP_SHIFT));
+ WM8994_VMID_DISCH);
- /* Disable main biases */
- snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1,
- WM8994_BIAS_ENA |
- WM8994_VMID_SEL_MASK, 0);
+ switch (wm8994->vmid_mode) {
+ case WM8994_VMID_FORCE:
+ msleep(350);
+ break;
+ default:
+ break;
+ }
- /* Discharge VMID */
- snd_soc_update_bits(codec, WM8994_ANTIPOP_2,
- WM8994_VMID_DISCH, WM8994_VMID_DISCH);
+ snd_soc_update_bits(codec, WM8994_ADDITIONAL_CONTROL,
+ WM8994_VROI, WM8994_VROI);
- /* Discharge line */
+ /* Active discharge */
snd_soc_update_bits(codec, WM8994_ANTIPOP_1,
WM8994_LINEOUT1_DISCH |
WM8994_LINEOUT2_DISCH,
WM8994_LINEOUT1_DISCH |
WM8994_LINEOUT2_DISCH);
- msleep(5);
+ msleep(150);
+
+ snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_3,
+ WM8994_LINEOUT1N_ENA |
+ WM8994_LINEOUT1P_ENA |
+ WM8994_LINEOUT2N_ENA |
+ WM8994_LINEOUT2P_ENA, 0);
+
+ snd_soc_update_bits(codec, WM8994_ADDITIONAL_CONTROL,
+ WM8994_VROI, 0);
/* Switch off startup biases */
snd_soc_update_bits(codec, WM8994_ANTIPOP_2,
@@ -855,6 +908,12 @@
WM8994_STARTUP_BIAS_ENA |
WM8994_VMID_BUF_ENA |
WM8994_VMID_RAMP_MASK, 0);
+
+ snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1,
+ WM8994_BIAS_ENA | WM8994_VMID_SEL_MASK, 0);
+
+ snd_soc_update_bits(codec, WM8994_ANTIPOP_2,
+ WM8994_VMID_RAMP_MASK, 0);
}
pm_runtime_put(codec->dev);
@@ -2197,6 +2256,55 @@
return 0;
}
+int wm8994_vmid_mode(struct snd_soc_codec *codec, enum wm8994_vmid_mode mode)
+{
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+
+ switch (mode) {
+ case WM8994_VMID_NORMAL:
+ if (wm8994->hubs.lineout1_se) {
+ snd_soc_dapm_disable_pin(&codec->dapm,
+ "LINEOUT1N Driver");
+ snd_soc_dapm_disable_pin(&codec->dapm,
+ "LINEOUT1P Driver");
+ }
+ if (wm8994->hubs.lineout2_se) {
+ snd_soc_dapm_disable_pin(&codec->dapm,
+ "LINEOUT2N Driver");
+ snd_soc_dapm_disable_pin(&codec->dapm,
+ "LINEOUT2P Driver");
+ }
+
+ /* Do the sync with the old mode to allow it to clean up */
+ snd_soc_dapm_sync(&codec->dapm);
+ wm8994->vmid_mode = mode;
+ break;
+
+ case WM8994_VMID_FORCE:
+ if (wm8994->hubs.lineout1_se) {
+ snd_soc_dapm_force_enable_pin(&codec->dapm,
+ "LINEOUT1N Driver");
+ snd_soc_dapm_force_enable_pin(&codec->dapm,
+ "LINEOUT1P Driver");
+ }
+ if (wm8994->hubs.lineout2_se) {
+ snd_soc_dapm_force_enable_pin(&codec->dapm,
+ "LINEOUT2N Driver");
+ snd_soc_dapm_force_enable_pin(&codec->dapm,
+ "LINEOUT2P Driver");
+ }
+
+ wm8994->vmid_mode = mode;
+ snd_soc_dapm_sync(&codec->dapm);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static int wm8994_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
struct snd_soc_codec *codec = dai->codec;
diff --git a/sound/soc/codecs/wm8994.h b/sound/soc/codecs/wm8994.h
index 2f4d2d1..c724112 100644
--- a/sound/soc/codecs/wm8994.h
+++ b/sound/soc/codecs/wm8994.h
@@ -32,6 +32,11 @@
#define WM8994_FLL_SRC_LRCLK 3
#define WM8994_FLL_SRC_BCLK 4
+enum wm8994_vmid_mode {
+ WM8994_VMID_NORMAL,
+ WM8994_VMID_FORCE,
+};
+
typedef void (*wm8958_micdet_cb)(u16 status, void *data);
int wm8994_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack,
@@ -39,6 +44,8 @@
int wm8958_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack,
wm8958_micdet_cb cb, void *cb_data);
+int wm8994_vmid_mode(struct snd_soc_codec *codec, enum wm8994_vmid_mode mode);
+
int wm8958_aif_ev(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event);
@@ -75,6 +82,7 @@
int vmid_refcount;
int active_refcount;
+ enum wm8994_vmid_mode vmid_mode;
int dac_rates[2];
int lrclk_shared[2];