Merge branch 'for-3.5-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/tj...
[firefly-linux-kernel-4.4.55.git] / sound / soc / codecs / wm_hubs.c
index 6c028c4706016e1f56bb36114d6ee9108e597736..dfe957a47f29b02073de0de666de9cfab51c96e4 100644 (file)
@@ -109,12 +109,103 @@ irqreturn_t wm_hubs_dcs_done(int irq, void *data)
 }
 EXPORT_SYMBOL_GPL(wm_hubs_dcs_done);
 
+static bool wm_hubs_dac_hp_direct(struct snd_soc_codec *codec)
+{
+       int reg;
+
+       /* If we're going via the mixer we'll need to do additional checks */
+       reg = snd_soc_read(codec, WM8993_OUTPUT_MIXER1);
+       if (!(reg & WM8993_DACL_TO_HPOUT1L)) {
+               if (reg & ~WM8993_DACL_TO_MIXOUTL) {
+                       dev_vdbg(codec->dev, "Analogue paths connected: %x\n",
+                                reg & ~WM8993_DACL_TO_HPOUT1L);
+                       return false;
+               } else {
+                       dev_vdbg(codec->dev, "HPL connected to mixer\n");
+               }
+       } else {
+               dev_vdbg(codec->dev, "HPL connected to DAC\n");
+       }
+
+       reg = snd_soc_read(codec, WM8993_OUTPUT_MIXER2);
+       if (!(reg & WM8993_DACR_TO_HPOUT1R)) {
+               if (reg & ~WM8993_DACR_TO_MIXOUTR) {
+                       dev_vdbg(codec->dev, "Analogue paths connected: %x\n",
+                                reg & ~WM8993_DACR_TO_HPOUT1R);
+                       return false;
+               } else {
+                       dev_vdbg(codec->dev, "HPR connected to mixer\n");
+               }
+       } else {
+               dev_vdbg(codec->dev, "HPR connected to DAC\n");
+       }
+
+       return true;
+}
+
+struct wm_hubs_dcs_cache {
+       struct list_head list;
+       unsigned int left;
+       unsigned int right;
+       u16 dcs_cfg;
+};
+
+static bool wm_hubs_dcs_cache_get(struct snd_soc_codec *codec,
+                                 struct wm_hubs_dcs_cache **entry)
+{
+       struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec);
+       struct wm_hubs_dcs_cache *cache;
+       unsigned int left, right;
+
+       left = snd_soc_read(codec, WM8993_LEFT_OUTPUT_VOLUME);
+       left &= WM8993_HPOUT1L_VOL_MASK;
+
+       right = snd_soc_read(codec, WM8993_RIGHT_OUTPUT_VOLUME);
+       right &= WM8993_HPOUT1R_VOL_MASK;
+
+       list_for_each_entry(cache, &hubs->dcs_cache, list) {
+               if (cache->left != left || cache->right != right)
+                       continue;
+
+               *entry = cache;
+               return true;
+       }
+
+       return false;
+}
+
+static void wm_hubs_dcs_cache_set(struct snd_soc_codec *codec, u16 dcs_cfg)
+{
+       struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec);
+       struct wm_hubs_dcs_cache *cache;
+
+       if (hubs->no_cache_dac_hp_direct)
+               return;
+
+       cache = devm_kzalloc(codec->dev, sizeof(*cache), GFP_KERNEL);
+       if (!cache) {
+               dev_err(codec->dev, "Failed to allocate DCS cache entry\n");
+               return;
+       }
+
+       cache->left = snd_soc_read(codec, WM8993_LEFT_OUTPUT_VOLUME);
+       cache->left &= WM8993_HPOUT1L_VOL_MASK;
+
+       cache->right = snd_soc_read(codec, WM8993_RIGHT_OUTPUT_VOLUME);
+       cache->right &= WM8993_HPOUT1R_VOL_MASK;
+
+       cache->dcs_cfg = dcs_cfg;
+
+       list_add_tail(&cache->list, &hubs->dcs_cache);
+}
+
 /*
  * Startup calibration of the DC servo
  */
 static void calibrate_dc_servo(struct snd_soc_codec *codec)
 {
        struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec);
+       struct wm_hubs_dcs_cache *cache;
        s8 offset;
        u16 reg, reg_l, reg_r, dcs_cfg, dcs_reg;
 
@@ -129,10 +220,11 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec)
 
        /* If we're using a digital only path and have a previously
         * callibrated DC servo offset stored then use that. */
-       if (hubs->class_w && hubs->class_w_dcs) {
-               dev_dbg(codec->dev, "Using cached DC servo offset %x\n",
-                       hubs->class_w_dcs);
-               snd_soc_write(codec, dcs_reg, hubs->class_w_dcs);
+       if (wm_hubs_dac_hp_direct(codec) &&
+           wm_hubs_dcs_cache_get(codec, &cache)) {
+               dev_dbg(codec->dev, "Using cached DCS offset %x for %d,%d\n",
+                       cache->dcs_cfg, cache->left, cache->right);
+               snd_soc_write(codec, dcs_reg, cache->dcs_cfg);
                wait_for_dc_servo(codec,
                                  WM8993_DCS_TRIG_DAC_WR_0 |
                                  WM8993_DCS_TRIG_DAC_WR_1);
@@ -207,8 +299,8 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec)
 
        /* Save the callibrated offset if we're in class W mode and
         * therefore don't have any analogue signal mixed in. */
-       if (hubs->class_w && !hubs->no_cache_class_w)
-               hubs->class_w_dcs = dcs_cfg;
+       if (wm_hubs_dac_hp_direct(codec))
+               wm_hubs_dcs_cache_set(codec, dcs_cfg);
 }
 
 /*
@@ -223,9 +315,6 @@ static int wm8993_put_dc_servo(struct snd_kcontrol *kcontrol,
 
        ret = snd_soc_put_volsw(kcontrol, ucontrol);
 
-       /* Updating the analogue gains invalidates the DC servo cache */
-       hubs->class_w_dcs = 0;
-
        /* If we're applying an offset correction then updating the
         * callibration would be likely to introduce further offsets. */
        if (hubs->dcs_codes_l || hubs->dcs_codes_r || hubs->no_series_update)
@@ -530,6 +619,86 @@ static int lineout_event(struct snd_soc_dapm_widget *w,
        return 0;
 }
 
+void wm_hubs_update_class_w(struct snd_soc_codec *codec)
+{
+       struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec);
+       int enable = WM8993_CP_DYN_V | WM8993_CP_DYN_FREQ;
+
+       if (!wm_hubs_dac_hp_direct(codec))
+               enable = false;
+
+       if (hubs->check_class_w_digital && !hubs->check_class_w_digital(codec))
+               enable = false;
+
+       dev_vdbg(codec->dev, "Class W %s\n", enable ? "enabled" : "disabled");
+
+       snd_soc_update_bits(codec, WM8993_CLASS_W_0,
+                           WM8993_CP_DYN_V | WM8993_CP_DYN_FREQ, enable);
+}
+EXPORT_SYMBOL_GPL(wm_hubs_update_class_w);
+
+#define WM_HUBS_SINGLE_W(xname, reg, shift, max, invert) \
+{      .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+       .info = snd_soc_info_volsw, \
+       .get = snd_soc_dapm_get_volsw, .put = class_w_put_volsw, \
+       .private_value =  SOC_SINGLE_VALUE(reg, shift, max, invert) }
+
+static int class_w_put_volsw(struct snd_kcontrol *kcontrol,
+                             struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+       struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+       struct snd_soc_codec *codec = widget->codec;
+       int ret;
+
+       ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol);
+
+       wm_hubs_update_class_w(codec);
+
+       return ret;
+}
+
+#define WM_HUBS_ENUM_W(xname, xenum) \
+{      .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+       .info = snd_soc_info_enum_double, \
+       .get = snd_soc_dapm_get_enum_double, \
+       .put = class_w_put_double, \
+       .private_value = (unsigned long)&xenum }
+
+static int class_w_put_double(struct snd_kcontrol *kcontrol,
+                             struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+       struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+       struct snd_soc_codec *codec = widget->codec;
+       int ret;
+
+       ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol);
+
+       wm_hubs_update_class_w(codec);
+
+       return ret;
+}
+
+static const char *hp_mux_text[] = {
+       "Mixer",
+       "DAC",
+};
+
+static const struct soc_enum hpl_enum =
+       SOC_ENUM_SINGLE(WM8993_OUTPUT_MIXER1, 8, 2, hp_mux_text);
+
+const struct snd_kcontrol_new wm_hubs_hpl_mux =
+       WM_HUBS_ENUM_W("Left Headphone Mux", hpl_enum);
+EXPORT_SYMBOL_GPL(wm_hubs_hpl_mux);
+
+static const struct soc_enum hpr_enum =
+       SOC_ENUM_SINGLE(WM8993_OUTPUT_MIXER2, 8, 2, hp_mux_text);
+
+const struct snd_kcontrol_new wm_hubs_hpr_mux =
+       WM_HUBS_ENUM_W("Right Headphone Mux", hpr_enum);
+EXPORT_SYMBOL_GPL(wm_hubs_hpr_mux);
+
 static const struct snd_kcontrol_new in1l_pga[] = {
 SOC_DAPM_SINGLE("IN1LP Switch", WM8993_INPUT_MIXER2, 5, 1, 0),
 SOC_DAPM_SINGLE("IN1LN Switch", WM8993_INPUT_MIXER2, 4, 1, 0),
@@ -561,25 +730,25 @@ SOC_DAPM_SINGLE("IN1R Switch", WM8993_INPUT_MIXER4, 5, 1, 0),
 };
 
 static const struct snd_kcontrol_new left_output_mixer[] = {
-SOC_DAPM_SINGLE("Right Input Switch", WM8993_OUTPUT_MIXER1, 7, 1, 0),
-SOC_DAPM_SINGLE("Left Input Switch", WM8993_OUTPUT_MIXER1, 6, 1, 0),
-SOC_DAPM_SINGLE("IN2RN Switch", WM8993_OUTPUT_MIXER1, 5, 1, 0),
-SOC_DAPM_SINGLE("IN2LN Switch", WM8993_OUTPUT_MIXER1, 4, 1, 0),
-SOC_DAPM_SINGLE("IN2LP Switch", WM8993_OUTPUT_MIXER1, 1, 1, 0),
-SOC_DAPM_SINGLE("IN1R Switch", WM8993_OUTPUT_MIXER1, 3, 1, 0),
-SOC_DAPM_SINGLE("IN1L Switch", WM8993_OUTPUT_MIXER1, 2, 1, 0),
-SOC_DAPM_SINGLE("DAC Switch", WM8993_OUTPUT_MIXER1, 0, 1, 0),
+WM_HUBS_SINGLE_W("Right Input Switch", WM8993_OUTPUT_MIXER1, 7, 1, 0),
+WM_HUBS_SINGLE_W("Left Input Switch", WM8993_OUTPUT_MIXER1, 6, 1, 0),
+WM_HUBS_SINGLE_W("IN2RN Switch", WM8993_OUTPUT_MIXER1, 5, 1, 0),
+WM_HUBS_SINGLE_W("IN2LN Switch", WM8993_OUTPUT_MIXER1, 4, 1, 0),
+WM_HUBS_SINGLE_W("IN2LP Switch", WM8993_OUTPUT_MIXER1, 1, 1, 0),
+WM_HUBS_SINGLE_W("IN1R Switch", WM8993_OUTPUT_MIXER1, 3, 1, 0),
+WM_HUBS_SINGLE_W("IN1L Switch", WM8993_OUTPUT_MIXER1, 2, 1, 0),
+WM_HUBS_SINGLE_W("DAC Switch", WM8993_OUTPUT_MIXER1, 0, 1, 0),
 };
 
 static const struct snd_kcontrol_new right_output_mixer[] = {
-SOC_DAPM_SINGLE("Left Input Switch", WM8993_OUTPUT_MIXER2, 7, 1, 0),
-SOC_DAPM_SINGLE("Right Input Switch", WM8993_OUTPUT_MIXER2, 6, 1, 0),
-SOC_DAPM_SINGLE("IN2LN Switch", WM8993_OUTPUT_MIXER2, 5, 1, 0),
-SOC_DAPM_SINGLE("IN2RN Switch", WM8993_OUTPUT_MIXER2, 4, 1, 0),
-SOC_DAPM_SINGLE("IN1L Switch", WM8993_OUTPUT_MIXER2, 3, 1, 0),
-SOC_DAPM_SINGLE("IN1R Switch", WM8993_OUTPUT_MIXER2, 2, 1, 0),
-SOC_DAPM_SINGLE("IN2RP Switch", WM8993_OUTPUT_MIXER2, 1, 1, 0),
-SOC_DAPM_SINGLE("DAC Switch", WM8993_OUTPUT_MIXER2, 0, 1, 0),
+WM_HUBS_SINGLE_W("Left Input Switch", WM8993_OUTPUT_MIXER2, 7, 1, 0),
+WM_HUBS_SINGLE_W("Right Input Switch", WM8993_OUTPUT_MIXER2, 6, 1, 0),
+WM_HUBS_SINGLE_W("IN2LN Switch", WM8993_OUTPUT_MIXER2, 5, 1, 0),
+WM_HUBS_SINGLE_W("IN2RN Switch", WM8993_OUTPUT_MIXER2, 4, 1, 0),
+WM_HUBS_SINGLE_W("IN1L Switch", WM8993_OUTPUT_MIXER2, 3, 1, 0),
+WM_HUBS_SINGLE_W("IN1R Switch", WM8993_OUTPUT_MIXER2, 2, 1, 0),
+WM_HUBS_SINGLE_W("IN2RP Switch", WM8993_OUTPUT_MIXER2, 1, 1, 0),
+WM_HUBS_SINGLE_W("DAC Switch", WM8993_OUTPUT_MIXER2, 0, 1, 0),
 };
 
 static const struct snd_kcontrol_new earpiece_mixer[] = {
@@ -943,6 +1112,7 @@ int wm_hubs_add_analogue_routes(struct snd_soc_codec *codec,
        struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec);
        struct snd_soc_dapm_context *dapm = &codec->dapm;
 
+       INIT_LIST_HEAD(&hubs->dcs_cache);
        init_completion(&hubs->dcs_done);
 
        snd_soc_dapm_add_routes(dapm, analogue_routes,